Skip to main content

systemprompt_cli/commands/cloud/init/
mod.rs

1//! `cloud init` project scaffolding.
2//!
3//! Creates the `.systemprompt/` directory with its ignore files, Dockerfile,
4//! and entrypoint, and generates the default `services/` boilerplate for a new
5//! project.
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10use anyhow::{Context, Result};
11use std::path::Path;
12use systemprompt_cloud::ProjectContext;
13use systemprompt_logging::CliService;
14use systemprompt_models::CliPaths;
15
16pub mod scaffolding;
17pub mod templates;
18
19use crate::cli_settings::CliConfig;
20use scaffolding::generate_services_boilerplate;
21
22const GITIGNORE_CONTENT: &str = "# Ignore sensitive files
23credentials.json
24tenants.json
25**/secrets.json
26docker/
27storage/
28";
29
30const DOCKERIGNORE_CONTENT: &str = ".git
31.gitignore
32.gitmodules
33target/debug
34.cargo
35.systemprompt/credentials.json
36.systemprompt/tenants.json
37.systemprompt/**/secrets.json
38.systemprompt/docker
39.systemprompt/storage
40.env*
41backup
42docs
43instructions
44*.md
45web/node_modules
46.vscode
47.idea
48logs
49*.log
50";
51
52fn entrypoint_content() -> String {
53    format!(
54        r"#!/bin/sh
55set -e
56
57exec /app/bin/systemprompt {services_serve_cmd} --foreground
58",
59        services_serve_cmd = CliPaths::services_serve_cmd(),
60    )
61}
62
63pub(super) fn execute(force: bool, _config: &CliConfig) -> Result<()> {
64    let project_root = std::env::current_dir().context("Failed to get current directory")?;
65    let ctx = ProjectContext::new(project_root.clone());
66    let systemprompt_dir = ctx.systemprompt_dir();
67    let services_dir = project_root.join("services");
68
69    let project_name = project_root
70        .file_name()
71        .and_then(|n| n.to_str())
72        .unwrap_or("systemprompt")
73        .to_owned();
74
75    CliService::section("Initialize Project");
76    CliService::key_value("Project", &project_name);
77    CliService::key_value("Root", &project_root.display().to_string());
78
79    if systemprompt_dir.exists() {
80        CliService::info(".systemprompt/ already exists");
81    } else {
82        create_systemprompt_dir(&systemprompt_dir, &project_root)?;
83    }
84
85    if !services_dir.exists() || force {
86        if force && services_dir.exists() {
87            CliService::warning("Removing existing services directory...");
88            std::fs::remove_dir_all(&services_dir)
89                .context("Failed to remove services directory")?;
90        }
91        generate_services_boilerplate(&project_root, &project_name)?;
92    } else {
93        CliService::info("services/ already exists (use --force to regenerate)");
94    }
95
96    CliService::section("Next Steps");
97    CliService::info("1. systemprompt cloud auth login     # Authenticate");
98    CliService::info("2. systemprompt cloud tenant create  # Create a tenant");
99    CliService::info("3. systemprompt cloud profile create local  # Create a profile");
100
101    Ok(())
102}
103
104fn create_systemprompt_dir(dir: &Path, project_root: &Path) -> Result<()> {
105    std::fs::create_dir_all(dir).context("Failed to create .systemprompt directory")?;
106
107    std::fs::write(dir.join(".gitignore"), GITIGNORE_CONTENT)
108        .context("Failed to create .gitignore")?;
109    CliService::info("  Created .systemprompt/.gitignore");
110
111    std::fs::write(dir.join(".dockerignore"), DOCKERIGNORE_CONTENT)
112        .context("Failed to create .dockerignore")?;
113    CliService::info("  Created .systemprompt/.dockerignore");
114
115    let dockerfile_content = systemprompt_cloud::deploy::generate_dockerfile_content(project_root);
116    std::fs::write(dir.join("Dockerfile"), dockerfile_content)
117        .context("Failed to create Dockerfile")?;
118    CliService::info("  Created .systemprompt/Dockerfile");
119
120    std::fs::write(dir.join("entrypoint.sh"), entrypoint_content())
121        .context("Failed to create entrypoint.sh")?;
122    CliService::info("  Created .systemprompt/entrypoint.sh");
123
124    CliService::success("Created .systemprompt/");
125    Ok(())
126}
127
128pub(super) fn ensure_project_scaffolding(project_root: &Path) -> Result<()> {
129    let services_dir = project_root.join("services");
130    let web_dir = project_root.join("web");
131
132    if services_dir.exists() && web_dir.exists() {
133        return Ok(());
134    }
135
136    let project_name = project_root
137        .file_name()
138        .and_then(|n| n.to_str())
139        .unwrap_or("systemprompt")
140        .to_owned();
141
142    if !services_dir.exists() {
143        CliService::info("Scaffolding services/ directory...");
144        generate_services_boilerplate(project_root, &project_name)?;
145    }
146
147    if !web_dir.exists() {
148        std::fs::create_dir_all(&web_dir)
149            .with_context(|| format!("Failed to create directory: {}", web_dir.display()))?;
150        CliService::info("Created web/ directory");
151    }
152
153    Ok(())
154}