mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
hanzoai/dev went public defaulting to someone else's cloud. Four things were
wrong, and all four now have a test that fails if they come back.
DEFAULT PROVIDER AND MODEL. built_in_model_providers() gains `hanzo` -- the
Hanzo Cloud at api.hanzo.ai/v1, Responses wire, bearer from HANZO_USER_KEY --
and it is the default. The default model is zen5-coder. This was not cosmetic:
the old default is not in the gateway catalog, so it 400s against our own
cloud. Measured, not assumed:
POST api.hanzo.ai/v1/responses model=gpt-5-codex -> 400 "model is not
available. Use GET /v1/models to list available models"
POST api.hanzo.ai/v1/responses model=zen5-coder -> 200, clean output
Our own CLI already documented the bug from the other side -- dev.rs says
"Name the model too -- codex's built-in default is not in the gateway catalog
and would 400" and passes `-c model=` on every launch to work around it. It
also asserts that on the native gateway NO model_provider override is needed,
which is only true once `hanzo` is the built-in default. It is now.
zen5-coder over enso because dev is a coding agent and because enso returns
system-prompt text on the Responses wire that dev actually speaks; zen5-coder
returns the answer. Model ids were read from GET /v1/models with a bearer, not
guessed. Other providers stay fully configurable -- this is the default, not
the only choice.
THE LOGIN COMMAND. Messages said `code login` / `codex login`. The real command
is `hanzo auth login` (`hanzo login` is not a command -- it is swallowed as an
AI task). It now lives once, as auth::LOGIN_COMMAND, so no message can drift
into naming a command that does not exist. References to this binary's own
subcommand became `dev login`.
A BINARY NAMED dev. HEAD built `code` and `codex` and nothing named `dev`, so a
release cut today would ship no `dev` -- and `hanzo code` launches a bare `dev`
off PATH, so every new install would break. `dev` is now the canonical bin,
declared first; `code` remains as an alias to the same program, never the
reverse. The CLI identifies as `dev` / "Hanzo Dev", not "OpenAI Codex".
OUR NAMESPACES. The pipeline published to @just-every/* on npm and pushed
formulae to just-every/homebrew-tap. Both of ours already exist and are already
populated at v0.6.91 -- @hanzo/dev plus all five @hanzo/dev-<target> packages,
and hanzoai/homebrew-tap's Formula/hanzo-dev.rb, which installs `dev` and
expects assets named dev-<triple>. Nothing here is squatted; this is our own
distribution, reclaimed. Assets are renamed to match the formula.
Two live supply-chain bugs fell out of that, both worse than a naming problem:
postinstall.js (runs on every npm install) and the tui auto-updater fetched
from a third party's releases -- the updater also told our users to
`npm install -g @just-every/code@latest`. Worse, a shadowed `isWSL` const put
the two fast paths in the temporal dead zone, so their catch blocks swallowed
the error and EVERY install fell through to that download: the five published
platform packages were never used, and the third-party fetch was not an edge
case but the only path that ran. Fixed and exercised in a sandbox across all
three paths. codex-cli/.pack/ -- a committed `npm pack` artifact CI
regenerates, and the reason the bad URL had two homes -- is now untracked.
DURABILITY, because upstream-merge.yml merges openai/codex every 30 minutes.
A one-time edit to a default is a suggestion that survives until the next
merge. Two mechanisms:
- .github/merge-policy.json prefer_ours_globs listed only codex-rs/** while
the workspace that SHIPS is code-rs/**, so it protected nothing that
ships. The identity-bearing code-rs files are now listed.
- code-rs/core/tests/hanzo_identity.rs asserts all four properties -- the
provider, a Zen default, a bin named dev, and no third-party publish
target. Upstream has no such file, so a merge cannot conflict with it or
carry the guard away with the thing it guards. Policy can be edited;
a red build cannot be ignored.
The gate is not decorative: it failed on the un-retargeted pipeline
("release.yml still references `@just-every/`") and went green only when the
pipeline was actually fixed.
Verified: cargo build --bin dev clean; hanzo_identity 4/4; code-core lib
332 passed, 1 failed -- git_info::test_collect_git_info_with_remote, which
fails identically on a clean origin/main checkout here because this machine
has a global [email protected]:.insteadof rewrite. Pre-existing and
environmental, not from this change.
$ dev exec "say OK" # fresh profile, no config
Hanzo Dev v0.0.0
model: zen5-coder
provider: hanzo
Co-authored-by: Hanzo Dev <[email protected]>
183 lines
6.8 KiB
PowerShell
183 lines
6.8 KiB
PowerShell
param(
|
|
[string]$Version = "latest",
|
|
[switch]$FixCache
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Step($msg) { Write-Host ("---- " + $msg) -ForegroundColor Cyan }
|
|
function Info($msg) { Write-Host $msg }
|
|
function Warn($msg) { Write-Host $msg -ForegroundColor Yellow }
|
|
function Err ($msg) { Write-Host $msg -ForegroundColor Red }
|
|
|
|
# 1) Basics
|
|
Step "Environment"
|
|
$envs = [ordered]@{
|
|
"Node" = (node -v 2>$null)
|
|
"npm" = (npm -v 2>$null)
|
|
"PowerShell" = $PSVersionTable.PSVersion.ToString()
|
|
"OSArch" = $env:PROCESSOR_ARCHITECTURE
|
|
"APPDATA" = $env:APPDATA
|
|
"LOCALAPPDATA" = $env:LOCALAPPDATA
|
|
}
|
|
$envs.GetEnumerator() | ForEach-Object { Info ("{0} = {1}" -f $_.Key, $_.Value) }
|
|
|
|
Step "npm config (registry/proxy/flags)"
|
|
try {
|
|
Info ("registry = " + (npm config get registry))
|
|
Info ("ignore-scripts = " + (npm config get ignore-scripts))
|
|
Info ("strict-ssl = " + (npm config get strict-ssl))
|
|
$hp = npm config get https-proxy; if ($hp -ne "null") { Info ("https-proxy = " + $hp) }
|
|
$p = npm config get proxy; if ($p -ne "null") { Info ("proxy = " + $p) }
|
|
} catch { Warn ("npm config read failed: " + $_) }
|
|
|
|
# 2) Resolve versions and tarballs from NPM
|
|
Step "Resolve @hanzo/dev@$Version (npm)"
|
|
$mainVersion = $null
|
|
try { $mainVersion = (npm view "@hanzo/dev@$Version" version) } catch {}
|
|
if (-not $mainVersion) { Err "Could not resolve @hanzo/dev@$Version"; exit 1 }
|
|
$mainTar = $null
|
|
try { $mainTar = (npm view "@hanzo/dev@$mainVersion" dist.tarball) } catch {}
|
|
Info ("main version = " + $mainVersion)
|
|
Info ("main tarball = " + ($mainTar ?? "<none>"))
|
|
|
|
Step "Resolve @hanzo/dev-win32-x64@$mainVersion (npm)"
|
|
$winVersion = $null; $winTar = $null
|
|
try { $winVersion = (npm view "@hanzo/dev-win32-x64@$mainVersion" version) } catch {}
|
|
if ($winVersion) {
|
|
try { $winTar = (npm view "@hanzo/dev-win32-x64@$mainVersion" dist.tarball) } catch {}
|
|
Info ("win version = " + $winVersion)
|
|
Info ("win tarball = " + ($winTar ?? "<none>"))
|
|
} else {
|
|
Warn ("@hanzo/dev-win32-x64@$mainVersion not found on npm")
|
|
}
|
|
|
|
# 3) Download + inspect npm tarballs
|
|
$tmp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("code-diag-" + [guid]::NewGuid().ToString("N"))) -Force
|
|
Step ("Work dir: " + $tmp.FullName)
|
|
Set-Location $tmp.FullName
|
|
|
|
function Fetch($url, $dst) {
|
|
if (-not $url) { return $false }
|
|
try {
|
|
Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $dst -TimeoutSec 120
|
|
return $true
|
|
} catch {
|
|
Err ("Download failed: " + $url)
|
|
Err ($_.Exception.Message)
|
|
return $false
|
|
}
|
|
}
|
|
|
|
$t1 = Join-Path $pwd "code.tgz"
|
|
$t2 = Join-Path $pwd "win.tgz"
|
|
$gotMain = Fetch $mainTar $t1
|
|
$gotWin = $false
|
|
if ($winTar) { $gotWin = Fetch $winTar $t2 }
|
|
|
|
if ($gotMain) {
|
|
try {
|
|
mkdir p1 | Out-Null
|
|
tar -xzf $t1 -C p1
|
|
Step ("@hanzo/dev@" + $mainVersion + " contents")
|
|
dir p1/package
|
|
Info "\nHead of bin/coder.js:"
|
|
Get-Content p1/package/bin/coder.js -TotalCount 30 | ForEach-Object { " " + $_ }
|
|
$isESM = Select-String -Path p1/package/package.json -Pattern '"type"\s*:\s*"module"' -Quiet
|
|
Info ("package.json type=module? " + [string]$isESM)
|
|
} catch {
|
|
Warn ("Could not extract or inspect main tarball: " + $_)
|
|
}
|
|
} else {
|
|
Err "Could not fetch main tarball from npm"
|
|
}
|
|
|
|
if ($gotWin) {
|
|
try {
|
|
mkdir p2 | Out-Null
|
|
tar -xzf $t2 -C p2
|
|
Step ("@hanzo/dev-win32-x64@" + $mainVersion + " contents")
|
|
dir p2/package/bin
|
|
} catch {
|
|
Warn ("Could not extract or inspect win tarball: " + $_)
|
|
}
|
|
}
|
|
|
|
# 4) Compute target triple and cache paths
|
|
$triple = "x86_64-pc-windows-msvc.exe" # Windows x64
|
|
$cacheDir = Join-Path $env:LOCALAPPDATA ("hanzoai\dev\" + $mainVersion)
|
|
$expectOK = Join-Path $cacheDir ("code-" + $triple) # correct
|
|
$expectBAD = $expectOK + ".exe" # incorrect (double .exe)
|
|
Step "Cache expectations"
|
|
Info ("cache dir = " + $cacheDir)
|
|
Info ("expect OK = " + $expectOK)
|
|
Info ("expect BAD = " + $expectBAD)
|
|
Info ("exists OK? " + ([string](Test-Path $expectOK)))
|
|
Info ("exists BAD? " + ([string](Test-Path $expectBAD)))
|
|
|
|
# 5) GitHub release asset (fallback path)
|
|
$ghZip = "https://github.com/hanzoai/dev/releases/download/v$mainVersion/dev-$triple.zip"
|
|
Step "GitHub asset (fallback) HEAD check"
|
|
try {
|
|
$resp = Invoke-WebRequest -UseBasicParsing -Uri $ghZip -Method Head
|
|
Info ("GitHub asset reachable? " + [string]$resp.StatusCode)
|
|
} catch {
|
|
Warn ("GitHub HEAD failed: " + $_)
|
|
}
|
|
|
|
# Optional: download and test unzip to cache
|
|
$zipTmp = Join-Path $pwd ("code-" + $triple + ".zip")
|
|
$unzipOk = $false
|
|
$existedBefore = Test-Path $expectOK
|
|
try {
|
|
if (Fetch $ghZip $zipTmp) {
|
|
Step "Test Expand-Archive to cache dir"
|
|
if (-not (Test-Path $cacheDir)) { New-Item -ItemType Directory -Path $cacheDir | Out-Null }
|
|
$sysRoot = $env:SystemRoot
|
|
$psFull = Join-Path $sysRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
|
|
$psCmd = "Expand-Archive -Path `"$zipTmp`" -DestinationPath `"$cacheDir`" -Force"
|
|
$ok = $false
|
|
try { & "$psFull" -NoProfile -NonInteractive -Command $psCmd 2>$null; $ok = $true } catch {}
|
|
if (-not $ok) { try { powershell -NoProfile -NonInteractive -Command $psCmd 2>$null; $ok = $true } catch {} }
|
|
if (-not $ok) { try { pwsh -NoProfile -NonInteractive -Command $psCmd 2>$null; $ok = $true } catch {} }
|
|
if (-not $ok) { try { tar -xf $zipTmp -C $cacheDir; $ok = $true } catch {} }
|
|
$unzipOk = $ok
|
|
Info ("unzip ok? " + [string]$unzipOk)
|
|
Info ("post-unzip OK exists? " + ([string](Test-Path $expectOK)))
|
|
Info ("post-unzip BAD exists? " + ([string](Test-Path $expectBAD)))
|
|
}
|
|
} catch {
|
|
Warn ("Unzip test failed: " + $_)
|
|
} finally {
|
|
try { Remove-Item -Force $zipTmp } catch {}
|
|
if (-not $FixCache -and -not $existedBefore) {
|
|
try { if (Test-Path $expectOK) { Remove-Item -Force $expectOK } } catch {}
|
|
}
|
|
}
|
|
|
|
# 6) Global locations that the error messages referenced
|
|
Step "Global npm prefix + expected node_modules bin path"
|
|
$prefix = $null
|
|
try { $prefix = (npm prefix -g 2>$null) } catch {}
|
|
$nmBin = if ($prefix) { Join-Path $prefix "node_modules\@hanzo\dev\bin" } else { "<unknown>" }
|
|
Info ("prefix = " + ($prefix ?? "<none>"))
|
|
Info ("bin dir= " + $nmBin)
|
|
if ($prefix -and (Test-Path $nmBin)) { dir $nmBin } else { Info "(bin dir not present - expected for npx, present for global installs)" }
|
|
|
|
# 7) Summary
|
|
Step "RESULTS SUMMARY"
|
|
$lines = @()
|
|
$lines += ("main version: " + $mainVersion)
|
|
$lines += ("platform pkg present on npm: " + ([string]([bool]$winVersion)))
|
|
$lines += ("cache ok path exists: " + ([string](Test-Path $expectOK)))
|
|
$lines += ("cache bad path exists: " + ([string](Test-Path $expectBAD)))
|
|
$lines += ("github asset unzip ok: " + ([string]$unzipOk))
|
|
$esm = $false; try { $esm = Select-String -Path p1/package/package.json -Pattern '"type"\s*:\s*"module"' -Quiet } catch {}
|
|
$lines += ("esm bin (type:module) in npm tarball: " + ([string]$esm))
|
|
$lines | ForEach-Object { Info $_ }
|
|
|
|
Info ""
|
|
Info "Done. Paste the RESULTS SUMMARY back if anything looks off."
|
|
|