Skip to main content

mars_agents/cli/
build.rs

1//! `mars build` — build artifacts from static project state.
2
3use clap::{ArgAction, ValueEnum};
4
5use crate::build::{LaunchBundleRequest, build_launch_bundle};
6use crate::cli::MarsContext;
7use crate::error::MarsError;
8
9#[derive(Debug, clap::Args)]
10pub struct BuildArgs {
11    #[command(subcommand)]
12    pub command: BuildCommand,
13}
14
15#[derive(Debug, clap::Subcommand)]
16pub enum BuildCommand {
17    /// Build a harness-targeted launch scaffold/bundle for an agent or ad-hoc launch.
18    LaunchBundle(LaunchBundleArgs),
19}
20
21#[derive(Debug, Clone, ValueEnum)]
22enum HarnessArg {
23    Claude,
24    Codex,
25    Opencode,
26    Cursor,
27    Pi,
28}
29
30impl HarnessArg {
31    fn as_str(&self) -> &'static str {
32        match self {
33            Self::Claude => "claude",
34            Self::Codex => "codex",
35            Self::Opencode => "opencode",
36            Self::Cursor => "cursor",
37            Self::Pi => "pi",
38        }
39    }
40}
41
42#[derive(Debug, Clone, ValueEnum)]
43enum EffortArg {
44    Low,
45    Medium,
46    High,
47    Xhigh,
48}
49
50impl EffortArg {
51    fn as_str(&self) -> &'static str {
52        match self {
53            Self::Low => "low",
54            Self::Medium => "medium",
55            Self::High => "high",
56            Self::Xhigh => "xhigh",
57        }
58    }
59}
60
61#[derive(Debug, Clone, ValueEnum)]
62enum ApprovalArg {
63    Default,
64    Auto,
65    Confirm,
66    Yolo,
67}
68
69impl ApprovalArg {
70    fn as_str(&self) -> &'static str {
71        match self {
72            Self::Default => "default",
73            Self::Auto => "auto",
74            Self::Confirm => "confirm",
75            Self::Yolo => "yolo",
76        }
77    }
78}
79
80#[derive(Debug, Clone, ValueEnum)]
81enum SandboxArg {
82    Default,
83    ReadOnly,
84    WorkspaceWrite,
85    DangerFullAccess,
86}
87
88impl SandboxArg {
89    fn as_str(&self) -> &'static str {
90        match self {
91            Self::Default => "default",
92            Self::ReadOnly => "read-only",
93            Self::WorkspaceWrite => "workspace-write",
94            Self::DangerFullAccess => "danger-full-access",
95        }
96    }
97}
98
99#[derive(Debug, clap::Args)]
100pub struct LaunchBundleArgs {
101    /// Agent name from `.mars/agents/<name>.md`.
102    /// Omit for ad-hoc mode; without `--model`, the resolved harness uses its default model.
103    #[arg(long)]
104    pub agent: Option<String>,
105
106    /// Override model token or canonical model id.
107    #[arg(long)]
108    pub model: Option<String>,
109
110    /// Override harness target.
111    #[arg(long, value_enum)]
112    harness: Option<HarnessArg>,
113
114    /// Override effort level.
115    #[arg(long, value_enum)]
116    effort: Option<EffortArg>,
117
118    /// Override approval mode.
119    #[arg(long, value_enum)]
120    approval: Option<ApprovalArg>,
121
122    /// Override sandbox mode.
123    #[arg(long, value_enum)]
124    sandbox: Option<SandboxArg>,
125
126    /// Add extra skills by name. Supports `--skill a --skill b` and `--skill a,b`.
127    #[arg(long = "skill", value_delimiter = ',', action = ArgAction::Append)]
128    pub extra_skills: Vec<String>,
129
130    /// Refresh models.dev catalog and harness probes synchronously before building (blocks until complete).
131    #[arg(long, conflicts_with = "no_refresh_models")]
132    pub refresh_models: bool,
133
134    /// Skip automatic models-cache refresh; use disk cache only (no probe background refresh).
135    #[arg(long, conflicts_with = "refresh_models")]
136    pub no_refresh_models: bool,
137}
138
139pub fn run(args: &BuildArgs, ctx: &MarsContext, _json: bool) -> Result<i32, MarsError> {
140    match &args.command {
141        BuildCommand::LaunchBundle(subargs) => run_launch_bundle(subargs, ctx),
142    }
143}
144
145fn run_launch_bundle(args: &LaunchBundleArgs, ctx: &MarsContext) -> Result<i32, MarsError> {
146    let models_refresh =
147        crate::models::resolve_models_refresh_control(args.refresh_models, args.no_refresh_models)?;
148    let bundle = build_launch_bundle(
149        ctx,
150        LaunchBundleRequest {
151            agent: args.agent.clone(),
152            model: args.model.clone(),
153            harness: args.harness.as_ref().map(|h| h.as_str().to_string()),
154            effort: args.effort.as_ref().map(|e| e.as_str().to_string()),
155            approval: args.approval.as_ref().map(|a| a.as_str().to_string()),
156            sandbox: args.sandbox.as_ref().map(|s| s.as_str().to_string()),
157            extra_skills: args.extra_skills.clone(),
158            models_refresh,
159        },
160    )?;
161
162    println!(
163        "{}",
164        serde_json::to_string_pretty(&bundle).map_err(|err| MarsError::Internal(format!(
165            "failed to serialize launch bundle: {err}"
166        )))?
167    );
168
169    Ok(0)
170}