diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b93b6621..579642e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,9 @@ jobs: - name: Typecheck run: pnpm typecheck + - name: Justification check + run: pnpm justification:check + - name: Build run: pnpm build diff --git a/CLAUDE.md b/CLAUDE.md index 92193875..9cb20404 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. diff --git a/README.md b/README.md index e51003af..0e55f531 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,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 @@ -255,7 +255,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 [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 [args] [--options]`. + +Most resource commands — organization, user, role, permission, membership, invitation, session, event, feature-flag, org-domain, portal, webhook, config — authenticate with your WorkOS dashboard session from `workos auth login` and target the active environment (see `workos env`). Pass `--environment-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 @@ -264,68 +268,72 @@ workos organization create [domain:state ...] workos organization update [domain] [state] workos organization get workos organization list [--domain] [--limit] [--before] [--after] [--order] -workos organization delete +workos organization delete [--yes] ``` #### user ```bash workos user get -workos user list [--email] [--organization] [--limit] -workos user update [--first-name] [--last-name] [--email-verified] [--password] [--external-id] -workos user delete +workos user list [--email] [--limit] [--before] [--after] [--order] +workos user update [--first-name] [--last-name] [--email] [--locale] [--external-id] +workos user delete [--yes] ``` #### role +Role mutations change the privilege surface; non-interactive callers must pass `--yes`. + ```bash workos role list [--org ] workos role get [--org ] -workos role create --slug --name [--org ] -workos role update [--name] [--description] [--org ] -workos role delete --org -workos role set-permissions --permissions [--org ] -workos role add-permission [--org ] -workos role remove-permission --org +workos role create --slug --name [--description] [--org ] [--yes] +workos role update [--name] [--description] [--org ] [--yes] +workos role delete --org [--yes] +workos role set-permissions --permissions [--org ] [--yes] +workos role add-permission [--org ] [--yes] +workos role remove-permission --org [--yes] ``` #### permission ```bash -workos permission list [--limit] +workos permission list workos permission get -workos permission create --slug --name [--description] -workos permission update [--name] [--description] -workos permission delete +workos permission create --slug --name [--description] [--yes] +workos permission update [--name] [--description] [--yes] +workos permission delete [--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 | --user ) [--limit] [--before] [--after] [--order] workos membership get workos membership create --org --user [--role] -workos membership update [--role] -workos membership delete -workos membership deactivate +workos membership update [--role] [--yes] +workos membership delete [--yes] +workos membership deactivate [--yes] workos membership reactivate ``` #### invitation ```bash -workos invitation list [--org] [--email] [--limit] +workos invitation list [--org] [--email] [--limit] [--before] [--after] workos invitation get workos invitation send --email [--org] [--role] [--expires-in-days] -workos invitation revoke +workos invitation revoke [--yes] workos invitation resend ``` #### session ```bash -workos session list [--limit] -workos session revoke +workos session list [--limit] [--before] [--after] +workos session revoke [--yes] ``` #### connection @@ -349,7 +357,7 @@ workos directory list-groups --directory [--limit] #### event ```bash -workos event list --events [--org] [--range-start] [--range-end] [--limit] +workos event list --events [--range-start] [--range-end] [--after] [--limit] ``` #### audit-log @@ -366,7 +374,7 @@ workos audit-log get-retention #### feature-flag ```bash -workos feature-flag list [--limit] +workos feature-flag list [--limit] [--before] [--after] [--order] workos feature-flag get workos feature-flag enable workos feature-flag disable @@ -379,7 +387,7 @@ workos feature-flag remove-target ```bash workos webhook list workos webhook create --url --events -workos webhook delete +workos webhook delete [--yes] ``` #### config @@ -393,9 +401,11 @@ workos config homepage-url set #### portal ```bash -workos portal generate-link --intent --org [--return-url] [--success-url] +workos portal generate-link --intent --org ``` +Supported intents: `sso`, `dsync`, `log_streams`, `domain_verification`, `certificate_renewal`. Generating a link expires prior links of the same intent. + #### vault ```bash @@ -425,7 +435,7 @@ workos api-key delete workos org-domain get workos org-domain create --org workos org-domain verify -workos org-domain delete +workos org-domain delete [--yes] ``` ### Installer Options @@ -464,11 +474,11 @@ mkdir my-app && cd my-app && npx workos@latest install # With visual dashboard (experimental) npx workos@latest 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' @@ -494,7 +504,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 @@ -504,8 +514,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 @@ -555,13 +565,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 diff --git a/package.json b/package.json index 1a904b58..65a83767 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,9 @@ "eval:diff": "tsx tests/evals/index.ts diff", "eval:prune": "tsx tests/evals/index.ts prune", "eval:logs": "tsx tests/evals/index.ts logs", - "eval:show": "tsx tests/evals/index.ts show" + "eval:show": "tsx tests/evals/index.ts show", + "catalog:vendor": "tsx scripts/vendor-catalog.ts", + "justification:check": "tsx scripts/check-justification.ts" }, "author": "WorkOS", "license": "MIT" diff --git a/scripts/check-justification.ts b/scripts/check-justification.ts new file mode 100644 index 00000000..33c172ef --- /dev/null +++ b/scripts/check-justification.ts @@ -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(); diff --git a/scripts/smoke-dashboard-plane.sh b/scripts/smoke-dashboard-plane.sh new file mode 100755 index 00000000..1437d2dd --- /dev/null +++ b/scripts/smoke-dashboard-plane.sh @@ -0,0 +1,337 @@ +#!/usr/bin/env bash +# Live smoke test for the dashboard-plane resource commands (organization, user, +# role, permission, membership, invitation, session, event, feature-flag, +# org-domain, portal, webhook, config) against the real WorkOS API. +# +# Prereq: `workos auth login` (device flow) with the environment you want to +# target set as the active env. Then: +# +# ./scripts/smoke-dashboard-plane.sh # read-only (safe) +# ./scripts/smoke-dashboard-plane.sh --mutate # + CRUD round-trips in a disposable org +# ./scripts/smoke-dashboard-plane.sh --config-writes # + config redirect/cors add (snapshot & restore) +# ./scripts/smoke-dashboard-plane.sh --keep # don't clean up created resources +# +# Env overrides: +# WORKOS_BIN command to invoke the CLI (default: node /dist/bin.js) +# SMOKE_EVENT_TYPES comma-separated event types for `event list` (default: user.created) +# +# Exit code: 0 if every executed test passed, 1 otherwise. +set -u + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MUTATE=0 CONFIG_WRITES=0 KEEP=0 +for a in "$@"; do + case "$a" in + --mutate) MUTATE=1 ;; + --config-writes) CONFIG_WRITES=1 ;; + --keep) KEEP=1 ;; + -h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}"; exit 0 ;; + *) echo "unknown flag: $a (see --help)"; exit 2 ;; + esac +done + +if [ -n "${WORKOS_BIN:-}" ]; then + # shellcheck disable=SC2206 + BIN=($WORKOS_BIN) +else + [ -f "$REPO/dist/bin.js" ] || { echo "dist/bin.js not found — run: pnpm build"; exit 1; } + BIN=(node "$REPO/dist/bin.js") +fi + +# The migrated commands must run on the OAuth token alone; surface any REST +# leftovers by removing the API-key plane from the environment entirely. +if [ -n "${WORKOS_API_KEY:-}" ]; then + echo "note: WORKOS_API_KEY is set — unsetting it for this run (migrated commands must not need it)" + unset WORKOS_API_KEY +fi +unset WORKOS_FORCE_TTY 2>/dev/null || true + +WORK="$(mktemp -d "${TMPDIR:-/tmp}/workos-smoke.XXXXXX")" +c_g=$'\033[32m' c_r=$'\033[31m' c_y=$'\033[33m' c_d=$'\033[2m' c_0=$'\033[0m' +N=0 NPASS=0 NFAIL=0 NSKIP=0 +FAILURES=() +LAST="" + +say() { printf '\n%s\n' "$1"; } + +# t