feat(cli): device login and account management over /v2 - #1
Merged
Conversation
AJCJ1
marked this pull request as draft
August 17, 2026 10:50
AJCJ1
force-pushed
the
feat/account-management
branch
from
August 19, 2026 07:44
fee63bb to
617bad5
Compare
AJCJ1
marked this pull request as ready for review
August 19, 2026 07:44
- urlbox login device flow: browser approve, org/project pickers, session plus the project's render key/secret pair stored so rendering works immediately - session commands: logout, whoami/me, orgs list/select, projects list/select/create/rename/enable/disable/delete/defaults, usage - org credential resources: storage, proxies, llm groups (list/show/create/update/delete, llm test/models) and projects storage|proxy|llm assign/unassign - BREAKING: urlbox auth removed; login is the interactive path, URLBOX_API_SECRET stays for CI; doctor reworked with session-aware checks; CHANGELOG v0.11.0 entry included - text mode renders shared lipgloss tables and KV boxes; link prints its signed URL; doctor prints per-check hints with correct failure glyph - confirm gates (retype for delete, y/n for disable, --yes for agents); creates accept a positional name; deleting the active project re-selects; disabling a disabled project is a stated no-op - --no-retry/--max-retries on session commands; compatibility suite pins every pre-existing command across legacy and post-login configs
AJCJ1
force-pushed
the
feat/account-management
branch
from
August 19, 2026 08:30
617bad5 to
e032f89
Compare
…not serve The device flow needs a browser, so removing `urlbox auth` left no way to write a secret into a fresh config on a headless box, in CI, or under an agent. `config set api_secret` cannot fill the gap: with zero profiles it errors "No profiles configured" on main and on this branch alike, so the migration path the changelog suggested never worked from a clean machine. Restores auth.go and its tests, minus the stdin-TTY helpers and maskSecret that moved to stdin_tty.go and masking.go on this branch. SURFACE.txt gains its nine `urlbox auth` lines back, so this release is purely additive. Hints now name both onboarding paths via a shared credentialHint: `login` for a browser, `auth` / URLBOX_API_SECRET for everything else. Pointing a browserless environment at `login` is a dead end. TestNoAuthCommandRemains becomes TestAuthCommandStillRegistered, and the ghost-command blocklist goes back to pinning the removed `--api-key` flag rather than the whole command.
…mpt session probe `doctor` exited 3 on a perfectly healthy machine. The session, active_org and active_project checks read profile fields that only `login` writes, so any setup authenticating with a render credential — URLBOX_API_SECRET, a stored profile, the repo overlay — failed three checks even with a valid, working credential and render_credential reporting ok. Verified against a local API: main exits 0 in that configuration, this branch exited 3, which retired `doctor` as a CI health gate. When a credential resolves, those three checks now report warn and explain themselves; overall status returns to ok. With no credentials at all the behaviour is unchanged — still fail, still exit 3. Also drops the session probe to a single attempt. Every other check here probes once; retrying four times with backoff only made `doctor` hang ~7s against an unreachable host without changing the diagnosis.
The session retry tests assert how many attempts were made, never how long they took, but they slept the real 1s/2s/4s budget: internal/cmd went from 6s on main to 40s here, and `make test` adds -race on top. RetryConfig.Sleep was already injectable. sessionRetrySleep threads a no-op through the one session-client construction site, installed by TestMain in the test binary only — nil in production, so real backoff is untouched, and scoped to session clients so render and status keep their own sleep. Every assertion is unchanged.
…y in text Masking was a text-mode affordance: `storage show` printed `sup3…3t` while the same command with stdout on a pipe returned `"secret":"sup3rs3cr3t"` in full. Because the CLI resolves a non-TTY stdout to JSON, that made the unmasked form the default for exactly the consumers that persist it — agents, CI logs, `| tee` transcripts — and left `--reveal` as a no-op there, a flag that reads like a protection while protecting nothing. JSON now masks the same fields the KV views mask: storage key/secret/sasToken, every LLM provider secret (apiKey, the three AWS fields, the GCP service account), the password component of a proxy URL, and a project's webhookKey. `--reveal` unhides both surfaces from one switch, and joins `list` — previously show-only, though `proxies list` and `storage list` carry the same material. Non-secret fields pass through byte-for-byte, so `--jq` over ids, names, buckets and regions is unaffected; only the named fields change, and only when they hold a non-empty string, so a null stays null instead of becoming "***". The masked copy is always fresh: the KV builders mask the raw response themselves, and redacting in place would double-mask what a human sees. Proxy masking keeps scheme, host and port legible and replaces only the password, so pools stay tellable apart without exposing the credential. TestProjectsShow_JSON_ByteIdenticalToServerResponse pinned the old contract; it becomes …PassesServerResponseThroughWithSecretsMasked, still asserting the raw passthrough for every non-secret field and now asserting --reveal restores the response verbatim.
Switching organisations drops the stored render credential on purpose — it belongs to a project in the org being left, and keeping it would let `render` bill the previous organisation silently. That part is right and stays. Three things around it were not. It cleared api_secret but left api_key, so a publishable key from the previous org survived in the profile; the credential is a pair and both halves go. When the new org had several projects and no terminal to pick with, the switch finished in a deliberately incomplete state — no active project, no credential — and reported it only on stderr, returning ok:true with an unqualified "Active organisation: X". An agent reads stdout, so it saw plain success and discovered the missing credential on its next render. `login` hits the identical branch of resolveActiveProject and fails loudly there; the same situation now travels in the envelope here too: the summary names the project count and a breadcrumb points at `urlbox projects select`. The exit code stays 0 — the org did switch — but the envelope no longer claims the context is complete when it isn't. And there was no way to avoid the gap at all non-interactively. `orgs select` now takes --project, mirroring `login --org --project`, so the switch and the new active project land in one call. An unresolvable --project is a hard error rather than a half-finished switch: the caller named something that isn't there. resolveActiveProject returns the project count alongside the choice so callers can report it; login ignores it.
…y promise
v1.0.0 is published and immutable, so v1.0 cannot be reissued; v1.1.0 is the
closest available version and the correct one by semver anyway — this release
adds a command surface rather than patching one. The other free slot near 1.0,
v1.0.5, would drop a live release into the middle of the deprecated v1.0.x
range, where anyone pinning ~1.0 sees five deprecated versions and one live.
SURFACE.txt stops describing itself as pre-1.0. From here the file is the
promise: nothing listed is removed or renamed inside the v1 line without a
major bump. That is the sentence the 0.x reset existed to be able to write.
The v0.10.0 entry's closing line ("The v1.x line is deprecated on npm") is now
false and says so, pointing at the v1.1.0 entry. Clearing the five npm
deprecation notices is a publish-time step, not a code one.
Also flags the installer consequence of leaving 0.x: npm/install.js gates its
sigstore policy on the version string, so on v1 a missing bundle is a hard
install failure rather than a sha256-only fallback. goreleaser signs
checksums.txt, so this is a tightening, not a break — but it is now
load-bearing and belongs in the release notes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds browser-based login, account management, and org credential resources, ported from the urlbox/cli repo. Removes
urlbox auth.New commands:
login/logout: device flow in the browser; stores the session token and the active project's API key/secret, so rendering works straight after loginwhoami(aliasme),usageorgs list|selectprojects list|select|show|create|rename|enable|disable|deleteandprojects defaults show|set|removestorage,proxies,llm: list/show/create/update/delete per group, plusllm testandllm modelsprojects storage|proxy|llm assign|unassignBreaking:
urlbox authis removed.urlbox loginis the interactive path; CI and headless keep usingURLBOX_API_SECRET.doctornow runs nine checks including session, active org/project, and render-credential validity. A CHANGELOG entry is included (drafted as v0.11.0; version number pending discussion).Also:
linkdidn't print the signed URL, anddoctordidn't print per-check hints and showed a tick on failuredeleteasks you to retype the name,disableasks y/n, and--yesskips all prompts; deleting the active project re-selects the survivor or offers a picker; disabling an already-disabled project is a no-op that says so--nameworks too)--no-retry/--max-retrieson the session commands, matchingrenderandstatus🤖 Generated with Claude Code