2026-05-24 08:50:48 -07:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
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-03-01 14:36:51 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct PortMapping {
|
|
|
|
|
pub host_port: u16,
|
|
|
|
|
pub container_port: u16,
|
|
|
|
|
#[serde(default = "default_protocol")]
|
|
|
|
|
pub protocol: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_protocol() -> String {
|
|
|
|
|
"tcp".to_string()
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 08:58:13 -07:00
|
|
|
fn default_full_permissions() -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 11:35:42 -07:00
|
|
|
/// `use_shared_auth_token` defaults to **on**: once the user has run
|
|
|
|
|
/// `claude setup-token` once, every existing Anthropic-backend project should
|
|
|
|
|
/// pick the token up without being edited one by one. Projects deliberately
|
|
|
|
|
/// pinned to their own `claude login` identity opt out.
|
|
|
|
|
fn default_use_shared_auth_token() -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 10:51:34 -07:00
|
|
|
/// How much autonomy Claude Code is granted inside the container.
|
|
|
|
|
///
|
|
|
|
|
/// Maps onto Claude Code CLI flags — see [`PermissionMode::cli_args`], which is
|
|
|
|
|
/// the single definition of that mapping and must be used by every call site.
|
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub enum PermissionMode {
|
|
|
|
|
/// Read-only planning mode.
|
|
|
|
|
Plan,
|
|
|
|
|
/// Claude Code's own default behavior (prompts for permission).
|
|
|
|
|
#[default]
|
|
|
|
|
Default,
|
|
|
|
|
/// Auto-accept file edits, prompt for everything else.
|
|
|
|
|
AcceptEdits,
|
|
|
|
|
/// Skip all permission prompts.
|
|
|
|
|
Bypass,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PermissionMode {
|
|
|
|
|
/// The CLI flags this mode adds to a `claude` invocation.
|
|
|
|
|
/// Defined once here so every call site stays in sync.
|
|
|
|
|
pub fn cli_args(&self) -> Vec<String> {
|
|
|
|
|
match self {
|
|
|
|
|
PermissionMode::Plan => vec!["--permission-mode".to_string(), "plan".to_string()],
|
|
|
|
|
PermissionMode::Default => Vec::new(),
|
|
|
|
|
PermissionMode::AcceptEdits => {
|
|
|
|
|
vec!["--permission-mode".to_string(), "acceptEdits".to_string()]
|
|
|
|
|
}
|
|
|
|
|
PermissionMode::Bypass => vec!["--dangerously-skip-permissions".to_string()],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The wire value used for the `TRIPLE_C_PERMISSION_MODE` container env var.
|
|
|
|
|
/// Matches the serde `camelCase` representation.
|
|
|
|
|
pub fn as_env_value(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
PermissionMode::Plan => "plan",
|
|
|
|
|
PermissionMode::Default => "default",
|
|
|
|
|
PermissionMode::AcceptEdits => "acceptEdits",
|
|
|
|
|
PermissionMode::Bypass => "bypass",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 08:46:03 -07:00
|
|
|
/// Settings for Claude Code CLI behavior inside the container.
|
|
|
|
|
/// These map to Claude Code env vars and ~/.claude/settings.json entries.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
|
|
|
|
pub struct ClaudeCodeSettings {
|
|
|
|
|
/// TUI rendering mode: None = default, Some("fullscreen") = flicker-free alt-screen
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub tui_mode: Option<String>,
|
|
|
|
|
/// Effort level: None = default, Some("low"|"medium"|"high")
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub effort: Option<String>,
|
|
|
|
|
/// Disable auto-scroll in fullscreen TUI mode
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub auto_scroll_disabled: bool,
|
|
|
|
|
/// Enable focus mode (collapsed tool output)
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub focus_mode: bool,
|
|
|
|
|
/// Show thinking summaries in responses
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub show_thinking_summaries: bool,
|
|
|
|
|
/// Enable session recap when returning to a session
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub enable_session_recap: bool,
|
|
|
|
|
/// Strip credentials from subprocess environments
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub env_scrub: bool,
|
|
|
|
|
/// Enable 1-hour prompt cache TTL (vs default 5-minute)
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub prompt_caching_1h: bool,
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2026-03-12 09:26:58 -07:00
|
|
|
#[serde(alias = "auth_mode")]
|
|
|
|
|
pub backend: Backend,
|
2026-02-27 14:29:40 +00:00
|
|
|
pub bedrock_config: Option<BedrockConfig>,
|
2026-03-11 13:05:52 -07:00
|
|
|
pub ollama_config: Option<OllamaConfig>,
|
2026-08-09 16:55:28 -07:00
|
|
|
#[serde(default, alias = "llama_cpp_config")]
|
|
|
|
|
pub llamacpp_config: Option<LlamaCppConfig>,
|
2026-03-13 06:16:05 -07:00
|
|
|
#[serde(alias = "litellm_config")]
|
|
|
|
|
pub openai_compatible_config: Option<OpenAiCompatibleConfig>,
|
2026-02-27 04:29:51 +00:00
|
|
|
pub allow_docker_access: bool,
|
2026-03-05 19:32:04 -08:00
|
|
|
#[serde(default)]
|
2026-05-01 12:58:54 -07:00
|
|
|
pub sandbox_mode_enabled: bool,
|
|
|
|
|
#[serde(default)]
|
2026-03-05 19:32:04 -08:00
|
|
|
pub mission_control_enabled: bool,
|
2026-08-09 11:35:42 -07:00
|
|
|
/// Opt in to the auth bridge: while the container runs, its loopback
|
|
|
|
|
/// listeners are mirrored onto the host's loopback so browser OAuth
|
|
|
|
|
/// callbacks (`claude login`, `fly login`, `aws sso login`) can reach them.
|
|
|
|
|
/// Purely host-side — it deliberately has no container-recreation label,
|
|
|
|
|
/// because toggling it changes nothing about the container itself.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub auth_bridge_enabled: bool,
|
2026-08-09 16:55:28 -07:00
|
|
|
/// Opt in to the browser-view pane, which watches and takes over the
|
|
|
|
|
/// browser Claude drives with Playwright inside the container. Purely
|
|
|
|
|
/// host-side like `auth_bridge_enabled`, so it likewise has no
|
|
|
|
|
/// container-recreation label.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub browser_view_enabled: bool,
|
2026-08-09 11:35:42 -07:00
|
|
|
/// Use the shared, long-lived Claude Code OAuth token (from
|
|
|
|
|
/// `claude setup-token`, held in the OS keychain) for this project instead
|
|
|
|
|
/// of requiring its own `claude login`. Only consulted when `backend` is
|
|
|
|
|
/// [`Backend::Anthropic`] and a token has actually been stored.
|
|
|
|
|
///
|
|
|
|
|
/// Defaults to **true** so a single `setup-token` run covers every project;
|
|
|
|
|
/// turn it off to pin a project to the identity it logged in with inside
|
|
|
|
|
/// its own container.
|
|
|
|
|
#[serde(default = "default_use_shared_auth_token")]
|
|
|
|
|
pub use_shared_auth_token: bool,
|
2026-08-09 10:51:34 -07:00
|
|
|
/// Legacy binary permission flag. Superseded by `permission_mode`, but kept
|
|
|
|
|
/// because it is the value already stored in users' `projects.json`; it is
|
|
|
|
|
/// the fallback in `effective_permission_mode()` so old projects keep
|
|
|
|
|
/// behaving identically without a data migration.
|
2026-03-13 08:58:13 -07:00
|
|
|
#[serde(default = "default_full_permissions")]
|
|
|
|
|
pub full_permissions: bool,
|
2026-08-09 10:51:34 -07:00
|
|
|
/// Per-project permission mode. `None` means "not set yet" → fall back to
|
|
|
|
|
/// the legacy `full_permissions` flag.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub permission_mode: Option<PermissionMode>,
|
2026-02-27 04:29:51 +00:00
|
|
|
pub ssh_key_path: Option<String>,
|
2026-08-10 10:40:21 -07:00
|
|
|
/// Per-project override for the corporate CA certificate path (file or
|
|
|
|
|
/// directory). Blank falls back to `AppSettings::ca_cert_path`.
|
|
|
|
|
///
|
|
|
|
|
/// `#[serde(default)]` rather than a required field: every project stored
|
|
|
|
|
/// before this existed must keep loading.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub ca_cert_path: Option<String>,
|
2026-03-05 17:39:34 -08:00
|
|
|
#[serde(skip_serializing, default)]
|
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)]
|
2026-03-01 14:36:51 +00:00
|
|
|
pub port_mappings: Vec<PortMapping>,
|
|
|
|
|
#[serde(default)]
|
2026-02-27 18:39:20 -08:00
|
|
|
pub claude_instructions: Option<String>,
|
2026-03-04 08:57:12 -08:00
|
|
|
#[serde(default)]
|
2026-04-16 08:46:03 -07:00
|
|
|
pub claude_code_settings: Option<ClaudeCodeSettings>,
|
2026-05-24 08:50:48 -07:00
|
|
|
/// User-defined display names for terminal tabs, keyed by session id.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub renamed_session_names: HashMap<String, 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,
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 09:26:58 -07:00
|
|
|
/// Which AI model backend/provider the project uses.
|
|
|
|
|
/// - `Anthropic`: Direct Anthropic API (user runs `claude login` inside the container)
|
|
|
|
|
/// - `Bedrock`: AWS Bedrock with per-project AWS credentials
|
|
|
|
|
/// - `Ollama`: Local or remote Ollama server
|
2026-08-09 16:55:28 -07:00
|
|
|
/// - `LlamaCpp`: A local or remote `llama-server` (llama.cpp)
|
|
|
|
|
/// - `OpenAiCompatible`: Any endpoint that speaks the Anthropic Messages API
|
|
|
|
|
/// (e.g. LiteLLM). See [`Backend::uses_custom_endpoint`].
|
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
2026-02-27 04:29:51 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2026-03-12 09:26:58 -07:00
|
|
|
pub enum Backend {
|
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-03-11 13:05:52 -07:00
|
|
|
Ollama,
|
2026-08-09 16:55:28 -07:00
|
|
|
/// Serialises as `llama_cpp`; the aliases accept the spellings a
|
|
|
|
|
/// hand-edited `projects.json` is likely to contain.
|
|
|
|
|
#[serde(alias = "llamacpp", alias = "llama-cpp", alias = "llama.cpp")]
|
|
|
|
|
LlamaCpp,
|
2026-03-13 06:16:05 -07:00
|
|
|
#[serde(alias = "lite_llm", alias = "litellm")]
|
|
|
|
|
OpenAiCompatible,
|
2026-02-27 04:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-12 09:26:58 -07:00
|
|
|
impl Default for Backend {
|
2026-02-27 04:29:51 +00:00
|
|
|
fn default() -> Self {
|
2026-03-01 03:10:57 +00:00
|
|
|
Self::Anthropic
|
2026-02-27 04:29:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 16:55:28 -07:00
|
|
|
impl Backend {
|
|
|
|
|
/// Whether this backend points Claude Code at a non-Anthropic HTTP endpoint
|
|
|
|
|
/// via `ANTHROPIC_BASE_URL`.
|
|
|
|
|
///
|
|
|
|
|
/// Those endpoints serve whatever model *they* were started with, so
|
|
|
|
|
/// Claude Code's built-in `opus`/`sonnet`/`haiku`/`fable` aliases resolve to
|
|
|
|
|
/// Anthropic model ids the server has never heard of. Every backend for
|
|
|
|
|
/// which this returns `true` therefore gets the
|
|
|
|
|
/// `ANTHROPIC_DEFAULT_*_MODEL` alias vars pinned to the configured model —
|
|
|
|
|
/// see `docker::container::compute_model_aliases`.
|
|
|
|
|
///
|
|
|
|
|
/// Bedrock is deliberately excluded: it talks to AWS, which does host the
|
|
|
|
|
/// real Anthropic model ids, so Claude Code's own defaults are correct
|
|
|
|
|
/// there. Anthropic is excluded for the same reason.
|
|
|
|
|
pub fn uses_custom_endpoint(&self) -> bool {
|
|
|
|
|
matches!(
|
|
|
|
|
self,
|
|
|
|
|
Backend::Ollama | Backend::LlamaCpp | Backend::OpenAiCompatible
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-03-05 17:39:34 -08:00
|
|
|
#[serde(skip_serializing, default)]
|
2026-02-27 14:29:40 +00:00
|
|
|
pub aws_access_key_id: Option<String>,
|
2026-03-05 17:39:34 -08:00
|
|
|
#[serde(skip_serializing, default)]
|
2026-02-27 14:29:40 +00:00
|
|
|
pub aws_secret_access_key: Option<String>,
|
2026-03-05 17:39:34 -08:00
|
|
|
#[serde(skip_serializing, default)]
|
2026-02-27 14:29:40 +00:00
|
|
|
pub aws_session_token: Option<String>,
|
|
|
|
|
pub aws_profile: Option<String>,
|
2026-03-05 17:39:34 -08:00
|
|
|
#[serde(skip_serializing, default)]
|
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-05-01 12:58:54 -07:00
|
|
|
/// Optional value for the `ANTHROPIC_BEDROCK_SERVICE_TIER` env var
|
|
|
|
|
/// (e.g. "priority"). Empty/None means leave unset.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub service_tier: Option<String>,
|
2026-02-27 14:29:40 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-11 13:05:52 -07:00
|
|
|
/// Ollama configuration for a project.
|
2026-08-09 16:55:28 -07:00
|
|
|
/// Ollama natively implements the Anthropic Messages API at `/v1/messages`.
|
2026-03-11 13:05:52 -07:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct OllamaConfig {
|
|
|
|
|
/// The base URL of the Ollama server (e.g., "http://host.docker.internal:11434" or "http://192.168.1.100:11434")
|
|
|
|
|
pub base_url: String,
|
|
|
|
|
/// Optional model override (e.g., "qwen3.5:27b")
|
|
|
|
|
pub model_id: Option<String>,
|
2026-08-09 16:55:28 -07:00
|
|
|
/// Optional override for the model the `haiku` alias resolves to.
|
|
|
|
|
/// Blank falls back to `model_id`. See [`Backend::uses_custom_endpoint`].
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub haiku_model_id: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// llama.cpp (`llama-server`) configuration for a project.
|
|
|
|
|
///
|
|
|
|
|
/// `llama-server` natively implements the Anthropic Messages API at
|
|
|
|
|
/// `POST /v1/messages` (plus `/v1/messages/count_tokens`), so Claude Code can
|
|
|
|
|
/// talk to it directly through `ANTHROPIC_BASE_URL` — exactly like Ollama, with
|
|
|
|
|
/// no translation shim.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct LlamaCppConfig {
|
|
|
|
|
/// The base URL of the llama-server instance. `llama-server`'s default
|
|
|
|
|
/// listen port is 8080 (`--port PORT | port to listen (default: 8080)`).
|
|
|
|
|
pub base_url: String,
|
|
|
|
|
/// Optional model override. `llama-server` serves whatever model it was
|
|
|
|
|
/// started with, so this is mostly the id Claude Code should *say* it is
|
|
|
|
|
/// using — but it is also what the model aliases are pinned to.
|
|
|
|
|
pub model_id: Option<String>,
|
|
|
|
|
/// Optional override for the model the `haiku` alias resolves to.
|
|
|
|
|
/// Blank falls back to `model_id`.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub haiku_model_id: Option<String>,
|
2026-03-11 13:05:52 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-13 06:16:05 -07:00
|
|
|
/// OpenAI Compatible endpoint configuration for a project.
|
2026-08-09 16:55:28 -07:00
|
|
|
///
|
|
|
|
|
/// Despite the name (kept for backward compatibility with existing
|
|
|
|
|
/// `projects.json` data), the endpoint must implement the **Anthropic Messages
|
|
|
|
|
/// API** — Claude Code only ever speaks `POST /v1/messages`. Gateways such as
|
|
|
|
|
/// LiteLLM expose an Anthropic-shaped route and work; a bare
|
|
|
|
|
/// `/v1/chat/completions` server does not.
|
2026-03-11 13:05:52 -07:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
2026-03-13 06:16:05 -07:00
|
|
|
pub struct OpenAiCompatibleConfig {
|
2026-08-09 16:55:28 -07:00
|
|
|
/// The base URL of the endpoint (e.g., "http://host.docker.internal:4000" or "https://api.example.com")
|
2026-03-11 13:05:52 -07:00
|
|
|
pub base_url: String,
|
2026-08-09 16:55:28 -07:00
|
|
|
/// API key for the endpoint
|
2026-03-11 13:05:52 -07:00
|
|
|
#[serde(skip_serializing, default)]
|
|
|
|
|
pub api_key: Option<String>,
|
|
|
|
|
/// Optional model override
|
|
|
|
|
pub model_id: Option<String>,
|
2026-08-09 16:55:28 -07:00
|
|
|
/// Optional override for the model the `haiku` alias resolves to.
|
|
|
|
|
/// Blank falls back to `model_id`.
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub haiku_model_id: Option<String>,
|
2026-03-11 13:05:52 -07:00
|
|
|
}
|
|
|
|
|
|
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,
|
2026-03-12 09:26:58 -07:00
|
|
|
backend: Backend::default(),
|
2026-02-27 14:29:40 +00:00
|
|
|
bedrock_config: None,
|
2026-03-11 13:05:52 -07:00
|
|
|
ollama_config: None,
|
2026-08-09 16:55:28 -07:00
|
|
|
llamacpp_config: None,
|
2026-03-13 06:16:05 -07:00
|
|
|
openai_compatible_config: None,
|
2026-02-27 04:29:51 +00:00
|
|
|
allow_docker_access: false,
|
2026-05-01 12:58:54 -07:00
|
|
|
sandbox_mode_enabled: false,
|
2026-03-05 19:32:04 -08:00
|
|
|
mission_control_enabled: false,
|
2026-08-09 11:35:42 -07:00
|
|
|
auth_bridge_enabled: false,
|
2026-08-09 16:55:28 -07:00
|
|
|
browser_view_enabled: false,
|
2026-08-09 11:35:42 -07:00
|
|
|
use_shared_auth_token: default_use_shared_auth_token(),
|
2026-03-13 08:58:13 -07:00
|
|
|
full_permissions: false,
|
2026-08-09 10:51:34 -07:00
|
|
|
permission_mode: None,
|
2026-02-27 04:29:51 +00:00
|
|
|
ssh_key_path: None,
|
2026-08-10 10:40:21 -07:00
|
|
|
ca_cert_path: None,
|
2026-02-27 04:29:51 +00:00
|
|
|
git_token: None,
|
|
|
|
|
git_user_name: None,
|
|
|
|
|
git_user_email: None,
|
2026-02-27 18:39:20 -08:00
|
|
|
custom_env_vars: Vec::new(),
|
2026-03-01 14:36:51 +00:00
|
|
|
port_mappings: Vec::new(),
|
2026-02-27 18:39:20 -08:00
|
|
|
claude_instructions: None,
|
2026-04-16 08:46:03 -07:00
|
|
|
claude_code_settings: None,
|
2026-05-24 08:50:48 -07:00
|
|
|
renamed_session_names: HashMap::new(),
|
2026-02-27 04:29:51 +00:00
|
|
|
created_at: now.clone(),
|
|
|
|
|
updated_at: now,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 10:51:34 -07:00
|
|
|
/// The permission mode to actually use for this project.
|
|
|
|
|
/// Falls back to the legacy `full_permissions` boolean when the newer
|
|
|
|
|
/// `permission_mode` field has never been set.
|
|
|
|
|
pub fn effective_permission_mode(&self) -> PermissionMode {
|
|
|
|
|
self.permission_mode.unwrap_or(if self.full_permissions {
|
|
|
|
|
PermissionMode::Bypass
|
|
|
|
|
} else {
|
|
|
|
|
PermissionMode::Default
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 04:29:51 +00:00
|
|
|
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
|
|
|
}
|