Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cf2ca8b
feat: allow overriding CLI auth client ID via WORKOS_CLIENT_ID
nicknisi Jun 15, 2026
186ddb3
feat: point the CLI at a non-prod API via WORKOS_API_URL
nicknisi Jun 22, 2026
2dac1b4
feat: add `workos whoami` command
nicknisi Jun 23, 2026
86f44e9
feat: vendor Management catalog snapshot + loader
nicknisi Jun 25, 2026
835b707
feat(catalog): add justification manifest, curation, and reused confi…
nicknisi Aug 19, 2026
2cdd0db
feat: add account-plane lifecycle commands (environment, project, team)
nicknisi Jun 25, 2026
e407716
feat: add authkit app config commands (redirect URIs, CORS, logout UR…
nicknisi Jun 25, 2026
755607f
docs(catalog): note deferred userland->user ops and retained test fix…
nicknisi Jul 10, 2026
62af02c
feat: command-path auth guard with silent token refresh
nicknisi Jul 22, 2026
a9fcf49
feat: target the active environment on dashboard-plane commands
nicknisi Jul 22, 2026
a9252e6
feat!: migrate organization + user commands to the dashboard account …
nicknisi Jul 22, 2026
2f79a8a
feat!: migrate membership, invitation, session to the dashboard accou…
nicknisi Jul 22, 2026
9497d0a
feat!: migrate role, permission, feature-flag to the dashboard accoun…
nicknisi Jul 23, 2026
10675ff
feat!: migrate event + org-domain to the dashboard account plane; dir…
nicknisi Jul 23, 2026
38ae16b
feat!: migrate webhook, portal, config to the dashboard account plane
nicknisi Jul 23, 2026
1564619
chore: remove dead REST-plane code and align docs with dashboard auth
nicknisi Jul 23, 2026
3231673
test: add live smoke script for dashboard-plane resource commands
nicknisi Jul 23, 2026
a8f19f7
chore: formatting
nicknisi Jul 23, 2026
d3dd7e4
chore: remove unused declarations flagged by oxlint
nicknisi Jul 23, 2026
40408f6
chore: formatting
nicknisi Jul 23, 2026
40c7a09
fix: address review feedback on client-id override, 403 handling, and…
nicknisi Jul 23, 2026
0c76503
refactor: extract dashboard-operation executor seam and shared resour…
nicknisi Jul 23, 2026
14b434d
feat: add branding image upload to the CLI
nicknisi Jul 25, 2026
4356258
fix: point remaining clack imports at the ui facade
nicknisi Jul 25, 2026
f619172
feat!: move branding to a top-level command with a positional form
nicknisi Jul 25, 2026
c852df6
docs: document the authkit and branding commands in the README
nicknisi Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ WorkOS CLI for installing AuthKit integrations and managing WorkOS resources (or
## Non-TTY Behavior

- **Output**: Auto-switches to JSON when piped or `--json` flag. `WORKOS_FORCE_TTY=1` overrides.
- **Auth**: Exits code 4 instead of opening browser. Requires prior `workos auth login` or `WORKOS_API_KEY` env var.
- **Auth**: Exits code 4 instead of opening browser. Resource commands (organization, user, role, permission, membership, invitation, session, event, feature-flag, org-domain, portal, webhook, config) use the dashboard session from a prior `workos auth login`; expired access tokens refresh silently while the stored refresh token is valid, so only a truly dead session exits 4. `WORKOS_API_KEY` applies only to `workos api` and the still-REST commands (`connection`, `directory`, `audit-log`, `api-key`, `vault`, plus the workflow/debug commands `seed`, `setup-org`, `onboard-user`, `verify-login`, `debug-sso`, `debug-sync`, `migrations`).
- **Errors**: Structured JSON to stderr: `{ "error": { "code": "...", "message": "..." } }`
- **Exit codes**: 0=success, 1=error, 2=cancelled, 4=auth required (follows `gh` CLI convention)
- **Headless flags**: `--no-branch`, `--no-commit`, `--create-pr`, `--no-git-check`. CI mode (`WORKOS_MODE=ci`) auto-continues past a dirty tree without `--no-git-check`; agent mode requires the flag.
Expand Down
138 changes: 96 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Resource Management:
feature-flag Manage feature flags
webhook Manage webhooks
config Manage redirect URIs, CORS, homepage URL
authkit Manage AuthKit app config (redirect URIs, CORS, logout URIs)
branding Manage branding images (logo, icon, favicon)
portal Generate Admin Portal links
vault Manage encrypted secrets
api-key Manage per-org API keys
Expand All @@ -144,7 +146,7 @@ Workflows:
debug-sync Diagnose directory sync issues
```

All management commands support `--json` for structured output (auto-enabled in non-TTY) and `--api-key` to override the active environment's key.
All management commands support `--json` for structured output (auto-enabled in non-TTY). Still-REST commands (`connection`, `directory`, `audit-log`, `api-key`, `vault`, `workos api`, and the workflow/debug commands) also accept `--api-key` to override the active environment's key; the other resource commands authenticate with your `workos auth login` session (see [Resource Management](#resource-management)).

### Unclaimed Environments

Expand Down Expand Up @@ -303,7 +305,11 @@ API keys are stored in the system keychain via `@napi-rs/keyring`, with a JSON f

### Resource Management

All resource commands follow the same pattern: `workos <resource> <action> [args] [--options]`. API keys resolve via: `--api-key` flag → `WORKOS_API_KEY` env var → active environment's stored key.
All resource commands follow the same pattern: `workos <resource> <action> [args] [--options]`.

Most resource commands — organization, user, role, permission, membership, invitation, session, event, feature-flag, org-domain, portal, webhook, config, authkit, branding — authenticate with your WorkOS dashboard session from `workos auth login` and target the active environment (see `workos env`). Pass `--environment-id <id>` on any of them to target a different environment for a single invocation. Access tokens refresh automatically while the session is valid, so a logged-in machine keeps working headlessly; a dead session exits with code 4.

The remaining commands (`connection`, `directory`, `audit-log`, `api-key`, `vault`) still use the REST plane, as does the raw escape hatch `workos api`. Those resolve an API key via: `--api-key` flag → `WORKOS_API_KEY` env var → active environment's stored key.

#### organization

Expand All @@ -312,68 +318,72 @@ workos organization create <name> [domain:state ...]
workos organization update <orgId> <name> [domain] [state]
workos organization get <orgId>
workos organization list [--domain] [--limit] [--before] [--after] [--order]
workos organization delete <orgId>
workos organization delete <orgId> [--yes]
```

#### user

```bash
workos user get <userId>
workos user list [--email] [--organization] [--limit]
workos user update <userId> [--first-name] [--last-name] [--email-verified] [--password] [--external-id]
workos user delete <userId>
workos user list [--email] [--limit] [--before] [--after] [--order]
workos user update <userId> [--first-name] [--last-name] [--email] [--locale] [--external-id]
workos user delete <userId> [--yes]
```

#### role

Role mutations change the privilege surface; non-interactive callers must pass `--yes`.

```bash
workos role list [--org <orgId>]
workos role get <slug> [--org <orgId>]
workos role create --slug <slug> --name <name> [--org <orgId>]
workos role update <slug> [--name] [--description] [--org <orgId>]
workos role delete <slug> --org <orgId>
workos role set-permissions <slug> --permissions <slugs> [--org <orgId>]
workos role add-permission <slug> <permissionSlug> [--org <orgId>]
workos role remove-permission <slug> <permissionSlug> --org <orgId>
workos role create --slug <slug> --name <name> [--description] [--org <orgId>] [--yes]
workos role update <slug> [--name] [--description] [--org <orgId>] [--yes]
workos role delete <slug> --org <orgId> [--yes]
workos role set-permissions <slug> --permissions <slugs> [--org <orgId>] [--yes]
workos role add-permission <slug> <permissionSlug> [--org <orgId>] [--yes]
workos role remove-permission <slug> <permissionSlug> --org <orgId> [--yes]
```

#### permission

```bash
workos permission list [--limit]
workos permission list
workos permission get <slug>
workos permission create --slug <slug> --name <name> [--description]
workos permission update <slug> [--name] [--description]
workos permission delete <slug>
workos permission create --slug <slug> --name <name> [--description] [--yes]
workos permission update <slug> [--name] [--description] [--yes]
workos permission delete <slug> [--yes]
```

#### membership

`--org` and `--user` are mutually exclusive on `list`; pagination flags apply to `--org` listings only.

```bash
workos membership list [--org] [--user] [--limit]
workos membership list (--org <orgId> | --user <userId>) [--limit] [--before] [--after] [--order]
workos membership get <id>
workos membership create --org <orgId> --user <userId> [--role]
workos membership update <id> [--role]
workos membership delete <id>
workos membership deactivate <id>
workos membership update <id> [--role] [--yes]
workos membership delete <id> [--yes]
workos membership deactivate <id> [--yes]
workos membership reactivate <id>
```

#### invitation

```bash
workos invitation list [--org] [--email] [--limit]
workos invitation list [--org] [--email] [--limit] [--before] [--after]
workos invitation get <id>
workos invitation send --email <email> [--org] [--role] [--expires-in-days]
workos invitation revoke <id>
workos invitation revoke <id> [--yes]
workos invitation resend <id>
```

#### session

```bash
workos session list <userId> [--limit]
workos session revoke <sessionId>
workos session list <userId> [--limit] [--before] [--after]
workos session revoke <sessionId> [--yes]
```

#### connection
Expand All @@ -397,7 +407,7 @@ workos directory list-groups --directory <id> [--limit]
#### event

```bash
workos event list --events <types> [--org] [--range-start] [--range-end] [--limit]
workos event list --events <types> [--range-start] [--range-end] [--after] [--limit]
```

#### audit-log
Expand All @@ -414,7 +424,7 @@ workos audit-log get-retention <orgId>
#### feature-flag

```bash
workos feature-flag list [--limit]
workos feature-flag list [--limit] [--before] [--after] [--order]
workos feature-flag get <slug>
workos feature-flag enable <slug>
workos feature-flag disable <slug>
Expand All @@ -427,7 +437,7 @@ workos feature-flag remove-target <slug> <targetId>
```bash
workos webhook list
workos webhook create --url <endpoint> --events <types>
workos webhook delete <id>
workos webhook delete <id> [--yes]
```

#### config
Expand All @@ -438,12 +448,53 @@ workos config cors add <origin>
workos config homepage-url set <url>
```

`config` adds a single entry to a list. To replace a whole list, use `authkit` below.

#### authkit

Per-environment AuthKit app configuration. Unlike `config`, these setters replace the entire list, and each accepts `--dry-run` to validate without saving.

```bash
workos authkit redirect-uris list [--limit]
workos authkit redirect-uris set --uri <uri> [--uri <uri> ...] [--default <uri>] [--dry-run]
workos authkit cors get
workos authkit cors set --origin <origin> [--origin <origin> ...] [--dry-run]
workos authkit logout-uris list [--limit]
workos authkit logout-uris set --uri <uri> [--uri <uri> ...] [--default <uri>] [--dry-run]
```

Wildcard web origins are rejected — an accepted `*` would allow every browser origin, which is equivalent to disabling CORS.

#### branding

The logo, icon, and favicon an environment renders, each with a light and dark variant. Branding is not AuthKit-only: the same record drives hosted AuthKit pages and transactional emails.

```bash
workos branding get
workos branding set <slot> <file>
workos branding set [--logo] [--logo-dark] [--icon] [--icon-dark] [--favicon] [--favicon-dark]
```

Slots are `logo`, `logo-dark`, `icon`, `icon-dark`, `favicon`, and `favicon-dark`. Set one image positionally, or several at once with the matching flags — the two forms cannot be combined in a single invocation.

```bash
# One image
workos branding set icon ./icon.png

# Several at once
workos branding set --logo ./logo.png --logo-dark ./logo-dark.png --favicon ./favicon.ico
```

Images must be under 400 KB each and one of `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`, `.avif`, or `.ico`. Only the images you name are changed; the rest are left as they are. There is no way to clear an image from the CLI — upload a replacement instead.

#### portal

```bash
workos portal generate-link --intent <intent> --org <orgId> [--return-url] [--success-url]
workos portal generate-link --intent <intent> --org <orgId>
```

Supported intents: `sso`, `dsync`, `log_streams`, `domain_verification`, `certificate_renewal`. Generating a link expires prior links of the same intent.

#### vault

```bash
Expand Down Expand Up @@ -473,7 +524,7 @@ workos api-key delete <id>
workos org-domain get <id>
workos org-domain create <domain> --org <orgId>
workos org-domain verify <id>
workos org-domain delete <id>
workos org-domain delete <id> [--yes]
```

### Installer Options
Expand Down Expand Up @@ -512,11 +563,11 @@ mkdir my-app && cd my-app && workos install
# With visual dashboard (experimental)
workos dashboard

# JSON output (explicit)
workos org list --json --api-key sk_test_xxx
# JSON output (explicit; requires a prior `workos auth login`)
workos org list --json

# Pipe-friendly (auto-detects non-TTY)
workos org list --api-key sk_test_xxx | jq '.data[].name'
workos org list | jq '.data[].name'

# Machine-readable command discovery
workos --help --json | jq '.commands[].name'
Expand All @@ -542,7 +593,7 @@ The CLI also auto-detects non-TTY environments (piped output, CI, coding agents)
All commands produce structured JSON when piped or with `--json`:

```bash
workos org list --api-key sk_test_xxx | jq .
workos org list | jq .
# → { "data": [...], "list_metadata": { "before": null, "after": "..." } }

workos env list --json
Expand All @@ -552,8 +603,8 @@ workos env list --json
Errors go to stderr as structured JSON:

```bash
workos org list 2>&1
# → { "error": { "code": "no_api_key", "message": "No API key configured..." } }
workos org list 2>&1 # not logged in → exit code 4
# → { "error": { "code": "auth_required", "message": "Not logged in..." } }
```

### Agent Mode
Expand Down Expand Up @@ -603,13 +654,16 @@ workos install --api-key sk_test_xxx --client-id client_xxx --no-commit 2>/dev/n

### Environment Variables

| Variable | Effect |
| ------------------------ | --------------------------------------------------------- |
| `WORKOS_API_KEY` | API key for management commands (bypasses stored config) |
| `WORKOS_API_BASE_URL` | Override API base URL (set automatically by `workos dev`) |
| `WORKOS_MODE` | Interaction mode: `human`, `agent`, or `ci` |
| `WORKOS_FORCE_TTY=1` | Force human (non-JSON) **output** mode even when piped |
| `WORKOS_TELEMETRY=false` | Disable telemetry |
| Variable | Effect |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `WORKOS_API_KEY` | API key for `workos api`, the still-REST commands (connection, directory, audit-log, api-key, vault), and workflow/debug commands. Resource commands use the `workos auth login` session instead |
| `WORKOS_API_URL` | Override the API base URL for all CLI commands (e.g. a local API) |
| `WORKOS_API_BASE_URL` | Accepted alias for `WORKOS_API_URL` (what `workos dev` sets) |
| `WORKOS_MODE` | Interaction mode: `human`, `agent`, or `ci` |
| `WORKOS_FORCE_TTY=1` | Force human (non-JSON) **output** mode even when piped |
| `WORKOS_TELEMETRY=false` | Disable telemetry |

> `WORKOS_API_URL` controls where the **CLI's own** commands (`workos user`, `workos api`, etc.) send requests — set it to point the CLI at a locally-running API. This is distinct from `workos dev`, which sets API vars for your **app's** dev process so it talks to the in-process emulator.

### Command Discovery

Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@
"eval:diff": "bun run ./tests/evals/index.ts diff",
"eval:prune": "bun run ./tests/evals/index.ts prune",
"eval:logs": "bun run ./tests/evals/index.ts logs",
"eval:show": "bun run ./tests/evals/index.ts show"
"eval:show": "bun run ./tests/evals/index.ts show",
"gen:routes": "bun run ./scripts/gen-routes.ts",
"catalog:vendor": "bun run ./scripts/vendor-catalog.ts",
"check:coverage": "bun run ./scripts/check-coverage.ts",
"justification:check": "bun run ./scripts/check-justification.ts"
},
"author": "WorkOS",
"license": "MIT"
Expand Down
39 changes: 39 additions & 0 deletions scripts/check-justification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env tsx
/**
* CI gate for the command justification manifest.
*
* Loads the curated manifest and the vendored Management catalog, runs
* `validateManifest`, and exits non-zero if any entry is incomplete or maps to
* an operation the catalog does not contain. Wired to `pnpm justification:check`
* and run in CI so an unjustified or drifted command cannot ship.
*
* Usage:
* pnpm justification:check
*/

import { getManifest } from '../src/catalog/manifest.js';
import { loadManagementCatalog } from '../src/catalog/loader.js';
import { validateManifest } from '../src/catalog/justification.js';

function main(): void {
const manifest = getManifest();
// Validate against the full catalog (including feature-flag-gated ops): a
// manifest entry may legitimately target a flagged operation, and we only
// want to fail on genuinely-unknown `mapsTo` values, not on visibility.
const catalog = loadManagementCatalog(undefined, { includeFeatureFlagged: true });

const { ok, errors } = validateManifest(manifest, catalog);

if (ok) {
console.log(`justification:check — OK (${manifest.length} command(s) validated)`);
process.exit(0);
}

console.error(`justification:check — FAILED (${errors.length} error(s)):`);
for (const error of errors) {
console.error(` - ${error}`);
}
process.exit(1);
}

main();
Loading
Loading