feat(config): add show/get/set subcommands for runtime config inspection and modification
This commit is contained in:
parent
f6278373cb
commit
20ed60d2a0
@ -24,7 +24,7 @@ Last verified: **February 28, 2026**.
|
||||
| `integrations` | Inspect integration details |
|
||||
| `skills` | List/install/remove skills |
|
||||
| `migrate` | Import from external runtimes (currently OpenClaw) |
|
||||
| `config` | Export machine-readable config schema |
|
||||
| `config` | Inspect, query, and modify runtime configuration |
|
||||
| `completions` | Generate shell completion scripts to stdout |
|
||||
| `hardware` | Discover and introspect USB hardware |
|
||||
| `peripheral` | Configure and flash peripherals |
|
||||
@ -267,8 +267,17 @@ Skill manifests (`SKILL.toml`) support `prompts` and `[[tools]]`; both are injec
|
||||
|
||||
### `config`
|
||||
|
||||
- `zeroclaw config show`
|
||||
- `zeroclaw config get <key>`
|
||||
- `zeroclaw config set <key> <value>`
|
||||
- `zeroclaw config schema`
|
||||
|
||||
`config show` prints the full effective configuration as pretty JSON with secrets masked as `***REDACTED***`. Environment variable overrides are already applied.
|
||||
|
||||
`config get <key>` queries a single value by dot-separated path (e.g. `gateway.port`, `security.estop.enabled`). Scalars print raw values; objects and arrays print pretty JSON. Sensitive fields are masked.
|
||||
|
||||
`config set <key> <value>` updates a configuration value and persists it atomically to `config.toml`. Types are inferred automatically (`true`/`false` → bool, integers, floats, JSON syntax → object/array, otherwise string). Type mismatches are rejected before writing.
|
||||
|
||||
`config schema` prints a JSON Schema (draft 2020-12) for the full `config.toml` contract to stdout.
|
||||
|
||||
### `completions`
|
||||
|
||||
@ -14,9 +14,12 @@ ZeroClaw logs the resolved config on startup at `INFO` level:
|
||||
|
||||
- `Config loaded` with fields: `path`, `workspace`, `source`, `initialized`
|
||||
|
||||
Schema export command:
|
||||
CLI commands for config inspection and modification:
|
||||
|
||||
- `zeroclaw config schema` (prints JSON Schema draft 2020-12 to stdout)
|
||||
- `zeroclaw config show` — print effective config as JSON (secrets masked)
|
||||
- `zeroclaw config get <key>` — query a value by dot-path (e.g. `zeroclaw config get gateway.port`)
|
||||
- `zeroclaw config set <key> <value>` — update a value and save to `config.toml`
|
||||
- `zeroclaw config schema` — print JSON Schema (draft 2020-12) to stdout
|
||||
|
||||
## Core Keys
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ Xác minh lần cuối: **2026-02-28**.
|
||||
| `integrations` | Kiểm tra chi tiết tích hợp |
|
||||
| `skills` | Liệt kê/cài đặt/gỡ bỏ skills |
|
||||
| `migrate` | Nhập dữ liệu từ runtime khác (hiện hỗ trợ OpenClaw) |
|
||||
| `config` | Xuất schema cấu hình dạng máy đọc được |
|
||||
| `config` | Kiểm tra, truy vấn và sửa đổi cấu hình runtime |
|
||||
| `completions` | Tạo script tự hoàn thành cho shell ra stdout |
|
||||
| `hardware` | Phát hiện và kiểm tra phần cứng USB |
|
||||
| `peripheral` | Cấu hình và nạp firmware thiết bị ngoại vi |
|
||||
@ -124,8 +124,17 @@ Skill manifest (`SKILL.toml`) hỗ trợ `prompts` và `[[tools]]`; cả hai đ
|
||||
|
||||
### `config`
|
||||
|
||||
- `zeroclaw config show`
|
||||
- `zeroclaw config get <key>`
|
||||
- `zeroclaw config set <key> <value>`
|
||||
- `zeroclaw config schema`
|
||||
|
||||
`config show` xuất toàn bộ cấu hình hiệu lực dưới dạng JSON với các trường nhạy cảm được ẩn thành `***REDACTED***`. Các ghi đè từ biến môi trường đã được áp dụng.
|
||||
|
||||
`config get <key>` truy vấn một giá trị theo đường dẫn phân tách bằng dấu chấm (ví dụ: `gateway.port`, `security.estop.enabled`). Giá trị đơn in trực tiếp; đối tượng và mảng in dạng JSON.
|
||||
|
||||
`config set <key> <value>` cập nhật giá trị cấu hình và lưu nguyên tử vào `config.toml`. Kiểu dữ liệu được suy luận tự động (`true`/`false` → bool, số nguyên, số thực, cú pháp JSON → đối tượng/mảng, còn lại → chuỗi). Sai kiểu sẽ bị từ chối trước khi ghi.
|
||||
|
||||
`config schema` xuất JSON Schema (draft 2020-12) cho toàn bộ hợp đồng `config.toml` ra stdout.
|
||||
|
||||
### `completions`
|
||||
|
||||
@ -14,9 +14,12 @@ ZeroClaw ghi log đường dẫn config đã giải quyết khi khởi động
|
||||
|
||||
- `Config loaded` với các trường: `path`, `workspace`, `source`, `initialized`
|
||||
|
||||
Lệnh xuất schema:
|
||||
Lệnh CLI để kiểm tra và sửa đổi cấu hình:
|
||||
|
||||
- `zeroclaw config schema` (xuất JSON Schema draft 2020-12 ra stdout)
|
||||
- `zeroclaw config show` — xuất cấu hình hiệu lực dạng JSON (ẩn secrets)
|
||||
- `zeroclaw config get <key>` — truy vấn giá trị theo đường dẫn (ví dụ: `zeroclaw config get gateway.port`)
|
||||
- `zeroclaw config set <key> <value>` — cập nhật giá trị và lưu vào `config.toml`
|
||||
- `zeroclaw config schema` — xuất JSON Schema (draft 2020-12) ra stdout
|
||||
|
||||
## Khóa chính
|
||||
|
||||
|
||||
154
src/main.rs
154
src/main.rs
@ -520,13 +520,13 @@ Examples:
|
||||
#[command(long_about = "\
|
||||
Manage ZeroClaw configuration.
|
||||
|
||||
Inspect and export configuration settings. Use 'schema' to dump \
|
||||
the full JSON Schema for the config file, which documents every \
|
||||
available key, type, and default value.
|
||||
Inspect, query, and modify configuration settings.
|
||||
|
||||
Examples:
|
||||
zeroclaw config schema # print JSON Schema to stdout
|
||||
zeroclaw config schema > schema.json")]
|
||||
zeroclaw config show # show effective config (secrets masked)
|
||||
zeroclaw config get gateway.port # query a specific value by dot-path
|
||||
zeroclaw config set gateway.port 8080 # update a value and save to config.toml
|
||||
zeroclaw config schema # print full JSON Schema to stdout")]
|
||||
Config {
|
||||
#[command(subcommand)]
|
||||
config_command: ConfigCommands,
|
||||
@ -551,6 +551,20 @@ Examples:
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum ConfigCommands {
|
||||
/// Show the current effective configuration (secrets masked)
|
||||
Show,
|
||||
/// Get a specific configuration value by dot-path (e.g. "gateway.port")
|
||||
Get {
|
||||
/// Dot-separated config path, e.g. "security.estop.enabled"
|
||||
key: String,
|
||||
},
|
||||
/// Set a configuration value and save to config.toml
|
||||
Set {
|
||||
/// Dot-separated config path, e.g. "gateway.port"
|
||||
key: String,
|
||||
/// New value (string, number, boolean, or JSON for objects/arrays)
|
||||
value: String,
|
||||
},
|
||||
/// Dump the full configuration JSON Schema to stdout
|
||||
Schema,
|
||||
}
|
||||
@ -1182,6 +1196,94 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
Commands::Config { config_command } => match config_command {
|
||||
ConfigCommands::Show => {
|
||||
let mut json = serde_json::to_value(&config)
|
||||
.context("Failed to serialize config")?;
|
||||
redact_config_secrets(&mut json);
|
||||
println!("{}", serde_json::to_string_pretty(&json)?);
|
||||
Ok(())
|
||||
}
|
||||
ConfigCommands::Get { key } => {
|
||||
let mut json = serde_json::to_value(&config)
|
||||
.context("Failed to serialize config")?;
|
||||
redact_config_secrets(&mut json);
|
||||
|
||||
let mut current = &json;
|
||||
for segment in key.split('.') {
|
||||
current = current
|
||||
.get(segment)
|
||||
.with_context(|| format!("Config path not found: {key}"))?;
|
||||
}
|
||||
|
||||
match current {
|
||||
serde_json::Value::String(s) => println!("{s}"),
|
||||
serde_json::Value::Bool(b) => println!("{b}"),
|
||||
serde_json::Value::Number(n) => println!("{n}"),
|
||||
serde_json::Value::Null => println!("null"),
|
||||
_ => println!("{}", serde_json::to_string_pretty(current)?),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ConfigCommands::Set { key, value } => {
|
||||
let mut json = serde_json::to_value(&config)
|
||||
.context("Failed to serialize config")?;
|
||||
|
||||
// Parse the new value: try bool, then integer, then float, then JSON, then string
|
||||
let new_value = if value == "true" {
|
||||
serde_json::Value::Bool(true)
|
||||
} else if value == "false" {
|
||||
serde_json::Value::Bool(false)
|
||||
} else if value == "null" {
|
||||
serde_json::Value::Null
|
||||
} else if let Ok(n) = value.parse::<i64>() {
|
||||
serde_json::json!(n)
|
||||
} else if let Ok(n) = value.parse::<f64>() {
|
||||
serde_json::json!(n)
|
||||
} else if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&value) {
|
||||
// JSON object/array (e.g. '["a","b"]' or '{"key":"val"}')
|
||||
parsed
|
||||
} else {
|
||||
serde_json::Value::String(value.clone())
|
||||
};
|
||||
|
||||
// Navigate to the parent and set the leaf
|
||||
let segments: Vec<&str> = key.split('.').collect();
|
||||
if segments.is_empty() {
|
||||
bail!("Config key cannot be empty");
|
||||
}
|
||||
let (parents, leaf) = segments.split_at(segments.len() - 1);
|
||||
|
||||
let mut target = &mut json;
|
||||
for segment in parents {
|
||||
target = target
|
||||
.get_mut(*segment)
|
||||
.with_context(|| format!("Config path not found: {key}"))?;
|
||||
}
|
||||
|
||||
let leaf_key = leaf[0];
|
||||
if target.get(leaf_key).is_none() {
|
||||
bail!("Config path not found: {key}");
|
||||
}
|
||||
target[leaf_key] = new_value.clone();
|
||||
|
||||
// Deserialize back to Config and save.
|
||||
// Preserve runtime-only fields lost during JSON round-trip (#[serde(skip)]).
|
||||
let config_path = config.config_path.clone();
|
||||
let workspace_dir = config.workspace_dir.clone();
|
||||
config = serde_json::from_value(json)
|
||||
.context("Invalid value for this config key — type mismatch")?;
|
||||
config.config_path = config_path;
|
||||
config.workspace_dir = workspace_dir;
|
||||
config.save().await?;
|
||||
|
||||
// Show the saved value
|
||||
let display = match &new_value {
|
||||
serde_json::Value::String(s) => s.clone(),
|
||||
other => other.to_string(),
|
||||
};
|
||||
println!("Set {key} = {display}");
|
||||
Ok(())
|
||||
}
|
||||
ConfigCommands::Schema => {
|
||||
let schema = schemars::schema_for!(config::Config);
|
||||
println!(
|
||||
@ -1194,6 +1296,48 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keys whose values are masked in `config show` / `config get` output.
|
||||
const REDACTED_CONFIG_KEYS: &[&str] = &[
|
||||
"api_key",
|
||||
"api_keys",
|
||||
"bot_token",
|
||||
"paired_tokens",
|
||||
"db_url",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"all_proxy",
|
||||
"secret_key",
|
||||
"webhook_secret",
|
||||
];
|
||||
|
||||
fn redact_config_secrets(value: &mut serde_json::Value) {
|
||||
match value {
|
||||
serde_json::Value::Object(map) => {
|
||||
for (k, v) in map.iter_mut() {
|
||||
if REDACTED_CONFIG_KEYS.contains(&k.as_str()) {
|
||||
match v {
|
||||
serde_json::Value::String(s) if !s.is_empty() => {
|
||||
*v = serde_json::Value::String("***REDACTED***".to_string());
|
||||
}
|
||||
serde_json::Value::Array(arr) if !arr.is_empty() => {
|
||||
*v = serde_json::json!(["***REDACTED***"]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
redact_config_secrets(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
serde_json::Value::Array(arr) => {
|
||||
for item in arr.iter_mut() {
|
||||
redact_config_secrets(item);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_estop_command(
|
||||
config: &Config,
|
||||
estop_command: Option<EstopSubcommands>,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user