Commit Graph
471 Commits
Author SHA1 Message Date
zeekay d319c6b0bc release(cli): hanzo 0.4.4 — auth login actually works
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.
2026-07-26 11:26:33 -07:00
zeekay 50d442eb7d fix(cli): auth login was broken end-to-end — Cloudflare 1010, no PKCE, legacy paths
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.
2026-07-26 11:09:23 -07:00
zeekayandHanzo Dev f3cb1892eb kms: migrate hanzo-kms off Infisical paths onto the luxfi/kms surface
kms.hanzo.ai runs luxfi/kms, which has never served an /api/* route. This
SDK was sending Infisical's: /api/v3/secrets/raw for every secret operation,
/api/v3/auth/login for user auth, /api/v1/auth/kubernetes-auth/login for k8s.
Only one call in the whole package — /v1/kms/auth/login — was ever real.

It stayed invisible because older luxfi/kms builds embedded a console SPA
behind a root catch-all that answered every unmatched path with 200
text/html. A wrong URL came back as a JSON decode error, which reads like a
parsing bug in the client rather than "this endpoint does not exist." That
catch-all is gone (cmd/kms/main.go ends in notFoundJSON), so those paths now
return honest JSON 404s and the SDK fails outright.

The route table is not the only thing that was wrong. The data model was
Infisical's too — project_id, workspace, secret version, secret comment,
shared-vs-personal type — and luxfi/kms has none of those concepts. It keys
one value by (org, path, name, env) in ZapDB at kms/secrets/{path}/{env}/{name}
and a write upserts it in place. Half-migrating would have left response
models that cannot validate what the server sends, so the vocabulary moves
with the paths:

  list_secrets(path, env) -> names   GET  /v1/kms/orgs/{org}/secrets
  get_secret(path, name, env)        GET  /v1/kms/orgs/{org}/secrets/{path}/{name}
  put_secret(path, name, value, env) POST /v1/kms/orgs/{org}/secrets   (create AND replace)
  delete_secret(path, name, env)     DELETE .../secrets/{path}/{name}
  health()                           GET  /v1/kms/healthz

Two server behaviors now have one home, hanzo_kms/routes.py, shared by the
sync and async clients so they stay mirror images:

  - The server splits the trailing path at its LAST slash into (path, name),
    so each segment is escaped individually. Escaping the joined string
    encodes the separators away and the server reads one long name. A name
    containing "/" is rejected outright: it would be written under one key
    and read back under another, so the write looks like it succeeded and
    the read never finds it.
  - There is no versioned read. get_secret(version=N) raises
    VersionUnsupportedError rather than quietly returning the current value.

org is new and required — it scopes both the URL and the JWT owner claim.
Constructor field, HANZO_KMS_ORG, defaults to "hanzo".

Auth collapses to what the server actually offers: client credentials
exchanged at /v1/kms/auth/login, or a pre-issued IAM bearer token. The AWS,
Azure, GCP, Kubernetes and SRP methods were Infisical's and none were served.
That also fixes a divergence where the async client silently ignored
HANZO_KMS_TOKEN — env parsing is now one function.

Callers migrated with it: hanzo-cli's `hanzo kms`, the hanzo-tools-kms MCP
tool, and hanzo-tools-auth's session client, all of which were building the
removed auth models and calling the removed create/update pair. Each now
constructs KMSClient() and lets it read the environment.

Tests: pkg/hanzo-kms/tests pins the wire shape — no request URL may contain
"/api/", per-segment escaping survives the server's last-slash split, sync
and async emit byte-identical request lines, and a decoder rejects the
200-HTML/JSON-404 shapes instead of reading them as empty results. Wired into
hanzo-packages-ci without `|| true`, so a regression turns CI red instead of
waiting for a decode error in production.

hanzo-kms 1.1.0 -> 1.1.1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 08:29:59 -07:00
zeekay 0a5b2a1f4e refactor(iam): route every OIDC call through the canonical path constants
The client had OIDC paths written inline at each call site, so the canonical
route existed in as many places as it was used and drifted per method. Moves
them to named constants in models.py — OIDC_DISCOVERY_PATH, OIDC_JWKS_PATH,
OIDC_TOKEN_PATH, OIDC_INTROSPECT_PATH — and has both the sync and async
clients read those.

Values match the canonical IAM surface (HIP-0111): discovery
/.well-known/openid-configuration, jwks /v1/iam/.well-known/jwks, token
/v1/iam/oauth/token, introspect /v1/iam/oauth/introspect, authorize
/v1/iam/oauth/authorize. No legacy /oauth/* and no /api/ prefix.

One definition per route, so sync and async can no longer disagree.
2026-07-26 01:55:28 -07:00
zeekay f99cf2a19a research-sdk: rename the running-log method note()->log() (fix method/field shadow)
The one-shot constructor note= sets self.note (a str), which shadowed the
note() method → 'str object is not callable' at call time. The running-log
accumulator is now log(); note= stays the one-shot note. Proven end-to-end
logging 9 kernel-perf experiments (4 proven / 5 refuted) to the live
api.hanzo.ai/v1/research with hypothesis+verdict+provenance.
2026-07-22 22:11:38 -07:00
zeekay 53cc3979d5 research-sdk: structure runs as falsifiable tests — hypothesis, notes, verdict
Add the scientific-method frame to the experiment handle: state a hypothesis
+ prediction up front, .note() the running log, and .conclude(verdict ∈
proven|refuted|inconclusive, because=...) to seal it. A refutation is
recorded as clearly and durably as a proof. finish() defaults a stated
hypothesis to inconclusive — a finished run never silently reads as proof.
All fields serialize into the record's meta alongside the auto-captured
provenance (git sha + lib versions), so a verdict is queryable evidence.
Backward-compatible: every new param is optional.
2026-07-22 21:56:07 -07:00
zeekay a0599c1fee research SDK: content-address artifacts server-side; drop X-User-Id minting (Red L1/M1)
snapshot/report now SUBMIT the bytes (base64 content); the server hashes them and owns
the sha256 + ref — the SDK no longer asserts a (poisonable) hash or a client ref. _headers
sends ONLY the per-org key (Bearer); it never mints X-User-Id/X-Org-Id (a cross-tenant
forge the gateway strips) — any dev bypass is server-side.
2026-07-22 19:57:31 -07:00
zeekay 6bb89b86cc research: hanzo-research SDK — ergonomic, zero-config, auto-instrumenting client
The ONE way producers record/query R&D evidence via /v1/research (HIP-0512),
generated-shape from openapi/research and wrapped in a tiny verb surface so
hand-rolling is the worse choice:

  exp = research.experiment(kind, subject, task)   # get/create → handle
  exp.record(item, model, result)                  # attempt, idempotent by stable id
  exp.snapshot(bytes) / exp.report(text)           # diary artifacts (sha256-addressed)
  exp.finish(value)                                # seal the run
  research.query(project=, kind=)                  # read canonical

Zero-config auto-instrumentation (caller supplies no provenance): captures git
sha/branch/dirty, lib versions, host, and THREE self-documenting narrative sources —
the calling code's docstring (what this experiment is), the commit messages since this
experiment's last recorded run (what changed + why), and an optional note. The project
self-documents as a side effect of running.

Importer-agnostic: ingest() is one idempotent POST; any source (SQLite backfill, git
history, kernel-perf campaign) maps to stable ids and re-imports as a no-op. Stdlib
only (urllib); per-org key auth; private-by-default.
2026-07-22 18:24:12 -07:00
zeekay 22aa49321c chore(hanzo-flags): commit uv.lock 2026-07-19 20:02:48 -07:00
zeekay 363c35bafe feat(tools): hybrid local+cloud tools -- HanzoCloud client, cloud code/vector actions, vision
Adds a shared HanzoCloud client (core/cloud.py; lazy httpx, api.hanzo.ai /v1)
and wires the cloud half of the hybrid tools: code gains cross-repo
search/context/ask/index over the cloud index, vector gains a cloud-backed
store plus an infinity-embedded local option, net gains a vision tool. Bumps
hanzo-tools 0.3.2->0.3.3, -code 0.1.2->0.1.3, -net 0.1.2->0.1.3, -vector
0.2.0->0.2.1 and declares httpx where the cloud path is used. New tests cover
the code/net cloud actions. import hanzo_tools.core is green.
2026-07-19 20:02:48 -07:00
zeekay 82ed2626cb hanzo-flags: the Python client for the native flags engine
Flags were natively supported in Rust (the hanzo-flags crate — the evaluation
core, pub-exported for Rust callers and FFI-exported for the rest), Go (in-process
via CGO to that core), and TypeScript (@hanzo/flags over /v1/flags). Python was the
gap. This closes it.

hanzo-flags POSTs an evaluation context to cloud /v1/flags — the same
PostHog-compatible endpoint every other client speaks — and answers is_enabled /
variant / payload from the response. It mirrors @hanzo/flags exactly: a HanzoFlags
client bound to one host, load(distinct_id, person_properties=, groups=), the same
three-field result, an async twin, and a one-shot evaluate().

Two invariants match the family: fail-open (a transport error yields the last good
or empty result with errors_while_computing set, never a raise on the hot path)
and cache-by-context+TTL. Zero runtime dependencies — stdlib urllib only, so a
flag check never drags httpx/pydantic into a service.

Tests stand up a local /v1/flags stub and prove the request shape is
PostHog-compatible, the accessors resolve booleans/variants/payloads, the TTL
cache holds, and both a 5xx and an unreachable host fail open. 7/7 green.
2026-07-17 13:00:33 -07:00
zeekay 5489e2c560 feat(hanzo-train): add Tinker-shaped training client (0.1.0)
Hand-written package for the engine /v1/training API: ServiceClient +
TrainingClient (create/list/get/delete/forward_backward/optim_step/
sample/save_weights), dataclasses, sync httpx, completed-future
.result() wrapper so tinker fut.result() code ports 1:1. 22 pytest
tests over httpx.MockTransport, no network.

Wired like siblings: publish-pypi hanzo-train-* tag case, rye+uv
workspace member, uv source, pyright exclude. uv.lock relock also
catches the lockfile up to on-disk truth (hanzoai 3.1.1,
hanzo-tools-browser 0.5.10) -- no package versions changed.
2026-07-15 18:16:32 -07:00
zeekay df2e3160bf release(hanzo-tools-browser): 0.5.10 — zapd connect-or-spawn auto-start 2026-07-08 08:50:48 -07:00
zeekayandGitHub 49ad34a2c9 feat(browser): auto-start zapd when absent (connect-or-spawn) (#47)
The browser MCP tool no longer requires a human to have started zapd.
ZapdConsumer.connect() now dials the router and, on failure, auto-starts
the shared singleton (locate the zapd binary, spawn it detached, wait for
the socket) then dials once more — mirroring zapd's own host-mode
connect-or-spawn. No consumer-side lock: the singleton invariant lives in
zapd (advisory-lock bind), so concurrent spawns are safe. The OS unit is
the primary starter; this is the bare-machine fallback so the tool just
works.
2026-07-08 08:49:21 -07:00
zeekay df0becc3cc fix(hanzoai): 3.1.1 — guard optional ZAP import so import hanzoai works without the [zap] extra
zap.py is an httpx.BaseTransport; httpx is not a base dep (client core is urllib3).
3.1.0 imported zap.py eagerly in __init__, so `import hanzoai` crashed on missing
httpx in any clean install. Guard the import (try/except ImportError) and declare
httpx under the [zap] extra. import hanzoai now works with or without extras.
2026-07-05 19:49:03 -07:00
a777df1efb feat(hanzoai): optional ZAP-native transport (opt-in, public API unchanged) (#46)
Add hanzoai.zap: an httpx.BaseTransport / AsyncBaseTransport that routes the
SAME typed client calls through hanzo_zap.CloudClient over the ZAP binary wire.
Opt in with Hanzo(http_client=hanzoai.zap_http_client(...)); default stays HTTPS.

- hanzo-zap is an optional extra [zap], imported lazily so "import hanzoai"
  still works when it (and zap-proto) are not installed.
- Public __all__ is byte-identical; new names are additive and kept out of __all__.
- Request to ZAP mapping: path to dotted method, api-key header to Bearer auth,
  body forwarded verbatim.
- Tests: back-compat lock + request-translation via mock CloudClient (no network).

Co-authored-by: hanzo-dev <[email protected]>
2026-07-05 19:18:09 -07:00
zeekay 7902881446 fix(hanzoai): restore core SDK modules wiped by #45 regen — unbreaks hanzo-mcp (3.0.1)
#45 regenerated hanzoai as a pure OpenAPI client and DELETED the hand-written
core-SDK modules (config/mcp/session/protocols/…) that live in the same package
but aren't part of API generation. hanzo-mcp imports all four → the published
hanzoai 3.0.0 crashed EVERY hanzo-mcp consumer on startup ('No module named
hanzoai.protocols') → the MCP server wouldn't boot → agent couldn't
reconnect. Restore the 4 self-contained modules hanzo-mcp needs (verified: zero
imports of the removed Stainless internals); they coexist with the generated
api/models client. Left agents/auth/cluster/llm_client OUT — they import the
removed ._client and need migration to the new client (separate follow-up, no
reconnect-path consumer). Bump 3.0.0→3.0.1. Boot-proven: hanzo-mcp starts,
26 tool packages / 29 tools, MCP initialize OK.
2026-07-05 09:50:51 -07:00
zeekay 04741734d5 fix(browser): route MCP register through _result_to_mcp — ToolImage→ImageContent
The browser tool's bespoke register() returned execute()'s dict RAW, so FastMCP
flat-JSON-serialized a screenshot's ToolImage (base64 inline, 250K chars) →
overflowed the agent context and wedged the run — the SAME bug main's
ImageContent converter (711eb498) fixed for every OTHER tool via
BaseTool.register→_result_to_mcp, but the browser tool bypassed that path.
Now both registration paths share the one converter: nested ToolImage anywhere
in the result becomes a native MCP ImageContent block the client SEES. One way.
2026-07-05 04:29:07 -07:00
bcd56c6910 feat(hanzoai): regenerate client from unified OpenAPI spec (retire Stainless) (#45)
Replace the Stainless-generated `hanzoai` client (188 legacy LLM-gateway
endpoints) with an openapi-generator `python` (urllib3 + pydantic v2)
client built from hanzoai/openapi `hanzo.yaml` — the full unified
api.hanzo.ai/v1 surface (493 API groups, 1710 models, all /v1 products).

- scripts/generate.sh: the one-way generator; emits only pkg/hanzoai
  (other pkg/hanzo-* packages untouched).
- pyproject: deps -> urllib3 / python-dateutil / pydantic>=2 /
  typing-extensions; version 2.2.3 -> 3.0.0 (breaking surface change).
- tests/: Stainless client tests replaced with a surface smoke test.
- .github/workflows/generate.yml: regenerate on openapi spec-update.
- Base URL https://api.hanzo.ai, Bearer hk- / JWT auth.

Verified: wheel hanzoai-3.0.0 builds, installs into a clean venv, and
`import hanzoai` + product API construction succeed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-04 20:07:32 -07:00
zeekay 565426aa84 Merge branch 'chore/ui-rag-v1-paths'
# Conflicts:
#	pkg/hanzo-tools-browser/hanzo_tools/browser/browser_tool.py
2026-07-04 13:59:42 -07:00
zeekay 61d932766c fix(browser): screenshots/pdf save-to-disk + return path, never inline huge base64
A full-page PNG returned as inline base64 (100K+ chars) overflows an agent's
context window and wedges the whole run — the #1 cause of 'the browser tool
hangs' (two mobile-QA agents died on 6.6MB transcripts). Both capture paths
(native-zap _extension_command + the playwright fallback) now persist to
~/.hanzo/screenshots (or a caller path) and return a compact {path,size};
base64 is inlined ONLY for a small PNG (<=40KB). Adds _save_capture + _extract_b64.
2026-07-04 12:33:52 -07:00
zeekayandHanzo Dev 93330d3091 fix(billing): drop extraneous /api prefix — api.hanzo.ai/v1/billing
The billing tool hardcoded api.hanzo.ai/api/v1/billing (double prefix). Per
the /v1-only canonical API contract, the gateway serves everything under bare
/v1/* — never /api/. Scrub to api.hanzo.ai/v1/billing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-03 12:31:09 -07:00
zeekayandHanzo Dev 3da02092db browser/zapd_consumer: self-heal a dead cached socket
connect() trusted a cached ZapClient forever; if the router restarted, every
call raised BrokenPipeError and the consumer stayed wedged. list_providers/route
now drop the dead client and reconnect once.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-03 12:31:09 -07:00
zeekay 711eb4987a feat(mcp): native MCP ImageContent — tools return images the client can SEE
HIP-0300 tools serialize results to JSON text, which flattened images into
useless base64-in-text. Add a ToolImage value type + one converter in
BaseTool.register: any ToolImage anywhere in a result becomes a real
mcp.types.ImageContent block; text-only results stay byte-for-byte JSON.
Emitters wired: fs read of image files, browser screenshots (bidi+playwright).
This gives hanzo-mcp vision parity with zai-mcp — an agent with native vision
(Claude) sees pixels directly, no per-task vision tool needed.

Also decomplect the core-package collision that caused this class of bug:
hanzo-tools-core shipped a DUPLICATE hanzo_tools/core/ that nondeterministically
shadowed canonical hanzo-tools (dropping ToolImage, the sophisticated register,
etc. — same shadow class as the 0.3.0 error-reexport gap). Finish the
'merge core into hanzo-tools' refactor: move id_tool into hanzo-tools, make
hanzo-tools-core a pure empty metapackage. Now ONE package owns hanzo_tools.core.

Versions: hanzo-tools 0.3.2, -core 0.3.1 (shim), -browser 0.5.9, -fs 0.3.4,
hanzo-mcp 0.15.12 (floors bumped). Verified: fs read PNG -> [text, image];
fs stat -> [text]; 29 tools intact; 9 tests pass.
2026-07-03 00:45:49 -07:00
zeekay 5bfc8f9399 test(hanzo-mcp): guard every hanzo.tools entry point imports cleanly
Regression guard for the hanzo-tools 0.3.0 core re-export gap that silently
dropped fs/exec/code/git/fetch/agent, leaving hanzo-mcp with only 13 tools.
Fails loudly if any entry point stops loading or the core error contract
(ToolError/ConflictError/InvalidParamsError/NotFoundError) regresses.
2026-07-02 23:57:21 -07:00
zeekay babfd616fa release(hanzo-tools): 0.3.1 — ship core error-class re-exports
PyPI hanzo-tools 0.3.0 shipped a core/__init__.py that never re-exported
ToolError/ConflictError/InvalidParamsError/NotFoundError from core.unified.
Six tool packages (fs, shell, agent, code, vcs, net) import those names at
entry-point load and silently failed, so hanzo-mcp exposed only 13 tools
instead of the full axis set. The re-export fix already landed in git
(0d8d52d9) but 0.3.0 was never republished. Bump to 0.3.1 to ship it.

Verified: entry points 20→26 loaded, hanzo-mcp tools/list 13→29.
2026-07-02 23:55:25 -07:00
zeekay 421af5170a fix(hanzo-mcp): 'tool list' crash on @property descriptions (v0.15.11)
DynamicToolRegistry stored getattr(tool_class, 'description'), which for a
class-level @property returns the property DESCRIPTOR (not a str). 'tool list'
then did len(config.description) → 'object of type property has no len()',
breaking the entire tool-catalog command. Add _extract_tool_description
(mirrors _extract_tool_name): resolve a @property via a cheap instance, else
coerce to str, always returns str. Plus a defensive isinstance guard in the
list display path. Regression test covers property/unevaluable-property/plain/
absent + asserts every registered entry's description is len()-safe. 2 passed.
2026-07-02 23:00:52 -07:00
zeekayandHanzo Dev c3ac3efd06 rip: migrate KMS universal-auth login to canonical /v1/kms/auth/login
hanzo-kms (sync + async): /api/v1/auth/universal-auth/login →
/v1/kms/auth/login. Body already canonical {clientId,clientSecret};
TokenResponse already parses {accessToken,expiresIn,tokenType}.

Scoped per CTO ruling: ONLY endpoints with a real canonical /v1/kms/*
target are migrated. The remaining /api/v3/secrets/raw/*,
/api/v1/auth/{kubernetes-auth,aws-auth}/login and /api/v3/auth/login
have NO canonical route (or a different API shape) on the kms.hanzo.ai
server and are left intact — rewriting them would mint dead /v1 paths.
They are flagged for a separate SDK-port task. Clean break, no aliases
(org rule: one way, /v1/, never /api/).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 13:16:42 -07:00
zeekay 3076f1587f release: bump auth-fixed packages for publish
Ship the canonical hanzo-app client_id + /v1/iam/get-account fixes:
- hanzoai 2.2.2 → 2.2.3
- hanzo-cli 0.2.1 → 0.2.2
- hanzo 0.4.2 → 0.4.3
- hanzo-tools-auth 0.1.0 → 0.1.1
2026-06-30 22:21:53 -07:00
zeekay 73774a1b51 auth: fix get-account endpoint (bare /v1/get-account 404s)
get_user_info hit {api_base_url}/v1/get-account (api.hanzo.ai/v1/get-account),
which 404s — the account read lives on the IAM issuer at /v1/iam/get-account
(same base as mint_api_key). Response is a {status,sub,name,data,data2}
envelope; accessKey is under data (get_api_key already reads it there).
2026-06-30 17:56:31 -07:00
zeekay 133472997a auth: align all SDK packages to canonical <org>-<app> client ids
IAM seeds apps as <org>-<app> (iam cmd/iam/cli/init_apps.go:57-59):
hanzo-app, zoo-app, lux-app — AppName == ClientID. Several packages used the
inverted 'app-hanzo'/'app-hanzobot' or placeholder '*-client-id', none of which
IAM knows — device/PKCE login would fail. One value, everywhere:

- hanzoai/auth.py: login_with_pkce default -> IAM_CLIENT_ID (hanzo-app)
- hanzo-cli/auth.py: DEFAULT_APP, DEFAULT_CLIENT_ID -> hanzo-app
- hanzo/commands/auth.py: IAM_CLIENT_ID -> hanzo-app
- hanzo-tools-auth/session.py: DEFAULT_APP, DEFAULT_CLIENT_ID -> hanzo-app
- hanzo/commands/iam.py: --app default + doc example -> hanzo-app
- hanzo-cli/bot/commands.py: BOT_IAM_APP/CLIENT_ID app-hanzobot -> hanzo-bot
2026-06-30 17:23:10 -07:00
zeekay 5ecb393e8a auth: fix device login + API-key flow to real hanzo.id/IAM contract
Verified against iam (controllers/token.go, user.go, init_apps.go), ai
(controllers/account.go), and login (middleware.ts) sources:

- client_id hanzo-app (canonical brand CLI/desktop/mobile app seeded with the
  device_code grant), not the unseeded hanzo-dev/app-hanzo.
- Device flow uses RFC paths hanzo.id proxies to IAM — POST /oauth/device and
  /oauth/token — form-encoded (IAM reads device-grant params via Query(), skips
  JSON), keyed off the response body so a proxy can't desync polling.
- get_user_info reads /v1/get-account (IAM Claims with accessKey inlined),
  not the nonexistent /v1/user.
- Replace fictional named-key CRUD (/v1/api-keys) with Hanzo's real single
  per-user hk- key model: get_api_key (read accessKey), mint_api_key (self via
  /v1/iam/mint-user-keys), get_or_create_api_key (read-first), revoke_api_key.

Email/SMS/Google/GitHub/web3 all work since the device flow delegates login UI
to the browser approval page (providers wired per iam wire_providers.go).
2026-06-30 17:23:10 -07:00
44dab1e8a5 chore(tools-ui): RAG client paths /api/* -> /v1/* (#43)
The hanzo-tools-ui docs-RAG client targets api.hanzo.ai (host already
canonicalized on main). Move the paths off the residual /api/ prefix to
top-level /v1/, matching the cloud-api route rename and the openapi
v1.0.0 lock-in (no /api/ prefix):

  /api/search-docs       -> /v1/search-docs
  /api/chat-docs         -> /v1/chat-docs
  /api/index-docs        -> /v1/index-docs
  /api/search-docs/stats -> /v1/search-docs/stats

Hard cutover, coordinated with hanzoai/cloud (productsvc + ai serve these
at /v1), universe cloud-api-v1 AUTH_PUBLIC_PATHS, hanzo-docs RAG clients,
and the openapi cloud spec.

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 17:06:30 -07:00
zeekay d6d3d1b07d chore(tools-ui): RAG client paths /api/* -> /v1/*
The hanzo-tools-ui docs-RAG client targets api.hanzo.ai (host already
canonicalized on main). Move the paths off the residual /api/ prefix to
top-level /v1/, matching the cloud-api route rename and the openapi
v1.0.0 lock-in (no /api/ prefix):

  /api/search-docs       -> /v1/search-docs
  /api/chat-docs         -> /v1/chat-docs
  /api/index-docs        -> /v1/index-docs
  /api/search-docs/stats -> /v1/search-docs/stats

Hard cutover, coordinated with hanzoai/cloud (productsvc + ai serve these
at /v1), universe cloud-api-v1 AUTH_PUBLIC_PATHS, hanzo-docs RAG clients,
and the openapi cloud spec.
2026-06-30 16:58:58 -07:00
f2ca58ca6c Point cloud clients at canonical api.hanzo.ai front door (#42)
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]>
2026-06-30 16:49:00 -07:00
0ea7627203 refactor(iam): drop Casdoor branding — Hanzo IAM only (white-label) (#41)
Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 14:56:32 -07:00
zeekay b5f9d939c4 fix(tools): correct PyPI long_description metadata + stale tool-count tests
Packaging metadata (twine check was PASSED-with-warnings -> clean PASS):
- config/llm/mcp/vector: add missing 'readme = README.md' ([project] key);
  README files existed but were never wired into the dist metadata.
- net/plan/vcs: fill empty (0-byte) README.md files referenced by the
  readme key, so long_description is no longer missing.

Tests (hanzo-tools-core/test_all_tools.py drifted from current sources):
- browser: 1 -> 3 tools (browser, cdp, playwright after the zapd split)
- mcp_tools: 4 -> 5 tools
- required-tool total: 21 -> 23
2026-06-26 11:57:48 -07:00
zeekay 576dcf7ecc release: hanzoai 2.2.2
Patch release carrying the HIP-0111 IAM endpoint migration in
hanzoai.resources.iam (/iam/api/get-account + /iam/api/userinfo ->
canonical /v1/iam/oauth/userinfo), already in-tree since be917695 but
unreleased. 2.2.1 is taken on PyPI; bump to 2.2.2 (x.x.x+1).
2026-06-26 11:57:39 -07:00
zeekayandGitHub be91769513 fix(iam): migrate userinfo/get-account to canonical /v1/iam/oauth/userinfo (HIP-0111) (#38)
Co-authored-by: Zach Kelling <[email protected]>
2026-06-24 19:16:18 -07:00
zeekay 1161e11281 fix(iam): canonical /v1/iam paths, drop forbidden /api/ (HIP-0111)
hanzo-tools-iam built URLs as {base}/api/{path} — forbidden per HIP-0111
(no /api/ prefix; one way). Now {base}/v1/iam/{path}; health probe is
/v1/iam/healthz. Asserts JSON shape, not bare 200, to dodge the IAM SPA
catch-all that serves 200 text/html for unregistered paths. Adds a test
for _iam_url + IAM_BASE_URL. (Test harness wiring for this non-member
package is a separate packaging task — code verified: 0 /api/ remain.)
2026-06-24 13:50:24 -07:00
zeekay 3aff6628b6 refactor(s3): back hanzo-s3 with boto3 over hanzoai/s3, drop minio
hanzoai/s3 (SeaweedFS fork w/ consensus+ZAP) is the canonical object
store; the minio pip package and its branding are gone. hanzo-s3 is now
a thin native boto3 adapter exposing a stable surface (S3Client + Bucket/
Object/Stat) so the hanzo s3 CLI and s3 MCP tool are unchanged. Removed
the dead MinioAdmin re-export (no consumer) and all MinIO naming.

CLI (7) + MCP S3Tool (14) tests green; ruff clean.
2026-06-24 13:50:24 -07:00
zeekay 587084ac72 feat(cli,mcp): complete S3 control surface — hanzo s3 + s3 MCP tool
Closes the last gap in the one-way Hanzo control surface. IAM/KMS/PaaS
already had both CLI subcommands and MCP tools; S3 had neither.

- hanzo s3: buckets/mb/rb/ls/stat/rm/presign over hanzo_s3 (MinIO) client,
  creds from env (inject from KMS, never plaintext). Registered in the
  unified hanzo CLI alongside iam/kms/paas.
- hanzo-tools-s3: S3Tool MCP tool (same action-dispatch pattern as PaaSTool),
  exported via the hanzo.tools entry point; s3 added to loader prefix map.
- uv workspace: map hanzo-cli/iam/kms/s3 + tools-s3 to local sources so the
  unified CLI resolves on-disk packages (fixes hanzo_iam ImportError from a
  stale published 1.29.0 wheel shadowing local 1.30.0).

Tests: 7 CLI + 14 MCP, all green.
2026-06-24 13:50:24 -07:00
zeekay 53d876c320 kms: use canonical KMS env + client names 2026-06-24 10:50:44 -07:00
zeekay 1603fbb433 hanzo-mcp 0.15.10: harden tool floors (shell>=0.6.5, core>=0.3.0) so the pre-ToolError core can't resolve
Root cause of dead shell-exec: stale installs froze hanzo-tools-core 0.2.0 (no ToolError)
before 0.3.0 published, shadowing hanzo_tools.core and crashing the shell package import.
Floors now exclude it; a fresh/upgraded install always lands the consistent set.
2026-06-23 11:55:07 -07:00
zeekay a3f58d0ec3 refactor(zap): consume canonical zap-proto, drop hand-rolled wire
hanzo-tools-browser: the zapd router envelope (frame.rs mirror) is no
longer reimplemented — zapd_consumer now uses zap.frame + zap.ZapClient
(dep zap-proto>=1.2.0). The end-to-end browser command codec stays local
and untagged to match the extension's decodeCmd (frame.encode_cmd is
tagged and would corrupt it); a new test proves the byte layout.

hanzo-zap: wire.py now delegates the zero-copy codec (Message/Builder/
ObjectBuilder/object readers) to canonical zap.wire instead of a 3rd
hand-rolled copy; the hanzo cloud-service schema + handshake + frame I/O
remain as a thin layer. dep zap-proto>=1.2.0. 114 tests pass.
2026-06-21 01:29:06 -07:00
zeekay 4330f687b3 release: hanzo-tools-browser 0.5.8 + hanzo-mcp 0.15.9 (Firefox BiDi driving) 2026-06-20 13:04:39 -07:00
zeekay 18e4de1c19 feat(browser): route Firefox actions through BiDi fast-path
When the firefox backend is targeted and Firefox exposes a WebDriver BiDi remote agent (--remote-debugging-port=9222), drive it directly via bidi_client (real navigation + trusted input) instead of the half-implemented extension path or a dead headless Playwright. Additive + gated: only fires for target firefox with live BiDi and a BiDi-mapped action; otherwise falls through to the existing extension/Playwright routing unchanged. Takes effect after the MCP server reloads.
2026-06-20 13:04:39 -07:00
zeekay 7362aae39d fix(browser/bidi): drive Firefox 153 (BiDi-only) seamlessly
Firefox 129+ runs WebDriver BiDi-only by default (CDP off), so /json/version 404s and the socket is served directly at /session. Four fixes make the BiDi client actually work against it:

- probe(): fall back to a TCP check + ws://host:port/session when /json/version isn't advertised (don't open a probe WebSocket — that establishes session state and conflicts with session.new).

- websockets>=13 compat: ClientConnection dropped .closed; _ws_open() reads .state/.close_code across versions.

- session lifecycle: session.end on close() so a session never orphans (Firefox keeps one at a time, and an orphan blocks every later session.new); retry session.new once after ending.

- add create_context/close_context/get_url so automation runs in a fresh tab without disturbing the user's tabs.
2026-06-20 13:04:39 -07:00
zeekay cbb783896e feat(computer): ydotool/uinput backend (Linux) — avoid xdotool/XTEST crashing Tauri/WebKitGTK 2026-06-17 22:06:55 +00:00
zeekay 999c39af6e feat(tools): hanzo-tools-gimp (clean-room BSD-3) 2026-06-17 21:53:05 +00:00