Files
cli/Cargo.toml
zooqueen 6a4ad27c7c client: the platform client is a crate, and the CLI is one consumer of it
There was no Rust client for api.hanzo.ai anywhere in the estate — hanzoai/rust-sdk
is the agent/MCP/crypto SDK and carries no HTTP client for the platform — so every
Rust caller that wanted one had to write a transport. This binary had already
written one, in src/http.rs, and that is the file this deletes.

crates/client holds what the shipped TypeScript contract holds (@hanzo/ai grant.ts,
result.ts, http.ts), with the same four rules and the same names:

  as(subject)   one credential scoped to ONE end user, minted through IAM's act
                grant at POST {issuer}/v1/iam/tokens/issue?id=<subject> — IAM's own
                host, target in the query, no body, camelCase back. Held until 30s
                before expiry, dropped on a 401 which re-mints and retries once. No
                method takes a user id, so none can be passed wrong.
  Outcome<T>    a 202 is a success status, so a held call is a separate arm of one
                enum sharing no value with Done. Rust can enforce what TypeScript
                could only encourage: reading a held call as done does not misbehave
                at runtime, it fails to compile.
  Error         one enum. A non-2xx is Error::Api{status, body} — the server's own
                words, never a string, never a loose map.
  Transport     one request in, one Reply{status, body} out, status handed back and
                never flattened. A stub over the same trait proves the mint address,
                the cache, the re-mint and the held arm without a network.

src/zap.rs now returns that same Reply, so the two wires the CLI speaks meet at
Seam::send with no conversion between them — and zap stops widening its own u16 into
a StatusCode only for the caller to ask is_success() of it.

Spelled r#as at the call site because `as` is a Rust keyword. The name is `as`.
2026-08-21 07:25:20 -07:00

97 lines
4.3 KiB
TOML

[package]
name = "hanzo-cli"
version = "1.9.56"
edition = "2021"
authors = ["Hanzo AI"]
description = "Unified CLI for Hanzo AI development tools"
license = "MIT"
repository = "https://github.com/hanzoai/cli"
[[bin]]
name = "hanzo"
path = "src/main.rs"
# genspec pulls the cloud command surface forward: it reads cloud's live route
# table over the wire and the authored master (YAML) from a hanzoai/openapi
# checkout. Both are MAINTAINER inputs — the shipped `hanzo` parses no YAML,
# fetches no spec and links no cloud code — so its parser is compiled only when
# the seam is run.
[[bin]]
name = "genspec"
path = "src/bin/genspec.rs"
required-features = ["maintainer"]
# driftgate is the GATE, not a generator: it reads the committed spec, the live
# route table and — where neither can answer — the running host, and it reads the
# built `hanzo` to ask what a person can actually reach. No YAML, no authored
# master, so CI runs it with nothing but a checkout, a build and the network.
[[bin]]
name = "driftgate"
path = "src/bin/driftgate.rs"
required-features = ["maintainer"]
# genproduct rewrites the committed command tree from the committed spec. It was
# auto-discovered from src/bin/ and so installed into every user PATH — where a
# bare (it parses no arguments) rewrote generated.rs inside the
# reader own cargo registry cache. It is a maintainer tool; it is gated like one.
[[bin]]
name = "genproduct"
path = "src/bin/genproduct.rs"
required-features = ["maintainer"]
# ONE gate for the three maintainer tools. A user installs the hanzo binary;
# genspec, driftgate and genproduct are how that binary's command tree is MADE,
# and a reader has no use for them. genproduct in particular parses no arguments,
# so a bare invocation rewrote generated.rs inside the caller's own registry cache.
[features]
maintainer = ["dep:serde_norway"]
# The two library crates the CLI is BUILT ON, each in its own crate so another
# consumer shares the exact same wire via a pinned git dependency — one source of
# truth, several consumers. `client` is the platform client itself: the act grant,
# the held outcome and the typed refusal live there, not in this binary.
[workspace]
members = ["crates/event", "crates/client"]
[dependencies]
hanzo-event = { path = "crates/event", version = "0.1.0" }
hanzo-client = { path = "crates/client", version = "0.1.0" }
clap = { version = "4.5", features = ["derive", "env"] }
tokio = { version = "1.36", features = ["full"] }
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_norway = { version = "0.9", optional = true } # genspec only — reads hanzoai/openapi's YAML master
toml = "0.8"
dirs = "5.0"
colored = "2.1"
dialoguer = "0.11"
reqwest = { version = "0.12", default-features = false, features = ["json", "gzip", "rustls-tls", "http2", "charset"] }
which = "6.0"
libc = "0.2" # SIGTERM/liveness for the local cloud host daemon
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tempfile = "3.10"
chrono = { version = "0.4", features = ["serde"] }
# Cloud auth (HIP-0111 OIDC PKCE S256).
sha2 = "0.10" # PKCE S256 challenge
base64 = "0.22" # base64url (no pad)
rand = "0.8" # OsRng for PKCE verifier + state + local wallet entropy
# A scannable link, so signing a headless box in is a phone camera rather than
# 40 characters typed by hand. Terminal half-blocks, no image, no C.
qr2term = "0.3"
webbrowser = "1" # open the IAM authorize URL
# Local / imported wallets — HD derivation + EVM address. Cloud-custody (KMS/MPC)
# wallets need NONE of this; these are only for offline `wallet create --local`
# and `wallet import`. Pure-Rust RustCrypto; secrets live in `iam::token`'s store.
bip39 = { package = "tiny-bip39", version = "1" } # BIP-39 mnemonic (all word counts) -> seed
bip32 = "0.5" # secp256k1 BIP-32 HD derivation from a seed
k256 = "0.13" # secp256k1 (matches bip32) — public key -> EVM address
sha3 = "0.10" # Keccak-256 for EVM address
[dev-dependencies]
assert_cmd = "2.0"
predicates = "3.1"