diff --git a/.config/nextest.toml b/.config/nextest.toml index 8434bd242..450bc2254 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -13,7 +13,7 @@ medium = { max-threads = 4 } # 18-30 MB binaries [[profile.default.overrides]] filter = """ - package(fabro-api) + package(fabro-server) | package(fabro-workflows) | package(fabro-agent) | package(fabro-cli) diff --git a/AGENTS.md b/AGENTS.md index 443dce2f1..392feb1f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Rust - `cargo build --workspace` — build all crates - `cargo nextest run --workspace` — run all unit tests -- `cargo nextest run -p fabro-api` — test a single crate +- `cargo nextest run -p fabro-server` — test a single crate - `cargo nextest run -p fabro-workflows -- test_name` — run a single test - `set -a && source .env && set +a && cargo nextest run --workspace --profile e2e --run-ignored only` — run all E2E live tests (requires credentials in `.env`, see `.env.example`) - `set -a && source .env && set +a && cargo nextest run -p fabro-llm --profile e2e --run-ignored only` — run E2E tests for a single crate @@ -41,8 +41,8 @@ The OpenAPI spec at `docs/api-reference/fabro-api.yaml` is the source of truth f 1. Edit `docs/api-reference/fabro-api.yaml` 2. `cargo build -p fabro-api-types` — build.rs regenerates Rust types via typify -3. Write/update handler in `lib/crates/fabro-api/src/server.rs`, add route to `build_router()` -4. `cargo nextest run -p fabro-api` — conformance test catches spec/router drift +3. Write/update handler in `lib/crates/fabro-server/src/server.rs`, add route to `build_router()` +4. `cargo nextest run -p fabro-server` — conformance test catches spec/router drift 5. `cd lib/packages/fabro-api-client && bun run generate` — regenerates TypeScript Axios client ## Architecture @@ -53,7 +53,7 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as - **fabro-cli** — CLI entry point. Commands: `run`, `exec`, `serve`, `validate`, `parse`, `cp`, `model`, `doctor`, `init`, `install`, `ps`, `system prune`, `llm` - **fabro-workflows** — Core workflow engine. Parses Graphviz graphs, runs stages, manages checkpoints/resume, hooks, retros, and human-in-the-loop interactions - **fabro-agent** — AI coding agent with tool use (Bash, Read, Write, Edit, Glob, Grep, WebFetch). `Sandbox` trait abstracts execution environments -- **fabro-api** — Axum HTTP server. Routes for runs, sessions, models, completions, usage. SSE event streaming. Demo mode via header +- **fabro-server** — Axum HTTP server. Routes for runs, sessions, models, completions, usage. SSE event streaming. Demo mode via header - **fabro-exe** — SSH-based sandbox implementation (`ExeSandbox`) - **fabro-sprites** — Sprites VM sandbox implementation via `sprite` CLI - **fabro-llm** — Unified LLM client with providers: Anthropic, OpenAI, Gemini, OpenAI-compatible, plus retry/middleware/streaming diff --git a/Cargo.lock b/Cargo.lock index 9ac388476..bfe841776 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1442,64 +1442,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "fabro-api" -version = "0.176.2" -dependencies = [ - "anyhow", - "axum", - "base64", - "bytes", - "chrono", - "clap", - "dirs", - "fabro-agent", - "fabro-api-types", - "fabro-config", - "fabro-db", - "fabro-github", - "fabro-graphviz", - "fabro-hooks", - "fabro-interview", - "fabro-llm", - "fabro-model", - "fabro-retro", - "fabro-sandbox", - "fabro-store", - "fabro-types", - "fabro-util", - "fabro-workflows", - "futures-util", - "hex", - "hmac", - "http-body-util", - "hyper", - "hyper-util", - "jsonwebtoken", - "object_store", - "openapiv3", - "reqwest", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_yaml", - "sha2", - "sqlx", - "tempfile", - "tokio", - "tokio-rustls", - "tokio-stream", - "toml 0.8.23", - "tower", - "tower-service", - "tracing", - "ulid", - "uuid", - "x509-parser", -] - [[package]] name = "fabro-api-types" version = "0.176.2" @@ -1534,7 +1476,6 @@ dependencies = [ "dirs", "dotenvy", "fabro-agent", - "fabro-api", "fabro-config", "fabro-devcontainer", "fabro-git-storage", @@ -1549,6 +1490,7 @@ dependencies = [ "fabro-proctitle", "fabro-retro", "fabro-sandbox", + "fabro-server", "fabro-store", "fabro-telemetry", "fabro-types", @@ -1863,6 +1805,64 @@ dependencies = [ "uuid", ] +[[package]] +name = "fabro-server" +version = "0.176.2" +dependencies = [ + "anyhow", + "axum", + "base64", + "bytes", + "chrono", + "clap", + "dirs", + "fabro-agent", + "fabro-api-types", + "fabro-config", + "fabro-db", + "fabro-github", + "fabro-graphviz", + "fabro-hooks", + "fabro-interview", + "fabro-llm", + "fabro-model", + "fabro-retro", + "fabro-sandbox", + "fabro-store", + "fabro-types", + "fabro-util", + "fabro-workflows", + "futures-util", + "hex", + "hmac", + "http-body-util", + "hyper", + "hyper-util", + "jsonwebtoken", + "object_store", + "openapiv3", + "reqwest", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_yaml", + "sha2", + "sqlx", + "tempfile", + "tokio", + "tokio-rustls", + "tokio-stream", + "toml 0.8.23", + "tower", + "tower-service", + "tracing", + "ulid", + "uuid", + "x509-parser", +] + [[package]] name = "fabro-slack" version = "0.176.2" diff --git a/lib/crates/fabro-cli/Cargo.toml b/lib/crates/fabro-cli/Cargo.toml index 3ee033b55..08f41c32f 100644 --- a/lib/crates/fabro-cli/Cargo.toml +++ b/lib/crates/fabro-cli/Cargo.toml @@ -11,7 +11,7 @@ path = "src/main.rs" [features] default = [] -server = ["dep:fabro-api"] +server = ["dep:fabro-server"] exedev = ["fabro-sandbox/exe", "fabro-config/exedev", "fabro-workflows/exedev", "fabro-types/exedev"] sleep_inhibitor = ["dep:core-foundation"] @@ -36,7 +36,7 @@ fabro-git-storage = { path = "../fabro-git-storage" } fabro-graphviz = { path = "../fabro-graphviz" } fabro-validate = { path = "../fabro-validate" } fabro-workflows = { path = "../fabro-workflows" } -fabro-api = { path = "../fabro-api", optional = true } +fabro-server = { path = "../fabro-server", optional = true } fabro-telemetry = { path = "../fabro-telemetry" } fabro-store = { path = "../fabro-store" } fabro-types = { path = "../fabro-types" } diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs index 705395ff4..abc29dcb1 100644 --- a/lib/crates/fabro-cli/src/args.rs +++ b/lib/crates/fabro-cli/src/args.rs @@ -772,7 +772,7 @@ pub(crate) enum Commands { }, /// Start the HTTP API server #[cfg(feature = "server")] - Serve(fabro_api::serve::ServeArgs), + Serve(fabro_server::serve::ServeArgs), /// Check environment and integration health Doctor { /// Show detailed information for each check diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 26663c408..a6df3818f 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -185,7 +185,7 @@ async fn main_inner() -> (String, Result<()>) { #[cfg(feature = "server")] Commands::Serve(args) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); - fabro_api::serve::serve_command(args, styles).await?; + fabro_server::serve::serve_command(args, styles).await?; } Commands::Doctor { verbose, dry_run } => { let cli_settings = cli_config::load_cli_settings(None)?; diff --git a/lib/crates/fabro-api/Cargo.toml b/lib/crates/fabro-server/Cargo.toml similarity index 96% rename from lib/crates/fabro-api/Cargo.toml rename to lib/crates/fabro-server/Cargo.toml index 3f17e6b62..15c7b8472 100644 --- a/lib/crates/fabro-api/Cargo.toml +++ b/lib/crates/fabro-server/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "fabro-api" +name = "fabro-server" edition.workspace = true version.workspace = true license.workspace = true -description = "HTTP API server for Fabro pipelines" +description = "HTTP server for Fabro pipelines" [lib] doctest = false diff --git a/lib/crates/fabro-api/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs similarity index 100% rename from lib/crates/fabro-api/src/demo/mod.rs rename to lib/crates/fabro-server/src/demo/mod.rs diff --git a/lib/crates/fabro-api/src/error.rs b/lib/crates/fabro-server/src/error.rs similarity index 100% rename from lib/crates/fabro-api/src/error.rs rename to lib/crates/fabro-server/src/error.rs diff --git a/lib/crates/fabro-api/src/github_webhooks.rs b/lib/crates/fabro-server/src/github_webhooks.rs similarity index 100% rename from lib/crates/fabro-api/src/github_webhooks.rs rename to lib/crates/fabro-server/src/github_webhooks.rs diff --git a/lib/crates/fabro-api/src/jwt_auth.rs b/lib/crates/fabro-server/src/jwt_auth.rs similarity index 100% rename from lib/crates/fabro-api/src/jwt_auth.rs rename to lib/crates/fabro-server/src/jwt_auth.rs diff --git a/lib/crates/fabro-api/src/lib.rs b/lib/crates/fabro-server/src/lib.rs similarity index 100% rename from lib/crates/fabro-api/src/lib.rs rename to lib/crates/fabro-server/src/lib.rs diff --git a/lib/crates/fabro-api/src/serve.rs b/lib/crates/fabro-server/src/serve.rs similarity index 100% rename from lib/crates/fabro-api/src/serve.rs rename to lib/crates/fabro-server/src/serve.rs diff --git a/lib/crates/fabro-api/src/server.rs b/lib/crates/fabro-server/src/server.rs similarity index 100% rename from lib/crates/fabro-api/src/server.rs rename to lib/crates/fabro-server/src/server.rs diff --git a/lib/crates/fabro-api/src/sessions.rs b/lib/crates/fabro-server/src/sessions.rs similarity index 100% rename from lib/crates/fabro-api/src/sessions.rs rename to lib/crates/fabro-server/src/sessions.rs diff --git a/lib/crates/fabro-api/src/tls.rs b/lib/crates/fabro-server/src/tls.rs similarity index 100% rename from lib/crates/fabro-api/src/tls.rs rename to lib/crates/fabro-server/src/tls.rs diff --git a/lib/crates/fabro-api/tests/it/api.rs b/lib/crates/fabro-server/tests/it/api.rs similarity index 95% rename from lib/crates/fabro-api/tests/it/api.rs rename to lib/crates/fabro-server/tests/it/api.rs index aa9581e70..46fedcab5 100644 --- a/lib/crates/fabro-api/tests/it/api.rs +++ b/lib/crates/fabro-server/tests/it/api.rs @@ -8,10 +8,10 @@ mod mtls_e2e { use std::path::Path; use std::process::{Command, Stdio}; - use fabro_api::jwt_auth::{AuthMode, AuthStrategy}; - use fabro_api::server::{build_router, create_app_state}; - use fabro_api::server_config::TlsSettings; - use fabro_api::tls::{ClientAuth, build_rustls_config}; + use fabro_server::jwt_auth::{AuthMode, AuthStrategy}; + use fabro_server::server::{build_router, create_app_state}; + use fabro_server::server_config::TlsSettings; + use fabro_server::tls::{ClientAuth, build_rustls_config}; use fabro_workflows::pipeline::LlmSpec; use tokio::net::TcpListener; @@ -195,7 +195,7 @@ mod mtls_e2e { let router = build_router(state, auth_mode); tokio::spawn(async move { - let _ = fabro_api::tls::serve_tls(listener, tls_acceptor, router).await; + let _ = fabro_server::tls::serve_tls(listener, tls_acceptor, router).await; }); addr @@ -391,7 +391,7 @@ mod mtls_e2e { AuthStrategy::Mtls, AuthStrategy::Jwt { key: Arc::new(decoding_key), - validation: Arc::new(fabro_api::jwt_auth::jwt_validation()), + validation: Arc::new(fabro_server::jwt_auth::jwt_validation()), allowed_usernames: vec!["brynary".to_string()], }, ]); @@ -428,8 +428,8 @@ mod server_lifecycle { use axum::body::Body; use axum::http::{Request, StatusCode}; - use fabro_api::server::{build_router, create_app_state_with_registry_factory}; use fabro_interview::Interviewer; + use fabro_server::server::{build_router, create_app_state_with_registry_factory}; use fabro_workflows::handler::HandlerRegistry; use fabro_workflows::handler::agent::AgentHandler; use fabro_workflows::handler::exit::ExitHandler; @@ -471,8 +471,11 @@ mod server_lifecycle { async fn full_http_lifecycle_approve_and_complete() { let state = create_app_state_with_registry_factory(test_db().await, test_llm_spec, gate_registry); - fabro_api::server::spawn_scheduler(Arc::clone(&state)); - let app = build_router(Arc::clone(&state), fabro_api::jwt_auth::AuthMode::Disabled); + fabro_server::server::spawn_scheduler(Arc::clone(&state)); + let app = build_router( + Arc::clone(&state), + fabro_server::jwt_auth::AuthMode::Disabled, + ); // 1. Start run let req = Request::builder() @@ -568,8 +571,11 @@ mod server_lifecycle { async fn full_http_lifecycle_cancel() { let state = create_app_state_with_registry_factory(test_db().await, test_llm_spec, gate_registry); - fabro_api::server::spawn_scheduler(Arc::clone(&state)); - let app = build_router(Arc::clone(&state), fabro_api::jwt_auth::AuthMode::Disabled); + fabro_server::server::spawn_scheduler(Arc::clone(&state)); + let app = build_router( + Arc::clone(&state), + fabro_server::jwt_auth::AuthMode::Disabled, + ); // Start a run that will block at the human gate let req = Request::builder() @@ -621,7 +627,7 @@ mod sse_events { use axum::body::Body; use axum::http::{Request, StatusCode}; - use fabro_api::server::{build_router, create_app_state}; + use fabro_server::server::{build_router, create_app_state}; use http_body_util::BodyExt; use tower::ServiceExt; @@ -636,8 +642,11 @@ mod sse_events { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn sse_stream_contains_expected_event_types() { let state = create_app_state(test_db().await, test_llm_spec); - fabro_api::server::spawn_scheduler(Arc::clone(&state)); - let app = build_router(Arc::clone(&state), fabro_api::jwt_auth::AuthMode::Disabled); + fabro_server::server::spawn_scheduler(Arc::clone(&state)); + let app = build_router( + Arc::clone(&state), + fabro_server::jwt_auth::AuthMode::Disabled, + ); // Start run let req = Request::builder() @@ -765,7 +774,7 @@ mod serve_dry_run { use axum::body::Body; use axum::http::{Request, StatusCode}; - use fabro_api::server::{build_router, create_app_state}; + use fabro_server::server::{build_router, create_app_state}; use tower::ServiceExt; const MINIMAL_DOT: &str = r#"digraph Test { @@ -778,8 +787,8 @@ mod serve_dry_run { /// Build the router exactly as `serve_command` does in dry-run mode. async fn dry_run_app() -> axum::Router { let state = create_app_state(test_db().await, test_llm_spec); - fabro_api::server::spawn_scheduler(Arc::clone(&state)); - build_router(state, fabro_api::jwt_auth::AuthMode::Disabled) + fabro_server::server::spawn_scheduler(Arc::clone(&state)); + build_router(state, fabro_server::jwt_auth::AuthMode::Disabled) } async fn body_json(body: Body) -> serde_json::Value { diff --git a/lib/crates/fabro-api/tests/it/helpers.rs b/lib/crates/fabro-server/tests/it/helpers.rs similarity index 100% rename from lib/crates/fabro-api/tests/it/helpers.rs rename to lib/crates/fabro-server/tests/it/helpers.rs diff --git a/lib/crates/fabro-api/tests/it/main.rs b/lib/crates/fabro-server/tests/it/main.rs similarity index 100% rename from lib/crates/fabro-api/tests/it/main.rs rename to lib/crates/fabro-server/tests/it/main.rs diff --git a/lib/crates/fabro-api/tests/it/openapi_conformance.rs b/lib/crates/fabro-server/tests/it/openapi_conformance.rs similarity index 98% rename from lib/crates/fabro-api/tests/it/openapi_conformance.rs rename to lib/crates/fabro-server/tests/it/openapi_conformance.rs index efaea832d..4c156d60b 100644 --- a/lib/crates/fabro-api/tests/it/openapi_conformance.rs +++ b/lib/crates/fabro-server/tests/it/openapi_conformance.rs @@ -5,13 +5,13 @@ use std::collections::BTreeSet; use axum::body::Body; use axum::http::{Method, Request, StatusCode}; -use fabro_api::jwt_auth::AuthMode; -use fabro_api::server::{build_router, create_app_state}; -use fabro_api::server_config::*; use fabro_config::run::*; use fabro_config::sandbox::SandboxSettings; use fabro_hooks::*; use fabro_sandbox::daytona::*; +use fabro_server::jwt_auth::AuthMode; +use fabro_server::server::{build_router, create_app_state}; +use fabro_server::server_config::*; use tower::ServiceExt; fn load_spec() -> openapiv3::OpenAPI { diff --git a/lib/crates/fabro-api/tests/it/pagination.rs b/lib/crates/fabro-server/tests/it/pagination.rs similarity index 97% rename from lib/crates/fabro-api/tests/it/pagination.rs rename to lib/crates/fabro-server/tests/it/pagination.rs index 5625a5ad3..30987f043 100644 --- a/lib/crates/fabro-api/tests/it/pagination.rs +++ b/lib/crates/fabro-server/tests/it/pagination.rs @@ -3,8 +3,8 @@ use super::helpers::{test_db, test_llm_spec}; use axum::body::Body; use axum::http::{Request, StatusCode}; -use fabro_api::jwt_auth::AuthMode; -use fabro_api::server::{build_router, create_app_state}; +use fabro_server::jwt_auth::AuthMode; +use fabro_server::server::{build_router, create_app_state}; use tower::ServiceExt; async fn get_json(app: axum::Router, uri: &str) -> serde_json::Value {