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
+48
-2
@@ -158,7 +158,53 @@ pub fn router(app: AppState) -> Router {
|
||||
let index = web_dir.join("index.html");
|
||||
let static_files = tower_http::services::ServeDir::new(&web_dir).fallback(tower_http::services::ServeFile::new(index));
|
||||
|
||||
Router::new().nest("/api", api).fallback_service(static_files).with_state(app)
|
||||
Router::new()
|
||||
.nest("/api", api)
|
||||
.route("/cli/install.sh", get(cli_install_script))
|
||||
.route("/cli/download/{target}", get(cli_download))
|
||||
.fallback_service(static_files)
|
||||
.with_state(app)
|
||||
}
|
||||
|
||||
fn cli_dir() -> PathBuf {
|
||||
std::env::var("CODECRAFT_CLI_DIR").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from("/app/cli"))
|
||||
}
|
||||
|
||||
/// The server's public base URL (config, else from the request).
|
||||
fn public_base(app: &App, headers: &axum::http::HeaderMap) -> String {
|
||||
let cfg = app.config();
|
||||
if !cfg.auth.public_url.is_empty() {
|
||||
return cfg.auth.public_url.trim_end_matches('/').to_string();
|
||||
}
|
||||
let host = headers.get("x-forwarded-host").or(headers.get(axum::http::header::HOST)).and_then(|v| v.to_str().ok()).unwrap_or("localhost:8787");
|
||||
let proto = headers.get("x-forwarded-proto").and_then(|v| v.to_str().ok()).unwrap_or("http");
|
||||
format!("{proto}://{host}")
|
||||
}
|
||||
|
||||
/// `curl -fsSL <server>/cli/install.sh | sh` installs the terminal client.
|
||||
async fn cli_install_script(State(app): State<AppState>, headers: axum::http::HeaderMap) -> Response {
|
||||
let base = public_base(&app, &headers);
|
||||
let script = include_str!("../resources-cli/install.sh").replace("__CODECRAFT_URL__", &base);
|
||||
([(axum::http::header::CONTENT_TYPE, "text/x-shellscript; charset=utf-8")], script).into_response()
|
||||
}
|
||||
|
||||
/// Static CLI binaries, e.g. /cli/download/linux-x86_64.
|
||||
async fn cli_download(Path(target): Path<String>) -> Response {
|
||||
if !target.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
|
||||
return (StatusCode::BAD_REQUEST, "invalid target").into_response();
|
||||
}
|
||||
let path = cli_dir().join(format!("codecraft-{target}"));
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(bytes) => (
|
||||
[
|
||||
(axum::http::header::CONTENT_TYPE, "application/octet-stream".to_string()),
|
||||
(axum::http::header::CONTENT_DISPOSITION, "attachment; filename=\"codecraft\"".to_string()),
|
||||
],
|
||||
bytes,
|
||||
)
|
||||
.into_response(),
|
||||
Err(_) => (StatusCode::NOT_FOUND, format!("no codecraft build for {target} on this server")).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a session the user may at least read.
|
||||
@@ -1022,7 +1068,7 @@ async fn backup(Extension(user): Extension<User>, State(app): State<AppState>) -
|
||||
|
||||
/// Liveness for monitoring (no auth needed – see auth::is_public).
|
||||
async fn health(State(app): State<AppState>) -> Json<Value> {
|
||||
Json(json!({"ok": true, "version": env!("CARGO_PKG_VERSION"), "sandbox": crate::sandbox::available(), "providers": app.registry().providers.len()}))
|
||||
Json(json!({"ok": true, "version": crate::update::VERSION, "commit": crate::update::commit(), "sandbox": crate::sandbox::available(), "providers": app.registry().providers.len()}))
|
||||
}
|
||||
|
||||
// --- budget -------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user