Skip to main content

outlayer_cli/commands/
create.rs

1use anyhow::{Context, Result};
2use std::path::PathBuf;
3
4use crate::config::{
5    self, BuildSection, DeploySection, NetworkConfig, ProjectConfig, ProjectSection,
6    RunSection,
7};
8
9// ── Embedded templates ──────────────────────────────────────────────────
10
11mod basic {
12    pub const CARGO_TOML: &str = include_str!("../../templates/basic/Cargo.toml.tmpl");
13    pub const MAIN_RS: &str = include_str!("../../templates/basic/src/main.rs");
14    pub const BUILD_SH: &str = include_str!("../../templates/basic/build.sh");
15    pub const GITIGNORE: &str = include_str!("../../templates/basic/gitignore.tmpl");
16}
17
18mod contract {
19    pub const CARGO_TOML: &str = include_str!("../../templates/contract/Cargo.toml.tmpl");
20    pub const MAIN_RS: &str = include_str!("../../templates/contract/src/main.rs");
21    pub const BUILD_SH: &str = include_str!("../../templates/contract/build.sh");
22    pub const GITIGNORE: &str = include_str!("../../templates/contract/gitignore.tmpl");
23}
24
25mod shared {
26    pub const SKILL_MD: &str = include_str!("../../templates/shared/skill.md");
27}
28
29struct TemplateFiles {
30    cargo_toml: &'static str,
31    main_rs: &'static str,
32    build_sh: &'static str,
33    gitignore: &'static str,
34}
35
36fn get_template(name: &str) -> Result<TemplateFiles> {
37    match name {
38        "basic" => Ok(TemplateFiles {
39            cargo_toml: basic::CARGO_TOML,
40            main_rs: basic::MAIN_RS,
41            build_sh: basic::BUILD_SH,
42            gitignore: basic::GITIGNORE,
43        }),
44        "contract" => Ok(TemplateFiles {
45            cargo_toml: contract::CARGO_TOML,
46            main_rs: contract::MAIN_RS,
47            build_sh: contract::BUILD_SH,
48            gitignore: contract::GITIGNORE,
49        }),
50        _ => anyhow::bail!(
51            "Unknown template: {name}. Available: basic, contract"
52        ),
53    }
54}
55
56/// `outlayer create <name>` — create a new agent project from template
57pub async fn create(
58    network: &NetworkConfig,
59    name: &str,
60    template: &str,
61    dir: Option<String>,
62) -> Result<()> {
63    let creds = config::load_credentials(network)?;
64
65    // Determine project directory
66    let parent = dir
67        .map(PathBuf::from)
68        .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
69    let project_dir = parent.join(name);
70
71    if project_dir.exists() && project_dir.join("outlayer.toml").exists() {
72        anyhow::bail!(
73            "Project already exists at {}. Use 'outlayer deploy' to update.",
74            project_dir.display()
75        );
76    }
77
78    std::fs::create_dir_all(&project_dir)
79        .with_context(|| format!("Failed to create directory {}", project_dir.display()))?;
80
81    eprintln!(
82        "Creating project \"{}\" ({} template) for {} in {}/",
83        name,
84        template,
85        creds.account_id,
86        project_dir.display()
87    );
88
89    // Scaffold project files from template
90    scaffold(&project_dir, name, template)?;
91
92    // Write outlayer.toml
93    let project_config = ProjectConfig {
94        project: ProjectSection {
95            name: name.to_string(),
96            owner: creds.account_id.clone(),
97        },
98        build: Some(BuildSection {
99            target: "wasm32-wasip2".to_string(),
100            source: "github".to_string(),
101        }),
102        deploy: Some(DeploySection {
103            repo: None,
104            wasm_path: None,
105        }),
106        run: Some(RunSection {
107            max_instructions: Some(1_000_000_000),
108            max_memory_mb: Some(128),
109            max_execution_seconds: Some(60),
110            secrets_profile: Some("default".to_string()),
111            payment_key_nonce: None,
112        }),
113        network: Some(network.network_id.clone()),
114    };
115
116    let toml_data = toml::to_string_pretty(&project_config)?;
117    std::fs::write(project_dir.join("outlayer.toml"), toml_data)?;
118    eprintln!("  Created outlayer.toml");
119
120    eprintln!("\nYour agent is ready. Next steps:");
121    eprintln!("  cd {name}");
122    eprintln!("  git init && git remote add origin <your-repo-url>");
123    eprintln!("  # edit src/main.rs");
124    eprintln!("  git push");
125    eprintln!("  outlayer deploy {name}");
126    eprintln!(
127        "  outlayer run {}/{name} '{{\"command\": \"hello\"}}'",
128        creds.account_id
129    );
130    eprintln!();
131    eprintln!("To create a payment key for HTTPS calls:");
132    eprintln!("  outlayer keys create");
133
134    Ok(())
135}
136
137fn scaffold(project_dir: &PathBuf, project_name: &str, template_name: &str) -> Result<()> {
138    let cargo_name = project_name.replace('-', "_");
139    let tmpl = get_template(template_name)?;
140
141    let substitute = |s: &str| s.replace("{{PROJECT_NAME}}", &cargo_name);
142
143    // Cargo.toml
144    std::fs::write(project_dir.join("Cargo.toml"), substitute(tmpl.cargo_toml))?;
145
146    // src/main.rs
147    std::fs::create_dir_all(project_dir.join("src"))?;
148    std::fs::write(project_dir.join("src/main.rs"), substitute(tmpl.main_rs))?;
149
150    // build.sh
151    let build_sh = project_dir.join("build.sh");
152    std::fs::write(&build_sh, substitute(tmpl.build_sh))?;
153    #[cfg(unix)]
154    {
155        use std::os::unix::fs::PermissionsExt;
156        std::fs::set_permissions(&build_sh, std::fs::Permissions::from_mode(0o755))?;
157    }
158
159    // .gitignore
160    std::fs::write(project_dir.join(".gitignore"), tmpl.gitignore)?;
161
162    // Shared files (copied to every project)
163    std::fs::write(project_dir.join("skill.md"), shared::SKILL_MD)?;
164
165    let desc = match template_name {
166        "contract" => "Rust + wasm32-wasip2 + OutLayer SDK (VRF, storage, RPC)",
167        _ => "Rust + wasm32-wasip2",
168    };
169    eprintln!("  Generated project scaffold ({desc})");
170    Ok(())
171}