feat: add zeroclaw config schema for JSON Schema export

Add a `config schema` subcommand that dumps the full configuration
schema as JSON Schema (draft 2020-12) to stdout. This enables
downstream consumers (like PankoAgent) to programmatically validate
configs, generate forms, and stay in sync with zeroclaw's evolving
config surface without hand-maintaining copies of the schema.

- Add schemars 1.2 dependency and derive JsonSchema on all config
  structs/enums (schema.rs, policy.rs, email_channel.rs)
- Add `Config` subcommand group with `Schema` sub-command
- Output is valid JSON Schema with $defs for all 56 config types
This commit is contained in:
s04 2026-02-18 20:49:15 +01:00 committed by Chummy
parent d44dc5a048
commit 996f66b6a7
6 changed files with 156 additions and 63 deletions

63
Cargo.lock generated
View file

@ -1392,6 +1392,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]] [[package]]
name = "ecb" name = "ecb"
version = "0.1.2" version = "0.1.2"
@ -4465,6 +4471,26 @@ dependencies = [
"thiserror 2.0.18", "thiserror 2.0.18",
] ]
[[package]]
name = "ref-cast"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.12.3" version = "1.12.3"
@ -4909,6 +4935,31 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "schemars"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
dependencies = [
"dyn-clone",
"ref-cast",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.116",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@ -5018,6 +5069,17 @@ dependencies = [
"syn 2.0.116", "syn 2.0.116",
] ]
[[package]]
name = "serde_derive_internals"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
]
[[package]] [[package]]
name = "serde_html_form" name = "serde_html_form"
version = "0.2.8" version = "0.2.8"
@ -7098,6 +7160,7 @@ dependencies = [
"rusqlite", "rusqlite",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",

View file

@ -37,6 +37,9 @@ directories = "6.0"
toml = "1.0" toml = "1.0"
shellexpand = "3.1" shellexpand = "3.1"
# JSON Schema generation for config export
schemars = "1.2"
# Logging - minimal # Logging - minimal
tracing = { version = "0.1", default-features = false } tracing = { version = "0.1", default-features = false }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter"] }

View file

@ -20,6 +20,7 @@ use lettre::{Message, SmtpTransport, Transport};
use mail_parser::{MessageParser, MimeHeaders}; use mail_parser::{MessageParser, MimeHeaders};
use rustls::{ClientConfig, RootCertStore}; use rustls::{ClientConfig, RootCertStore};
use rustls_pki_types::DnsName; use rustls_pki_types::DnsName;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
@ -35,7 +36,7 @@ use uuid::Uuid;
use super::traits::{Channel, ChannelMessage, SendMessage}; use super::traits::{Channel, ChannelMessage, SendMessage};
/// Email channel configuration /// Email channel configuration
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct EmailConfig { pub struct EmailConfig {
/// IMAP server hostname /// IMAP server hostname
pub imap_host: String, pub imap_host: String,

View file

@ -2,6 +2,7 @@ use crate::providers::{is_glm_alias, is_zai_alias};
use crate::security::AutonomyLevel; use crate::security::AutonomyLevel;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use directories::UserDirs; use directories::UserDirs;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
@ -45,7 +46,7 @@ static RUNTIME_PROXY_CLIENT_CACHE: OnceLock<RwLock<HashMap<String, reqwest::Clie
// ── Top-level config ────────────────────────────────────────────── // ── Top-level config ──────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Config { pub struct Config {
/// Workspace directory - computed from home, not serialized /// Workspace directory - computed from home, not serialized
#[serde(skip)] #[serde(skip)]
@ -146,7 +147,7 @@ pub struct Config {
// ── Delegate Agents ────────────────────────────────────────────── // ── Delegate Agents ──────────────────────────────────────────────
/// Configuration for a delegate sub-agent used by the `delegate` tool. /// Configuration for a delegate sub-agent used by the `delegate` tool.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DelegateAgentConfig { pub struct DelegateAgentConfig {
/// Provider name (e.g. "ollama", "openrouter", "anthropic") /// Provider name (e.g. "ollama", "openrouter", "anthropic")
pub provider: String, pub provider: String,
@ -173,7 +174,7 @@ fn default_max_depth() -> u32 {
// ── Hardware Config (wizard-driven) ───────────────────────────── // ── Hardware Config (wizard-driven) ─────────────────────────────
/// Hardware transport mode. /// Hardware transport mode.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub enum HardwareTransport { pub enum HardwareTransport {
#[default] #[default]
None, None,
@ -194,7 +195,7 @@ impl std::fmt::Display for HardwareTransport {
} }
/// Wizard-driven hardware configuration for physical world interaction. /// Wizard-driven hardware configuration for physical world interaction.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct HardwareConfig { pub struct HardwareConfig {
/// Whether hardware access is enabled /// Whether hardware access is enabled
#[serde(default)] #[serde(default)]
@ -240,7 +241,7 @@ impl Default for HardwareConfig {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct AgentConfig { pub struct AgentConfig {
/// When true: bootstrap_max_chars=6000, rag_chunk_limit=2. Use for 13B or smaller models. /// When true: bootstrap_max_chars=6000, rag_chunk_limit=2. Use for 13B or smaller models.
#[serde(default)] #[serde(default)]
@ -281,7 +282,7 @@ impl Default for AgentConfig {
// ── Identity (AIEOS / OpenClaw format) ────────────────────────── // ── Identity (AIEOS / OpenClaw format) ──────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct IdentityConfig { pub struct IdentityConfig {
/// Identity format: "openclaw" (default) or "aieos" /// Identity format: "openclaw" (default) or "aieos"
#[serde(default = "default_identity_format")] #[serde(default = "default_identity_format")]
@ -310,7 +311,7 @@ impl Default for IdentityConfig {
// ── Cost tracking and budget enforcement ─────────────────────────── // ── Cost tracking and budget enforcement ───────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CostConfig { pub struct CostConfig {
/// Enable cost tracking (default: false) /// Enable cost tracking (default: false)
#[serde(default)] #[serde(default)]
@ -337,7 +338,7 @@ pub struct CostConfig {
pub prices: std::collections::HashMap<String, ModelPricing>, pub prices: std::collections::HashMap<String, ModelPricing>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ModelPricing { pub struct ModelPricing {
/// Input price per 1M tokens /// Input price per 1M tokens
#[serde(default)] #[serde(default)]
@ -451,7 +452,7 @@ fn get_default_pricing() -> std::collections::HashMap<String, ModelPricing> {
// ── Peripherals (hardware: STM32, RPi GPIO, etc.) ──────────────────────── // ── Peripherals (hardware: STM32, RPi GPIO, etc.) ────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct PeripheralsConfig { pub struct PeripheralsConfig {
/// Enable peripheral support (boards become agent tools) /// Enable peripheral support (boards become agent tools)
#[serde(default)] #[serde(default)]
@ -465,7 +466,7 @@ pub struct PeripheralsConfig {
pub datasheet_dir: Option<String>, pub datasheet_dir: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct PeripheralBoardConfig { pub struct PeripheralBoardConfig {
/// Board type: "nucleo-f401re", "rpi-gpio", "esp32", etc. /// Board type: "nucleo-f401re", "rpi-gpio", "esp32", etc.
pub board: String, pub board: String,
@ -501,7 +502,7 @@ impl Default for PeripheralBoardConfig {
// ── Gateway security ───────────────────────────────────────────── // ── Gateway security ─────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GatewayConfig { pub struct GatewayConfig {
/// Gateway port (default: 3000) /// Gateway port (default: 3000)
#[serde(default = "default_gateway_port")] #[serde(default = "default_gateway_port")]
@ -597,7 +598,7 @@ impl Default for GatewayConfig {
// ── Composio (managed tool surface) ───────────────────────────── // ── Composio (managed tool surface) ─────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ComposioConfig { pub struct ComposioConfig {
/// Enable Composio integration for 1000+ OAuth tools /// Enable Composio integration for 1000+ OAuth tools
#[serde(default)] #[serde(default)]
@ -626,7 +627,7 @@ impl Default for ComposioConfig {
// ── Secrets (encrypted credential store) ──────────────────────── // ── Secrets (encrypted credential store) ────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SecretsConfig { pub struct SecretsConfig {
/// Enable encryption for API keys and tokens in config.toml /// Enable encryption for API keys and tokens in config.toml
#[serde(default = "default_true")] #[serde(default = "default_true")]
@ -641,7 +642,7 @@ impl Default for SecretsConfig {
// ── Browser (friendly-service browsing only) ─────────────────── // ── Browser (friendly-service browsing only) ───────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct BrowserComputerUseConfig { pub struct BrowserComputerUseConfig {
/// Sidecar endpoint for computer-use actions (OS-level mouse/keyboard/screenshot) /// Sidecar endpoint for computer-use actions (OS-level mouse/keyboard/screenshot)
#[serde(default = "default_browser_computer_use_endpoint")] #[serde(default = "default_browser_computer_use_endpoint")]
@ -688,7 +689,7 @@ impl Default for BrowserComputerUseConfig {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct BrowserConfig { pub struct BrowserConfig {
/// Enable `browser_open` tool (opens URLs in Brave without scraping) /// Enable `browser_open` tool (opens URLs in Brave without scraping)
#[serde(default)] #[serde(default)]
@ -741,7 +742,7 @@ impl Default for BrowserConfig {
// ── HTTP request tool ─────────────────────────────────────────── // ── HTTP request tool ───────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct HttpRequestConfig { pub struct HttpRequestConfig {
/// Enable `http_request` tool for API interactions /// Enable `http_request` tool for API interactions
#[serde(default)] #[serde(default)]
@ -767,7 +768,7 @@ fn default_http_timeout_secs() -> u64 {
// ── Web search ─────────────────────────────────────────────────── // ── Web search ───────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct WebSearchConfig { pub struct WebSearchConfig {
/// Enable `web_search_tool` for web searches /// Enable `web_search_tool` for web searches
#[serde(default = "default_true")] #[serde(default = "default_true")]
@ -812,7 +813,7 @@ impl Default for WebSearchConfig {
// ── Proxy ─────────────────────────────────────────────────────── // ── Proxy ───────────────────────────────────────────────────────
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ProxyScope { pub enum ProxyScope {
Environment, Environment,
@ -821,7 +822,7 @@ pub enum ProxyScope {
Services, Services,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ProxyConfig { pub struct ProxyConfig {
/// Enable proxy support for selected scope. /// Enable proxy support for selected scope.
#[serde(default)] #[serde(default)]
@ -1271,19 +1272,19 @@ fn parse_proxy_enabled(raw: &str) -> Option<bool> {
} }
// ── Memory ─────────────────────────────────────────────────── // ── Memory ───────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct StorageConfig { pub struct StorageConfig {
#[serde(default)] #[serde(default)]
pub provider: StorageProviderSection, pub provider: StorageProviderSection,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct StorageProviderSection { pub struct StorageProviderSection {
#[serde(default)] #[serde(default)]
pub config: StorageProviderConfig, pub config: StorageProviderConfig,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct StorageProviderConfig { pub struct StorageProviderConfig {
/// Storage engine key (e.g. "postgres", "sqlite"). /// Storage engine key (e.g. "postgres", "sqlite").
#[serde(default)] #[serde(default)]
@ -1332,7 +1333,7 @@ impl Default for StorageProviderConfig {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct MemoryConfig { pub struct MemoryConfig {
/// "sqlite" | "lucid" | "postgres" | "markdown" | "none" (`none` = explicit no-op memory) /// "sqlite" | "lucid" | "postgres" | "markdown" | "none" (`none` = explicit no-op memory)
@ -1482,7 +1483,7 @@ impl Default for MemoryConfig {
// ── Observability ───────────────────────────────────────────────── // ── Observability ─────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ObservabilityConfig { pub struct ObservabilityConfig {
/// "none" | "log" | "prometheus" | "otel" /// "none" | "log" | "prometheus" | "otel"
pub backend: String, pub backend: String,
@ -1508,7 +1509,7 @@ impl Default for ObservabilityConfig {
// ── Autonomy / Security ────────────────────────────────────────── // ── Autonomy / Security ──────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct AutonomyConfig { pub struct AutonomyConfig {
pub level: AutonomyLevel, pub level: AutonomyLevel,
pub workspace_only: bool, pub workspace_only: bool,
@ -1593,7 +1594,7 @@ impl Default for AutonomyConfig {
// ── Runtime ────────────────────────────────────────────────────── // ── Runtime ──────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct RuntimeConfig { pub struct RuntimeConfig {
/// Runtime kind (`native` | `docker`). /// Runtime kind (`native` | `docker`).
#[serde(default = "default_runtime_kind")] #[serde(default = "default_runtime_kind")]
@ -1604,7 +1605,7 @@ pub struct RuntimeConfig {
pub docker: DockerRuntimeConfig, pub docker: DockerRuntimeConfig,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DockerRuntimeConfig { pub struct DockerRuntimeConfig {
/// Runtime image used to execute shell commands. /// Runtime image used to execute shell commands.
#[serde(default = "default_docker_image")] #[serde(default = "default_docker_image")]
@ -1680,7 +1681,7 @@ impl Default for RuntimeConfig {
// ── Reliability / supervision ──────────────────────────────────── // ── Reliability / supervision ────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ReliabilityConfig { pub struct ReliabilityConfig {
/// Retries per provider before failing over. /// Retries per provider before failing over.
#[serde(default = "default_provider_retries")] #[serde(default = "default_provider_retries")]
@ -1755,7 +1756,7 @@ impl Default for ReliabilityConfig {
// ── Scheduler ──────────────────────────────────────────────────── // ── Scheduler ────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SchedulerConfig { pub struct SchedulerConfig {
/// Enable the built-in scheduler loop. /// Enable the built-in scheduler loop.
#[serde(default = "default_scheduler_enabled")] #[serde(default = "default_scheduler_enabled")]
@ -1807,7 +1808,7 @@ impl Default for SchedulerConfig {
/// ``` /// ```
/// ///
/// Usage: pass `hint:reasoning` as the model parameter to route the request. /// Usage: pass `hint:reasoning` as the model parameter to route the request.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ModelRouteConfig { pub struct ModelRouteConfig {
/// Task hint name (e.g. "reasoning", "fast", "code", "summarize") /// Task hint name (e.g. "reasoning", "fast", "code", "summarize")
pub hint: String, pub hint: String,
@ -1824,7 +1825,7 @@ pub struct ModelRouteConfig {
/// Automatic query classification — classifies user messages by keyword/pattern /// Automatic query classification — classifies user messages by keyword/pattern
/// and routes to the appropriate model hint. Disabled by default. /// and routes to the appropriate model hint. Disabled by default.
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct QueryClassificationConfig { pub struct QueryClassificationConfig {
#[serde(default)] #[serde(default)]
pub enabled: bool, pub enabled: bool,
@ -1833,7 +1834,7 @@ pub struct QueryClassificationConfig {
} }
/// A single classification rule mapping message patterns to a model hint. /// A single classification rule mapping message patterns to a model hint.
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct ClassificationRule { pub struct ClassificationRule {
/// Must match a `[[model_routes]]` hint value. /// Must match a `[[model_routes]]` hint value.
pub hint: String, pub hint: String,
@ -1856,7 +1857,7 @@ pub struct ClassificationRule {
// ── Heartbeat ──────────────────────────────────────────────────── // ── Heartbeat ────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct HeartbeatConfig { pub struct HeartbeatConfig {
pub enabled: bool, pub enabled: bool,
pub interval_minutes: u32, pub interval_minutes: u32,
@ -1873,7 +1874,7 @@ impl Default for HeartbeatConfig {
// ── Cron ──────────────────────────────────────────────────────── // ── Cron ────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CronConfig { pub struct CronConfig {
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub enabled: bool, pub enabled: bool,
@ -1896,7 +1897,7 @@ impl Default for CronConfig {
// ── Tunnel ────────────────────────────────────────────────────── // ── Tunnel ──────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TunnelConfig { pub struct TunnelConfig {
/// "none", "cloudflare", "tailscale", "ngrok", "custom" /// "none", "cloudflare", "tailscale", "ngrok", "custom"
pub provider: String, pub provider: String,
@ -1926,13 +1927,13 @@ impl Default for TunnelConfig {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CloudflareTunnelConfig { pub struct CloudflareTunnelConfig {
/// Cloudflare Tunnel token (from Zero Trust dashboard) /// Cloudflare Tunnel token (from Zero Trust dashboard)
pub token: String, pub token: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TailscaleTunnelConfig { pub struct TailscaleTunnelConfig {
/// Use Tailscale Funnel (public internet) vs Serve (tailnet only) /// Use Tailscale Funnel (public internet) vs Serve (tailnet only)
#[serde(default)] #[serde(default)]
@ -1941,7 +1942,7 @@ pub struct TailscaleTunnelConfig {
pub hostname: Option<String>, pub hostname: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct NgrokTunnelConfig { pub struct NgrokTunnelConfig {
/// ngrok auth token /// ngrok auth token
pub auth_token: String, pub auth_token: String,
@ -1949,7 +1950,7 @@ pub struct NgrokTunnelConfig {
pub domain: Option<String>, pub domain: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CustomTunnelConfig { pub struct CustomTunnelConfig {
/// Command template to start the tunnel. Use {port} and {host} placeholders. /// Command template to start the tunnel. Use {port} and {host} placeholders.
/// Example: "bore local {port} --to bore.pub" /// Example: "bore local {port} --to bore.pub"
@ -1962,7 +1963,7 @@ pub struct CustomTunnelConfig {
// ── Channels ───────────────────────────────────────────────────── // ── Channels ─────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ChannelsConfig { pub struct ChannelsConfig {
pub cli: bool, pub cli: bool,
pub telegram: Option<TelegramConfig>, pub telegram: Option<TelegramConfig>,
@ -2015,7 +2016,7 @@ impl Default for ChannelsConfig {
} }
/// Streaming mode for channels that support progressive message updates. /// Streaming mode for channels that support progressive message updates.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum StreamMode { pub enum StreamMode {
/// No streaming -- send the complete response as a single message (default). /// No streaming -- send the complete response as a single message (default).
@ -2029,7 +2030,7 @@ fn default_draft_update_interval_ms() -> u64 {
1000 1000
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TelegramConfig { pub struct TelegramConfig {
pub bot_token: String, pub bot_token: String,
pub allowed_users: Vec<String>, pub allowed_users: Vec<String>,
@ -2045,7 +2046,7 @@ pub struct TelegramConfig {
pub mention_only: bool, pub mention_only: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DiscordConfig { pub struct DiscordConfig {
pub bot_token: String, pub bot_token: String,
pub guild_id: Option<String>, pub guild_id: Option<String>,
@ -2061,7 +2062,7 @@ pub struct DiscordConfig {
pub mention_only: bool, pub mention_only: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SlackConfig { pub struct SlackConfig {
pub bot_token: String, pub bot_token: String,
pub app_token: Option<String>, pub app_token: Option<String>,
@ -2070,7 +2071,7 @@ pub struct SlackConfig {
pub allowed_users: Vec<String>, pub allowed_users: Vec<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct MattermostConfig { pub struct MattermostConfig {
pub url: String, pub url: String,
pub bot_token: String, pub bot_token: String,
@ -2087,18 +2088,18 @@ pub struct MattermostConfig {
pub mention_only: Option<bool>, pub mention_only: Option<bool>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct WebhookConfig { pub struct WebhookConfig {
pub port: u16, pub port: u16,
pub secret: Option<String>, pub secret: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct IMessageConfig { pub struct IMessageConfig {
pub allowed_contacts: Vec<String>, pub allowed_contacts: Vec<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct MatrixConfig { pub struct MatrixConfig {
pub homeserver: String, pub homeserver: String,
pub access_token: String, pub access_token: String,
@ -2110,7 +2111,7 @@ pub struct MatrixConfig {
pub allowed_users: Vec<String>, pub allowed_users: Vec<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SignalConfig { pub struct SignalConfig {
/// Base URL for the signal-cli HTTP daemon (e.g. "http://127.0.0.1:8686"). /// Base URL for the signal-cli HTTP daemon (e.g. "http://127.0.0.1:8686").
pub http_url: String, pub http_url: String,
@ -2133,7 +2134,7 @@ pub struct SignalConfig {
pub ignore_stories: bool, pub ignore_stories: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct WhatsAppConfig { pub struct WhatsAppConfig {
/// Access token from Meta Business Suite /// Access token from Meta Business Suite
pub access_token: String, pub access_token: String,
@ -2150,7 +2151,7 @@ pub struct WhatsAppConfig {
pub allowed_numbers: Vec<String>, pub allowed_numbers: Vec<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct LinqConfig { pub struct LinqConfig {
/// Linq Partner API token (Bearer auth) /// Linq Partner API token (Bearer auth)
pub api_token: String, pub api_token: String,
@ -2164,7 +2165,7 @@ pub struct LinqConfig {
pub allowed_senders: Vec<String>, pub allowed_senders: Vec<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct IrcConfig { pub struct IrcConfig {
/// IRC server hostname /// IRC server hostname
pub server: String, pub server: String,
@ -2199,7 +2200,7 @@ fn default_irc_port() -> u16 {
/// ///
/// - `websocket` (default) — persistent WSS long-connection; no public URL required. /// - `websocket` (default) — persistent WSS long-connection; no public URL required.
/// - `webhook` — HTTP callback server; requires a public HTTPS endpoint. /// - `webhook` — HTTP callback server; requires a public HTTPS endpoint.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum LarkReceiveMode { pub enum LarkReceiveMode {
#[default] #[default]
@ -2209,7 +2210,7 @@ pub enum LarkReceiveMode {
/// Lark/Feishu configuration for messaging integration. /// Lark/Feishu configuration for messaging integration.
/// Lark is the international version; Feishu is the Chinese version. /// Lark is the international version; Feishu is the Chinese version.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct LarkConfig { pub struct LarkConfig {
/// App ID from Lark/Feishu developer console /// App ID from Lark/Feishu developer console
pub app_id: String, pub app_id: String,
@ -2239,7 +2240,7 @@ pub struct LarkConfig {
// ── Security Config ───────────────────────────────────────────────── // ── Security Config ─────────────────────────────────────────────────
/// Security configuration for sandboxing, resource limits, and audit logging /// Security configuration for sandboxing, resource limits, and audit logging
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct SecurityConfig { pub struct SecurityConfig {
/// Sandbox configuration /// Sandbox configuration
#[serde(default)] #[serde(default)]
@ -2255,7 +2256,7 @@ pub struct SecurityConfig {
} }
/// Sandbox configuration for OS-level isolation /// Sandbox configuration for OS-level isolation
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SandboxConfig { pub struct SandboxConfig {
/// Enable sandboxing (None = auto-detect, Some = explicit) /// Enable sandboxing (None = auto-detect, Some = explicit)
#[serde(default)] #[serde(default)]
@ -2281,7 +2282,7 @@ impl Default for SandboxConfig {
} }
/// Sandbox backend selection /// Sandbox backend selection
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum SandboxBackend { pub enum SandboxBackend {
/// Auto-detect best available (default) /// Auto-detect best available (default)
@ -2300,7 +2301,7 @@ pub enum SandboxBackend {
} }
/// Resource limits for command execution /// Resource limits for command execution
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ResourceLimitsConfig { pub struct ResourceLimitsConfig {
/// Maximum memory in MB per command /// Maximum memory in MB per command
#[serde(default = "default_max_memory_mb")] #[serde(default = "default_max_memory_mb")]
@ -2347,7 +2348,7 @@ impl Default for ResourceLimitsConfig {
} }
/// Audit logging configuration /// Audit logging configuration
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct AuditConfig { pub struct AuditConfig {
/// Enable audit logging /// Enable audit logging
#[serde(default = "default_audit_enabled")] #[serde(default = "default_audit_enabled")]
@ -2390,7 +2391,7 @@ impl Default for AuditConfig {
} }
/// DingTalk configuration for Stream Mode messaging /// DingTalk configuration for Stream Mode messaging
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DingTalkConfig { pub struct DingTalkConfig {
/// Client ID (AppKey) from DingTalk developer console /// Client ID (AppKey) from DingTalk developer console
pub client_id: String, pub client_id: String,
@ -2402,7 +2403,7 @@ pub struct DingTalkConfig {
} }
/// QQ Official Bot configuration (Tencent QQ Bot SDK) /// QQ Official Bot configuration (Tencent QQ Bot SDK)
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct QQConfig { pub struct QQConfig {
/// App ID from QQ Bot developer console /// App ID from QQ Bot developer console
pub app_id: String, pub app_id: String,

View file

@ -242,6 +242,18 @@ enum Commands {
#[command(subcommand)] #[command(subcommand)]
peripheral_command: zeroclaw::PeripheralCommands, peripheral_command: zeroclaw::PeripheralCommands,
}, },
/// Manage configuration
Config {
#[command(subcommand)]
config_command: ConfigCommands,
},
}
#[derive(Subcommand, Debug)]
enum ConfigCommands {
/// Dump the full configuration JSON Schema to stdout
Schema,
} }
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
@ -766,6 +778,18 @@ async fn main() -> Result<()> {
Commands::Peripheral { peripheral_command } => { Commands::Peripheral { peripheral_command } => {
peripherals::handle_command(peripheral_command.clone(), &config) peripherals::handle_command(peripheral_command.clone(), &config)
} }
Commands::Config { config_command } => match config_command {
ConfigCommands::Schema => {
let schema = schemars::schema_for!(config::Config);
println!(
"{}",
serde_json::to_string_pretty(&schema)
.expect("failed to serialize JSON Schema")
);
Ok(())
}
},
} }
} }

View file

@ -1,10 +1,11 @@
use parking_lot::Mutex; use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Instant; use std::time::Instant;
/// How much autonomy the agent has /// How much autonomy the agent has
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum AutonomyLevel { pub enum AutonomyLevel {
/// Read-only: can observe but not act /// Read-only: can observe but not act