The claude-code target listed a hardcoded HANZO_MODELS set (glm-5.2,
deepseek-v4-flash, deepseek-v4-pro, zen5-max — all verified routable) but
dropped the user's actual selection if it wasn't among them. Merge creds.model
in and dedupe so the picker never omits the chosen default, while keeping the
short curated list (the live catalog carries 60+ ids). Also add the missing
ClaudeSettings.model type and drop the redundant 'ultra' effort alias (== max).
v1.0.7
Live infra drift (gateway redeploy): api.hanzo.ai/v1/kms is NOT proxied (404),
and the bare /health path moved (/health 404, /v1/health 200). Two fixes:
- KMS runs on its dedicated host kms.<brand> (kms.hanzo.ai, per IAM's per-brand
KMSHost). Added brand.kms + endpoints.kms (HANZO_KMS_URL override); KMS client
targets it instead of api.hanzo.ai/v1/kms. Verified live: kms.hanzo.ai/v1/kms/
orgs/hanzo/secrets → 401 (gated, live).
- doctor now probes the STABLE endpoints the tool actually depends on — IAM
/.well-known/openid-configuration + api /v1/models — not a bare /health that
varies by gateway config. Both 200.
- kms-secrets action base-url default → kms.hanzo.ai.
Release 1.0.6 (helper + @hanzo/kms + lux/zoo/zen forks).
Two real bugs:
- npx @hanzo/helper broke when the kms bin was added — a package with TWO bins
can't be run by 'npx <pkg>' (could not determine executable). Reverted helper
to its single 'hanzo' bin; kms stays as 'hanzo kms' AND as its own package.
- version.ts was hardcoded '1.0.0' — 'hanzo --version' always lied. Now injected
from package.json at build (tsup define __HANZO_VERSION__), one source of truth.
- packages/kms: new standalone @hanzo/kms (single 'kms' bin) that re-runs the
helper's kms CLI via the new ./kms-cli export. npm i -g @hanzo/kms → 'kms'.
- exports: add ./kms-cli so the standalone package imports it cleanly.
Published: @hanzo/helper@1.0.5, @hanzo/kms@1.0.5, @luxfi/@zenlm/@zooai/helper@1.0.5.
Bump @luxfi/@zenlm/@zooai/helper to 1.0.3 with @hanzo/helper ^1.0.3 (picks up
the get-account/health endpoint fixes + kms login). npm pkg fix: bin paths and
repository URL normalized so publishes are warning-free.
Three live-verified endpoint bugs, all where a call hit the wrong host/path:
- readApiKey + fetchUser hit api.hanzo.ai/v1/get-account (404). The account read
lives on the IAM issuer: {iam}/get-account (…/v1/iam/get-account, 200). Same
base as mint/revoke. Added IAM_PATHS.account.
- fetchUser also parsed the account at the top level, but GetAccount wraps it in
a { status, sub, name, data, data2 } envelope — email/owner are under data, so
org came back EMPTY (which kms pull needs). Now reads data.{email,owner,...}.
- doctor: api /v1/health (404) -> /health (200); iam /api/health -> /health
(drop the /api/ prefix per house style).
Verified live: hanzo doctor now green (IAM 200, Cloud API 200); models table,
--tiers, and detail all work. Release 1.0.3.
The gap behind 'kms pull must work' was that the standalone kms had no way to
sign in — and an hk- API key can't auth to KMS (it needs an IAM JWT the KMS
verifies against hanzo.id JWKS). Fix: one device-login primitive, every door.
- lib/signin.ts: deviceSignIn() — the RFC 8628 flow against hanzo.id, persists
accessToken + user to the shared ~/.hanzo/config.json. Extracted from login.ts.
- kms-cli.ts: 'kms login' + pull/list. Self-sufficient standalone tool.
- auth.ts: 'hanzo auth login' — same session.
- login.ts: browserLogin now composes deviceSignIn + key mint (DRY).
One token store: sign in via kms login, hanzo login, or hanzo auth login — all
write the same session, so kms pull works after any of them. Verified live:
kms login returns a real device code from hanzo.id.
KMS already serves the one-way secret surface (hanzoai/kms embed.go): the dev's
IAM login token goes straight to /v1/kms/orgs/{org}/secrets — KMS verifies it
against hanzo.id JWKS (authorize + canActOnOrg). No /v1/kms/oidc/login or
/v1/kms/get-secrets exchange (those were invented; they 404). One token, one way.
- lib/kms.ts: listSecrets + getSecret + pullSecrets over the real endpoints.
Respects KMS's security boundary — list returns KEYS ONLY; values are read
one-by-one (bounded concurrency), no bulk-value dump.
- commands/kms.ts: org comes from the login session (--org override); IAM token
from config.accessToken. pull/list unchanged in UX.
- actions/kms-secrets: same canonical list-then-get; token input is an
IAM-issued token KMS trusts (GitHub OIDC only if KMS trusts that issuer).
- npm-release.yml: pass org=hanzo + KMS_IAM_TOKEN.
Verified: GET /v1/kms/orgs/hanzo/secrets?env=devnet is LIVE (401, gated) on
api.hanzo.ai — endpoint exists, needs a real IAM token to list.
A developer's `hanzo login` already mints an IAM OIDC token; `kms pull --env
devnet` exchanges it for a short-lived KMS token and writes a local .env. Clean
orthogonal separation — IAM is a pure issuer (/v1/iam), KMS owns secrets/envs/
authz (/v1/kms); they compose through the token, neither imports the other. No
broker, no /api/, no compound words (oidc, not oidc-auth).
- src/lib/kms.ts: kmsLogin + kmsSecrets + pullSecrets over /v1/kms/oidc/login
and /v1/kms/secrets; tolerant of {K:V} or [{secretKey,secretValue}] shapes.
- src/commands/kms.ts + src/kms-cli.ts: `hanzo kms` and standalone `kms` bin —
one implementation, two entry points. pull/list, env devnet|testnet|mainnet|
production (default devnet), dotenv out.
- CI shares the SAME mechanism: .github/actions/kms-secrets loads secrets via a
GitHub OIDC token through /v1/kms/*; reusable .github/workflows/npm-release.yml
publishes any package with NPM_TOKEN from KMS (self-hosted runners, no
long-lived token in GitHub); release.yml ships helper + forks on tag.
- DRY: dev and CI use one /v1/kms exchange; one login, one token store.
The forks (@luxfi/@zenlm/@zooai/helper) re-run our CLI with HANZO_BRAND set,
but exports only published '.', so import('@hanzo/helper/dist/cli.js') failed
with ERR_PACKAGE_PATH_NOT_EXPORTED. Add a stable './cli' export and point the
forks at @hanzo/helper/cli (not a deep dist path). Bump fork deps to ^1.0.1.
Fix stale zoo.id → zoolabs.id comment.
Published: @hanzo/helper@1.0.1, @luxfi/helper@1.0.0, @zenlm/helper@1.0.0,
@zooai/helper@1.0.0.
hanzo models now renders the same rich data as the console Models page —
provider (normalized: glm→Zhipu, qwen→Qwen, Zen first-party), plan tier,
context window, params/arch, and input/output $/Mtok — joined with your live
routing set for honest availability. Catalog ids are OpenRouter-style for
third-party (z-ai/glm-5.2); the routing id is the last path segment, so the
join keys on {id, name, last-segment} and each row shows the id you actually
pass to --model. Adds `hanzo models <name>` detail view + --all; falls back to
the plain id list if the catalog is unreachable. Pricing host derives from the
brand site (pricing.<host>), white-label correct.
- models: read the catalog live from api.hanzo.ai/v1/models; nothing hardcoded.
Effort words (auto/fast/high/max/code/agent) are sugar over the cloud's own
zen-* routing aliases — the cloud picks the concrete model. Default = zen-auto.
- claude-code: write the live model list into providers.hanzo (was a static array).
- credentials carry the live model list so configure() stays sync + orthogonal.
- config: honor HANZO_API_KEY env so the CLI works headless / in CI.
- ecosystem: one data-driven registry (dev, mcp, node, desktop+Enso, extension,
ide, slack, github); 'hanzo install' checklist + per-component npm/guide runner.
- login wizard offers ecosystem setup as its final out-of-box step.
- add eslint flat config (deps were present, config was missing); lint clean.
- brand: add public web root (site) so fork install links white-label correctly.
- Two install targets, user picks (or --mode shell|tools):
* tools — additive providers.hanzo block in tool config (Claude Code /
Codex / Claude Desktop); keeps the default Anthropic login intact, switch
per-session with /model hanzo/<model>. This is how Claude Code reads a
custom provider (verified against a live ~/.claude/settings.json).
* shell — managed export block in the detected shell rc (zsh/bash/fish):
ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN + ANTHROPIC_MODEL → Hanzo becomes
the default for every Anthropic-compatible tool. Sentinel-delimited;
install/uninstall round-trip preserves all other rc content (tested).
- Friendly model tiers → live ids (all verified against api.hanzo.ai/v1/models):
default=glm-5.2, flash=deepseek-v4-flash, pro=deepseek-v4-pro,
ultra=qwen3.5-397b, max=zen5-max. resolveModel() accepts a tier or a raw id;
wired into login/use; `hanzo models --tiers` shows the map.
- Default model is glm-5.2 (fast all-rounder, works on both /v1/chat/completions
and /v1/messages). Zen models confirmed live (zen5-pro returned exact output).
Verified live: tier 'pro' → deepseek-v4-pro → real completion.
zen.id is not a domain we control; use the id.<domain> pattern on the owned
domain (matches id.lux.network / id.zoo.network style). Zoo already correctly
uses zoolabs.id + api.zoo.ngo (no zoo.network).
- brand.ts: one codebase, four ecosystems. HANZO_BRAND selects defaults for
Hanzo (hanzo.id/api.hanzo.ai/hanzo-app), Lux (lux.id/api.lux.network/lux-app),
Zoo (zoolabs.id/api.zoo.ngo/zoo-app), Zen (zen.id/api.zenlm.org/zen-app). IAM
hosts verified against login/middleware.ts tenant map. Explicit
HANZO_IAM_URL/API_URL/CLIENT_ID still override everything.
- endpoints.ts derives defaults from the active brand; CLI name/description are
brand-aware so a fork shows its own identity (white-label rule: never show
another brand).
- forks/{lux,zoo,zen}: thin packages (@luxfi/helper, @zooai/helper, @zenlm/helper)
that set HANZO_BRAND and depend on @hanzo/helper — DRY, no code duplication.
Verified: all four brands resolve correct hosts; all three coding-tool targets
(Claude Code, Claude Desktop, Codex) round-trip configure/unconfigure cleanly.
claude-code.ts: switch from env.ANTHROPIC_* (session hijack) to providers.hanzo
entry — additive, non-destructive. Users switch with /model hanzo/deepseek-v4-pro
without losing the default Anthropic connection. HANZO_MODELS list reflects the
live api.hanzo.ai catalog (Zen + GLM + DeepSeek + Qwen + Kimi + Nemotron).
claude-desktop.ts: new target. Writes mcpServers.hanzo into claude_desktop_config.json.
Config path is platform-aware: ~/Library/... (macOS), %APPDATA%\Claude (Windows),
~/.config/Claude (Linux). API key passed as env var to `npx @hanzo/mcp`.
index.ts: register claudeDesktop in TARGETS.
- Single coherent endpoint model matching the dev CLI: IAM issuer base
https://hanzo.id/v1/iam (HANZO_IAM_URL), all IAM ops issuer-relative
(/oauth/device, /oauth/token, /mint-user-keys, /revoke-user-keys); cloud API
https://api.hanzo.ai for /v1/get-account + inference. Device auth carries
params in the query string with response_type=device_code, matching dev.
- Bare `hanzo` / `npx @hanzo/helper`: runs login+setup when signed out, shows
status when signed in.
- `hanzo login` now offers browser device login OR pasting an existing hk- key;
`--key` supports non-interactive setup.
- White-label override verified (HANZO_IAM_URL/API_URL/CLIENT_ID → lux-app etc).
Verified end-to-end: paste-key → Claude Code settings.json wired correctly.
Verified against the actual Hanzo sources (iam, ai, console2, login):
- Device login (RFC 8628) against hanzo.id /oauth/device + /oauth/token,
form-encoded, client_id hanzo-app, body-keyed pending/slow_down handling.
- API key = the per-user IAM accessKey (hk-): read via /v1/get-account,
self-minted via /v1/iam/mint-user-keys (authorized as self by the login JWT).
- CodingTarget abstraction: one interface, Claude Code (settings.json env) and
Codex (config.toml provider) implementations. Verified round-trip preserves
all unrelated user config.
- Commands: login, use/unuse, auth (status/key/rotate/revoke/logout),
status, models, install (dev/mcp/extension), doctor.
- Endpoints overridable for white-label (HANZO_IAM_URL/API_URL/CLIENT_ID).
- TypeScript 6, tsup bundle, clean typecheck.