fix: KMS on its own host (kms.<brand>), stable doctor probes

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).
This commit is contained in:
Hanzo Dev
2026-06-30 22:37:29 -07:00
parent 925e692b8b
commit b8235f55de
10 changed files with 33 additions and 15 deletions
+2 -2
View File
@@ -20,9 +20,9 @@ inputs:
trust the GitHub issuer; otherwise pass an IAM machine-identity token.
required: true
base-url:
description: Gateway base that serves /v1/kms (KMS owns this namespace).
description: KMS host that serves /v1/kms (its own host, not api.<brand>).
required: false
default: https://api.hanzo.ai
default: https://kms.hanzo.ai
export:
description: "Where to put secrets: 'env' (job environment) or 'file'."
required: false
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@luxfi/helper",
"version": "1.0.5",
"version": "1.0.6",
"description": "Lux CLI helper — sign in to Lux and use its AI models in Claude Code, Codex, and more",
"type": "module",
"bin": {
@@ -28,6 +28,6 @@
},
"homepage": "https://lux.network",
"dependencies": {
"@hanzo/helper": "^1.0.5"
"@hanzo/helper": "^1.0.6"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@zenlm/helper",
"version": "1.0.5",
"version": "1.0.6",
"description": "Zen CLI helper — sign in to Zen and use its AI models in Claude Code, Codex, and more",
"type": "module",
"bin": {
@@ -29,6 +29,6 @@
},
"homepage": "https://zenlm.org",
"dependencies": {
"@hanzo/helper": "^1.0.5"
"@hanzo/helper": "^1.0.6"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@zooai/helper",
"version": "1.0.5",
"version": "1.0.6",
"description": "Zoo CLI helper — sign in to Zoo and use its AI models in Claude Code, Codex, and more",
"type": "module",
"bin": {
@@ -28,6 +28,6 @@
},
"homepage": "https://zoo.ngo",
"dependencies": {
"@hanzo/helper": "^1.0.5"
"@hanzo/helper": "^1.0.6"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hanzo/helper",
"version": "1.0.5",
"version": "1.0.6",
"description": "Hanzo CLI helper for API access, cloud services, MCP, and plugins",
"type": "module",
"main": "./dist/index.js",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hanzo/kms",
"version": "1.0.5",
"version": "1.0.6",
"description": "Standalone `kms` CLI — sign in with IAM and pull environment secrets for local dev",
"type": "module",
"bin": {
@@ -31,6 +31,6 @@
},
"homepage": "https://hanzo.ai",
"dependencies": {
"@hanzo/helper": "^1.0.5"
"@hanzo/helper": "^1.0.6"
}
}
+4 -2
View File
@@ -19,8 +19,10 @@ export const doctorCmd = new Command('doctor')
.action(async () => {
const checks: Check[] = [];
checks.push(await reachable('IAM', `${endpoints.iam}/health`));
checks.push(await reachable('Cloud API', `${endpoints.api}/health`));
// Probe the real endpoints the tool depends on (stable), not a bare /health
// path (which varies by gateway config): IAM issuer discovery + the model list.
checks.push(await reachable('IAM', `${endpoints.iam}/.well-known/openid-configuration`));
checks.push(await reachable('Cloud API', `${endpoints.api}/v1/models`));
const { apiKey, user } = await getConfig();
checks.push({
+7
View File
@@ -25,6 +25,9 @@ export interface Brand {
clientId: string;
/** Public web root — ecosystem install/download pages hang off it. */
site: string;
/** KMS host (secrets). KMS owns its own host; matches IAM's per-brand KMSHost
* (iam cmd/iam/cli/init_apps.go brandSpecs). */
kms: string;
}
const BRANDS: Record<Brand['id'], Brand> = {
@@ -36,6 +39,7 @@ const BRANDS: Record<Brand['id'], Brand> = {
api: 'https://api.hanzo.ai',
clientId: 'hanzo-app',
site: 'https://hanzo.ai',
kms: 'https://kms.hanzo.ai',
},
lux: {
id: 'lux',
@@ -45,6 +49,7 @@ const BRANDS: Record<Brand['id'], Brand> = {
api: 'https://api.lux.network',
clientId: 'lux-app',
site: 'https://lux.network',
kms: 'https://kms.lux.network',
},
zoo: {
id: 'zoo',
@@ -54,6 +59,7 @@ const BRANDS: Record<Brand['id'], Brand> = {
api: 'https://api.zoo.ngo',
clientId: 'zoo-app',
site: 'https://zoo.ngo',
kms: 'https://kms.zoo.network',
},
zen: {
id: 'zen',
@@ -63,6 +69,7 @@ const BRANDS: Record<Brand['id'], Brand> = {
api: 'https://api.zenlm.org',
clientId: 'zen-app',
site: 'https://zenlm.org',
kms: 'https://kms.zenlm.org',
},
};
+8
View File
@@ -41,6 +41,14 @@ export const endpoints = {
get pricing(): string {
return env('HANZO_PRICING_URL', `https://pricing.${new URL(brand.site).host}`);
},
/**
* KMS — secrets, on its own host (`kms.<brand>`, per IAM's KMSHost). KMS is
* NOT proxied under api.<brand>/v1/kms today, so target the dedicated host;
* HANZO_KMS_URL overrides if a deployment fronts it elsewhere.
*/
get kms(): string {
return env('HANZO_KMS_URL', brand.kms);
},
} as const;
/** IAM ops, all relative to the issuer base. */
+3 -2
View File
@@ -10,7 +10,8 @@
*
* By KMS's design the bulk list returns KEYS ONLY; values are read one at a
* time. `pullSecrets` composes the two: list the env, then read each value.
* IAM lives under /v1/iam/*, KMS under /v1/kms/*the helper never crosses them.
* KMS runs on its own host (`kms.<brand>`, endpoints.kms)it is not proxied
* under api.<brand>/v1/kms — and IAM lives under /v1/iam/*; never crossed.
*/
import { endpoints } from './endpoints';
@@ -32,7 +33,7 @@ interface SecretMeta {
env: string;
}
const base = (org: string) => `${endpoints.api}/v1/kms/orgs/${encodeURIComponent(org)}/secrets`;
const base = (org: string) => `${endpoints.kms}/v1/kms/orgs/${encodeURIComponent(org)}/secrets`;
const authFail = (res: Response, what: string): KmsError =>
res.status === 401 || res.status === 403