camber-cli 0.1.6

CLI, project scaffolding, and config-driven proxy for Camber
Documentation
use std::fs;
use std::io;

pub const LLMS_TXT: &str = r#"# Camber API Reference
# A Rust runtime for IO-bound services. Async handlers on a Tokio core.

## Import Patterns

```rust
use camber::http::{self, Request, Response, Router};
use camber::{spawn, JoinHandle, RuntimeError};
use camber::channel;
use camber::runtime::RuntimeBuilder;
```

## Entry Points

```rust
// Serve HTTP on address with router
http::serve("0.0.0.0:8080", router) -> Result<(), RuntimeError>

// Custom runtime configuration
RuntimeBuilder::new()
    .worker_threads(4)
    .shutdown_timeout(Duration::from_secs(30))
    .run(|| { ... }) -> Result<T, RuntimeError>

// Run closure in default runtime (non-HTTP use cases)
runtime::run(|| { ... }) -> Result<T, RuntimeError>
```

## Handler Signature

```rust
// All handlers are async — closure returns a future
router.get("/path", |req: &Request| async { Response::text(200, "ok") });

// Named function form
fn handler(req: &Request) -> impl Future<Output = Response> {
    async { Response::text(200, "ok") }
}

// Handlers can return Result for automatic error mapping
fn handler(req: &Request) -> impl Future<Output = Result<Response, RuntimeError>> {
    async { Ok(Response::text(200, "ok")) }
}

// With path params — clone/extract before async block
router.get("/users/:id", |req: &Request| {
    let id = req.param("id").unwrap_or("missing").to_owned();
    async move { Response::text(200, &id) }
});
```

## Request

```rust
req.method()                     -> &str       // "GET", "POST", etc.
req.path()                       -> &str       // URL path without query
req.param("id")                  -> Option<&str>   // Path parameter :id
req.query("page")                -> Option<&str>   // Query string value
req.query_all("tag")             -> impl Iterator   // All values for key
req.form("email")                -> Option<&str>   // Form field
req.header("Authorization")      -> via headers()
req.headers()                    -> impl Iterator<Item = (&str, &str)>
req.body()                       -> &str           // UTF-8 body
req.body_bytes()                 -> &[u8]          // Raw bytes
req.json::<T>()                  -> Result<T, RuntimeError>
```

## Response

```rust
Response::text(200, "hello")             // text/plain
Response::json(200, &value)              // application/json (impl Serialize)
Response::bytes(200, data)               // raw bytes
Response::empty(204)                     // no body
resp.with_header("X-Custom", "value")    // add header
resp.with_content_type("text/html")      // set content type
```

## Router

```rust
let mut router = Router::new();
router.get("/path", handler);                      // async handler
router.post("/path", handler);
router.put("/path", handler);
router.delete("/path", handler);
router.get("/users/:id", handler);                  // path parameters
router.static_files("/assets", "./public");         // static file serving
router.proxy("/api", "http://backend:3000");        // reverse proxy
router.use_middleware(logger);                      // async middleware
```

## Middleware

```rust
// All middleware is async — next.call(req) returns a future
router.use_middleware(|req: &Request, next: Next| async move {
    let start = std::time::Instant::now();
    let resp = next.call(req).await;
    println!("{} {} {}ms", req.method(), req.path(), start.elapsed().as_millis());
    resp
});

// First registered = outermost. Can short-circuit by not calling next.
```

## Structured Concurrency

```rust
// Spawn a background task
let handle: JoinHandle<T> = spawn(|| { ... });
let result = handle.join()?;
handle.cancel(); // request cancellation

// Channels with cancellation support
let (tx, rx) = channel::new::<String>();   // bounded, default capacity
let (tx, rx) = channel::bounded::<i32>(64); // explicit capacity
tx.send(value)?;       // Err on closed or cancelled
let val = rx.recv()?;  // blocks, respects cancellation
for val in rx.iter() { ... } // cancel-aware iterator
```

## Outbound HTTP

```rust
// All client functions are async
let resp = http::get("https://api.example.com/data").await?;
let resp = http::post("https://api.example.com/data", body).await?;
let resp = http::post_json("https://api.example.com/data", json_str).await?;

// With timeouts
let resp = http::client()
    .connect_timeout(Duration::from_secs(5))
    .get("https://api.example.com/data").await?;
```

## Error Handling

```rust
// RuntimeError variants: Io, ChannelClosed, Timeout, Cancelled,
//   TaskPanicked, Http, BadRequest, Database, Tls
// Use ? operator for propagation. BadRequest maps to 400 in handlers.

// Handler returning Result — async closure form
router.get("/user", |req: &Request| async {
    let user: User = req.json()?;  // BadRequest on parse failure
    Ok(Response::json(200, &user))
});
```

## Feature-Gated APIs

```rust
// WebSocket (feature = "ws")
router.ws("/chat", |req, mut conn: WsConn| {
    while let Some(msg) = conn.recv() {
        conn.send(&msg)?;
    }
    Ok(())
});

// gRPC (feature = "grpc")
router.grpc(GrpcRouter::new().add_service(my_service));
```

## Avoid

- Do not call `tokio::spawn` directly. Use `camber::spawn` for cancellation support.
- Do not use `unwrap()` or `panic!()`. Return `Result` and use `?`.
- Do not use `std::thread::spawn`. Use `camber::spawn` for runtime integration.
- Do not create a Tokio runtime manually. Camber manages the runtime.
"#;

pub fn run() -> io::Result<()> {
    fs::write("llms.txt", LLMS_TXT)?;
    println!("Wrote llms.txt");
    Ok(())
}