[package]
name = "nab"
version = "0.10.1"
edition = "2024"
authors = ["Mikko Parkkola"]
description = "Token-optimized HTTP client for LLMs — fetches any URL as clean markdown"
license = "MIT"
rust-version = "1.93"
repository = "https://github.com/MikkoParkkola/nab"
homepage = "https://github.com/MikkoParkkola/nab"
documentation = "https://docs.rs/nab"
readme = "README.md"
keywords = ["http3", "quic", "llm", "markdown", "browser"]
categories = ["web-programming::http-client", "command-line-utilities", "development-tools"]
exclude = ["benches/", "tests/", ".github/", ".claude/", "HomebrewFormula/", "python/", "man/"]

[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }{ archive-suffix }"
bin-dir = "{ bin }{ binary-ext }"
pkg-fmt = "bin"

[dependencies]
# ═══════════════════════════════════════════════════════════════════════════════
# HTTP CLIENT - ALL ACCELERATION FEATURES
# ═══════════════════════════════════════════════════════════════════════════════
reqwest = { version = "0.13", default-features = false, features = [
    "cookies",           # Cookie jar support
    "brotli",            # Brotli decompression (20-25% better than gzip)
    "zstd",              # Zstd decompression (40% faster than brotli)
    "gzip",              # Gzip fallback
    "deflate",           # Deflate fallback
    "rustls",            # TLS via rustls-platform-verifier (native OS certs + bundled roots)
    "http2",             # HTTP/2 multiplexing (now an explicit feature in 0.13)
    "stream",            # Streaming responses
    "hickory-dns",       # DNS caching + Happy Eyeballs (IPv4/6 racing)
    "charset",           # Auto charset detection
    "blocking",          # Blocking client for auto-update initialization
    "json",              # JSON support for API responses
    "multipart",         # Multipart form data (audio upload to vLLM/OpenAI ASR endpoints)
    "socks",             # SOCKS5 proxy support (required for Tor routing)
] }

# HTTP/3 + QUIC (0-RTT connection resumption)
# Latest compatible versions as of 2026-01
quinn = { version = "0.11", optional = true }
h3 = { version = "0.0.8", optional = true }
h3-quinn = { version = "0.0.10", optional = true }
bytes = "1"                         # Required for h3

# TLS fingerprint impersonation (Chrome/Safari/Firefox via BoringSSL)
# Bypasses anti-bot TLS fingerprinting (LinkedIn HTTP 999, etc.)
#
# `rquest` was renamed to `wreq` upstream. Signatures are unchanged on the 5.x
# line, so we alias via Cargo's `package` renaming: source keeps saying
# `rquest` / `rquest_util`, Cargo resolves them to the renamed crates.
# Upstream issue: MikkoParkkola/nab#59
rquest = { package = "wreq", version = "5.3", optional = true, default-features = false, features = [
    "cookies",
    "brotli",
    "zstd",
    "gzip",
    "deflate",
    "webpki-roots",
] }
rquest-util = { package = "wreq-util", version = "2.2.6", optional = true }

# WebSocket support
tokio-tungstenite = { version = "0.29", features = ["rustls-tls-webpki-roots"] }
tungstenite = "0.29"
rustls = { version = "0.23", features = ["aws_lc_rs"] }
rustls-native-certs = "0.8"

# ═══════════════════════════════════════════════════════════════════════════════
# HTML PARSING (Browser-grade, from Servo)
# ═══════════════════════════════════════════════════════════════════════════════
# markup5ever_rcdom removed - scraper provides DOM manipulation
scraper = "0.26"                    # CSS selectors + DOM manipulation

# ═══════════════════════════════════════════════════════════════════════════════
# JAVASCRIPT ENGINE (QuickJS - 1MB, ES2020)
# ═══════════════════════════════════════════════════════════════════════════════
rquickjs = { version = "0.11", features = [
    "bindgen",           # Auto-generate bindings
    "classes",           # ES6 class support
    "futures",           # Async/Promise integration with Rust futures
    "macro",             # Convenient macros
    "parallel",          # Multi-context support
    "loader",            # Module loading
] }

# ═══════════════════════════════════════════════════════════════════════════════
# AUTHENTICATION - Passkeys/WebAuthn via 1Password CLI metadata + browser handoff
# ═══════════════════════════════════════════════════════════════════════════════
rand = "0.10"                           # Randomization for fingerprints
regex = "1"                             # OTP pattern extraction
chrono = { version = "0.4", features = ["serde"] }  # Date/time for version staleness

# ═══════════════════════════════════════════════════════════════════════════════
# ASYNC RUNTIME & UTILITIES
# ═══════════════════════════════════════════════════════════════════════════════
tokio = { version = "1", features = ["full"] }
futures = "0.3"

# ═══════════════════════════════════════════════════════════════════════════════
# SERIALIZATION
# ═══════════════════════════════════════════════════════════════════════════════
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "1.1"                            # Plugin config parsing

# ═══════════════════════════════════════════════════════════════════════════════
# WASM PROVIDER MARKETPLACE (optional — feature = "wasm-providers")
# ═══════════════════════════════════════════════════════════════════════════════
# Sandboxed WASM runtime for third-party site extractors.
# No unsafe code needed — wasmtime's safe API is used throughout.
wasmtime = { version = "43", optional = true, default-features = false, features = [
    "cranelift",         # JIT compiler backend
    "runtime",           # Core runtime
    "component-model",   # WIT Component Model (bindgen!, Linker<T>, Component)
] }

# ═══════════════════════════════════════════════════════════════════════════════
# CONTENT PROCESSING
# ═══════════════════════════════════════════════════════════════════════════════
html2md = "0.2"                     # HTML to Markdown
url = "2"                           # URL parsing
addr = "0.15"                       # eTLD+1 via Mozilla's public suffix list
urlencoding = "2"                   # URL encoding for forms
readability = { version = "0.3", default-features = false }  # Mozilla-style article extraction

# OOXML parsing (Google Docs/Sheets/Slides export)
zip = { version = "8.4", default-features = false, features = ["deflate"] }
roxmltree = "0.21"

# PDF extraction (optional - requires pdfium dynamic library)
pdfium-render = { version = "0.9", default-features = true, optional = true }

# Browser automation via Chrome DevTools Protocol (optional)
chromiumoxide = { version = "0.9", optional = true, default-features = false, features = ["rustls", "zip8"] }

# ═══════════════════════════════════════════════════════════════════════════════
# CROSS-PLATFORM ASR BACKENDS (optional — analyze-sherpa / analyze-whisper features)
# ═══════════════════════════════════════════════════════════════════════════════
# sherpa-onnx: Parakeet TDT v3 ONNX runtime, 26-language EU set, ~30× RTFx CPU
# Install model: `nab models fetch sherpa-onnx`
sherpa-onnx = { version = "1.12", optional = true }
# whisper-rs: whisper-large-v3-turbo GGUF, 99-language universal fallback
# Install model: `nab models fetch whisper`
whisper-rs = { version = "0.16", optional = true }
# WAV decoding — shared by both backends (also optional, activated alongside them)
hound = { version = "3", optional = true }

# ═══════════════════════════════════════════════════════════════════════════════
# ERROR HANDLING & LOGGING
# ═══════════════════════════════════════════════════════════════════════════════
thiserror = "2"
async-trait = "0.1"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# ═══════════════════════════════════════════════════════════════════════════════
# UTILITIES
# ═══════════════════════════════════════════════════════════════════════════════
uuid = { version = "1", features = ["v4"] }

# ═══════════════════════════════════════════════════════════════════════════════
# ARENA ALLOCATION (for HTTP response buffering)
# ═══════════════════════════════════════════════════════════════════════════════
bumpalo = { version = "3.16", features = ["collections"] }
tempfile = "3"

# ═══════════════════════════════════════════════════════════════════════════════
# CLI (for testing)
# ═══════════════════════════════════════════════════════════════════════════════
clap = { version = "4", features = ["derive"] }
http = "1.4.0"
dirs = "6.0.0"
rust-mcp-sdk = { version = "0.9", default-features = false, features = ["server", "macros", "stdio", "streamable-http", "hyper-server"] }

# ═══════════════════════════════════════════════════════════════════════════════
# NATIVE COOKIE DECRYPTION (macOS: AES-128-CBC + PBKDF2-SHA1 + Keychain)
# ═══════════════════════════════════════════════════════════════════════════════
aes = "0.9"                         # AES-128 block cipher (RustCrypto)
cbc = { version = "0.2", features = ["block-padding"] }  # CBC mode + PKCS7 unpadding
hmac = "0.13"                       # HMAC-SHA1 PRF for Chromium cookie key derivation
sha1 = "0.11"                       # SHA-1 for PBKDF2 (Chrome/Brave use SHA1)
sha2 = "0.11"                       # SHA-256 for v24+ domain-integrity strip
hex = "0.4"                         # Hex encode/decode for sqlite3 blob round-trip
ego-tree = "0.11"
# which is cross-platform (Windows/macOS/Linux) — finds ffmpeg/streamlink in PATH
which = "8"
reqwest_cookie_store = "0.10"          # Persistent named-session cookie jars
cookie_store = "0.22"                  # Cookie serialization for encrypted session files
aes-gcm = "0.10.3"                     # AES-256-GCM for nab-owned session encryption
argon2 = "0.5.3"                       # Argon2id KDF for session data keys
scrypt = { version = "0.11", default-features = false, features = ["std"] }  # PoW KDF for AWS WAF replay solver
base64 = "0.22"                        # Base64 encoding for WAF challenge responses
webbrowser = { version = "1", optional = true }  # Cross-platform browser launcher for WAF escape hatch

[target.'cfg(target_os = "macos")'.dependencies]
security-framework = "3.5"         # macOS Keychain API (transitive via rustls, make direct)
objc2 = "0.6"                      # Objective-C runtime bindings
objc2-vision = { version = "0.3.2", features = ["VNRecognizeTextRequest", "VNRequest", "VNRequestHandler", "VNObservation", "VNTypes"] }
objc2-foundation = { version = "0.3.2", features = ["NSArray", "NSError", "NSString", "NSData", "NSDictionary"] }
objc2-core-graphics = { version = "0.3.2", features = ["CGImage"] }

[features]
default = ["cli", "http3", "impersonate", "analyze", "browser-launcher", "js-dom-full"]
# js-dom-full is default-on — extended DOM shim for WAF challenge scripts.
# Disable with: cargo build --no-default-features --features cli,http3,impersonate,analyze,browser-launcher
# Base ASR trait + audio extraction — no extra runtime deps.
# FluidAudio is invoked via subprocess (`fluidaudiocli` binary), not as a Rust dep.
# Install with: `nab models fetch fluidaudio` or build manually from
# https://github.com/FluidInference/FluidAudio
analyze = []
cli = []
# HTTP/3 + QUIC - enabled by default for maximum performance
# Disable with: cargo build --no-default-features --features cli
http3 = ["quinn", "h3", "h3-quinn"]
# PDF to Markdown conversion via pdfium (Chromium's PDF library)
# Requires pdfium dynamic library on the system
pdf = ["pdfium-render"]
# Browser automation for SPA login and CAPTCHA handling (optional)
# Requires running Chrome/Chromium with --remote-debugging-port=9222
browser = ["chromiumoxide"]
# Extended DOM shim for WAF challenge JS (navigator/performance/crypto.subtle/canvas)
# Default-on so lean builds that need the stubs stay green without extra flags.
js-dom-full = []
# Cross-platform default-browser launcher (used by waf browser escape hatch)
browser-launcher = ["webbrowser"]
# TLS fingerprint impersonation — bypasses anti-bot TLS fingerprinting
# BoringSSL-backed Chrome/Safari/Firefox TLS profiles
impersonate = ["rquest", "rquest-util"]
# Cross-platform ASR backends (opt-in — not in default to keep the binary lean)
# Requires downloading model weights via `nab models fetch sherpa-onnx`
analyze-sherpa = ["analyze", "dep:sherpa-onnx", "dep:hound"]
# Requires downloading model weights via `nab models fetch whisper`
analyze-whisper = ["analyze", "dep:whisper-rs", "dep:hound"]
# WASM provider marketplace — install third-party extractors as .wasm modules.
# Disabled by default to keep the default binary lean; opt-in with:
#   cargo build --features wasm-providers
wasm-providers = ["wasmtime"]

[dev-dependencies]
criterion = "0.8"
tokio-test = "0.4"
assert_cmd = "2"
predicates = "3"
# Used in WASM provider tests to compile WAT → WASM bytes without enabling
# the full wasmtime `wat` feature in production builds.
wat = { version = "1", optional = false }

[[bin]]
name = "nab"
path = "src/main.rs"
required-features = ["cli"]

[[bin]]
name = "nab-mcp"
path = "src/bin/mcp_server/main.rs"

[[bench]]
name = "arena_benchmark"
harness = false

[[bench]]
name = "content_bench"
harness = false

[[bench]]
name = "router_bench"
harness = false

[profile.release]
lto = true
codegen-units = 1
# panic = "abort"  # Disabled - conflicts with some deps
strip = true
opt-level = 3

[lints.clippy]
pedantic = { level = "warn", priority = -1 }
# Allow common pedantic false positives
module_name_repetitions = "allow"
must_use_candidate = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
# unnecessary_wraps fires on `fn foo() -> Result<T>` shims (e.g. cfg-gated
# stub variants of fallible helpers, or PoW solvers that are infallible
# today but reserved for future error returns). The Result is part of the
# stable API; rewriting it would force every caller to match a new shape.
unnecessary_wraps = "allow"
