clarence

Crates.ioclarence
lib.rsclarence
version0.1.0
created_at2025-11-25 17:39:31.88628+00
updated_at2025-11-25 17:39:31.88628+00
descriptionBuild powerful CLI tools backed by HTTP APIs
homepage
repositoryhttps://github.com/osscorplabs/clarence
max_upload_size
id1950157
size165,491
Iñigo Taibo (itaibo)

documentation

README

Clarence

Build powerful CLI tools backed by HTTP APIs in Rust.

use clarence::Clarence;

fn main() {
  Clarence::builder()
    .name("mycli")
    .base_url("https://api.example.com")
    .run();
}
mycli users list --active
# → GET https://api.example.com/users/list?active=true

Features

  • Automatic HTTP routing - Commands map to API endpoints
  • Built-in authentication - Store tokens, use in headers
  • Multi-environment support - Optional dev, staging, prod configs
  • Git context - Send repository info (working directory) as HTTP headers
  • Custom handlers - Override default behavior
  • JSON CLI actions - Let your API control the CLI (print, download, execute, storage)

Quick Start

cargo add clarence

Basic HTTP CLI

use clarence::Clarence;

fn main() {
  Clarence::builder()
    .name("mycli")
    .base_url("https://api.example.com")
    .help("My CLI tool")
    .version(env!("CARGO_PKG_VERSION"))
    .run();
}

Usage:

mycli deploy staging --force true
# → GET https://api.example.com/deploy/staging?force=true

With Authentication

use clarence::{Clarence, Context, Handler, Result, StorageRef};

fn main() {
  Clarence::builder()
    .name("mycli")
    .base_url("https://api.example.com")
    .header("Authorization", StorageRef::get("token"))
    .on("login", Handler::custom(login))
    .fallback(Handler::http().build())
    .run();
}

fn login(ctx: &mut Context) -> Result<i32> {
  let token = ctx.args.get_flag("token")
    .ok_or("Usage: mycli login --token YOUR_TOKEN")?;

  ctx.store.set("token", format!("Bearer {}", token))?;
  ctx.end().success_with("Logged in successfully")
}

Usage:

mycli login --token abc123
mycli users list
# → GET https://api.example.com/users/list (Authorization: Bearer abc123)

Custom Handlers

use clarence::{Clarence, Context, Handler, Method, Result};

fn main() {
  Clarence::builder()
    .name("mycli")
    .base_url("https://api.example.com")
    .on("login", Handler::custom(login))
    .on("deploy", Handler::http()
      .method(Method::Post)
      .build())
    .run();
}

fn login(ctx: &mut Context) -> Result<i32> {
  let user = ctx.args.positional.first().unwrap();
  println!("Logging in as {}", user);
  ctx.end().success()
}

Multi-Environment

use clarence::{Clarence, Environment, StorageRef};

fn main() {
  Clarence::builder()
    .name("mycli")
    .environments(
      Environment::builder()
        .flag("env")
        .default("dev")
        .add("prod", "https://api.prod.com", |env| {
          env.header("X-Env", "production")
        })
        .add("dev", "http://localhost:3000", |_| {})
        .build()
    )
    .run();
}

Usage:

mycli deploy             # Uses dev (default)
mycli deploy --env prod  # Uses prod

Git Context

Send git repository information as HTTP headers:

use clarence::{Clarence, Handler, Method};

fn main() {
  // Enable globally for all requests
  Clarence::builder()
    .name("mycli")
    .base_url("https://api.example.com")
    .send_git_context()  // Adds x-cli-git-* headers
    .run();

  // Or enable per-handler
  Clarence::builder()
    .name("mycli")
    .base_url("https://api.example.com")
    .on("deploy", Handler::http()
      .method(Method::Post)
      .send_git_context()  // Only for this handler
      .build())
    .run();

  // Access in custom handlers
  Handler::custom(|ctx| {
    if ctx.git.is_git_repo {
      println!("Branch: {:?}", ctx.git.branch);
      println!("Repo: {:?}", ctx.git.repo_name);
    }
    ctx.end().success()
  })
}

HTTP headers sent:

  • x-cli-git: true (or false)
  • x-cli-git-repo-url: https://github.com/user/repo.git
  • x-cli-git-repo-name: repo
  • x-cli-git-branch: main

JSON CLI Actions

Your API can control CLI behavior returning cli_actions:

{
  "cli_actions": [
    { "type": "print", "text": "✓ Deployed to production" },
    { "type": "execute", "command": "ls" },
    { "type": "download", "url": "https://...", "path": "./deploy.log" },
    { "type": "storage_set", "key": "last_deploy", "value": "prod" },
    { "type": "storage_unset", "key": "last_deploy" }
  ]
}

Enable in handler:

use clarence::{Handler, JsonCliActionsParser};

Handler::http()
  .parser(
     JsonCliActionsParser::new()
      .allow_print()
      .allow_execute() // (!) Allows commands to be executed remotely
      .allow_download()
      .allow_storage_set()
      .allow_storage_unset()
  )
  .build()

API Reference

Builder

Clarence::builder()
  .name("mycli")                              // Required
  .base_url("https://api.example.com")        // Required (unless only custom handlers)
  .header("key", "value")                     // Static header
  .header("key", StorageRef::get("k"))        // Dynamic from storage
  .send_git_context()                         // Send git info as headers
  .help("Help text")                          // Enable --help
  .version("1.0.0")                           // Enable --version
  .on("command", handler)                     // Register command
  .fallback(handler)                          // Fallback for unknown commands
  .environments(config)                       // Multi-environment
  .run()                                      // Start CLI

Handlers

// Custom function
Handler::custom(|ctx| { ... })

// HTTP request
Handler::http()
  .method(Method::Post)      // GET, POST, PUT, DELETE, PATCH
  .endpoint("/path")         // Custom endpoint
  .header("key", "value")    // Add custom header
  .parser(parser)            // Custom response parser
  .send_git_context()        // Send git info as headers
  .build()

Context

fn handler(ctx: &mut Context) -> Result<i32> {
  // Arguments
  ctx.args.command         // Vec<String>
  ctx.args.positional      // Vec<String>
  ctx.args.flags           // HashMap<String, String>
  ctx.args.get_flag("key") // Option<&String>
  ctx.args.has_flag("key") // bool

  // Storage
  ctx.store.set("key", value)?
  ctx.store.get("key")       // Option<String>
  ctx.store.unset("key")?

  // Git context
  ctx.git.is_git_repo      // bool
  ctx.git.repo_url         // Option<String>
  ctx.git.repo_name        // Option<String>
  ctx.git.branch           // Option<String>

  // Exit helpers
  ctx.end().success()               // Exit 0
  ctx.end().success_with("Done!")   // Exit 0, print
  ctx.end().error()                 // Exit 1
  ctx.end().error_with("Failed!")   // Exit 1, print error
  ctx.end().code(42)                // Custom exit code

  Ok(0)  // Or return directly
}

Examples

Run examples with:

cargo run --example complete -- --help
cargo run --example github -- login --token YOUR_TOKEN
cargo run --example environments -- deploy --env prod 

Testing

cargo test

License

MIT

Repository

https://github.com/osscorplabs/clarence

Authors

Commit count: 0

cargo fmt