cellstate/commands/
init.rs1use crate::http;
10use crate::ui;
11use console::style;
12use dialoguer::{theme::ColorfulTheme, Input, Select};
13use std::fs;
14use std::path::Path;
15
16const CONFIG_TOML: &str = r#"# CELLSTATE Project Configuration
17# Docs: https://cellstate.batterypack.dev/docs/config
18
19[project]
20name = "{name}"
21
22# API connection
23[api]
24# Set via CELLSTATE_API_KEY env var or .env file.
25# Get a key at https://cellstate.batterypack.dev or run `cellstate start` for local dev.
26base_url = "{base_url}"
27
28# Memory defaults
29[memory]
30max_tokens = 4096
31default_user_id = "default"
32default_agent_id = "default"
33
34# Agent definitions live in cstate.toml files (see: cellstate.batterypack.dev/docs/packs)
35[packs]
36# path = "./agents"
37"#;
38
39const ENV_EXAMPLE: &str = r#"# CELLSTATE Environment Variables
40# Copy this to .env and fill in your values.
41# Never commit .env to version control.
42
43# API key from https://cellstate.batterypack.dev (or `cellstate start` for local dev)
44CELLSTATE_API_KEY=cst_...
45
46# API endpoint — defaults to local dev server
47CELLSTATE_BASE_URL=http://localhost:3000
48"#;
49
50const LOCK_INIT: &str = r#"# cellstate.lock — DO commit this file.
51# Auto-generated checksums of your agent definitions.
52# Run `cellstate pack emit all` to update.
53
54version = 1
55agents = []
56"#;
57
58const GITIGNORE_ADDITION: &str = r#"
59# CELLSTATE
60.cellstate/local/
61.env
62"#;
63
64pub fn run() -> anyhow::Result<()> {
65 ui::print_banner();
66
67 let cwd = std::env::current_dir()?;
68 let project_name = cwd
69 .file_name()
70 .and_then(|n| n.to_str())
71 .unwrap_or("my-project")
72 .to_string();
73
74 if Path::new(".cellstate/config.toml").exists() {
76 ui::warn("Already initialized (.cellstate/config.toml exists).");
77 let reinit = dialoguer::Confirm::with_theme(&ColorfulTheme::default())
78 .with_prompt("Re-initialize? This will overwrite config.toml.")
79 .default(false)
80 .interact()?;
81 if !reinit {
82 return Ok(());
83 }
84 }
85
86 ui::step("Project setup");
87
88 let name: String = Input::with_theme(&ColorfulTheme::default())
89 .with_prompt("Project name")
90 .default(project_name)
91 .interact_text()?;
92
93 let local_label = format!("Local dev ({})", http::DEFAULT_BASE_URL);
94 let env_options = &[
95 local_label.as_str(),
96 "Managed cloud (https://cst.batterypack.dev)",
97 "Self-hosted (enter URL)",
98 ];
99 let env_choice = Select::with_theme(&ColorfulTheme::default())
100 .with_prompt("CELLSTATE server")
101 .items(env_options)
102 .default(0)
103 .interact()?;
104
105 let base_url = match env_choice {
106 0 => http::DEFAULT_BASE_URL.to_string(),
107 1 => "https://cst.batterypack.dev".to_string(),
108 _ => {
109 let url: String = Input::with_theme(&ColorfulTheme::default())
110 .with_prompt("Server URL")
111 .interact_text()?;
112 url
113 }
114 };
115
116 ui::step("Creating project files...");
118 fs::create_dir_all(".cellstate")?;
119 fs::create_dir_all(".cellstate/local")?;
120
121 let config_content = CONFIG_TOML
123 .replace("{name}", &name)
124 .replace("{base_url}", &base_url);
125 fs::write(".cellstate/config.toml", &config_content)?;
126 ui::ok(".cellstate/config.toml");
127
128 fs::write(".cellstate/.env.example", ENV_EXAMPLE)?;
130 ui::ok(".cellstate/.env.example");
131
132 if !Path::new("cellstate.lock").exists() {
134 fs::write("cellstate.lock", LOCK_INIT)?;
135 ui::ok("cellstate.lock");
136 } else {
137 ui::info("cellstate.lock already exists — skipping");
138 }
139
140 let gitignore_path = Path::new(".gitignore");
142 if gitignore_path.exists() {
143 let existing = fs::read_to_string(gitignore_path)?;
144 if !existing.contains(".cellstate/local") {
145 fs::write(
146 gitignore_path,
147 format!("{}{}", existing, GITIGNORE_ADDITION),
148 )?;
149 ui::ok(".gitignore updated");
150 } else {
151 ui::info(".gitignore already has CELLSTATE entries");
152 }
153 } else {
154 fs::write(gitignore_path, GITIGNORE_ADDITION.trim_start())?;
155 ui::ok(".gitignore created");
156 }
157
158 println!();
160 println!("{}", style("Next steps:").bold());
161 if base_url.contains("localhost") {
162 println!(
163 " {} to start a local server",
164 style("cellstate start").cyan()
165 );
166 }
167 println!(
168 " Copy {} to {} and add your API key",
169 style(".cellstate/.env.example").dim(),
170 style(".env").cyan()
171 );
172 println!(
173 " Then: {}",
174 style(r#"from cellstate import SimpleMemory # or import { SimpleMemory } from '@cellstate/sdk'"#).dim()
175 );
176 println!();
177
178 Ok(())
179}