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
131pub fn run(args: &BuildArgs, ctx: &MarsContext, _json: bool) -> Result<i32, MarsError> {
132    match &args.command {
133        BuildCommand::LaunchBundle(subargs) => run_launch_bundle(subargs, ctx),
134    }
135}
136
137fn run_launch_bundle(args: &LaunchBundleArgs, ctx: &MarsContext) -> Result<i32, MarsError> {
138    let bundle = build_launch_bundle(
139        ctx,
140        LaunchBundleRequest {
141            agent: args.agent.clone(),
142            model: args.model.clone(),
143            harness: args.harness.as_ref().map(|h| h.as_str().to_string()),
144            effort: args.effort.as_ref().map(|e| e.as_str().to_string()),
145            approval: args.approval.as_ref().map(|a| a.as_str().to_string()),
146            sandbox: args.sandbox.as_ref().map(|s| s.as_str().to_string()),
147            extra_skills: args.extra_skills.clone(),
148        },
149    )?;
150
151    println!(
152        "{}",
153        serde_json::to_string_pretty(&bundle).map_err(|err| MarsError::Internal(format!(
154            "failed to serialize launch bundle: {err}"
155        )))?
156    );
157
158    Ok(0)
159}