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:
Richard ZinkandClaude Opus 5.5 committed 2026-11-09 00:13:06 +01:00
1 parent aa1f8cf016
commit 6dffcb501a
52 files changed
+6477 -765

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
//! UI texts (English and German).
use std::sync::atomic::{AtomicBool, Ordering};
static GERMAN: AtomicBool = AtomicBool::new(false);
pub fn set_lang(lang: &str) {
GERMAN.store(lang.starts_with("de"), Ordering::Relaxed);
}
pub fn system_lang() -> String {
std::env::var("LC_ALL").or_else(|_| std::env::var("LANG")).unwrap_or_default().chars().take(2).collect()
}
/// (key, English, German)
const TEXTS: &[(&str, &str, &str)] = &[
("placeholder", "Message… (/ commands, @ files, Shift+Enter new line)", "Nachricht… (/ Befehle, @ Dateien, Shift+Enter neue Zeile)"),
("busy_placeholder", "Message is queued and sent after this turn…", "Nachricht wird nach diesem Schritt gesendet…"),
("permission", "Permission required", "Freigabe erforderlich"),
("perm_keys", "[y] allow once [a] always in this session [n] reject [f] reject with feedback", "[y] einmal erlauben [a] immer in dieser Session [n] ablehnen [f] ablehnen mit Hinweis"),
("feedback", "Tell the agent what to do instead (Enter to send):", "Was soll der Agent stattdessen tun? (Enter zum Senden):"),
("question", "Question", "Frage"),
("question_keys", "↑↓ choose · Enter confirm · type your own answer · Esc dismiss", "↑↓ wählen · Enter bestätigen · eigene Antwort tippen · Esc verwerfen"),
("thinking", "Thinking", "Denkt nach"),
("working", "Working", "Arbeitet"),
("interrupt", "Esc to interrupt", "Esc unterbricht"),
("mode", "mode", "Modus"),
("context", "context", "Kontext"),
("quit_again", "Press Ctrl+C again to quit", "Nochmal Ctrl+C zum Beenden"),
("aborted", "interrupted", "unterbrochen"),
("no_model", "No model available – run /setup", "Kein Modell verfügbar – /setup ausführen"),
("help", "Commands", "Befehle"),
("choose_model", "Choose a model", "Modell wählen"),
("choose_mode", "Choose a mode", "Modus wählen"),
("choose_session", "Resume a session", "Session fortsetzen"),
("choose_theme", "Choose a theme", "Theme wählen"),
("filter", "type to filter", "tippen zum Filtern"),
("queued", "queued", "in der Warteschlange"),
("welcome", "Welcome to CodeCraft", "Willkommen bei CodeCraft"),
("setup_title", "Set up CodeCraft", "CodeCraft einrichten"),
("setup_intro", "Where should the models come from? You can add more later with /setup.", "Woher sollen die Modelle kommen? Weitere kannst du später mit /setup hinzufügen."),
("setup_server", "Sign in to a CodeCraft server", "Bei einem CodeCraft-Server anmelden"),
("setup_provider", "Add a model provider (API key or local)", "Modell-Anbieter hinzufügen (API-Key oder lokal)"),
("setup_done", "Done", "Fertig"),
("server_url", "Server URL", "Server-Adresse"),
("username", "Username", "Benutzer"),
("password", "Password", "Passwort"),
("api_key", "API key", "API-Key"),
("base_url", "Endpoint URL", "Endpunkt-URL"),
("provider_name", "Name for this provider", "Name für diesen Anbieter"),
("detected", "running locally", "läuft lokal"),
("saved", "Saved.", "Gespeichert."),
("models_found", "models found", "Modelle gefunden"),
("enter_next", "Enter = next · Esc = back", "Enter = weiter · Esc = zurück"),
];
pub fn t(key: &str) -> &'static str {
let de = GERMAN.load(Ordering::Relaxed);
TEXTS.iter().find(|(k, _, _)| *k == key).map(|(_, en, d)| if de { *d } else { *en }).unwrap_or("?")
}