一键导入
update-backend
Guide for making changes to the Rust backend of the server template, covering the engine crate, the CLI + HTTP API transports, and testing patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guide for making changes to the Rust backend of the server template, covering the engine crate, the CLI + HTTP API transports, and testing patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use whenever creating, editing, renaming, or deleting any file under .claude/skills/, .claude/agents/, .agents/skills/, or .codex/agents/. Teaches the dual-tool Claude/Codex layout and reminds to run `make sync-agent-config`.
Interview the user, inspect this template repo, run headless onboarding, and prune unused systems so a new Rust project gets running quickly.
Instructions for using prek as a replacement for pre-commit.
Instructions for running code quality checks and maintaining standards in the Rust-Template project.
Git branch hygiene - delete merged/closed branch, prune stale refs, sync dependencies
| name | update-backend |
| description | Guide for making changes to the Rust backend of the server template, covering the engine crate, the CLI + HTTP API transports, and testing patterns. |
Use this skill whenever you are modifying Rust backend logic - adding commands, probes, traits, or configuration in crates/engine or crates/cli.
The backend is split into two layers:
| Layer | Path | Role |
|---|---|---|
| engine | crates/engine/ | All real backend logic. No transport dependency - runs in the CLI, the HTTP API, and tests. |
| appctl | crates/cli/ | The appctl binary. Drives engine over the CLI (call/probe/doctor/run-scenario) and the axum HTTP API (serve), gated behind the cli / http-api cargo features. |
crates/engine.FilesystemOps, NetworkOps. Inject real platform or headless stubs via AppContext.CommandResult with run_id, status, error, timing_ms, and env_summary.SKIP or UNSUPPORTED error codes.snake_case for functions/modules/variablesPascalCase for structs/enumscargo fmt before committing; pass cargo clippy with no warningsCommands implement the typed, async Command trait (one input struct drives the
CLI args, the HTTP body schema, and the future MCP tool schema) and
self-register at link time via register_command! - there is no
hand-maintained registration list.
The fastest path is the scaffolder: appctl new <name> (or make new name=<name>) generates the file below from templates/command.rs.tpl and
inserts the mod <name>; line for you. To do it by hand:
crates/engine/src/commands/my_command.rs:use crate::commands::{Command, CommandError};
use crate::context::Ctx;
use crate::register_command;
use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub struct MyCommand;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct MyCommandInput {
pub key: String,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct MyCommandOutput {
pub result: String,
}
#[async_trait]
impl Command for MyCommand {
type Input = MyCommandInput;
type Output = MyCommandOutput;
fn name(&self) -> &'static str { "my_command" }
fn description(&self) -> &'static str { "One-line description." }
// Optional: restrict transports, e.g. `Expose::cli_only()` / `Expose::no_mcp()`.
async fn run(&self, input: MyCommandInput, cx: &Ctx<'_>)
-> Result<MyCommandOutput, CommandError>
{
// `cx.fs()`, `cx.network()` reach the capabilities;
// `cx.request_id` / `cx.deadline` are request-scoped.
Ok(MyCommandOutput { result: input.key })
}
}
register_command!(MyCommand);
Declare the module in crates/engine/src/commands/mod.rs (mod my_command;).
The register_command! line does the rest - CommandRegistry::new() collects
it automatically. (appctl new inserts this line for you.)
No transport change is needed: the HTTP POST /api/v1/commands/:name route is
auto-derived from the registry, and the CLI call path dispatches by name.
Smoke-test headlessly with appctl:
cargo build -p appctl
appctl call my_command --args '{"key": "value"}' --json
INVALID_INPUT automatically. Return typed
CommandError variants (Unsupported, NetworkError, Timeout, …) for
everything else; CapError from a capability converts via ?.Output; the CLI/scenario runner wrap it in the
CommandResult envelope. Introspect schemas via registry.schema(name).Implement the relevant trait from crates/engine/src/traits.rs:
use engine::traits::{NetworkOps, CapResult, CapError};
struct OfflineNetwork;
#[async_trait::async_trait]
impl NetworkOps for OfflineNetwork {
async fn dns_resolve(&self, _host: &str) -> CapResult<Vec<String>> {
Err(CapError::Unsupported("offline".into()))
}
async fn https_get(&self, _url: &str, _timeout_ms: u64) -> CapResult<(u16, String)> {
Err(CapError::Unsupported("offline".into()))
}
}
Inject via AppContext - AppContext::default() wires the real platform capabilities (used by appctl); pass stub implementations to AppContext::new(fs, network) to run a command against fakes in tests.
Config lives in its own crate, crates/config (crate name app-config).
Source of truth: crates/config/global_config.yaml (layered with
production_config.yaml / .global_config.yaml and APP__-prefixed env
overrides; APP_CONFIG_PATH points a deployed binary at its config file).
let config = app_config::get_config();
println!("Model: {}", config.default_llm.default_model);
crates/engine stays config-agnostic - do not import app-config there unless a
command genuinely needs config; prefer passing values in via the input struct.
The appctl CLI drives engine without a running HTTP server:
# Build
cargo build -p appctl
# Diagnostics
appctl doctor --json
# Call a command
appctl call ping --json
appctl call read_file --args '{"path": "/etc/hostname"}' --json
# Run a capability probe
appctl probe filesystem --json
appctl probe network --json
# Run a YAML scenario
appctl run-scenario scenario.yaml --json
Write scenario files for regression tests:
name: my feature smoke test
steps:
- call: "my_command"
args:
key: "value"
expect_status: "pass"
Every command result has this stable JSON schema:
{
"run_id": "uuid",
"command": "call|probe|doctor|run-scenario",
"target": "<cmd or probe name>",
"status": "pass|fail|skip|error",
"error": { "code": "ERROR_CODE", "message": "..." },
"timing_ms": { "total": 1234 },
"env_summary": { "os": "linux|macos", "arch": "x86_64|aarch64", "headless": true },
"data": {}
}
Error codes: INVALID_INPUT, UNSUPPORTED, UNIMPLEMENTED, DEPENDENCY_MISSING,
PERMISSION_DENIED, NETWORK_ERROR, IO_ERROR, TIMEOUT, EXTERNAL_INTERFERENCE, INTERNAL_ERROR.
cargo fmt appliedcargo clippy passes (no warnings)cargo test passesappctl call <cmd> --jsonengine crate has no transport imports (no CLI/axum/HTTP types)