Cloud's APIKeyPrefixes admits pk- and sk- only (auth_identity.go, 4b030b77).
The lsp tool still told the user to set an hk- key, which cloud resolves to no
principal — following the message produced a key that could never authenticate.
The message is now a single NO_KEY beside the resolver that mints it, exported
from hanzo_tools.core, so a second tool reporting it cannot spell it differently.
Resolution order is untouched: HANZO_API_KEY, then ~/.hanzo/config.json .apiKey,
then the CLI's IAM session.
vector_index names the split cloud actually draws: pk- publishable for read,
sk- secret for write.
Co-authored-by: Hanzo Dev <[email protected]>
Three published packages declared a console script named `hanzo`, competing with
the native CLI and with each other. Measured on a clean venv at hanzo==0.4.3:
`hanzo --help` printed hanzo-cli's program, not the one pkg/hanzo/README.md
documents. Renamed to `hanzo-py` and `hanzo-cli` — script named after its
distribution. Nothing is yanked; both packages still install and run.
- pkg/hanzo 0.4.4 — summary and README now say the CLI is a native binary
(curl -fsSL https://hanzo.sh | sh). Dropped the invented command tour
(`hanzo chat --model gpt-4`, `hanzo node start`, `hanzo router start` on
localhost:4000): none of those verbs exist in the program this package
installs. Kept the three library entry points, each import-checked.
- pkg/hanzo-cli 0.2.4 — `hanzo login` replaced with the native `hanzo auth login`
plus its own `hanzo-cli login`. Verified `hanzo iam users list` and
`hanzo kms secrets list` against the shipped v1.9.18 binary.
- pkg/hanzo-node 0.1.1 — states the two meanings of the name: the `hanzo-node`
COMMAND is a symlink to the Hanzo CLI; this package fetches a different binary
from hanzoai/node, which is private, so the download 404s for the public.
Documentation URL moved off docs.hanzo.ai/node (404) to hanzo.sh.
- root README (published as `hanzoai`) — `pip install hanzo` no longer advertised
as the CLI; model ids zen-coder -> zen5-coder (zen-coder is not in the catalog);
"2452 operations, 1798 schemas" dropped for the spec URL, since the live
surface is 1058 paths / 1465 operations / 1139 schemas.
Co-authored-by: Hanzo Dev <[email protected]>
`hanzo` depends on `hanzo-cli`, and BOTH declared `[project.scripts] hanzo`.
Entry-point resolution is install-order dependent, so which CLI a user got was
a coin flip — and hanzo-cli kept winning. The result:
$ hanzo auth login
Error: No such command 'auth'.
So the "auth login is broken by Cloudflare 1010" story was real but unreachable:
users never got as far as the HTTP call, because the binary they had did not
have an `auth` command at all. Publishing that fix would have changed nothing.
One command, one owner:
- hanzo-cli drops its console script. `hanzo` is the ONE command.
- `paas` and `bot` are mounted into it — the only two groups with no equivalent.
`kms` is NOT mounted: `hanzo secrets` is a strict superset (audit/grant/
revoke/rollback/rotate/versions on top of the same get/list/set/delete), and
two commands for one concern is what we are removing.
- hanzo_cli grows a __main__ so it stays invocable as `python -m hanzo_cli`,
and its e2e suite targets that instead of whatever `hanzo` resolves to.
Endpoints, one definition:
- models.IAMConfig (the class the package EXPORTS) gains the HIP-0111 endpoint
properties. config.IAMConfig had them, but nothing imports that one — a
second same-named class nobody uses is how the legacy path survived.
- config.py + fastapi.py stop hand-assembling paths and read the constants.
- password_login posted to `/oauth/token`, which is not a 404: IAM serves a
200 text/html SPA catch-all for unregistered paths, so it received a login
PAGE and json() blew up on HTML. Verified: legacy 200 text/html vs canonical
401 application/json.
PKCE, because this is a public client:
- browser_login ships no client_secret, so the authorization code was the only
secret in the flow and it arrives over a plaintext loopback redirect. The SDK
already accepted code_challenge/code_verifier; the CLI simply never passed
them. RFC 8252 §8.1 / RFC 7636 S256.
Version, one source:
- pyproject said 0.4.4, __init__ said 0.3.47, cli.py said 0.3.48, and the
installed binary reported 0.1.0 — four answers, none right. Now resolved
from the installed distribution.
Verified on a clean venv: entry-point providers 2 -> 1, commands 10 -> 58,
`hanzo --version` 0.1.0 -> 0.4.4, all four endpoints canonical. hanzo-iam
19/19 pass; hanzo-cli's 20 failures are unchanged before and after and are all
live-service auth (no stored token in this environment).
Co-authored-by: Hanzo Dev <[email protected]>
Ships 50d442eb. 0.4.3 cannot complete a login at all: Cloudflare rejects
urllib's default User-Agent in front of hanzo.id with `error code: 1010`, so the
token exchange 403s AFTER the user has already signed in through the browser.
Also adds the PKCE S256 that flow never had, and moves off the legacy
/oauth/* + /api/device/code paths onto the canonical /v1/iam/oauth/* surface
hanzo.id advertises.
Patch bump from the last published version (0.4.3), per the release rule.
Three defects in `hanzo auth login`, found while auditing HIP-0111 compliance.
1. LOGIN DID NOT WORK AT ALL. urllib defaults its User-Agent to
`Python-urllib/3.x`, which Cloudflare refuses in front of hanzo.id with
`error code: 1010` — a 403 that never reaches IAM. The browser leg looked
fine, the user signed in, and THEN the token exchange died, so the failure
landed after the user had already authenticated. Measured on the live host:
POST /v1/iam/oauth/token UA=Python-urllib/3.11 -> 403 (CF 1010)
POST /v1/iam/oauth/token UA=hanzo-cli/python -> 400 (reaches IAM)
POST /v1/iam/oauth/device UA=Python-urllib/3.11 -> 403 (CF 1010)
POST /v1/iam/oauth/device UA=hanzo-cli/python -> 400 (reaches IAM)
Every outbound request now identifies itself. The device flow was dead the
same way.
2. NO PKCE. The flow had zero code_challenge/code_verifier — `state` only,
which is CSRF protection, not interception protection. HIP-0111 Security
Considerations makes PKCE S256 mandatory, and RFC 8252 §8.1 requires it for
native apps specifically: this redirects to a fixed loopback port (1456) that
any local process can bind or race, so an intercepted authorization code was
directly redeemable. Now S256, with the verifier bound into the exchange.
hanzo.id advertises `code_challenge_methods_supported: ["S256"]`.
3. LEGACY PATHS (HIP-0111 §4.4). It called `/oauth/authorize`, `/oauth/token`
and `/api/device/code` — the `/oauth/*` and `/api/*` spellings the standard
retired, the latter breaking the absolute "no /api/" rule. Replaced with the
canonical paths hanzo.id actually advertises in its discovery document, held
in ONE constant block so no call site spells a path itself (§4.3):
/v1/iam/oauth/authorize /v1/iam/oauth/token /v1/iam/oauth/device
`/api/device/code` was not merely non-canonical — it answers 401; the real
device endpoint is `/v1/iam/oauth/device`, per discovery.
Also drops a redundant function-local `import base64` now that the module
imports it.
The cloud API now sits behind one front door (ingress -> gateway -> cloud).
api.cloud.hanzo.ai / cloud-api.hanzo.ai / functions.hanzo.ai are redundant
aliases routing to the same backend, so migrate clients to api.hanzo.ai.
- hanzo-tools-ui: HANZO_CLOUD_API default cloud-api.hanzo.ai -> api.hanzo.ai
(docs RAG paths /api/search-docs|chat-docs|index-docs are served at /api on
the cloud backend per AUTH_PUBLIC_PATHS; kept as-is)
- hanzo base status: Functions remote host functions.hanzo.ai ->
api.hanzo.ai/v1/functions (the canonical gateway path)
Non-breaking: the aliases still resolve to the same backend.
Co-authored-by: Hanzo Dev <[email protected]>
Follow-up to 9c377be (hanzo-iam 1.30.0). Consumer packages now read only
the canonical IAM_ prefix — no upstream-brand alias chain, matching the
new IAMConfig.ENV_PREFIX contract enforced by test_env.py.
Touched modules:
- hanzo-cli: hanzo_cli/auth.py, hanzo_cli/bot/commands.py
- hanzo-tools-auth: hanzo_tools/auth/session.py
- hanzo-tools-iam: hanzo_tools/iam/iam_tool.py
- hanzo: src/hanzo/commands/{auth,iam}.py
- hanzoai: auth.py, llm_client.py
- tests: test_auth_pkce.py (import IAM_CLIENT_ID)
Also adds the generated pkg/hanzo-iam/uv.lock — matches the convention
of the other 17 axis pkgs that track their lockfiles.
- exec_tool: add Windows shell detection (pwsh/cmd), use taskkill for process kill
- shell_detect: skip Unix paths on Windows, add pwsh/cmd fallback chain
- dev.py: replace all `which` subprocess calls with shutil.which()
- test_hanzo_dev: use shutil.which() instead of subprocess which
Reformat multiline string arguments in write_text() calls and
collapse a ternary expression to satisfy black<26.1 style check
in the Test Hanzo Python SDK CI workflow.
* feat(iam): add set-password and enforce-hashing CLI commands
Adds two new top-level IAM commands:
- `hanzo iam set-password USER` — directly set/reset a user's password
via the /api/set-password endpoint (server-side argon2id hashing)
- `hanzo iam enforce-hashing` — set org passwordType to argon2id and
audit all users for plaintext passwords
* style: format iam.py with black line-length rules
---------
Co-authored-by: Hanzo Dev <[email protected]>
Replace bare except with except Exception, add noqa for legitimate
URL opens, tarfile extraction, chmod on installed binaries.
Co-authored-by: Hanzo Dev <[email protected]>
Every service command (vector, kv, search, doc, events, pubsub, queues,
jobs, secrets, tasks, fn, auto, flow, ml, o11y, cx, growth, platform)
now makes authenticated HTTP calls via shared service_request/check_response
utilities in base.py. No stubs, no hardcoded data, no placeholders.
Bump hanzo 0.4.0→0.4.1, hanzo-cli 0.2.0→0.2.1.
- Add `hanzo bot` command group (status, logs, deploy, env, events) via PaaS API
- Replace all storage placeholders with real boto3 S3 API calls to s3.hanzo.ai
- Rename `hanzo storage` → `hanzo s3` (storage kept as alias)
- Rewrite `hanzo dev` as lazy-install passthrough to @hanzo/dev binary
- Rewrite `hanzo net`/`hanzo node` as passthrough to hanzod binary
- Remove ~350 lines of inline orchestrator/node code in favor of os.execvp()
- Add `hanzo k8s` command group to hanzo-cli that wraps kubectl with
Hanzo-managed auth and default namespace. Uses KubectlGroup that
dynamically proxies unknown subcommands to kubectl (e.g.
`hanzo k8s get pods` -> `kubectl get pods -n hanzo`).
- Remove REPL mode from hanzo wrapper: bare `hanzo` now shows help
instead of dropping into interactive REPL.
- Remove hanzo-ai, hanzo-chat, hanzo-repl entry points.
- Make hanzo-cli a core dependency of hanzo (was optional [cli] extra).
- Remove unused typer dep, move openai/anthropic/prompt-toolkit to
optional extras ([ai], [interactive]).
Co-authored-by: Hanzo Dev <[email protected]>
hanzo-cli and hanzo-kms need to be published to PyPI first before
hanzo can depend on them. Move to optional [cli] extra for now.
Co-authored-by: Hanzo Dev <[email protected]>
Run ruff check --fix and ruff format across the repo to fix ~495
auto-fixable lint errors (mostly I001 import sorting) and reformat
225 files to match the py312 target.
Co-authored-by: Hanzo Dev <[email protected]>
- Rewrite auth.py with full OAuth token management flow
- Rewrite iam.py with comprehensive IAM commands (users, orgs, apps)
- Add hanzo-iam package for IAM SDK
- Add hanzo-web3 package for blockchain SDK
- Update API providers for new auth flow
- Update uv.lock files in all sub-packages
- python-multipart 0.0.21 -> 0.0.22 (arbitrary file write fix)
- urllib3 2.6.1 -> 2.6.3 (decompression-bomb fix)
- Various other dependency updates
Note: docs/package-lock.json has next.js vulnerabilities that need
manual resolution due to npm dependency conflicts.
The hanzo CLI was exporting a hanzo-mcp command that conflicts with
the dedicated hanzo-mcp package. Removed to allow both to coexist.
Bump version to 0.3.47.
- Renamed infra.py to cloud.py with cloud_group command
- Updated CLI imports and registrations
- Bumped version to 0.3.35
- Updated Python requirement to >=3.12 (for hanzo-aci compatibility)
Commands:
- hanzo infra list - Show available services
- hanzo infra provision - Provision service on Hanzo Cloud
- hanzo infra connect - Get connection details
- hanzo infra env - Export environment variables
- hanzo infra status - Check service health
- hanzo infra destroy - Delete provisioned service
- hanzo infra init - Initialize from hanzo.yaml
Services: vector, kv, documentdb, storage, search, pubsub, tasks, queues, cron, functions
- dashboard.py: fetch real data from cluster API instead of fake data
- Shows [DISCONNECTED] with helpful message when cluster unavailable
- Uses HANZO_CLUSTER_URL env var for cluster endpoint
- Fetches agents from hanzoai.agents.list_agents()