Repository navigation
Conversation
Environment-scoped resource commands now take an API-key path when --api-key or WORKOS_API_KEY is given, or the active profile is an unclaimed environment. Signed-in users without an explicit key keep the dashboard session path unchanged. The path is chosen in one seam (resolveEnvScopedAuth in dashboard-operation.ts). On it, each catalog operation runs through a REST adapter that returns the dashboard operation's data shape, so the commands' existing shapers produce the --json output. Operations with no REST equivalent exit with a structured api_key_unsupported error. This commit adds the seam, the --api-key flag on the resource command groups, the organization/org-domain adapters, and rewords the API-key-set auth copy, which no longer says these commands reject API keys.
REST adapters for user list/get/update/delete and session list/revoke. Identities come from the user's organization memberships, with role IDs and names resolved from their slugs; user get adds auth factors. Fields REST has no source for (sessionCount, hasPassword, emailVerifiedAt, a factor's lastVerifiedAt) are null rather than invented; the human view still reports verification from REST's email_verified flag.
REST adapters for membership list/get/create/update/delete/deactivate/ reactivate and invitation list/get/send/revoke/resend. The CLI's --role takes a role ID; REST takes a slug, so the ID is resolved through the roles endpoints first. Organization names on invitations come from one extra read per organization.
REST adapters for environment and organization roles and for permissions. REST is slug-keyed while the commands' mutations are ID-keyed, so the list adapters remember each slug and scope by ID for the mutation that follows. Role types map EnvironmentRole/OrganizationRole to the same environment/organization values the session path emits.
…I key REST adapters for webhook list/create/delete, event list (newest first, same cursor contract), portal generate-link and feature-flag list/get/enable/disable. REST can't list a flag's targets, so add-target/remove-target call the target endpoints directly and flag get reports accessType and targets as null on this path.
REST adapters for redirect URIs and CORS origins (list, and set as a create/delete diff) back config redirect add / cors add and authkit redirect-uris / cors. Where REST lacks a piece the command fails before any write: no CORS delete, no way to choose the default redirect URI, no --dry-run. config homepage-url set uses REST's homepage endpoint and reports applicationId as null. AuthKit logout URIs and branding stay session-only and fail with api_key_unsupported.
api-key-auth.spec.ts covers when the API-key path is chosen (unclaimed profile, WORKOS_API_KEY, --api-key), that signed-in users keep the session otherwise, the unsupported errors, and each command's REST requests. api-key-parity.spec.ts runs every list/get command on both paths against the same entity, feeding real dashboard and REST casing, and requires identical --json apart from the documented null fields in API_KEY_NULL_FIELDS.
Runs each resource command's list/get on this branch twice, with the dashboard session and with WORKOS_API_KEY, and requires identical keys and values. The only allowed differences are the fields in API_KEY_ACCEPTED (shared with the offline spec via known-gaps.ts), where the key side must be null.
Narrow flag targets in the human flag view (null on the API-key path), return the never-typed REST error handler, and type the --api-key middleware helper without re-returning the yargs instance.
README and CLAUDE.md said the resource commands use only the dashboard session and that WORKOS_API_KEY applies only to workos api and the still-REST commands. Describe the new precedence (--api-key, then WORKOS_API_KEY, then an unclaimed active profile's key, else the session), the refused --environment-id, the two api_key_unsupported commands, and the session-only account commands.
- authkit redirect-uris set adds new URIs before deleting dropped ones, so a rejected URI or failed request never leaves fewer working URIs. - Role permission changes tell a 404 from an empty success, never fall back to the unchanged role, and confirm the server holds exactly the requested permissions before the command reports them. - Role lookups for memberships search the organization's roles first, so an organization role that reuses an environment role's slug wins, as on the dashboard. - REST webhook status maps to the dashboard's Active/InActive and session auth methods to its SessionProvider values (from the vendored catalog), so enumOut yields the same output on both paths. An OAuth session's provider, which REST doesn't name, is null.
Null gaps are now exact leaf paths (user.authenticationFactors[].lastVerifiedAt, session.impersonator.id, ...) judged by one shared apiKeyParityDiffs(): every object needs the same keys on both sides, arrays the same length, and every other leaf the same value, so an emptied element or an object going null fails. The offline spec and parity-smoke --api-key both use it. parity-smoke --api-key now proves the session side: whoami must succeed without a key, and every session-side command is pinned to its environment with --environment-id, which the CLI refuses whenever any API key applies (including an unclaimed profile's), so the session side can never quietly run on REST.
The flag test checked outputs and the enable call but not the target writes, so a target command that printed success without calling REST would pass. Assert the exact ordered list of writes.
A check now counts only when both sides returned real entities: lists whose rows each carry their ID, or a non-empty entity object. A missing envelope key no longer compares undefined with undefined and passes. A run that compared no entity at all exits 1 and says parity is unproven.
- Full-list reads (permissions, a user's memberships, redirect URIs, CORS origins) now request order=desc. Endpoints that default to normal walk toward newer records on after, so past 100 permissions the old loop stopped at the first page and get/update/delete could report not_found. - membership delete removes the membership ID the command just resolved instead of searching again; the fallback search, and membership/identity listings, pass statuses=active,inactive,pending rather than relying on REST's default status filter, so a deactivated membership can be listed and deleted.
Several REST lists default to the normal order, where the older-page cursor comes back in before and after walks toward newer records. The redirect URI read relied on after to detect more than one page, so config redirect add could merge over a truncated list and the setter would delete the URIs it never saw. Every key-path list now sends an explicit order (the user's --order, else desc: same newest-first rows), and the redirect read defaults to the dashboard's 100. Tests: config redirect add refuses past one page with no writes; every paginated list sends an order; config cors add keeps an existing origin and posts only the new one.
…n API key The key path mapped every REST domain's verificationContent to null, dropping the challenge REST returns as verification_prefix and verification_token. Build it the way the dashboard does (OrganizationDomain.verificationContent: `<prefix>=<token>`, prefix defaulting to verification_token, null without a token), so org list/get, org-domain get, create and verify carry it in the same field. Create includes it because REST adds domains pending; human create output now prints the record when there is one. verificationContent is no longer a documented gap. subdomain is listed instead: REST doesn't return it and the dashboard derives it with a public-suffix parser this CLI doesn't ship. Tests (fail on the old mapping): org-domain get/verify parity against the dashboard's challenge, the default-prefix case, and create/verify output.
Full-list reads stopped at 50 pages and returned what they had. Callers treat the result as complete (slug lookups, list replaces), so a partial list could report a permission missing or act on the wrong set. Exit with list_too_long instead.
The in-process test set the flag with setResourceApiKeyFlag(), skipping yargs and the resource-group middleware. Drive bin.ts as a subprocess against a local HTTP stub (temp HOME, file-only storage via the insecure-storage preload) and assert the Authorization header: --api-key beats WORKOS_API_KEY and an unclaimed profile, WORKOS_API_KEY beats the profile, the profile's key is used last, and --environment-id with a key is refused before any request.
parity-smoke --api-key passed the pagination cursor names to apiKeyParityDiffs, which skipped them at every depth, so a changed or missing metadata.before / metadata.after / metadata.pagination still passed. Both callers only ever pass the entity or its rows (the cursors live in the envelope, outside the comparison), so the gate drops the exemption parameter entirely and compares every key inside the entity. A test covers cursor-named metadata keys and fails on the old gate.
…y path
REST names a membership's roles by slug. The dashboard reports them by
role ID on membership records (UserlandUserOrganizationMembership.role /
.roles: membership list --user, get, update) and by display name on an
organization's identities (role { name } in membership list --org). The
key path passed REST's {slug} through, so it printed "member" where the
dashboard prints "role_..." or "Member". Resolve each slug through the
organization's roles (then the environment's) and emit the same values.
The parity fixtures now use what those dashboard queries actually select
(role IDs; role { name }), so the old mapping fails them. A role list
that 404s now reads as empty, like an organization's.
- An empty WORKOS_API_KEY now counts as unset, so the key in use and the reported key source always agree. --api-key mode also proves the key side is on the key before comparing (the CLI must refuse --environment-id there), so it can't compare the session to itself. - The default branch-vs-main mode no longer hands WORKOS_API_KEY to the branch's resource commands, which now take the key path when it is set: they run without it (still-REST commands keep it), and are pinned to the session's environment from whoami, so an unclaimed profile's key is refused instead of silently used. Env-scoped NEW checks get the pinned --environment-id at run time.
This branch has not been deployed
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.
Why
Decided
workos organization,user,role, and every other command gated the same way". That's job 320's brief, from his Pi session today, so the smaller split would … (Riker's call)workos apiand the still-… → Take (a). Update all four spots in this PR as one docs commit: - README.md 149, 310–312 and 688; - CLAUDE.md:16. Why: the user's goal asks to update the copy "so they no longer say these commands reject API keys". These lines make exactly that claim, so fixing them carries out the asked-for chan… (Riker's call)Not checked
(test -d node_modules || bun install --frozen-lockfile >/dev/null 2>&1) && timeout 480 bunx vitest run src/lib/command-auth.spec.ts src/commands -t "with an API key"running on this code.workos organization listandworkos user listoutput from a freshly provisioned unclaimed environment. If the worker couldn't provision one, the PR says so plainly.Opened by Riker (job 320).
Riker job 320: CLI resource commands accept API keys
(test -d node_modules || bun install --frozen-lockfile >/dev/null 2>&1) && time…, not yet checked on this head (fab3c63)riker job 320·riker watch 320