systemprompt-cli 0.6.1

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
use anyhow::{Context, Result};
use std::path::Path;
use systemprompt_cloud::ProjectContext;
use systemprompt_logging::CliService;
use systemprompt_models::CliPaths;

mod scaffolding;
pub mod templates;

use super::dockerfile;
use crate::cli_settings::CliConfig;
use scaffolding::generate_services_boilerplate;

const GITIGNORE_CONTENT: &str = "# Ignore sensitive files
credentials.json
tenants.json
**/secrets.json
docker/
storage/
";

const DOCKERIGNORE_CONTENT: &str = ".git
.gitignore
.gitmodules
target/debug
.cargo
.systemprompt/credentials.json
.systemprompt/tenants.json
.systemprompt/**/secrets.json
.systemprompt/docker
.systemprompt/storage
.env*
backup
docs
instructions
*.md
web/node_modules
.vscode
.idea
logs
*.log
";

fn entrypoint_content() -> String {
    format!(
        r#"#!/bin/sh
set -e

echo "Running database migrations..."
/app/bin/systemprompt {db_migrate_cmd}

echo "Starting services..."
exec /app/bin/systemprompt {services_serve_cmd} --foreground
"#,
        db_migrate_cmd = CliPaths::db_migrate_cmd(),
        services_serve_cmd = CliPaths::services_serve_cmd(),
    )
}

pub fn execute(force: bool, _config: &CliConfig) -> Result<()> {
    let project_root = std::env::current_dir().context("Failed to get current directory")?;
    let ctx = ProjectContext::new(project_root.clone());
    let systemprompt_dir = ctx.systemprompt_dir();
    let services_dir = project_root.join("services");

    let project_name = project_root
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("systemprompt")
        .to_string();

    CliService::section("Initialize Project");
    CliService::key_value("Project", &project_name);
    CliService::key_value("Root", &project_root.display().to_string());

    if systemprompt_dir.exists() {
        CliService::info(".systemprompt/ already exists");
    } else {
        create_systemprompt_dir(&systemprompt_dir, &project_root)?;
    }

    if !services_dir.exists() || force {
        if force && services_dir.exists() {
            CliService::warning("Removing existing services directory...");
            std::fs::remove_dir_all(&services_dir)
                .context("Failed to remove services directory")?;
        }
        generate_services_boilerplate(&project_root, &project_name)?;
    } else {
        CliService::info("services/ already exists (use --force to regenerate)");
    }

    CliService::section("Next Steps");
    CliService::info("1. systemprompt cloud auth login     # Authenticate");
    CliService::info("2. systemprompt cloud tenant create  # Create a tenant");
    CliService::info("3. systemprompt cloud profile create local  # Create a profile");

    Ok(())
}

fn create_systemprompt_dir(dir: &Path, project_root: &Path) -> Result<()> {
    std::fs::create_dir_all(dir).context("Failed to create .systemprompt directory")?;

    std::fs::write(dir.join(".gitignore"), GITIGNORE_CONTENT)
        .context("Failed to create .gitignore")?;
    CliService::info("  Created .systemprompt/.gitignore");

    std::fs::write(dir.join(".dockerignore"), DOCKERIGNORE_CONTENT)
        .context("Failed to create .dockerignore")?;
    CliService::info("  Created .systemprompt/.dockerignore");

    let dockerfile_content = dockerfile::generate_dockerfile_content(project_root);
    std::fs::write(dir.join("Dockerfile"), dockerfile_content)
        .context("Failed to create Dockerfile")?;
    CliService::info("  Created .systemprompt/Dockerfile");

    std::fs::write(dir.join("entrypoint.sh"), entrypoint_content())
        .context("Failed to create entrypoint.sh")?;
    CliService::info("  Created .systemprompt/entrypoint.sh");

    CliService::success("Created .systemprompt/");
    Ok(())
}

pub fn ensure_project_scaffolding(project_root: &Path) -> Result<()> {
    let services_dir = project_root.join("services");
    let web_dir = project_root.join("web");

    if services_dir.exists() && web_dir.exists() {
        return Ok(());
    }

    let project_name = project_root
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("systemprompt")
        .to_string();

    if !services_dir.exists() {
        CliService::info("Scaffolding services/ directory...");
        generate_services_boilerplate(project_root, &project_name)?;
    }

    if !web_dir.exists() {
        std::fs::create_dir_all(&web_dir)
            .with_context(|| format!("Failed to create directory: {}", web_dir.display()))?;
        CliService::info("Created web/ directory");
    }

    Ok(())
}