Files
cli/Cargo.toml
zeekay 31a9f50892 feat(iam): device-flow login (RFC 8628) + shared credential store
Integrate the native device flow into the HIP-0111 iam module so there is ONE
login command, one way. `hanzo login` runs the browser Authorization-Code+PKCE
flow by default and the RFC 8628 device flow when there is no browser to open —
headless (no DISPLAY/WAYLAND_DISPLAY on linux), the browser fails to launch, or
`--device` is passed. Endpoints resolve brand-aware via paths.rs; the device
grant runs as the `<brand>-app` client (hanzo-app for the default brand).

- src/iam/device.rs: device_auth + poll state machine (authorization_pending
  keeps polling, slow_down backs off +5s, expired_token/access_denied
  terminate) returning a keychain TokenSet; link + terminal QR + code display.
- src/iam/jwt.rs: signature-free JWT claim decode (identity for the flat store).
- src/iam/credentials.rs: the ecosystem-shared flat ~/.hanzo/credentials.json
  (0600, atomic temp+rename, unknown-key preserving). On every successful
  DEFAULT-brand login the keychain token set is ALSO mirrored here, so the Go
  hanzo CLI and hanzo-dev share one session. logout clears both stores; whoami
  falls back to this store when the keychain has no entry.
- oauth.rs surfaces a browser-launch failure (BROWSER_OPEN_FAILED) instead of
  hanging, so login can fall back to the device flow.
- paths.rs gains the RFC 8628 DEVICE endpoint; main.rs gains `login --device`.

Deps pinned exact: base64 0.22.1 (tightened), qr2term 0.3.3 (new).

Tests: 32 pass. New coverage — device client-id is <brand>-app, poll state
machine + DeviceTokenResp→TokenSet against a mocked IAM server (pending→issued,
denied, expiry), JWT claim decode, and the flat store (round-trip incl.
unknown-key preservation, 0600, mirror-from-TokenSet, logout preserves other
tools' keys and reports whether it cleared a session).
2026-07-13 11:17:42 -07:00

52 lines
2.2 KiB
TOML

[package]
name = "hanzo-cli"
version = "1.0.0"
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"
[dependencies]
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"
toml = "0.8"
dirs = "5.0"
colored = "2.1"
indicatif = "0.17"
dialoguer = "0.11"
reqwest = { version = "0.12", features = ["json", "gzip"] }
which = "6.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tempfile = "3.10"
subprocess = "0.2"
chrono = { version = "0.4", features = ["serde"] }
# Cloud auth (HIP-0111 OIDC PKCE S256 + RFC 8628 device flow).
sha2 = "0.10" # PKCE S256 challenge
base64 = "0.22.1" # base64url (no pad) — PKCE + JWT claim decode
rand = "0.8" # OsRng for PKCE verifier + state + local wallet entropy
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] } # OS keychain — secrets NEVER hit disk
webbrowser = "1" # open the IAM authorize URL
qr2term = "0.3.3" # terminal QR of the device verification link
# 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 the OS keychain.
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"