use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
#[derive(Parser, Debug, Clone, Default)]
pub struct GlobalOpts {
#[arg(long, global = true)]
pub config: Option<String>,
#[arg(long, global = true)]
pub workspace: Option<String>,
#[arg(long, global = true)]
pub no_tools: bool,
#[arg(long = "llm-context-tokens", global = true, value_name = "N")]
pub llm_context_tokens: Option<u32>,
#[arg(long, global = true, value_name = "FILE")]
pub log: Option<String>,
}
#[derive(Parser, Debug, Clone)]
pub struct ServeCmd {
#[arg(long = "port", value_name = "PORT")]
pub port: Option<u16>,
#[arg(value_name = "PORT", index = 1)]
pub port_positional: Option<u16>,
#[arg(long, value_name = "ADDR")]
pub host: Option<String>,
#[arg(long = "with-web", alias = "web")]
pub with_web: bool,
#[arg(long = "desktop-ready-json", visible_alias = "web-ready-json")]
pub desktop_ready_json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct BenchCmd {
#[arg(long, value_name = "TYPE")]
pub benchmark: Option<String>,
#[arg(long, value_name = "FILE")]
pub batch: Option<String>,
#[arg(long, value_name = "FILE")]
pub batch_output: Option<String>,
#[arg(long, value_name = "SECS", default_value = "300")]
pub task_timeout: u64,
#[arg(long, value_name = "N", default_value = "0")]
pub max_tool_rounds: usize,
#[arg(long, value_name = "N", default_value = "1")]
pub samples: usize,
#[arg(long)]
pub resume: bool,
#[arg(long, value_name = "FILE")]
pub bench_system_prompt: Option<String>,
}
#[derive(Parser, Debug, Clone)]
#[command(name = "web-bearer")]
pub struct WebBearerCmd {
#[command(subcommand)]
pub sub: WebBearerSubCmd,
}
#[derive(Subcommand, Debug, Clone)]
pub enum WebBearerSubCmd {
Status,
Set(WebBearerSetCmd),
Clear,
}
#[derive(Parser, Debug, Clone)]
pub struct WebBearerSetCmd {
#[arg(value_name = "TOKEN")]
pub token: Option<String>,
#[arg(long)]
pub stdin: bool,
#[arg(long)]
pub from_env: bool,
}
#[derive(Debug, Clone)]
pub enum WebBearerCli {
Status,
Set {
token: Option<String>,
stdin: bool,
from_env: bool,
},
Clear,
}
#[derive(Parser, Debug, Clone)]
pub struct McpCmd {
#[command(subcommand)]
pub sub: McpSubCmd,
}
#[derive(Subcommand, Debug, Clone)]
pub enum McpSubCmd {
List(McpListCmd),
Serve(McpServeCmd),
}
#[derive(Parser, Debug, Clone)]
pub struct McpListCmd {
#[arg(long)]
pub probe: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct McpServeCmd {
#[arg(long)]
pub no_tools: bool,
#[arg(long, default_value_t = 0)]
pub port: u16,
}
#[derive(Parser, Debug, Clone)]
pub struct WorkflowCmd {
#[command(subcommand)]
pub sub: WorkflowSubCmd,
}
#[derive(Subcommand, Debug, Clone)]
pub enum WorkflowSubCmd {
Compile(WorkflowFileCmd),
Validate(WorkflowFileCmd),
Run(WorkflowFileCmd),
}
#[derive(Parser, Debug, Clone)]
pub struct WorkflowFileCmd {
pub file: String,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Clone)]
pub struct WorkflowFileCli {
pub file: String,
pub json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct PluginCmd {
#[command(subcommand)]
pub sub: PluginSubCmd,
}
#[derive(Subcommand, Debug, Clone)]
pub enum PluginSubCmd {
Init(PluginInitCmd),
List(PluginListCmd),
Validate(PluginValidateCmd),
}
#[derive(Parser, Debug, Clone)]
pub struct PluginInitCmd {
#[arg(long, value_name = "NAME")]
pub name: String,
#[arg(long, value_name = "TEXT")]
pub description: Option<String>,
#[arg(long, value_name = "CMD")]
pub command: Option<String>,
#[arg(long = "arg", value_name = "ARG")]
pub args: Vec<String>,
#[arg(long, default_value_t = true)]
pub pass_args_json: bool,
#[arg(long, value_name = "FILE")]
pub output: Option<String>,
}
#[derive(Parser, Debug, Clone)]
pub struct PluginValidateCmd {
#[arg(long, value_name = "FILE")]
pub file: Option<String>,
#[arg(long)]
pub json: bool,
#[arg(long)]
pub jsonl: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct PluginListCmd {
#[arg(long, value_name = "FILE")]
pub file: Option<String>,
#[arg(long)]
pub json: bool,
#[arg(long)]
pub jsonl: bool,
}
#[derive(Parser, Debug, Clone, Default)]
pub struct ConfigCmd {
#[arg(long)]
pub dry_run: bool,
#[arg(long = "with-web", alias = "web")]
pub with_web: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct SaveSessionCmd {
#[arg(long, value_enum, default_value_t = SaveSessionFormat::Both)]
pub format: SaveSessionFormat,
#[arg(long, value_enum, default_value_t = SaveSessionProjection::Raw)]
pub projection: SaveSessionProjection,
#[arg(long, value_name = "FILE")]
pub session_file: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
pub enum SaveSessionFormat {
Json,
Markdown,
#[default]
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
pub enum SaveSessionProjection {
#[default]
Raw,
Display,
}
#[derive(Debug, Clone)]
pub struct SaveSessionCli {
pub format: SaveSessionFormat,
pub projection: SaveSessionProjection,
pub session_file: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ToolReplayCli {
Export {
session_file: Option<String>,
output: Option<String>,
note: Option<String>,
},
Run {
fixture: String,
compare_recorded: bool,
},
}
#[derive(Parser, Debug, Clone)]
pub struct ToolReplayCmd {
#[command(subcommand)]
pub sub: ToolReplaySubCmd,
}
#[derive(Subcommand, Debug, Clone)]
pub enum ToolReplaySubCmd {
Export(ToolReplayExportCmd),
Run(ToolReplayRunCmd),
}
#[derive(Parser, Debug, Clone)]
pub struct ToolReplayExportCmd {
#[arg(long, value_name = "FILE")]
pub session_file: Option<String>,
#[arg(long, value_name = "FILE")]
pub output: Option<String>,
#[arg(long, value_name = "TEXT")]
pub note: Option<String>,
}
#[derive(Parser, Debug, Clone)]
pub struct ToolReplayRunCmd {
#[arg(long, value_name = "FILE")]
pub fixture: String,
#[arg(long)]
pub compare_recorded: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct E2eCmd {
#[arg(long, value_name = "MODE", default_value = "real")]
pub mode: String,
#[arg(long = "output-dir", value_name = "DIR")]
pub output_dir: Option<String>,
#[arg(long = "recordings-dir", value_name = "DIR")]
pub recordings_dir: Option<String>,
#[arg(long = "scenarios-file", value_name = "FILE")]
pub scenarios_file: Option<String>,
#[arg(long)]
pub judge: bool,
}
#[derive(Debug, Clone)]
pub struct E2eCliArgs {
pub mode: String,
pub output_dir: Option<String>,
pub recordings_dir: Option<String>,
pub scenarios_file: Option<String>,
pub judge: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ExtraCliCommand {
#[default]
None,
Doctor,
Models,
Probe,
McpList {
probe: bool,
},
McpServe {
no_tools: bool,
port: u16,
},
}
#[derive(Subcommand, Debug, Clone)]
pub enum Commands {
Serve(ServeCmd),
Bench(BenchCmd),
Config(ConfigCmd),
Doctor,
#[command(name = "web-bearer")]
WebBearer(WebBearerCmd),
Models,
Probe,
#[command(name = "save-session", visible_alias = "export-session")]
SaveSession(SaveSessionCmd),
#[command(name = "tool-replay")]
ToolReplay(ToolReplayCmd),
#[command(name = "sse-replay")]
SseReplay(SseReplayCmd),
Mcp(McpCmd),
Plugin(PluginCmd),
Workflow(WorkflowCmd),
E2e(E2eCmd),
}
#[derive(Parser, Debug)]
#[command(
name = "crabmate",
version,
about = "基于 OpenAI 兼容 chat/completions 的 Rust AI Agent;本仓以 `serve` 为执行权威。官方终端为 Client 仓 `crabmate-tui`",
after_long_help = "官方对话请:`crabmate serve` + Client `crabmate-tui` / 桌面 / WASM。同进程 `chat|repl|tui` 入口已移除(见 docs/design/client_shell_split.md)。运维子命令与 SSE:**docs/命令行与路由.md**、**docs/SSE协议.md**。"
)]
pub struct RootCli {
#[command(flatten)]
pub global: GlobalOpts,
#[command(subcommand)]
pub command: Commands,
}
pub fn root_clap_command_for_man_page() -> clap::Command {
RootCli::command()
}
#[derive(Debug, Clone, Default)]
pub struct BenchmarkCliArgs {
pub benchmark: Option<String>,
pub batch: Option<String>,
pub batch_output: Option<String>,
pub task_timeout: u64,
pub max_tool_rounds: usize,
pub samples: usize,
pub resume: bool,
pub system_prompt_file: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ParsedCliArgs {
pub config_path: Option<String>,
pub serve_port: Option<u16>,
pub serve_desktop_ready_json: bool,
pub http_bind_host: String,
pub workspace_cli: Option<String>,
pub no_tools: bool,
pub with_web: bool,
pub dry_run: bool,
pub log_file: Option<String>,
pub bench_args: BenchmarkCliArgs,
pub extra_cli: ExtraCliCommand,
pub save_session: Option<SaveSessionCli>,
pub web_bearer: Option<WebBearerCli>,
pub tool_replay: Option<ToolReplayCli>,
pub sse_replay: Option<SseReplayCli>,
pub plugin_init: Option<PluginInitCli>,
pub plugin_validate: Option<PluginValidateCli>,
pub plugin_list: Option<PluginListCli>,
pub workflow_validate: Option<WorkflowFileCli>,
pub workflow_compile: Option<WorkflowFileCli>,
pub workflow_run: Option<WorkflowFileCli>,
pub llm_context_tokens_cli: Option<u32>,
pub e2e: Option<E2eCliArgs>,
}
#[derive(Debug, Clone)]
pub struct PluginInitCli {
pub name: String,
pub description: Option<String>,
pub command: Option<String>,
pub args: Vec<String>,
pub pass_args_json: bool,
pub output: Option<String>,
}
#[derive(Debug, Clone)]
pub struct PluginValidateCli {
pub file: Option<String>,
pub json: bool,
pub jsonl: bool,
}
#[derive(Debug, Clone)]
pub struct PluginListCli {
pub file: Option<String>,
pub json: bool,
pub jsonl: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct SseReplayCmd {
pub file: String,
#[arg(long, default_value = "rows")]
pub format: String,
#[arg(long, default_value_t = 0)]
pub job_id: u64,
}
#[derive(Debug, Clone)]
pub struct SseReplayCli {
pub file: String,
pub format: String,
pub job_id: u64,
}