Add terminal client, memory, model fallback, version and install improvements
- codecraft: full-screen terminal client (ratatui) – local tools and files, models via the CodeCraft server gateway or own providers (standalone with a guided setup), slash commands, @files, permission dialogs, resume, update - Static Linux CLI builds (x86_64/aarch64, cargo-zigbuild) served by the server: /cli/install.sh and /cli/download/<target> - Project memory (remember tool, .codecraft/memory.md) and personal memory - Model fallback when a host is unreachable or a budget is used up - Version/commit in UI and health, daily release check, "What's new" dialog - PWA: install button, maskable icon, manifest id and screenshots - Case-insensitive detection of connection errors for retries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa1f8cf016
commit
6dffcb501a
52 files changed
+6477
-765
No files matched your search
+102
@@ -0,0 +1,102 @@
|
||||
//! Local configuration (~/.config/codecraft/config.toml, readable only by the user).
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use codecraft_core::config::ProviderConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerLogin {
|
||||
pub url: String,
|
||||
pub token: String,
|
||||
#[serde(default)]
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct Config {
|
||||
/// Signed-in CodeCraft server (models, modes and rules come from there).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub server: Option<ServerLogin>,
|
||||
/// Model used for new sessions ("provider/model", server models as "server:provider/model").
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub default_model: Option<String>,
|
||||
/// Permission mode for new sessions (default: auto).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub permission_mode: Option<String>,
|
||||
/// UI language (en, de); default from the server or $LANG.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub lang: Option<String>,
|
||||
/// Theme override, e.g. "nord" and "dark" (default: your server theme).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub theme: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub theme_mode: Option<String>,
|
||||
/// Own model providers (standalone mode), same format as the server config.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub providers: BTreeMap<String, ProviderConfig>,
|
||||
}
|
||||
|
||||
pub fn config_dir() -> PathBuf {
|
||||
if let Some(x) = std::env::var_os("CODECRAFT_CONFIG_DIR") {
|
||||
return PathBuf::from(x);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Some(x) = std::env::var_os("APPDATA") {
|
||||
return PathBuf::from(x).join("codecraft");
|
||||
}
|
||||
let base = std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from).unwrap_or_else(|| home().join(".config"));
|
||||
base.join("codecraft")
|
||||
}
|
||||
|
||||
pub fn data_dir() -> PathBuf {
|
||||
if let Some(x) = std::env::var_os("CODECRAFT_DATA_DIR") {
|
||||
return PathBuf::from(x);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Some(x) = std::env::var_os("LOCALAPPDATA") {
|
||||
return PathBuf::from(x).join("codecraft");
|
||||
}
|
||||
let base = std::env::var_os("XDG_DATA_HOME").map(PathBuf::from).unwrap_or_else(|| home().join(".local").join("share"));
|
||||
base.join("codecraft")
|
||||
}
|
||||
|
||||
pub fn home() -> PathBuf {
|
||||
std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")).map(PathBuf::from).unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
fn path() -> PathBuf {
|
||||
config_dir().join("config.toml")
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Self> {
|
||||
let p = path();
|
||||
match std::fs::read_to_string(&p) {
|
||||
Ok(text) => toml::from_str(&text).with_context(|| format!("invalid {}", p.display())),
|
||||
Err(_) => Ok(Self::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
let p = path();
|
||||
std::fs::create_dir_all(p.parent().unwrap())?;
|
||||
let text = format!(
|
||||
"# CodeCraft terminal client – edit with `codecraft setup` or by hand.\n# API keys may be written as \"${{ENV_VAR}}\".\n\n{}",
|
||||
toml::to_string_pretty(self)?
|
||||
);
|
||||
std::fs::write(&p, text)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Nothing set up yet: the setup wizard runs first.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.server.is_none() && self.providers.is_empty()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user