Luxe

Providers

Signing in, configuring endpoints, and model metadata.

Luxe speaks four wire APIs — OpenAI chat/completions, OpenAI responses, Anthropic messages, and the ChatGPT-subscription Codex backend — and ships a catalog of providers that know which one they use, where they live, and what environment variable they answer to. Anything not in the catalog still works through a custom OpenAI-compatible endpoint.

Signing in

luxe auth login [provider]   # API key or subscription; omit the provider to pick from a list
luxe auth list               # every provider and how each is configured
luxe auth status [provider]  # credential kind, source, and OAuth expiry
luxe auth logout <provider>  # remove a stored credential (environment variables are untouched)

In the TUI: /login (or /login openai), /logout <provider>, /auth. A first launch with nothing configured opens the wizard by itself.

API keys work for every provider. Paste one at the prompt, or pipe it in with --stdin. The stored value may be a literal key, $ENV_VAR, or !command — so a password manager works without Luxe integrating with one:

{ "openai": { "type": "api_key", "key": "!op read op://vault/openai/credential" } }

Subscriptions are available for three providers:

ProviderSign-inNeeds
openai-codexBrowser (PKCE) or device codeChatGPT Plus or Pro
github-copilotGitHub device flow (GitHub or Enterprise)Copilot subscription
openrouterOpenRouter’s PKCE flow — mints an API key you ownOpenRouter credits

Usage on a plan is reported as usage, not as a bill. Tokens are counted in full; the published rates are shown as an equivalent (≈$0.42, covered by your subscription) and never added to spend. A session that mixes an API key and a subscription reports each separately.

Where a provider publishes it, Luxe also shows how much of the plan is left — the number a subscriber actually steers by. A ChatGPT plan reports its allowance on every response, so the statusline carries 12% weekly in the slot a dollar figure would occupy and /cost spells it out:

allowance     plus · 12% of weekly (resets in 6d 23h)

The Codex backend serves a different model set than OpenAI’s API — gpt-5.6-sol and friends rather than gpt-5.3-codex — so /model offers exactly what a ChatGPT account can run.

Anthropic and Google are API-key only, by decision. Anthropic’s terms forbid using a Claude Pro/Max subscription from a third-party harness (clarified in February 2026, enforced server-side, and the reason another OSS agent removed its Claude OAuth entirely), and Google’s terms forbid using Gemini CLI’s OAuth credentials from third-party software, with account suspensions attached. Luxe will not ship code that helps you violate terms you haven’t read. Use an API key — or use those vendors’ own clients for their subscriptions.

Credentials live in ~/.config/luxe/auth.json (0600, written atomically, never overwriting anything else in the file). OAuth tokens refresh automatically, once, under a lock — two Luxe processes can’t race a rotating refresh token. Nothing is written until the credential works.

Configuring providers by hand

Configure providers in ~/.config/luxe/auth.json or via environment variables. Provider-specific reasoning controls live in ~/.config/luxe/models.json. A map value of null marks a Luxe level as unsupported; Luxe safely clamps requests to the nearest supported level. ultra is Luxe’s name for provider max (max is also accepted as a map key):

{
  "models": {
    "openai_compat/gpt-5.6-sol": {
      "reasoning": true,
      "thinking_level_map": {
        "minimal": "low",
        "xhigh": "xhigh",
        "ultra": "max"
      }
    },
    "lmstudio/deepreinforce-ai/ornith-1.0-35b": {
      "reasoning": true,
      "thinking_level_map": {
        "off": "off",
        "minimal": null,
        "low": null,
        "medium": null,
        "high": "on",
        "xhigh": "on",
        "ultra": null
      }
    }
  },
  "providers": {
    "openai_compat": {
      "compat": {
        "supports_reasoning_effort": true,
        "thinking_format": "openai"
      }
    },
    "lmstudio": {
      "base_url": "http://127.0.0.1:1234/v1",
      "compat": {
        "thinking_format": "qwen-chat-template"
      }
    }
  }
}

Qwen/DashScope uses thinking_format: "qwen" and exposes a boolean thinking toggle, not distinct effort tiers. Local Qwen and Ornith chat templates use "qwen-chat-template"; mapping several Luxe levels to "on" keeps the UI vocabulary while sending the truthful on/off control.

Anthropic thinking modes. Claude’s generations disagree on how thinking is requested, and asking the wrong way is a hard 400. Luxe picks the encoding from what the model’s catalog entry declares:

The model offersLuxe sendsModels
effort levels onlythinking: {type: "adaptive", display: "summarized"} + output_config.effortClaude Sonnet 5, Opus 5, Opus 4.7/4.8, Fable 5
a thinking token budgetthinking: {type: "enabled", budget_tokens: N}Claude Sonnet 4.6/4.5, Opus 4.6/4.5, Haiku 4.5

A declared effort list is a closed set when effort is the only dial, so a level the model never advertised clamps instead of going out as an invented effort value (minimal on Sonnet 5 becomes low). thinking off sends {type: "disabled"} and no effort at all. If a model still rejects the encoding — a release the catalog hasn’t caught up with — Luxe flips it, retries once, and remembers the answer for the rest of the session; compat.force_adaptive_thinking pins adaptive by hand for an endpoint no catalog knows. Check what Luxe resolved with luxe models show anthropic/<id> (the thinking control row).

Model metadata

A model that shipped after your Luxe build still has to work properly — with its real context window, output cap, reasoning levels, and pricing, not silent fallbacks. Getting those wrong is quiet and expensive: an 8× undersized window compacts away context you paid for, and a 4096-token output cap truncates long edits.

Metadata resolves through four layers, each overriding the previous field by field:

LayerSourceNotes
1Embedded snapshotA models.dev catalog built into the binary — works offline, on first run, with no config
2Refreshed cache~/.cache/luxe/models-dev.json, revalidated in the background
3Your configThe models map in models.json/auth.json — always wins

Because layers merge per field, pinning just context_window still inherits pricing and thinking levels from the catalog. Anything nothing knows about falls back to safe defaults (128k context, no forced output cap).

Inspect exactly what Luxe resolved, and which layer each field came from:

$ luxe models show anthropic/claude-opus-5
anthropic/claude-opus-5
  name              Claude Opus 5  [models.dev]
  context window    1.0M  [models.dev]
  max output        128k  [models.dev]
  reasoning         true  [models.dev]
  thinking levels   high, low, medium, ultra, xhigh  [models.dev]
  thinking control  effort (adaptive thinking)  [models.dev]
  modalities        text, image  [models.dev]
  cost /Mtok        $5.00/$25.00  [models.dev]

$ luxe models list --provider anthropic   # everything routable, * active, + configured
$ luxe models refresh --force             # revalidate the cache now

The refresh is an If-None-Match conditional GET, so an unchanged catalog costs a 304 and zero bytes; it runs in the background from every entry point (TUI, headless, loop) and never blocks. A provider whose name isn’t a models.dev id can point at one with "models_dev": "alibaba", and a pinned -YYYYMMDD/-latest id inherits its base model’s metadata (the wire id is never rewritten).

VariableEffect
LUXE_MODELS_DEV_REFRESH=0Never touch the network (the snapshot still answers everything)
LUXE_MODELS_DEV_URLFetch from a mirror or self-hosted catalog
LUXE_MODELS_DEV_PATHRead a specific catalog file instead of the cache
LUXE_MODELS_DEV_TTLSeconds before the cache is revalidated (default 21600)