Skip to content

feat: add env provision + verify-login commands, fix non-interactive installs (agent/CI), Vite port detection, and misleading command hints#192

Open
nicknisi wants to merge 10 commits into
mainfrom
nicknisi/akshay
Open

feat: add env provision + verify-login commands, fix non-interactive installs (agent/CI), Vite port detection, and misleading command hints#192
nicknisi wants to merge 10 commits into
mainfrom
nicknisi/akshay

Conversation

@nicknisi

@nicknisi nicknisi commented Jul 8, 2026

Copy link
Copy Markdown
Member

Fixes every CLI-side item from Akshay Maniyar's AuthKit Hybrid + Full Agent friction log. Six implementation commits (plus a formatting pass), each independently gate-tested (pnpm build && pnpm test && pnpm typecheck green at every commit; final suite 2,310 tests).

Agent/CI path — 36c47e9

  • Bare npx workos in a non-TTY shell now emits the full JSON command tree on stdout (exit 0) instead of a degenerate two-option help dump (the old handler called showHelp() on a fresh yargs instance with no commands registered).
  • abortIfCancelled fails fast with a structured non_interactive_prompt error when prompts are disallowed — any future unguarded prompt dies loudly instead of hanging an agent forever.
  • New --router app|pages flag (flag wins); without it, ambiguous Next.js router detection defaults to app router with a warning in non-interactive mode. Same graceful defaults for React Router's ambiguous branches and the upload-env prompt.
  • WORKOS_MODE=ci now enforces the same behavior as the hidden --ci flag (dirty-tree auto-continue, package-manager auto-select) — previously two disconnected notions of "CI".

Port detection + validation — aa875e3

  • detectPort parses vite.config.{ts,js,mjs} first for TanStack Start (Lovable shape), with legacy app.config.ts fallback. Previously the installer wrote port 3000 into .env.local and the dashboard (redirect/CORS/homepage) for an app running on 8080.
  • All three JS redirect validators now flag a redirect-URI/dev-port mismatch on local hosts as a validation error — no more "Validation passed" over a broken install. Remediation hints use the real redirect origin instead of hardcoded localhost:3000.
  • The 11 new regression tests were stash-verified to fail on pre-fix code.

Messaging sweep — bb2c2b1

  • ~28 live hardcoded workos <subcommand> hints routed through the npx-aware formatWorkOSCommand helper, enforced by a new repo-walk regression guard with a curated allowlist — new hardcoded hints fail pnpm test.
  • "Using provided WorkOS credentials" only renders when the user actually provided credentials (source threaded to both adapters).
  • env remove help states it is local-config-only; env claim states claiming is permanent; migrations description reframed around the CSV path (e.g. Supabase) via a shared constant.

Install UX — f6c81d0

  • Post-install summary no longer discards the rich summary on success: a pure buildCompletionData builder assembles the lockfile-aware dev command, app URL with detected port, changed files, next steps, and per-framework docs/dashboard URLs, rendered by all three adapters (CLI/Dashboard/Headless) with a static fallback.
  • Removed the 2s setInterval that clobbered agent phase text; file:write/file:edit events stream as append-only step lines (CLI) and NDJSON (headless). Stretch: agent:tool Bash surfacing + a Next.js sign-in-link snippet.

Env provision + login safety — f3452cb

  • workos env provision: credentials-only unclaimed environment (apiKey/clientId/claimToken via --json), no code-gen, no project writes — closes the agent workaround of running install in throwaway directories just to extract keys.
  • auth login never silently repoints the active environment to a different account: mismatches (ownerEmail/clientId comparison) prompt in human mode, warn in agent/JSON mode, and every login ends with Now using: <env> (<email>).

verify-login — 738d8cc

  • workos verify-login proves the AuthKit loop headlessly: throwaway user → authenticateWithPassword → token assertion → deleteUser in a finally block. Unconditional production refusal (env type or sk_live_ prefix) before any SDK call, no override flag. 13 spec cases; live smoke-tested against the real API.

Merge note

Prefer rebase merge over squash: the commits are conventional-commit formatted (fix:/feat:), so release-please picks up each as its own changelog entry. A squash would collapse them into one line.

nicknisi added 6 commits July 7, 2026 15:55
detectPort now reads vite.config.{ts,js,mjs} for tanstack-start (modern
@tanstack/react-start is Vite-based), falling back to legacy Vinxi
app.config.ts, and the shared react/react-router/vanilla-js Vite loop is
extracted into a parseViteConfigPortFromDir helper.

Redirect-URI validators (Next.js, React Router, TanStack Start) now compare
the URI's effective port to the detected dev-server port for local hosts and
push an error-severity issue on mismatch, flipping validation to failed
instead of silently passing. Route-mismatch hints use the redirect URI's real
origin instead of a hardcoded localhost:3000.

Fixes the Akshay friction-log trust failure where a Lovable TanStack Start
app on port 8080 was configured against localhost:3000 yet certified as
"Validation passed".
Phase 4 (Install UX) of the Akshay friction-log fixes.

Completion summary:
- Add CompletionData type + pure buildCompletionData builder (lockfile-aware
  dev command via resolveDevCommand, app URL via detectPort, changed files,
  composed next steps, per-framework docs + dashboard URLs).
- New buildingCompletion machine state + actor computes the payload between
  postInstall and complete; errors degrade to the static fallback box.
- renderCompletionSummary renders the structured success box (files capped at
  5, next steps, per-framework docs footer); falls back to the static box when
  no completion data is present. Failure branch unchanged.
- Widen the complete event with an optional completion payload; all three
  adapters (CLI, Dashboard, Headless) render/emit it. Headless spreads the
  fields only when present, preserving the existing NDJSON shape.

Agent progress:
- CLI adapter: remove the 2s spinner-message reset that clobbered phase text;
  persist file:write/file:edit as append-only step lines above the spinner
  (stop -> log -> restart), path-only with consecutive-path dedupe.
- Headless adapter: stream path-only file:write/file:edit NDJSON (never content).
- Stretch: surface Bash commands via a new agent:tool event emitted from the
  post-permission tool_use branch; rendered as step lines (CLI) and NDJSON
  (headless).
- Stretch: optional UIConfig.getSignInSnippet surfaced as a next step; defined
  for Next.js (client-side refreshAuth guidance, per the AuthKit skill).

Validated: pnpm build, pnpm test (2241), pnpm typecheck, oxlint, oxfmt all pass.
Removes the two hard dead-ends an AI agent or CI pipeline hits first when
driving the CLI non-interactively:

- Bare `workos` in a non-TTY shell now emits the machine-readable command
  tree (JSON) or the fully-configured help, instead of a degenerate
  two-line block on stderr with empty stdout. Agents and CI can now
  discover `install`.
- Non-interactive installs no longer hang on interactive prompts.
  `abortIfCancelled` fails fast with a structured `non_interactive_prompt`
  error (Layer 1), and Next.js, React Router, and upload-env apply
  graceful per-site defaults so installs succeed (Layer 2).
- `--router app|pages` deterministically selects the Next.js router,
  winning over detection.

WORKOS_MODE=ci now enforces the same required-arg validation as the hidden
--ci flag and bridges to the downstream `options.ci` paths (git checks
auto-continue, package-manager auto-select). Behavior change: `WORKOS_MODE=ci
workos install` without --api-key/--client-id/--install-dir now errors with
a structured missing_args message instead of falling through to
auto-provisioning.
… semantics)

Messaging-accuracy sweep with no behavior changes to install/auth flows.

Area 1 — copy-pasteable command hints: route ~28 hardcoded `workos
<subcommand>` hint sites through formatWorkOSCommand()/getWorkOSCommand()
so users who launched via `npx workos@latest ...` are told to run the npx
form instead of a bare `workos` that npx never installed. Adds a repo-walk
regression guard (command-hints-guard.spec.ts) with a curated 4-entry
allowlist for genuinely-static strings; it fails on any new unrouted hint.

Area 2 — credential-source-aware copy: thread context.credentialSource into
agentOptions and branch getOrAskForWorkOSCredentials so "Using the WorkOS
credentials you provided" prints only for cli/manual (suppressed for
device/stored/env, gated on human output). Adds a source field to
staging:success so the CLI/headless adapters say the right thing instead of
"retrieved automatically" for both fresh and reused environments.

Area 3 — honest semantics + accurate migrations copy: env remove now states
it is local-only (help + runtime warning + localOnly/wasUnclaimed JSON
fields, warning that an unclaimed env's claim token is lost); env claim
states claiming is permanent (help + runtime note + permanent JSON field);
migrations description advertises the generic-CSV path (e.g. Supabase) via a
shared MIGRATIONS_DESCRIPTION constant, and the JSON help subcommand list is
reconciled with @workos/migrations v2.5.0 (adds export/export-template/
generate-package-template/validate-package; process-role-definitions →
process-roles).

Validation: build, typecheck, and full test suite (2277) pass; the hint
guard is now a pnpm test gate. Reviewed via validation-only fallback (the
ideation reviewer subagent was unavailable in this headless run).
Add `workos env provision`, a credentials-only subcommand that calls
provisionUnclaimedEnvironment() directly (no auth, no code-gen). It emits
credentials on stdout (JSON is the agent credential channel), stores the
result as a local active unclaimed env so a follow-up `env claim` works, and
never writes to the project directory or any .env file. Failures (incl. 429)
surface as structured errors with no fallback to login.

Rework auth login staging provisioning to stop silent cross-account switches.
provisionStagingEnvironment now takes the authenticated account, detects a
mismatch against the prior active env (ownerEmail wins over clientId), and on
mismatch writes the new account's Staging under a distinct key instead of
clobbering the active slot. runLogin prompts (human) / warns (agent-CI) /
emits structured JSON, and always ends with an explicit "Now using" line.

Stretch: persist ownerEmail/ownerUserId on environment records and carry them
through markEnvironmentClaimed; add setActiveEnvironment().

Regression tests added first and confirmed failing on pre-fix code
(env provision, mismatch/in-place-clobber guard, "Now using", owner fields).
Add `workos verify-login`, a top-level resource command that proves the
AuthKit login loop end-to-end against the active environment with no browser
and no email/OTP retrieval, closing the last agent-oriented gap in the
friction log (an autonomous agent could not self-verify the post-login flow).

The command runs a full round-trip against the bundled @workos-inc/node SDK:
createUser (throwaway, emailVerified) -> authenticateWithPassword -> assert
access + refresh tokens -> deleteUser in a finally block so cleanup always
runs, even on auth failure. A failed cleanup is surfaced machine-readably
(orphanedUserId + userCleanedUp:false) rather than swallowed, and does not
change the exit code.

Two unconditional safety rules (no override flag): production refusal on both
env.type === 'production' and an sk_live_ key prefix, before any SDK call; and
always-attempt cleanup. Output has human (checklist + verdict) and JSON
(flat verdict object) modes driven by the global --json flag and non-TTY
auto-detection. Auth-required exits 4 via resolveApiKey; refusal / missing
client id emit the standard error envelope and exit 1; a failed verification
emits the verdict with success:false and exits 1.

The --method magic-auth stretch is not shipped: its gate (empirical staging
confirmation that createMagicAuth returns the code in the API response) cannot
be exercised in this headless run, so per the spec the flag is dropped and
password grant remains the only shipped method.

Resolves friction-log goal 10.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was skipped because it would exceed your organization's monthly flex usage limit. Raise the limit in billing settings or wait until the next billing period resets limits.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread src/commands/env.ts
Comment on lines +153 to +162
const config = getOrCreateConfig();
config.environments['unclaimed'] = {
name: 'unclaimed',
type: 'unclaimed',
apiKey: result.apiKey,
clientId: result.clientId,
claimToken: result.claimToken,
};
config.activeEnvironment = 'unclaimed';
saveConfig(config);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Silent overwrite of existing unclaimed environment on repeated provision

The runEnvProvision function (src/commands/env.ts:153-162) unconditionally writes to the 'unclaimed' config key. If a user runs workos env provision twice without claiming the first environment, the first environment's claim token is silently replaced. The old claim token is permanently lost — there is no way to recover or claim that first environment. This is arguably by design (the test at src/commands/env.spec.ts:308-319 verifies the overwrite), but it could surprise agents that provision environments speculatively. A guard or warning before overwriting an existing unclaimed entry would make the behavior explicit.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ea953b0. env provision (and the install auto-provision path in unclaimed-env-provision.ts, which had the same clobber) now allocate a fresh key (unclaimed, unclaimed-2, …) via a shared freshEnvKey() helper, so an earlier environment's claim token is never destroyed. The newest env becomes active; earlier ones stay claimable via env switch + env claim. Regression tests cover both paths (provision twice → first claim token preserved, second env active).

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR expands the WorkOS CLI's agent and install workflows. The main changes are:

  • Adds non-interactive command-tree output for bare CLI invocations.
  • Adds env provision for credentials-only unclaimed environment setup.
  • Adds verify-login for headless AuthKit password-loop checks.
  • Improves CI-mode defaults, prompt handling, and router detection.
  • Adds Vite/TanStack port detection and redirect port validation.
  • Updates install completion output and npx-aware command hints.

Confidence Score: 5/5

Safe to merge based on the reviewed changed paths.

No new blocking issues were identified in the command, provisioning, validation, completion, and non-interactive flows reviewed.

src/commands/login.ts has an existing active-account message thread already on the PR; no new files need special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the Vitest suite in the repository and confirmed all tests passed, reporting 10 test files and 172 tests in total.
  • Executed the CLI in CI mode to generate a JSON manifest and confirmed stdout starts with a structured JSON object containing CLI metadata.
  • Executed the CLI help command in CI mode and confirmed the output shows the help command tree and options without hanging.
  • Uploaded four artifacts capturing the vitest results and CLI outputs to support review.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/bin.ts Registers verify-login/env provision, router flag, JSON help behavior, and npx-aware command hints.
src/commands/env.ts Implements credentials-only unclaimed env provisioning and clearer local removal behavior.
src/commands/login.ts Adds account ownership stamping and mismatch handling; previous active-account reporting concern remains out of scope for duplicate comments.
src/commands/verify-login.ts Introduces headless AuthKit password-loop verification with production-key refusal and cleanup.
src/lib/adapters/headless-adapter.ts Streams file/tool events and enriched completion data as NDJSON.
src/lib/port-detection.ts Parses Vite config ports before framework defaults/fallbacks.
src/lib/run-with-core.ts Threads credential sources, detected ports, and completion data through the core runner.
src/lib/validation/validator.ts Adds local redirect URI port validation for JS frameworks.
src/utils/clack-utils.ts Adds structured non_interactive_prompt failure and invocation-aware prompt hints.
src/utils/help-json.ts Updates static command tree with new commands/options and descriptions.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant User as User/Agent
  participant CLI as workos CLI
  participant Config as Config Store
  participant API as WorkOS API
  participant Validator as Install Validator

  User->>CLI: env provision / install / verify-login
  alt env provision
    CLI->>API: POST /x/one-shot-environments
    API-->>CLI: apiKey, clientId, claimToken, authkitDomain
    CLI->>Config: save fresh unclaimed env as active
    CLI-->>User: JSON or human credentials + env claim hint
  else install non-interactive
    CLI->>Config: resolve active/provided credentials
    CLI->>Validator: detect framework + dev port
    Validator-->>CLI: redirect/port validation issues or pass
    CLI-->>User: NDJSON progress + completion data
  else verify-login
    CLI->>Config: read active env metadata
    CLI->>API: create throwaway user
    CLI->>API: authenticateWithPassword
    CLI->>API: deleteUser in finally
    CLI-->>User: verification result
  end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
  participant User as User/Agent
  participant CLI as workos CLI
  participant Config as Config Store
  participant API as WorkOS API
  participant Validator as Install Validator

  User->>CLI: env provision / install / verify-login
  alt env provision
    CLI->>API: POST /x/one-shot-environments
    API-->>CLI: apiKey, clientId, claimToken, authkitDomain
    CLI->>Config: save fresh unclaimed env as active
    CLI-->>User: JSON or human credentials + env claim hint
  else install non-interactive
    CLI->>Config: resolve active/provided credentials
    CLI->>Validator: detect framework + dev port
    Validator-->>CLI: redirect/port validation issues or pass
    CLI-->>User: NDJSON progress + completion data
  else verify-login
    CLI->>Config: read active env metadata
    CLI->>API: create throwaway user
    CLI->>API: authenticateWithPassword
    CLI->>API: deleteUser in finally
    CLI-->>User: verification result
  end
Loading

Reviews (5): Last reviewed commit: "refactor: apply code-quality review clea..." | Re-trigger Greptile

… bridge)

- env provision (and install auto-provision) store each environment under a
  fresh key (unclaimed, unclaimed-2, ...) instead of overwriting the
  'unclaimed' entry, so a repeated provision never destroys an earlier claim
  token, which lives only in local config and is unrecoverable.
- Extract the key allocation into a shared freshEnvKey() in config-store
  (replaces login.ts's local freshStagingKey).
- Thread ci into the headless adapter: CI-mode installs auto-continue past a
  dirty git tree without --no-git-check, matching the documented --ci bridge.
  Agent mode keeps the strict gate.
@nicknisi

nicknisi commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Re greptile's dirty-tree finding: confirmed real — the legacy --ci auto-continue lived in clack-utils.ts helpers that nothing calls anymore, so CI-mode headless installs still exited on a dirty tree unless --no-git-check was passed. Fixed in ea953b0: ci is now threaded into the HeadlessAdapter (run-with-core.ts), and CI mode auto-continues the git check (NDJSON git:decision: continue). Agent mode intentionally keeps the strict gate, since a dirty tree there is usually the user's own uncommitted work. Regression test added in headless-adapter.spec.ts; CLAUDE.md's non-TTY section updated to document the split.

@nicknisi nicknisi changed the title feat: address Akshay's AuthKit friction log (agent/CI paths, port detection, messaging, install UX, env/auth safety, verify-login) feat: add env provision + verify-login commands, fix non-interactive installs (agent/CI), Vite port detection, and misleading command hints Jul 9, 2026
Comment thread src/commands/login.ts
- remove never-started agent update interval from CLIAdapter
- collapse duplicate file-op handlers into per-adapter helpers
- extract shared non-interactive fallback for React Router mode prompt
- drop write-only fields from StagingProvisionResult
- narrow VerifyLoginMethod to the implemented 'password' method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant