Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/e2e-requesty.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: E2E Requesty

on:
pull_request:
types: [labeled, opened, synchronize, reopened]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
e2e:
if: contains(github.event.pull_request.labels.*.name, 'test-requesty')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: e2e
run: cargo test --test e2e_requesty -- --ignored --nocapture
env:
REQUESTY_API_KEY: ${{ secrets.REQUESTY_API_KEY }}
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ cargo install --git https://github.com/jehna/humanify
### Usage

```shell
humanify <openai|gemini|anthropic|ollama|openrouter> [FLAGS] <INPUT>
humanify <openai|gemini|anthropic|ollama|openrouter|requesty> [FLAGS] <INPUT>
```

* `<INPUT>` is a file path or `-` for stdin.
Expand Down Expand Up @@ -243,6 +243,24 @@ Default model: `openai/gpt-oss-120b`. For free-tier usage:
humanify openrouter obfuscated.js -m qwen/qwen3-coder:free
```

### Requesty mode

[Requesty](https://requesty.ai/) provides an OpenAI-compatible router across
many backend models via a single API key.

You'll need a Requesty API key. Sign up at https://requesty.ai/.

```shell
export REQUESTY_API_KEY=your-token
humanify requesty obfuscated.js -o readable.js
```

Default model: `openai/gpt-4o-mini`. Override with `-m`:

```shell
humanify requesty obfuscated.js -m openai/gpt-4o-mini
```

## Features

* Uses LLMs to get smart suggestions to rename variable and function names, and
Expand Down Expand Up @@ -270,7 +288,7 @@ CI runs `cargo fmt --check`, `cargo clippy -D warnings`, `cargo test` on
every PR. Provider e2e suites run against `gemini` (every PR, free tier)
and `ollama` (every PR, runs on the GitHub runner). Other providers'
e2e suites are label-gated (`test-openai`, `test-anthropic`,
`test-openrouter`) to avoid burning API credits on every PR.
`test-openrouter`, `test-requesty`) to avoid burning API credits on every PR.

## Star History

Expand Down
2 changes: 2 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ pub mod ollama;
pub mod openai;
pub mod openrouter;
pub mod preset;
pub mod requesty;

pub use anthropic::run as run_anthropic;
pub use gemini::run as run_gemini;
pub use ollama::run as run_ollama;
pub use openai::run as run_openai;
pub use openrouter::run as run_openrouter;
pub use requesty::run as run_requesty;
56 changes: 56 additions & 0 deletions src/cli/requesty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::path::PathBuf;

use crate::cli::preset::{run_preset, PresetArgs, PresetDefaults, ProviderKind};

pub const DEFAULTS: PresetDefaults = PresetDefaults {
base_url: "https://router.requesty.ai/v1",
model: "openai/gpt-4o-mini",
api_key_env: "REQUESTY_API_KEY",
provider_kind: ProviderKind::OpenAICompat,
timeout_seconds: 60,
};

pub struct Args {
pub input: String,
pub output: Option<PathBuf>,
pub model: Option<String>,
pub api_key: Option<String>,
pub base_url: Option<String>,
pub context_size: usize,
pub json_mode: String,
pub verbose: bool,
pub timeout_seconds: Option<u64>,
}

impl From<Args> for PresetArgs {
fn from(a: Args) -> Self {
PresetArgs {
input: a.input,
output: a.output,
model: a.model,
api_key: a.api_key,
base_url: a.base_url,
context_size: a.context_size,
json_mode: a.json_mode,
verbose: a.verbose,
timeout_seconds: a.timeout_seconds,
}
}
}

pub fn run(args: Args) -> i32 {
run_preset(args.into(), DEFAULTS)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn requesty_defaults_constants() {
assert_eq!(DEFAULTS.base_url, "https://router.requesty.ai/v1");
assert_eq!(DEFAULTS.model, "openai/gpt-4o-mini");
assert_eq!(DEFAULTS.api_key_env, "REQUESTY_API_KEY");
assert!(matches!(DEFAULTS.provider_kind, ProviderKind::OpenAICompat));
}
}
18 changes: 17 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::{error::ErrorKind, Parser, Subcommand};
use humanify::cli::{anthropic, gemini, ollama, openai, openrouter};
use humanify::cli::{anthropic, gemini, ollama, openai, openrouter, requesty};
use std::path::PathBuf;

const EXIT_CLI_USAGE: i32 = 64;
Expand All @@ -22,6 +22,7 @@ enum Commands {
Anthropic(SubArgs),
Ollama(SubArgs),
Openrouter(SubArgs),
Requesty(SubArgs),
}

#[derive(Parser)]
Expand Down Expand Up @@ -133,6 +134,20 @@ fn into_openrouter_args(a: SubArgs) -> openrouter::Args {
}
}

fn into_requesty_args(a: SubArgs) -> requesty::Args {
requesty::Args {
input: a.input,
output: a.output,
model: a.model,
api_key: a.api_key,
base_url: a.base_url,
context_size: a.context_size,
json_mode: a.json_mode,
verbose: a.verbose,
timeout_seconds: a.timeout_seconds,
}
}

fn main() {
let cli = match Cli::try_parse() {
Ok(c) => c,
Expand All @@ -153,6 +168,7 @@ fn main() {
Commands::Anthropic(args) => anthropic::run(into_anthropic_args(args)),
Commands::Ollama(args) => ollama::run(into_ollama_args(args)),
Commands::Openrouter(args) => openrouter::run(into_openrouter_args(args)),
Commands::Requesty(args) => requesty::run(into_requesty_args(args)),
};

if exit_code != 0 {
Expand Down
15 changes: 15 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ impl JudgeConfig {
timeout: Duration::from_secs(60),
}
}

pub fn requesty(model: &str) -> Self {
Self {
kind: JudgeKind::OpenAICompat,
base_url: "https://router.requesty.ai/v1".to_string(),
api_key: std::env::var("REQUESTY_API_KEY").ok(),
model: model.to_string(),
timeout: Duration::from_secs(60),
}
}
}

pub async fn judge_with(cfg: &JudgeConfig, code: &str) -> anyhow::Result<String> {
Expand Down Expand Up @@ -308,6 +318,11 @@ impl HumanifyCmdBuilder {
self
}

pub fn requesty(mut self) -> Self {
self.subcommand = "requesty".to_string();
self
}

pub fn model(mut self, name: &str) -> Self {
self.model = Some(name.to_string());
self
Expand Down
16 changes: 16 additions & 0 deletions tests/e2e_requesty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
mod common;

use common::{given, humanify, JudgeConfig};

#[tokio::test]
#[ignore]
async fn unminifies_example_file_with_requesty() {
given("fixtures/example.min.js")
.judged_by(JudgeConfig::requesty("openai/gpt-4o-mini"))
.judge_says_minified()
.await
.when(humanify().requesty().model("openai/gpt-4o-mini"))
.await
.then_judge_says_one_of(&["EXCELLENT", "GOOD"])
.await;
}