2026-02-27 04:29:51 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
2026-02-27 18:39:20 -08:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct EnvVar {
|
|
|
|
|
pub key: String,
|
|
|
|
|
pub value: String,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 21:18:33 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct ProjectPath {
|
|
|
|
|
pub host_path: String,
|
|
|
|
|
pub mount_name: String,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 04:29:51 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Project {
|
|
|
|
|
pub id: String,
|
|
|
|
|
pub name: String,
|
2026-02-28 21:18:33 +00:00
|
|
|
pub paths: Vec<ProjectPath>,
|
2026-02-27 04:29:51 +00:00
|
|
|
pub container_id: Option<String>,
|
|
|
|
|
pub status: ProjectStatus,
|
|
|
|
|
pub auth_mode: AuthMode,
|
2026-02-27 14:29:40 +00:00
|
|
|
pub bedrock_config: Option<BedrockConfig>,
|
2026-02-27 04:29:51 +00:00
|
|
|
pub allow_docker_access: bool,
|
|
|
|
|
pub ssh_key_path: Option<String>,
|
2026-02-28 20:42:55 +00:00
|
|
|
#[serde(skip_serializing)]
|
2026-02-27 04:29:51 +00:00
|
|
|
pub git_token: Option<String>,
|
|
|
|
|
pub git_user_name: Option<String>,
|
|
|
|
|
pub git_user_email: Option<String>,
|
2026-02-27 18:39:20 -08:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub custom_env_vars: Vec<EnvVar>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub claude_instructions: Option<String>,
|
2026-02-27 04:29:51 +00:00
|
|
|
pub created_at: String,
|
|
|
|
|
pub updated_at: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum ProjectStatus {
|
|
|
|
|
Stopped,
|
|
|
|
|
Starting,
|
|
|
|
|
Running,
|
|
|
|
|
Stopping,
|
|
|
|
|
Error,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// How the project authenticates with Claude.
|
2026-03-01 03:10:57 +00:00
|
|
|
/// - `Anthropic`: User runs `claude login` inside the container (OAuth via Anthropic Console,
|
|
|
|
|
/// persisted in the config volume)
|
2026-02-27 14:29:40 +00:00
|
|
|
/// - `Bedrock`: Uses AWS Bedrock with per-project AWS credentials
|
2026-02-27 04:29:51 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
pub enum AuthMode {
|
2026-03-01 03:10:57 +00:00
|
|
|
/// Backward compat: old projects stored as "login" or "api_key" map to Anthropic.
|
|
|
|
|
#[serde(alias = "login", alias = "api_key")]
|
|
|
|
|
Anthropic,
|
2026-02-27 14:29:40 +00:00
|
|
|
Bedrock,
|
2026-02-27 04:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for AuthMode {
|
|
|
|
|
fn default() -> Self {
|
2026-03-01 03:10:57 +00:00
|
|
|
Self::Anthropic
|
2026-02-27 04:29:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 14:29:40 +00:00
|
|
|
/// How Bedrock authenticates with AWS.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
pub enum BedrockAuthMethod {
|
|
|
|
|
StaticCredentials,
|
|
|
|
|
Profile,
|
|
|
|
|
BearerToken,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for BedrockAuthMethod {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::StaticCredentials
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// AWS Bedrock configuration for a project.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct BedrockConfig {
|
|
|
|
|
pub auth_method: BedrockAuthMethod,
|
|
|
|
|
pub aws_region: String,
|
2026-02-28 20:42:55 +00:00
|
|
|
#[serde(skip_serializing)]
|
2026-02-27 14:29:40 +00:00
|
|
|
pub aws_access_key_id: Option<String>,
|
2026-02-28 20:42:55 +00:00
|
|
|
#[serde(skip_serializing)]
|
2026-02-27 14:29:40 +00:00
|
|
|
pub aws_secret_access_key: Option<String>,
|
2026-02-28 20:42:55 +00:00
|
|
|
#[serde(skip_serializing)]
|
2026-02-27 14:29:40 +00:00
|
|
|
pub aws_session_token: Option<String>,
|
|
|
|
|
pub aws_profile: Option<String>,
|
2026-02-28 20:42:55 +00:00
|
|
|
#[serde(skip_serializing)]
|
2026-02-27 14:29:40 +00:00
|
|
|
pub aws_bearer_token: Option<String>,
|
|
|
|
|
pub model_id: Option<String>,
|
|
|
|
|
pub disable_prompt_caching: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 04:29:51 +00:00
|
|
|
impl Project {
|
2026-02-28 21:18:33 +00:00
|
|
|
pub fn new(name: String, paths: Vec<ProjectPath>) -> Self {
|
2026-02-27 04:29:51 +00:00
|
|
|
let now = chrono::Utc::now().to_rfc3339();
|
|
|
|
|
Self {
|
|
|
|
|
id: uuid::Uuid::new_v4().to_string(),
|
|
|
|
|
name,
|
2026-02-28 21:18:33 +00:00
|
|
|
paths,
|
2026-02-27 04:29:51 +00:00
|
|
|
container_id: None,
|
|
|
|
|
status: ProjectStatus::Stopped,
|
|
|
|
|
auth_mode: AuthMode::default(),
|
2026-02-27 14:29:40 +00:00
|
|
|
bedrock_config: None,
|
2026-02-27 04:29:51 +00:00
|
|
|
allow_docker_access: false,
|
|
|
|
|
ssh_key_path: None,
|
|
|
|
|
git_token: None,
|
|
|
|
|
git_user_name: None,
|
|
|
|
|
git_user_email: None,
|
2026-02-27 18:39:20 -08:00
|
|
|
custom_env_vars: Vec::new(),
|
|
|
|
|
claude_instructions: None,
|
2026-02-27 04:29:51 +00:00
|
|
|
created_at: now.clone(),
|
|
|
|
|
updated_at: now,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn container_name(&self) -> String {
|
|
|
|
|
format!("triple-c-{}", self.id)
|
|
|
|
|
}
|
2026-02-28 21:18:33 +00:00
|
|
|
|
|
|
|
|
/// Migrate a project JSON value from old single-`path` format to new `paths` format.
|
|
|
|
|
/// If the value already has `paths`, it is returned unchanged.
|
|
|
|
|
pub fn migrate_from_value(mut val: serde_json::Value) -> serde_json::Value {
|
|
|
|
|
if let Some(obj) = val.as_object_mut() {
|
|
|
|
|
if obj.contains_key("paths") {
|
|
|
|
|
return val;
|
|
|
|
|
}
|
|
|
|
|
if let Some(path_val) = obj.remove("path") {
|
|
|
|
|
let path_str = path_val.as_str().unwrap_or("").to_string();
|
|
|
|
|
let mount_name = path_str
|
|
|
|
|
.trim_end_matches(['/', '\\'])
|
|
|
|
|
.rsplit(['/', '\\'])
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap_or("workspace")
|
|
|
|
|
.to_string();
|
|
|
|
|
let project_path = serde_json::json!([{
|
|
|
|
|
"host_path": path_str,
|
|
|
|
|
"mount_name": if mount_name.is_empty() { "workspace".to_string() } else { mount_name },
|
|
|
|
|
}]);
|
|
|
|
|
obj.insert("paths".to_string(), project_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val
|
|
|
|
|
}
|
2026-02-27 04:29:51 +00:00
|
|
|
}
|