zcode-cli 3.8.1-26
Make sign-in state first-class: instant identity switch after /login, custom-provider.env as the signed-out fallback with automatic hand-off
- Make sign-in state first-class: after /login completes browser OAuth the banner and status bar switch to the signed-in account immediately, and the custom-provider env file is repositioned as the signed-out-only channel with automatic hand-off in both directions (src/identity.ts, src/env-config.ts, src/launcher.ts, packages/zcode-tui/src/login-identity.ts, packages/zcode-tui/src/index.ts, test/identity.test.ts, test/login-identity.test.ts, test/env-config.test.ts, README.md, README_zh_hans.md, README_zh_hant.md, docs/CONFIGURATION.md,
.env.examplerenamed tocustom-provider.env.example).- Why: after completing OAuth in the TUI (/login), the banner kept showing the masked API key from the custom-provider file — a successful login with zero visible feedback. The root cause sits in the 3.8.1-25 decoupling design: identity display followed the
model.mainprefix, which the custom-provider file pinned to itsenv-<id>slot on every start, so the official slots and the credential vault written by OAuth never got a turn; and under the old design, seeing your login account required deleting the file and re-creating it after logout — unusable as a product. The ruling product principle: a user action must produce correct feedback — signed in shows the signed-in account, signed out shows Not signed in, and the two states must hand off automatically without manual file juggling. - What changed: (1) sign-in detection (new
readStoredOAuthLogin()in src/identity.ts): the presence ofoauth:<provider>:access_tokenin the credential vault~/.zcode/v2/credentials.jsonis the sign-in state (theoauth:active_providermarker takes priority; a stale marker falls back to scanning both providers' tokens); (2) login-first identity display (readLoginIdentitySnapshot()rewritten): signed in → show the login provider's account identity (vaultuser_infosnapshot → BigModel key-name mapping → masked key), regardless of which slotmodel.mainpoints to — visible on the refresh right after login; signed out with model access → new kindsignedOut, banner / status bar show "Not signed in" and the provider displays the declared value with theenv-prefix stripped; no access at all → the sign-in wizard warning stays. The TUI side (login-identity.ts) no longer mirrors the logic — it reuses the src snapshot functions (single implementation, no duplicate drift); (3) automatic model-ownership switch (env-config.ts): while signed in, the startup sync setsskipModelBlock(refresh only theenv-slot data, never rewrite themodelblock); a leftovermodelblock pointing at anenv-slot is switched to the login provider's official slot by the newswitchModelBlockToOfficialProvider()(model ID kept if the official slot already declares it, otherwise its first declared model); after logout the next start is signed out and the file takes themodelblock back — both directions seamless, the file never needs manual removal or restore; (4) file rename.env→custom-provider.env(template renamed tocustom-provider.env.examplewith its header rewritten to the new semantics): the first start automatically renames a legacy~/.zcode/cli/.envand prints one notice line (no migration whenZCODE_ENV_FILEpins an explicit path); (5) prefix-stripped display (newdisplayProviderId()/displayModelRef()in env-config.ts): the TUI model display, thezcode identityProvider line, and<provider-id>/<model-id>all show the value declared in the file (env-bigmodel/glm-5.3→bigmodel/glm-5.3); theenv-prefix lives only in config.json internal slot names (the isolation mechanism is unchanged); (6) the barezcode logingate now blocks only when already signed in: signed in it prints "Already signed in as " and exits (--oauthforces a re-login), signed out it lets OAuth proceed even when a custom-provider file is configured — "running login means the user wants to log in"; (7)zcode identity setis refused while signed out (display names follow the login account; nothing to set when signed out); (8)zcode identitywhile signed out printsIdentity: not signed in (model access via custom provider). - Behavior map (login / logout feedback loop): signed out + file → model via the
env-slot, banner "Not signed in", model shown as<declared id>/<model>;/loginOAuth completes → vault + official slot written, TUI refreshes instantly to "Signed in as ", next start the model block belongs to the official slot;/logout→ vault cleared, banner flips back to "Not signed in" instantly, next start the file takes the model back over. - Migration note: the first start after upgrading renames
~/.zcode/cli/.env→custom-provider.envautomatically (one console notice); setZCODE_ENV_FILEto the old path to opt out. Signed-in users'model.mainis moved fromenv-<id>/...back to the official slot on the next start. - Verification:
tsc --noEmitpasses; fullbun test674 pass / 0 fail (78 files; identity gains 3 sign-in-detection + 3 snapshot cases, login-identity re-signs signedOut semantics + 2 new cases, env-config gains 2 migration + 1 display + 1 skipModelBlock + 3 model-block-switch cases).
- Why: after completing OAuth in the TUI (/login), the banner kept showing the masked API key from the custom-provider file — a successful login with zero visible feedback. The root cause sits in the 3.8.1-25 decoupling design: identity display followed the
Install
npm install -g https://github.com/xhqing/zcode-cli/releases/download/v3.8.1-26/zcode-cli-3.8.1-26.tgz