Skip to main content

rbnx/cmd/
init.rs

1// SPDX-License-Identifier: MulanPSL-2.0
2// `rbnx init <name>` — scaffold a new robonix project.
3
4use anyhow::{Context, Result};
5use robonix_cli::output;
6use std::path::Path;
7
8/// Reject names containing path separators or traversal components.
9fn validate_name(name: &str) -> Result<()> {
10    if name.is_empty() {
11        anyhow::bail!("name must not be empty");
12    }
13    if name.contains('/') || name.contains('\\') || name == ".." || name.starts_with("../") {
14        anyhow::bail!("invalid name '{name}': must not contain path separators or '..' components");
15    }
16    Ok(())
17}
18
19pub async fn execute(name: &str, path: Option<&Path>) -> Result<()> {
20    validate_name(name)?;
21
22    let base = match path {
23        Some(p) => p.to_path_buf(),
24        None => std::env::current_dir()?,
25    };
26    let project_dir = base.join(name);
27
28    if project_dir.exists() {
29        anyhow::bail!("directory '{}' already exists", project_dir.display());
30    }
31
32    output::action("Init", &format!("creating project '{name}'"));
33
34    // Create directory structure.
35    for sub in ["primitives", "services", "skills"] {
36        std::fs::create_dir_all(project_dir.join(sub))
37            .with_context(|| format!("failed to create {sub}/ directory"))?;
38    }
39
40    // robonix_manifest.yaml (deployment manifest).
41    let manifest = format!(
42        r#"name: {name}
43
44env:
45  LOG: "INFO"
46
47system:
48  atlas:
49    listen: 127.0.0.1:50051
50    log: info
51  executor:
52    listen: 127.0.0.1:50061
53    log: info
54  pilot:
55    listen: 127.0.0.1:50071
56    log: info
57    vlm:
58      upstream: ${{VLM_BASE_URL}}
59      api_key: ${{VLM_API_KEY}}
60      model: ${{VLM_MODEL}}
61      api_format: openai
62  liaison:
63    listen: 127.0.0.1:50081
64    log: info
65  memory: []
66
67primitive: []
68
69service: []
70
71skill: []
72"#
73    );
74    std::fs::write(project_dir.join("robonix_manifest.yaml"), manifest)
75        .context("failed to write robonix_manifest.yaml")?;
76
77    // .gitignore
78    let gitignore = "\
79rbnx-build/
80rbnx-boot/
81__pycache__/
82*.pyc
83.venv/
84";
85    std::fs::write(project_dir.join(".gitignore"), gitignore)
86        .context("failed to write .gitignore")?;
87
88    output::success(&format!(
89        "Project '{name}' created at {}",
90        project_dir.display()
91    ));
92    output::sub_step("robonix_manifest.yaml");
93    output::sub_step("primitives/");
94    output::sub_step("services/");
95    output::sub_step("skills/");
96    output::sub_step(".gitignore");
97
98    Ok(())
99}