captchaforge 0.2.38

[DO NOT USE — UNDER ACTIVE DEVELOPMENT, NOT PRODUCTION-READY] Captcha solver scaffolding for Firefox + BiDi-driven browsers. The architecture is in place (vendor solvers, retry-loop iframe walking, VLM provider abstraction, real-WAF bench harness) but the live-vendor success rate is still 0% — Cloudflare Turnstile / hCaptcha / reCAPTCHA detect us at a TLS / BiDi fingerprint layer that no flag-based stealth has cleared. Watch the repo; do not depend on this for any real workload.
Documentation
//! Standalone Prometheus exporter — wire into your existing metrics scrape.
//!
//! ```sh
//! cargo run --example 08_prometheus_exporter -- 0.0.0.0:9090
//! curl http://localhost:9090/metrics
//! ```
//!
//! Binds a tiny TCP listener that responds to `GET /metrics` with
//! the captchaforge counter telemetry in Prometheus
//! exposition format.

use captchaforge::solver::CaptchaType;
use captchaforge::telemetry::{MetricsTelemetry, SolveEvent, SolveOutcome, SolverTelemetry};
use std::io::{Read, Write};
use std::sync::Arc;

fn main() -> std::io::Result<()> {
    let bind = std::env::args()
        .nth(1)
        .unwrap_or_else(|| "127.0.0.1:9090".to_string());

    // Pre-populate some events so the metric exposition isn't empty.
    let metrics = Arc::new(MetricsTelemetry::new());
    let kind = captchaforge::detect::DetectedCaptcha::Turnstile;
    let method = captchaforge::solver::SolveMethod::AutoPass;
    for vendor in &[CaptchaType::CloudflareTurnstile, CaptchaType::HCaptcha] {
        for i in 0..20 {
            let outcome = if i < 15 {
                SolveOutcome::Success
            } else {
                SolveOutcome::Failure
            };
            let evt = SolveEvent::new("Demo", vendor, &kind, "x", outcome, 100, Some(0.8), &method);
            metrics.record(&evt);
        }
    }

    let listener = std::net::TcpListener::bind(&bind)?;
    println!("captchaforge prometheus exporter listening on http://{bind}/metrics");
    for stream in listener.incoming() {
        let mut stream = stream?;
        let mut buf = [0u8; 1024];
        let _ = stream.read(&mut buf);
        let body = format!(
            "{}\n{}",
            metrics.snapshot().to_prometheus(),
            "metrics_placeholder".to_string()
        );
        let response = format!(
            "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\nContent-Length: {}\r\n\r\n{}",
            body.len(),
            body
        );
        let _ = stream.write_all(response.as_bytes());
    }
    Ok(())
}