Skip to content

One-line installer: Claude Code on the qBraid AI gateway - #2

Merged
BeLazy167 merged 5 commits into
mainfrom
feat/one-line-installer
Aug 20, 2026
Merged

One-line installer: Claude Code on the qBraid AI gateway#2
BeLazy167 merged 5 commits into
mainfrom
feat/one-line-installer

Conversation

@BeLazy167

@BeLazy167 BeLazy167 commented Aug 20, 2026

Copy link
Copy Markdown
Member

Installs Claude Code on a personal laptop and leaves the user in a working
session against qBraid — aimed at someone who should not have to configure
anything (a professor, a workshop attendee).

curl -fsSL https://qbraid.com/code.sh | bash     # macOS, Linux
irm https://qbraid.com/code.ps1 | iex            # Windows

What it does

  1. Installs Claude Code if absent — Anthropic's native binary, no Node.js, no admin rights.
  2. Finds a qBraid API key ($QBRAID_API_KEY~/.qbraid/qbraidrc → opens the key page and takes one paste, not echoed). Validates it, and distinguishes a rejected key from an unreachable network.
  3. Shows the organization the key belongs to, name and id, and asks for confirmation.
  4. Offers a model menu built from GET /api/v1/ai/v1/models, so new gateway models appear without a release here.
  5. Asks whether to skip Claude Code's introductory screens.
  6. Registers the qBraid MCP and runs claude mcp login there and then.
  7. Installs a statusline showing live qBraid credits.
  8. Runs a real /v1/messages request — and warns rather than discarding a finished install if it fails.

No proxy

claudeseek needed CLIProxyAPI because vLLM speaks only OpenAI chat-completions.
This does not: the gateway already serves an Anthropic-compatible surface at
/api/v1/ai/v1/messages, and apikey-strategy.service.ts accepts
Authorization: Bearer qbr_.... The launcher sets four environment variables and
execs claude. Nothing runs in the background, nothing listens on a port.

ANTHROPIC_AUTH_TOKEN is used rather than ANTHROPIC_API_KEY specifically to
avoid Claude Code's custom-API-key approval prompt on first run.

Statusline

qbraid-code ⎇ main │ Claude Opus 5 │ C13 █░░░░░ │ 4281 credits

Credits come from GET /api/v1/billing/credits/balance, cached for 60s and
refreshed out of band, so a slow network never stalls the prompt. The refresh
stamps its attempt, not its success, so a revoked key cannot spawn a
background fetch on every keystroke.

Windows

A .cmd shim on PATH, not a PowerShell profile function: it works from cmd.exe,
PowerShell and Windows Terminal with no profile edit and no execution-policy
change. Ships CRLF and BOM-free, both enforced in CI. --doctor delegates to
doctor.ps1 because batch cannot parse JSON.

Testing

CI runs on ubuntu, macOS and windows — every portability defect found in
review broke on exactly one platform.

  • shellcheck -S warning and bash -n on all four shell scripts, both platforms.
  • tests/statusline.sh — 12 cases: context boundaries, missing fields, empty
    stdin, escape-sequence injection, zero and rounded balances, and that file
    mtimes are readable on this platform.
  • Windows: PSScriptAnalyzer, a parse pass over every .ps1, CRLF/BOM
    enforcement, and the launcher's missing-install path.
  • End to end: curl <proxy>/code.sh | bash runs; bad key, unreachable host and
    shell-metacharacter key all produce distinct, correct errors.

Notable fixes from review

  • stat -f %m is BSD-only; on GNU coreutils it fails but still prints, so the
    credit balance froze at the first cached value forever on Linux.
  • @(...) | Where-Object unrolls a one-element pipeline to a String, so a
    single-model response made the chosen model one character.
  • --global wrote the API key into a world-readable settings.json.
  • GNU sed rejects line address 0, so typing 0 at the model prompt killed the installer.
  • exit /b %ERRORLEVEL% inside a .cmd if-block expands before the command runs.
  • The statusline passed the API key on a child process command line every 60s.

Trust

The installer is fetched from an unpinned main with no signature — the normal
trade-off for a one-line installer that must stay current. Branch protection
on main is therefore load-bearing and is not yet enabled.
The README says so
and shows the fetch-then-read form for anyone who wants it.

Companion files are served from qbraid.com first (see
qbraid-external-site#107), so the install works on networks that block
raw.githubusercontent.com.

Installs Claude Code if absent, wires it to the qBraid AI gateway, registers
the qBraid MCP, and adds a credit statusline. macOS, Linux and Windows.

No proxy: the gateway serves an Anthropic-compatible surface at
/api/v1/ai/v1/messages, so the launcher just sets ANTHROPIC_BASE_URL and
ANTHROPIC_AUTH_TOKEN and execs claude.
Portability, the two platforms that were never executed:
- statusline.sh used `stat -f %m`, which is BSD-only. On GNU coreutils it
  fails but still prints, so the balance froze at the first cached value
  forever and the fallback was dead code. Try the GNU form first, reject a
  non-numeric answer.
- install.ps1 built the model list as `@(...) | Where-Object`, so a
  single-model response unrolled to a String and the chosen model became one
  character.
- GNU sed rejects line address 0, so typing 0 at the model prompt killed the
  installer.
- qbraid-code.cmd now ships CRLF (.gitattributes) and uses delayed expansion;
  `exit /b %ERRORLEVEL%` inside an if-block expanded before the command ran.
- The gh fallback wrote a UTF-8 BOM, which cmd.exe rejects on line 1.

Secrets:
- --global wrote the API key into a world-readable settings.json; chmod 600
  on POSIX, an owner-only ACL on Windows.
- The pasted key is no longer echoed to the terminal.
- statusline.ps1 passed the key on a child process command line every 60s;
  the child now reads the env file itself.

Honest failures:
- A network failure was reported as a rejected key, in both the installer and
  --doctor. Transport and auth are now distinguished by HTTP status.
- A smoke-test failure no longer discards a finished install, and an empty
  wallet is named as such rather than reported as a broken gateway.
- confirm() says out loud when it auto-answers with no TTY, instead of
  printing "organization confirmed" when nobody confirmed anything.
- The final banner only claims --global worked if it actually did.
- write_settings distinguishes "no python3" from "python3 failed".

Also: fetch companion files from qbraid.com first, so the proxy covers the
whole install and not just its first step; resolve QBRAID_CODE_HOME once and
bake it into the launcher; write ~/.claude.json atomically and never fatally;
scope the org name to the data object; round the balance everywhere.

Adds tests/statusline.sh (12 cases) and CI on ubuntu + macos + windows —
every defect above was platform-specific and nothing ran them before.
Re-running with a key from a different organization kept rendering the old
organization's wallet until the cache expired.

The README now states plainly that the installer is fetched from an unpinned
`main` with no signature, which makes branch protection load-bearing, and
shows the fetch-then-read form for anyone who wants it.
macOS still ships bash 3.2, which cannot parse `case ... esac` inside a
command substitution. Moved the mtime probe into a function.

Caught by the macos-latest leg of the new CI matrix, which is what it is for.
The check asserted correctly and printed its success line, but `echo` does not
reset ERRORLEVEL in cmd, so the expected exit 1 from the launcher leaked out
and failed the step.
@BeLazy167
BeLazy167 merged commit 7ada7cc into main Aug 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant