1use std::path::PathBuf;
4
5use anyhow::Result;
6use clap::{Args, Parser, Subcommand};
7use clap_complete::Shell;
8
9use crate::config::Config;
10
11#[derive(Parser, Debug)]
12#[command(
13 name = "zagens",
14 author,
15 version,
16 about = "Zagens headless CLI for DeepSeek agent runtime",
17 long_about = "Scriptable CLI for the Zagens agent runtime.\n\nRun `zagens exec '…'` for one-shot tasks, `zagens doctor` for diagnostics, or `zagens serve --http` for the local API.\n\nNot affiliated with DeepSeek Inc."
18)]
19pub struct Cli {
20 #[command(subcommand)]
22 pub command: Option<Commands>,
23
24 #[command(flatten)]
25 pub feature_toggles: FeatureToggles,
26
27 #[arg(short, long)]
29 pub prompt: Option<String>,
30
31 #[arg(long)]
33 pub yolo: bool,
34
35 #[arg(long)]
37 pub max_subagents: Option<usize>,
38
39 #[arg(long, global = true)]
41 pub config: Option<PathBuf>,
42
43 #[arg(short, long, global = true)]
45 pub verbose: bool,
46
47 #[arg(long, global = true)]
49 pub profile: Option<String>,
50
51 #[arg(short, long, global = true)]
53 pub workspace: Option<PathBuf>,
54
55 #[arg(short, long)]
57 pub resume: Option<String>,
58
59 #[arg(short = 'c', long = "continue")]
61 pub continue_session: bool,
62
63 #[arg(long = "no-alt-screen")]
65 pub no_alt_screen: bool,
66
67 #[arg(long = "mouse-capture", conflicts_with = "no_mouse_capture")]
70 pub mouse_capture: bool,
71
72 #[arg(long = "no-mouse-capture", conflicts_with = "mouse_capture")]
74 pub no_mouse_capture: bool,
75
76 #[arg(long)]
78 pub skip_onboarding: bool,
79
80 #[arg(long = "fresh")]
82 pub fresh: bool,
83
84 #[arg(long = "no-project-config", global = true)]
86 pub no_project_config: bool,
87}
88
89#[derive(Subcommand, Debug, Clone)]
90#[allow(clippy::large_enum_variant)]
91pub enum Commands {
92 Doctor(DoctorArgs),
94 Setup(SetupArgs),
96 Completions {
98 #[arg(value_enum)]
100 shell: Shell,
101 },
102 Sessions {
104 #[arg(short, long, default_value = "20")]
106 limit: usize,
107 #[arg(short, long)]
109 search: Option<String>,
110 },
111 Init,
113 Login {
115 #[arg(long)]
117 api_key: Option<String>,
118 },
119 Logout,
121 Models(ModelsArgs),
123 Exec(ExecArgs),
125 Review(ReviewArgs),
127 Pr {
129 #[arg(value_name = "NUMBER")]
131 number: u32,
132 #[arg(short = 'R', long)]
135 repo: Option<String>,
136 #[arg(long, default_value_t = false)]
140 checkout: bool,
141 },
142 Apply(ApplyArgs),
144 Eval(EvalArgs),
146 CoverageGate(CoverageGateArgs),
148 Mcp {
150 #[command(subcommand)]
151 command: McpCommand,
152 },
153 Execpolicy(ExecpolicyCommand),
155 Features(FeaturesCli),
157 Sandbox(SandboxArgs),
159 Serve(ServeArgs),
161 Resume {
163 #[arg(value_name = "SESSION_ID")]
165 session_id: Option<String>,
166 #[arg(long = "last", default_value_t = false, conflicts_with = "session_id")]
168 last: bool,
169 },
170 Fork {
172 #[arg(value_name = "SESSION_ID")]
174 session_id: Option<String>,
175 #[arg(long = "last", default_value_t = false, conflicts_with = "session_id")]
177 last: bool,
178 },
179}
180
181#[derive(Args, Debug, Clone)]
182pub struct ExecArgs {
183 pub prompt: String,
185 #[arg(long)]
187 pub model: Option<String>,
188 #[arg(long, default_value_t = false)]
190 pub auto: bool,
191 #[arg(long, default_value_t = false)]
193 pub json: bool,
194}
195
196#[derive(Args, Debug, Clone, Default)]
197pub struct SetupArgs {
198 #[arg(long, default_value_t = false)]
200 pub mcp: bool,
201 #[arg(long, default_value_t = false)]
203 pub skills: bool,
204 #[arg(long, default_value_t = false)]
206 pub tools: bool,
207 #[arg(long, default_value_t = false)]
209 pub plugins: bool,
210 #[arg(long, default_value_t = false)]
212 pub all: bool,
213 #[arg(long, default_value_t = false)]
215 pub local: bool,
216 #[arg(long, default_value_t = false)]
218 pub force: bool,
219 #[arg(long, default_value_t = false, conflicts_with_all = ["mcp", "skills", "tools", "plugins", "all", "local", "clean"])]
221 pub status: bool,
222 #[arg(long, default_value_t = false, conflicts_with_all = ["mcp", "skills", "tools", "plugins", "all", "local", "status"])]
224 pub clean: bool,
225}
226
227#[derive(Args, Debug, Clone, Default)]
228pub struct DoctorArgs {
229 #[arg(long, default_value_t = false)]
231 pub json: bool,
232}
233
234#[derive(Args, Debug, Clone)]
235pub struct EvalArgs {
236 #[arg(long, value_name = "STEP")]
238 pub fail_step: Option<String>,
239 #[arg(long, default_value = "printf eval-harness")]
241 pub shell_command: String,
242 #[arg(long, default_value = "eval-harness")]
244 pub shell_expect_token: String,
245 #[arg(long, default_value_t = 240)]
247 pub max_output_chars: usize,
248 #[arg(long, default_value_t = false)]
250 pub json: bool,
251 #[arg(long, value_name = "DIR")]
254 pub record: Option<PathBuf>,
255}
256
257#[derive(Args, Debug, Clone, Default)]
258pub struct ModelsArgs {
259 #[arg(long, default_value_t = false)]
261 pub json: bool,
262}
263
264#[derive(Args, Debug, Default, Clone)]
265pub struct FeatureToggles {
266 #[arg(long = "enable", value_name = "FEATURE", action = clap::ArgAction::Append, global = true)]
268 pub enable: Vec<String>,
269
270 #[arg(long = "disable", value_name = "FEATURE", action = clap::ArgAction::Append, global = true)]
272 pub disable: Vec<String>,
273}
274
275impl FeatureToggles {
276 pub fn apply(&self, config: &mut Config) -> Result<()> {
277 for feature in &self.enable {
278 config.set_feature(feature, true)?;
279 }
280 for feature in &self.disable {
281 config.set_feature(feature, false)?;
282 }
283 Ok(())
284 }
285}
286
287#[derive(Args, Debug, Clone)]
288pub struct ReviewArgs {
289 #[arg(long, conflicts_with = "base")]
291 pub staged: bool,
292 #[arg(long)]
294 pub base: Option<String>,
295 #[arg(long)]
297 pub path: Option<PathBuf>,
298 #[arg(long)]
300 pub model: Option<String>,
301 #[arg(long, default_value_t = 200_000)]
303 pub max_chars: usize,
304 #[arg(long, default_value_t = false)]
306 pub json: bool,
307}
308
309#[derive(Args, Debug, Clone)]
310pub struct ApplyArgs {
311 #[arg(value_name = "PATCH_FILE")]
313 pub patch_file: Option<PathBuf>,
314}
315
316#[derive(Args, Debug, Clone)]
317pub struct ServeArgs {
318 #[arg(long)]
320 pub mcp: bool,
321 #[arg(long)]
323 pub http: bool,
324 #[arg(long)]
326 pub acp: bool,
327 #[arg(long, default_value = "127.0.0.1")]
329 pub host: String,
330 #[arg(long, default_value_t = 7878)]
332 pub port: u16,
333 #[arg(long, default_value_t = 8)]
335 pub workers: usize,
336 #[arg(long = "cors-origin", value_name = "URL")]
341 pub cors_origin: Vec<String>,
342 #[arg(long = "auth-token", value_name = "TOKEN")]
345 pub auth_token: Option<String>,
346}
347
348#[derive(Subcommand, Debug, Clone)]
349pub enum McpCommand {
350 List,
352 Init {
354 #[arg(long, default_value_t = false)]
356 force: bool,
357 },
358 Connect {
360 #[arg(value_name = "SERVER")]
362 server: Option<String>,
363 },
364 Tools {
366 #[arg(value_name = "SERVER")]
368 server: Option<String>,
369 },
370 Add {
372 name: String,
374 #[arg(long, conflicts_with = "url")]
376 command: Option<String>,
377 #[arg(long, conflicts_with = "command")]
379 url: Option<String>,
380 #[arg(long = "arg")]
382 args: Vec<String>,
383 },
384 Remove {
386 name: String,
388 },
389 Enable {
391 name: String,
393 },
394 Disable {
396 name: String,
398 },
399 Validate,
401 #[command(
406 name = "add-self",
407 long_about = "Register this DeepSeek binary as a local MCP stdio server.\n\nAdds a config entry to ~/.deepseek/mcp.json that launches `deepseek serve --mcp`\nvia the stdio transport. Other DeepSeek sessions (or any MCP client) can then\ndiscover and call tools exposed by this server.\n\nUse `deepseek serve --http` instead if you need the HTTP/SSE runtime API."
408 )]
409 AddSelf {
410 #[arg(long, default_value = "deepseek")]
412 name: String,
413 #[arg(long)]
415 workspace: Option<String>,
416 },
417}
418
419#[derive(Args, Debug, Clone)]
420pub struct ExecpolicyCommand {
421 #[command(subcommand)]
422 pub command: ExecpolicySubcommand,
423}
424
425#[derive(Subcommand, Debug, Clone)]
426pub enum ExecpolicySubcommand {
427 Check(crate::execpolicy::ExecPolicyCheckCommand),
429}
430
431#[derive(Args, Debug, Clone)]
432pub struct FeaturesCli {
433 #[command(subcommand)]
434 pub command: FeaturesSubcommand,
435}
436
437#[derive(Subcommand, Debug, Clone)]
438pub enum FeaturesSubcommand {
439 List,
441}
442
443#[derive(Args, Debug, Clone)]
444pub struct SandboxArgs {
445 #[command(subcommand)]
446 pub command: SandboxCommand,
447}
448
449#[derive(Subcommand, Debug, Clone)]
450pub enum SandboxCommand {
451 Poc {
453 #[command(subcommand)]
454 command: SandboxPocCommand,
455 },
456 Teardown {
458 #[arg(long)]
460 keep_logs: bool,
461 },
462 Setup,
464 AddReadDir {
466 path: PathBuf,
468 },
469 Run {
471 #[arg(long, default_value = "workspace-write")]
473 policy: String,
474 #[arg(long)]
476 network: bool,
477 #[arg(long, value_name = "PATH")]
479 writable_root: Vec<PathBuf>,
480 #[arg(long)]
482 exclude_tmpdir: bool,
483 #[arg(long)]
485 exclude_slash_tmp: bool,
486 #[arg(long)]
488 cwd: Option<PathBuf>,
489 #[arg(long, default_value_t = 60_000)]
491 timeout_ms: u64,
492 #[arg(required = true, trailing_var_arg = true)]
494 command: Vec<String>,
495 },
496}
497
498#[derive(Subcommand, Debug, Clone)]
499pub enum SandboxPocCommand {
500 DenyRead,
502}
503
504#[derive(Args, Debug, Clone)]
506pub struct CoverageGateArgs {
507 #[arg(short, long)]
509 pub workspace: Option<std::path::PathBuf>,
510 #[arg(long, default_value_t = true)]
512 pub require_checklist_complete: bool,
513 #[arg(long = "run-tests", default_value_t = false)]
515 pub run_tests: bool,
516 #[arg(long, default_value_t = false)]
518 pub json: bool,
519 #[arg(long)]
521 pub task_id: Option<String>,
522 #[arg(long, default_value_t = false)]
524 pub no_fail: bool,
525}