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
16mod 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
57echo "Running database migrations..."
58/app/bin/systemprompt {db_migrate_cmd}
59
60echo "Starting services..."
61exec /app/bin/systemprompt {services_serve_cmd} --foreground
62"#,
63        db_migrate_cmd = CliPaths::db_migrate_cmd(),
64        services_serve_cmd = CliPaths::services_serve_cmd(),
65    )
66}
67
68pub(super) fn execute(force: bool, _config: &CliConfig) -> Result<()> {
69    let project_root = std::env::current_dir().context("Failed to get current directory")?;
70    let ctx = ProjectContext::new(project_root.clone());
71    let systemprompt_dir = ctx.systemprompt_dir();
72    let services_dir = project_root.join("services");
73
74    let project_name = project_root
75        .file_name()
76        .and_then(|n| n.to_str())
77        .unwrap_or("systemprompt")
78        .to_owned();
79
80    CliService::section("Initialize Project");
81    CliService::key_value("Project", &project_name);
82    CliService::key_value("Root", &project_root.display().to_string());
83
84    if systemprompt_dir.exists() {
85        CliService::info(".systemprompt/ already exists");
86    } else {
87        create_systemprompt_dir(&systemprompt_dir, &project_root)?;
88    }
89
90    if !services_dir.exists() || force {
91        if force && services_dir.exists() {
92            CliService::warning("Removing existing services directory...");
93            std::fs::remove_dir_all(&services_dir)
94                .context("Failed to remove services directory")?;
95        }
96        generate_services_boilerplate(&project_root, &project_name)?;
97    } else {
98        CliService::info("services/ already exists (use --force to regenerate)");
99    }
100
101    CliService::section("Next Steps");
102    CliService::info("1. systemprompt cloud auth login     # Authenticate");
103    CliService::info("2. systemprompt cloud tenant create  # Create a tenant");
104    CliService::info("3. systemprompt cloud profile create local  # Create a profile");
105
106    Ok(())
107}
108
109fn create_systemprompt_dir(dir: &Path, project_root: &Path) -> Result<()> {
110    std::fs::create_dir_all(dir).context("Failed to create .systemprompt directory")?;
111
112    std::fs::write(dir.join(".gitignore"), GITIGNORE_CONTENT)
113        .context("Failed to create .gitignore")?;
114    CliService::info("  Created .systemprompt/.gitignore");
115
116    std::fs::write(dir.join(".dockerignore"), DOCKERIGNORE_CONTENT)
117        .context("Failed to create .dockerignore")?;
118    CliService::info("  Created .systemprompt/.dockerignore");
119
120    let dockerfile_content = systemprompt_cloud::deploy::generate_dockerfile_content(project_root);
121    std::fs::write(dir.join("Dockerfile"), dockerfile_content)
122        .context("Failed to create Dockerfile")?;
123    CliService::info("  Created .systemprompt/Dockerfile");
124
125    std::fs::write(dir.join("entrypoint.sh"), entrypoint_content())
126        .context("Failed to create entrypoint.sh")?;
127    CliService::info("  Created .systemprompt/entrypoint.sh");
128
129    CliService::success("Created .systemprompt/");
130    Ok(())
131}
132
133pub(super) fn ensure_project_scaffolding(project_root: &Path) -> Result<()> {
134    let services_dir = project_root.join("services");
135    let web_dir = project_root.join("web");
136
137    if services_dir.exists() && web_dir.exists() {
138        return Ok(());
139    }
140
141    let project_name = project_root
142        .file_name()
143        .and_then(|n| n.to_str())
144        .unwrap_or("systemprompt")
145        .to_owned();
146
147    if !services_dir.exists() {
148        CliService::info("Scaffolding services/ directory...");
149        generate_services_boilerplate(project_root, &project_name)?;
150    }
151
152    if !web_dir.exists() {
153        std::fs::create_dir_all(&web_dir)
154            .with_context(|| format!("Failed to create directory: {}", web_dir.display()))?;
155        CliService::info("Created web/ directory");
156    }
157
158    Ok(())
159}