All notable changes to NadirClaw will be documented in this file.
nadirclaw claude hook— structural views for file reads Claude Code would truncate. Claude Code serves at most ~25,000 tokens perReadand truncates the rest, so a large module arrives as a partial page and the agent either misses everything below the cut or pages through the file at full cost. The new opt-inPreToolUsehook answers those reads with a declaration-level view instead: every definition, decorator, docstring excerpt and module constant, each tagged with its original source lines. No model call, no network, no archive — the file is untouched on disk, so recovery is a narrowed re-read. This is separate fromoptimize/compress, which rewrite the messages array inside the proxy; the hook runs in the agent before the file enters the conversation, so it works without routing through NadirClaw. Only whole-file reads of.py/.pyipast the cap are served; narrowed reads, other languages, unparsable syntax, files under the cap and modules above 4 MiB pass through untouched.installpreserves any otherPreToolUsehooks and is idempotent;uninstallremoves only NadirClaw's entry, andnadirclaw claude uninstallnow clears it too.
- The prompt cache ignored every request parameter except the model and message text, so it could answer a request with a response that does not satisfy it (#90). The key was
sha256(model + [role, content]), while the request surface forwarded upstream also carriestools,tool_choice,response_format,reasoning_effort,thinking,max_tokens,temperature,top_p,n, and arbitrary provider extras. Two requests with the same messages therefore shared a cache entry: a plain chat answer could be served to a later request asking forresponse_format: {"type": "json_schema"}or fortool_calls, and a response generated under a highmax_tokenscould be served to a request that asked for a low one — silently overriding the client's contract, since the cache sits in front of the provider call. The key now includes every response-shaping field (request_cache_params()collects the declared ones plus everything inmodel_extraexceptstream), and message normalization keepstool_call_id/name/tool_callsso tool-result turns with identical text no longer collide. Non-serializable extras fall back toreprrather than raising. - The prompt cache keyed multi-modal messages on their text only, so the same question about a different image was a cache hit.
_normalize_messagereduced aChatMessagethroughtext_content(), which concatenates thetype: "text"parts and dropsimage_url/ attachment parts entirely —"What is in this image?"plus a photo of a cat and the same wording plus a photo of a dog hashed to one key, and the second request was answered with the first image's description without a provider call. Multi-modal content (acontentlist) is now folded into the key whole; plain-text messages key exactly as before.
NADIRCLAW_PREFER_ENV_KEYS— put provider env vars ahead of stored credentials. The resolution chain runs OpenClaw stored token → NadirClaw stored token → env var, which is right on a laptop and backwards on a server: a stored personal OAuth or subscription token carries that person's rate limit, so every request the router proxies is billed and throttled against one human. On a machine that also runs Claude Code there was no way to override it short of deleting a credentials file the box still needs. Set the flag and the environment moves to the front, falling back to the stored credential when the relevant env var is unset. Default off, so existing installs are unchanged.get_credential()andget_credential_source()now share one_env_credential()implementation, sonadirclaw statuscannot reportoauthwhile the server is actually sending the env key.
/v1/messagesforwarded parameters the routed model cannot accept, so cheap tiers 400 on every request — clients pick request parameters from the model id they can see, which behind the router is an alias (nadir-auto), so they assume the newest capabilities. When routing then selected an older, cheaper model, the request carried parameters that model rejects and every request failed (#83). The passthrough now reconciles the request against the 400 it gets back and retries within the same request, bounded to 4 attempts:thinking: {"type":"adaptive"}is rewritten to{"type":"enabled","budget_tokens":N}, an unsupportedefforthint is dropped, andsystem/developerturns are folded into the top-levelsystemfield (the same fix already applied to the OAuth completion path in 0.21.1). Each fix is remembered per model, so the discovery costs one round trip per model per process instead of one per request. Applied fixes are recorded inrequests.jsonlasmodifiers_appliedentries and signalled by anX-NadirClaw-Params-Reconciledresponse header. Verified against a live Anthropic subscription:claude-haiku-4-5went from 3 failed upstream calls per Claude Code request to a single successful one./v1/chat/completionshad the same 400 on its Anthropic OAuth path — the reconcile above only covered/v1/messages, so OpenClaw and Codex users routed to an older model still hitadaptive thinking is not supported on this model(#83). The direct Anthropic call behind the OpenAI-compatible endpoint now runs the same bounded reconcile loop and shares the per-model cache, and the fixes it applies are recorded asmodifiers_appliedon the request log. Two smaller corrections to the same mechanism: a reconciler that matched a 400 but declined to rewrite anything is no longer cached against the model, and the final attempt of the loop no longer applies a fix it has no round trip left to send.- Clamping
max_tokenscould strandthinking.budget_tokensabove it, turning one recoverable 400 into an unrecoverable one — Anthropic requires1024 <= budget_tokens < max_tokens, but the reconcile loop loweredmax_tokensto the routed model's ceiling (#73) without revisiting a thinking budget the client had sized for the ceiling it thought it had. A request withmax_tokens: 100000andbudget_tokens: 60000routed to a model capped lower was clamped into a body that 400s onbudget_tokens, an error no reconciler matches, so the loop gave up and surfaced the failure. The clamp now lowers the budget to fit under the new ceiling, or dropsthinkingoutright when no value clears Anthropic's 1024 floor. Reachable both from a client-supplied budget and from the adaptive downgrade above, whose budget is sized before a later attempt clampsmax_tokens. servecould not start without a tty. On first run it asked "No configuration found. Run setup wizard?" unconditionally. With no ttyclick.confirmraisesAbortand the process exits 1, so the shippedDockerfile(CMD ["nadirclaw", "serve", "--host", "0.0.0.0"]) could never boot a fresh container, and the same failure hit systemd units and CI. The prompt is now gated onsys.stdin.isatty(); headless starts fall through to the existing "Starting with defaults" path.- Python 3.10 installs were broken by litellm 1.98.0. That release imports
NotRequiredfromtypinginllms/anthropic/experimental_pass_through/context_management/editors/compact.py, buttyping.NotRequiredonly exists on 3.11+, soimport nadirclawraisedImportErroron every fresh 3.10 install — while litellm still declaresRequires-Python: >=3.10. The dependency is now capped below 1.98.0 for Python < 3.11 only; 3.11 and 3.12 continue to track the latest litellm. The cap comes off once upstream fixes the import.
- CI ran no server tests.
.github/workflows/ci.ymlhad excludedtests/test_server.pysince the workflow was first added, so every test covering/v1/messages,/v1/chat/completions, routing headers, request logging, and the parameter reconcile above was skipped on every push and pull request. The exclusion is removed; the file passes on 3.10–3.12.
- License changed from MIT to the PolyForm Noncommercial License 1.0.0. NadirClaw is now free for any noncommercial use (personal, research, education, evaluation, and noncommercial organizations). Commercial use requires a separate commercial license, available via getnadir.com. This applies to new versions; releases previously published under MIT remain available under MIT. The bundled
wide_deep_asym_v3classifier weights and thecascade-verifier-v1snapshot are now released under the same noncommercial terms.
v3+complex-gate is the default wide-deep routing. The bundled wide-deep classifier now defaults to thev3checkpoint under a Neyman-Pearson complex gate (complex_gate_v1.pt, τ=0.12) with a head medium/simple split, matching the routing that runs in Nadir Pro. Unlikeasym,v3keeps a realP(simple), and the head split under-routes less than the legacy companion LR. The gate is on by default forv3and off forasym/symmetric, so legacy behaviour is intact. Tunable viaNADIR_COMPLEX_GATE=0,NADIR_GATE_THRESHOLD, andNADIR_MS_SPLIT=companion. Measured on RouterArena (n=2,479): 8.2% miss-complex, ~41% cost reduction vs always-premium. NadirClaw's global default classifier staysbinary; see MODEL_CARD.md.
- Opt-in Claude Code identity injection for OAuth tokens (
NADIRCLAW_CLAUDE_CODE_IDENTITY=1) — Anthropic gates premium models (Sonnet/Opus) behind subscription/OAuth tokens (sk-ant-oat*) unless the request leads with the official Claude Code identity system block. The real Claude Code client always sends it; raw API/SDK callers omit it and get a barerate_limit_erroron those models while Haiku works (#74). When enabled,/v1/messagesand the OAuth completion path prepend"You are Claude Code, Anthropic's official CLI for Claude."as the firstsystemblock — only for Bearer/OAuth tokens (no effect onsk-ant-api*keys), only when not already present, preserving any caller-supplied system prompt after it. The decision is recorded asclaude_code_identityon the request log. Default off, since it changes the system prompt the model sees.
- OAuth completion path sent
systemturns as chat messages — the direct Anthropic OAuth call in/v1/chat/completionsforwardedrole: "system"messages inside themessagesarray, which Anthropic's/v1/messagesAPI rejects (system must be a top-level field). System/developer turns are now collected into the top-levelsystemfield before forwarding (#74).
/v1/messages/count_tokensendpoint — Anthropic-native clients (Claude Code, the officialanthropicSDK) callcount_tokensto size requests before sending; this previously 404'd, so clients silently fell back to approximate local token estimation. The new route resolves the model through the same router as/v1/messagesand forwards to Anthropic's realcount_tokens, returning{"input_tokens": N}verbatim. Non-billable, so it is excluded from cost/budget recording (#72).
/v1/messagestraffic was invisible to metrics and budget —record_requestshort-circuited ontype != "completion", dropping every/v1/messageslog entry (type="messages"). Since Claude Code and the Anthropic SDKs talk to/v1/messages, all of that traffic was missing from Prometheus counters and the budget tracker. The recorder now acceptsmessagesentries, and the/v1/messageshandler computes cost via the budget tracker and stamps status/latency/token counts on both the streaming (usage recovered from the SSEmessage_start/message_deltaevents) and non-streaming paths (#71).max_tokenswas not reconciled against the routed model's output ceiling — when the router rewrotemodelto a tier whose max-output is lower than the client-suppliedmax_tokens, Anthropic returned an intermittent 400 that was proxied straight back./v1/messagesnow detects amax_tokens: N > M400, clampsmax_tokensto the reported ceilingM, and retries once (both streaming and non-streaming), tagging the non-streaming response withX-NadirClaw-MaxTokens-Clamped: true(#73).nadirclaw testfailed for Claude subscription tokens — the command calledlitellm.completion()directly, which sendssk-ant-oat*subscription/OAuth tokens asx-api-keyinstead ofAuthorization: Bearer+ theoauth-2025-04-20beta header the server uses. It now probes Anthropic models through the same OAuth path as the running server, sonadirclaw testreflects real server behavior (#74).
- Context-optimizer compression upgrades (#65):
- Pluggable backend —
NADIRCLAW_OPTIMIZE_BACKENDselectsnative(default, built-in stdlib pipeline) orheadroom(opt-in, delegates to the Apache-2.0headroom-aipackage viapip install nadirclaw[headroom]). Headroom is lazy and fail-open: if it is not installed or raises, the optimizer transparently falls back tonativeand the request never fails. Per-request override viaoptimize_backendin the body. - Progressive (staged) compression —
--optimize progressive/NADIRCLAW_OPTIMIZE=progressiveruns an escalation ladder (native_safe → native_aggressive → headroom_structural → headroom_ml) that stops as soon asNADIRCLAW_OPTIMIZE_TARGET_TOKENSis met. With no budget set it stops afternative_aggressive(dependency-free, lossless); Headroom stages are skipped silently whenheadroom-aiis absent; the lossy ML stage runs only whenNADIRCLAW_OPTIMIZE_ALLOW_LOSSYis on. Tunable viaNADIRCLAW_OPTIMIZE_MAX_STAGE. New library entrypointnadirclaw.optimize.compress_progressive(). - Columnar JSON-array packing (
json_array_pack, aggressive mode) — rewrites homogeneous arrays of same-keyed objects (DB results, API list responses, large tool outputs) into a header plus one value-array per row, emitting each key once. Information-lossless and deterministically reversible; ~68% vs pretty-printed JSON. Never runs insafemode. - Native CCR (
nadirclaw/ccr.py) — deterministic offload +nadir_retrievefetch-back loop that moves oversized content out of the prompt behind a retrieve handle, fully reversible because the originals are kept server-side. Library-only for now (not yet wired intonadirclaw serve). - Apache-2.0 attribution for
headroom-aiinTHIRD_PARTY_NOTICES.md; docs, benchmarks, and tests (ccr, progressive, json_array_pack, backends, code-safety).
- Pluggable backend —
- Whitespace normalization corrupted unfenced source code — the
whitespace_normalizetransform collapsed the leading indentation of raw (unfenced) code arriving as file-read tool outputs, flattening nested Python/YAML/diffs into invalid syntax while reporting it as "savings". It now preserves leading indentation and only collapses interior multi-spaces, in bothsafeandaggressivemodes (#65).
- OpenAI OAuth login failed with
authorize_hydra_invalid_request—nadirclaw auth openai loginsentredirect_uri=http://127.0.0.1:1455/auth/callbackin the authorize request while the callback server bound and printedlocalhost. OpenAI's Hydra authorization server exact-matchesredirect_uriagainst the client allow-list and rejected the127.0.0.1variant before the login screen. Now useslocalhostconsistently, matching the callback server and the Antigravity/Gemini flows (#67, #69).
- Application Default Credentials (ADC) for Gemini — when no
GOOGLE_API_KEYis set, the Gemini path now falls back togoogle.auth.default()so users can authenticate viaGOOGLE_CLOUD_PROJECT/GOOGLE_CLOUD_LOCATION(Vertex AI / gcloud-managed creds) instead of pasting a key. Original work by @froody (#57). nadirclaw statusdisplays the mid-tier model when one is configured, alongside simple/complex (#57).
- Gemini streaming was broken —
_dispatch_model_streamconsumed_stream_gemini(an async generator) with a plainforloop, which would raiseTypeError: 'async_generator' object is not iterableon any actual streaming Gemini call. Now usesasync for, and chunk /finish_reasonparsing is robust to the google-genai SDK returning enum-like objects (#57). savingsno longer crashes onNonevalues in the request log forselected_modelandtier— these show up for failed / aborted requests and previously broke the report (#57).
- Configurable embedding backends for the centroid classifier —
NADIRCLAW_EMBEDDING_BACKEND(defaultsentence-transformers; alsoollamavia/api/embed),NADIRCLAW_EMBEDDING_MODEL,NADIRCLAW_EMBEDDING_API_BASE, andNADIRCLAW_CENTROID_DIR. Custom centroid directories require acentroid_metadata.json(schema-versioned, withprototypes_hashfor traceability) so users never silently mismatch a self-built centroid against a different encoder.nadirclaw build-centroidsgains--backend,--model,--api-base,--output-dirflags. Original work by @clawSean (#50). - Optional prompt-injection guard —
nadirclaw/prompt_guard.py. Heuristic detection of 7 patterns (instruction override, role reassignment, prompt extraction, JSON role confusion, delimiter injection, encoded payloads, DAN/jailbreak).NADIRCLAW_PROMPT_GUARD:log(default) /warn/block. Scans only user/tool messages — system/assistant treated as trusted. Original work by @pradumna-gautam (#55, supersedes #31). - Optional PII redactor —
nadirclaw/pii_redactor.py. Detects email, US phone, SSN, and Luhn-validated credit-card numbers.NADIRCLAW_PII_REDACTION:none(default) /log_only/redact. Non-streaming responses only. Original work by @pradumna-gautam (#55).
- Production hardening baseline — recommended for anyone exposing
nadirclaw servebeyond localhost. Original work by @pradumna-gautam (#30).- CORS: explicit allowlist via
NADIRCLAW_CORS_ORIGINS; localhost regex default; never wildcard + credentials. - Auth: constant-time token comparison via
hmac.compare_digestto defeat timing-side-channel guessing. - Security headers on every response:
X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: strict-origin-when-cross-origin,Cache-Control: no-storeon/v1/*, opt-in HSTS viaNADIRCLAW_HSTS=true. - Bounds validation on
ChatCompletionRequest: caps on messages (500),max_tokens(100K),temperature(0–2),top_p(0–1),n(1–8) — closes a cost-amplification surface. - Sanitized validation errors — Pydantic internals no longer leak to clients; full details still server-side logged.
- Async logging — SQLite writes moved off the event loop into a
ThreadPoolExecutor, withdone_callbackexception logging andshutdown(wait=True)on SIGTERM so queued entries drain instead of dropping. - Prompt truncation — 500-char default in SQLite request logs (configurable via
NADIRCLAW_LOG_PROMPT_TRUNCATE); API-key shaped tokens (sk-…,AIza…,ghp_…,gho_…,xox[bpars]-…) redacted from logged system prompts.
- CORS: explicit allowlist via
- Anthropic-compatible
/v1/messagesendpoint — Anthropic-native clients (Claude Code) now route through NadirClaw. The proxy classifies, rewrites themodelfield, forwards toapi.anthropic.com, and pipes SSE streaming through byte-for-byte (#51). - Seamless Claude Code integration —
nadirclaw claude onboard/shim/uninstall. Onboarding detects models, maps them into tiers, persistsANTHROPIC_BASE_URL+ANTHROPIC_MODELinto~/.claude/settings.json, and installs a launchd / systemd auto-start unit (#51). - Live model detection — onboarding queries Anthropic's
/v1/modelsusing the stored token (Bearer for subscription tokens,x-api-keyfor API keys) instead of a hardcoded list;--interactivelets you pick a model per tier (#51). - Pluggable complexity classifier —
NADIRCLAW_COMPLEXITY_ANALYZER=binary(default, ~10ms centroid) ordistilbert(3-class fine-tuned DistilBERT predicting simple/mid/complex natively). The DistilBERT artifact downloads from the Hugging Face Hub on first use with a graceful fallback to binary (#51, #52). - Pro upsell surfaces —
nadirclaw savings/serve/reportand the README now surface Nadir Pro at high-intent moments with attribution-tagged URLs; newdemo/cost_vs_opus.pyzero-API-key demo (#53). - Enriched
/v1/models— responses now include Anthropic-styletype/display_name/description/created_atalongside the OpenAI-style fields.
ANTHROPIC_BASE_URLis written as the bare host (Claude Code appends/v1/messagesitself; a/v1suffix produced a broken/v1/v1/messagespath) (#51).- Updated the stale Claude model fallback list from the 4.5/4.1 generation to the 4.6 family (#51).
nadirclaw update-modelscommand — writes refreshable model metadata to~/.nadirclaw/models.json, optionally merging a published registry JSON via--source-urlorNADIRCLAW_MODEL_REGISTRY_URL.- Local model metadata overrides — the router now merges
~/.nadirclaw/models.jsonand user-managed~/.nadirclaw/models.local.jsoninto the runtime model registry. - DeepSeek V4 explicit aliases — added
deepseek-v4,deepseek-v4-flash, anddeepseek-v4-prowhile preserving the existingdeepseekalias fordeepseek/deepseek-chat. - Model pool weighted load balancing — pool tier configuration with weighted round-robin across multiple models in the same tier (#36).
- Selective context compression module — opt-in compression for tool-heavy contexts (#40).
- Complex coding detection and enhanced reasoning markers — improved tier classification for coding-heavy prompts and Chinese reasoning markers (#38).
- Upgrade-only session cache for agent frameworks — caches routing decisions per session to avoid repeated downgrades on multi-turn agent flows (#27).
- Agent role detection for AI coding assistants — recognizes Claude Code / Cursor-style system prompts and routes accordingly (#37/#45).
- Fallback reasons logging — failed fallback attempts now record ordered per-model
fallback_reasonswith compact error types and sanitized messages (#47). - Provider health-aware fallback routing — optional
NADIRCLAW_PROVIDER_HEALTH=truemode tracks in-process model health and tries healthy fallback candidates before cooling-down ones; debug snapshot via/internal/provider_health(#48).
- Thinking/reasoning token passthrough — transparently forwards thinking parameters and extracts reasoning content from all provider paths:
- Request forwarding:
reasoning_effort(OpenAI o-series),thinking(Anthropic extended thinking),thinking_config(Gemini), andresponse_formatare now passed through to LiteLLM, Anthropic OAuth, and Gemini native paths. - Response extraction:
reasoning_content(DeepSeek),thinkingblocks (Anthropic), andthoughtparts (Gemini) are captured from LLM responses and included inchoices[].message. - Usage reporting:
completion_tokens_details.reasoning_tokenssurfaced when providers report thinking token counts. - Works in both streaming (real SSE and fake/cached SSE) and non-streaming response formats.
- Request forwarding:
- 15 new tests covering thinking parameter forwarding, response extraction, JSON serialization safety, and streaming passthrough.
- Context Optimize — new preprocessing stage that compacts bloated context before LLM dispatch, reducing input token cost by 30-70%. Two modes:
safe— five deterministic, lossless transforms: JSON minification, whitespace normalization, system prompt dedup, tool schema dedup, chat history trimming.aggressive— all safe transforms + diff-preserving semantic deduplication. Uses sentence embeddings (all-MiniLM-L6-v2) to detect near-duplicate messages (cosine similarity >= 0.85), then extracts only the unique diff phrases usingdifflib.SequenceMatcher. Refinements survive dedup — "return values, not indices" is preserved even when 90% similar to an earlier message.
- Accurate token counting with tiktoken — uses
cl100k_baseBPE tokenizer instead oflen//4heuristic. Falls back gracefully if tiktoken is not installed. - Shared sentence encoder — lazy-loaded
SentenceTransformersingleton innadirclaw/encoder.pyfor aggressive mode. No import cost when using safe mode or off. nadirclaw optimizecommand — dry-run CLI tool to test context compaction on files or stdin. Supports--mode safe|aggressiveand--format text|json.--optimizeflag onnadirclaw serve— set optimization mode at startup (off,safe,aggressive).- Per-request
optimizeoverride — pass"optimize": "safe"in the request body to override the server default for individual requests. - Optimization metrics —
tokens_saved,original_tokens,optimized_tokens, andoptimizations_appliedlogged per request in JSONL, SQLite, and Prometheus. Web dashboard shows aggregate savings. - New env vars:
NADIRCLAW_OPTIMIZE(default:off),NADIRCLAW_OPTIMIZE_MAX_TURNS(default:40). - 60 automated tests covering safe transforms, aggressive semantic dedup, accuracy preservation, edge cases, and roundtrip integrity.
- SQLite schema: added columns
optimization_mode,original_tokens,optimized_tokens,tokens_saved,optimizations_applied(auto-migrated on startup).
nadirclaw testcommand — probes each configured model tier with a short live request and reports latency, response, and pass/fail. Exits with code 1 on failure so it works in CI. Supports--simple-model,--complex-model, and--timeoutoverrides.classify --format json— new--format text|jsonflag onnadirclaw classify. JSON output includestier,is_complex,confidence,score,model, andprompt. Composable withjq.- Multi-word prompt support for
classify—nadirclaw classify What is 2+2?now works without quoting. Previously only the first word was captured.
nadirclaw savingsnow prefers SQLite — mirrorsnadirclaw report: reads fromrequests.dbwhen available, falls back torequests.jsonl. Previously only JSONL was read, giving empty or stale results for users without a JSONL file.nadirclaw dashboardnow prefers SQLite — same fix as savings; dashboard no longer shows empty data when onlyrequests.dbexists.SessionCacheLRU eviction is now O(1) — replacedList[str]+list.remove()(O(n) per cache hit) withcollections.OrderedDict+move_to_end()/popitem(last=False), both O(1). Affectsrouting.py.ModelRateLimiter.get_statusis now thread-safe — all reads of_limits,_hits, and_default_rpmare now taken inside the lock, eliminating a potential data race under concurrent requests.
auth statusindentation — the "no credentials" help block was over-indented (12 spaces) and the provider hint strings were misaligned. Fixed to consistent 4-space indentation.- Removed redundant
load_dotenv()inserve—settings.pyalready loads~/.nadirclaw/.envat import time; the extra bareload_dotenv()call in theservecommand was a no-op that could cause confusion when debugging env resolution.
- OpenClaw onboard: register nadirclaw provider without overriding the agent's primary model
- Configurable fallback chains — when a model fails (429, 5xx, timeout), cascade through a configurable list of fallback models. Set
NADIRCLAW_FALLBACK_CHAINto customize the order. - Real-time spend tracking and budget alerts — every request's cost is tracked by model, daily, and monthly. Set
NADIRCLAW_DAILY_BUDGETandNADIRCLAW_MONTHLY_BUDGETfor alerts at configurable thresholds. Newnadirclaw budgetCLI command and/v1/budgetAPI endpoint. - Prompt caching — LRU cache for identical prompts. Configurable TTL (
NADIRCLAW_CACHE_TTL, default 5min) and max size (NADIRCLAW_CACHE_MAX_SIZE, default 1000). Newnadirclaw cacheCLI command and/v1/cacheAPI endpoint. Toggle withNADIRCLAW_CACHE_ENABLED. - Web dashboard — browser-based dashboard at
/dashboardwith auto-refresh. Shows routing distribution, per-model stats, cost tracking, budget status, and recent requests. Dark theme, zero dependencies. - Docker support — official Dockerfile and docker-compose.yml.
docker compose upgives you NadirClaw + Ollama for a fully local zero-cost setup.
- Fallback logic upgraded from simple tier-swap to full chain cascade
- Request logs now include per-request cost and daily spend
- Budget state persists across restarts via
budget_state.json
- OAuth login for all major providers: OpenAI, Anthropic, Google Gemini, Google Antigravity
- Interactive Anthropic login — choose between setup token or API key
- Gemini OAuth PKCE flow with browser-based authorization
- Antigravity OAuth with hardcoded public client credentials (matching OpenClaw)
- Provider-specific token refresh (OpenAI, Anthropic, Gemini, Antigravity)
- Atomic credential file writes to prevent corruption
- Port-in-use error handling for OAuth callback server
- Test suite with pytest (credentials, OAuth, classifier, server)
- CONTRIBUTING.md and CHANGELOG.md
- Version is now single source of truth in
nadirclaw/__init__.py - Credential file writes use atomic temp-file-and-rename pattern
- Token refresh failures return
Noneinstead of silently returning stale tokens - OAuth callback server binds to
localhost(was127.0.0.1)
- Version mismatch between
__init__.py,cli.py,server.py, andpyproject.toml - README references to
nadirclaw auth gemini-cli(nownadirclaw auth gemini) - OAuth callback server getting stuck (now uses
serve_forever())
- OpenAI OAuth login via Codex CLI
- Credential storage in
~/.nadirclaw/credentials.json - Environment variable fallback for API keys
nadirclaw authcommand group
- Initial release
- Binary complexity classifier with sentence embeddings
- Smart routing between simple and complex models
- OpenAI-compatible API (
/v1/chat/completions) - SSE streaming support
- Rate limit fallback between tiers
- Gemini native SDK integration
- LiteLLM support for 100+ providers
- CLI:
serve,classify,status,build-centroids - OpenClaw and Codex onboarding commands