Skip to content

feat: add zero-auth install flow with claim command#91

Merged
nicknisi merged 27 commits intomainfrom
feat/one-shot-mode
Mar 13, 2026
Merged

feat: add zero-auth install flow with claim command#91
nicknisi merged 27 commits intomainfrom
feat/one-shot-mode

Conversation

@nicknisi
Copy link
Member

@nicknisi nicknisi commented Mar 10, 2026

Summary

Implements unclaimed environments — zero-friction workos install that works without prior authentication. When no credentials are found, the CLI silently provisions an "unclaimed" environment via the provisioning API, writes all credentials to .env.local, and proceeds with the install. Users can later link the environment to their WorkOS account via workos claim. Management commands on unclaimed environments show a non-blocking warning.

Also adds a hidden workos debug command for developer-facing CLI introspection (credentials, config, proxy, env vars).

Key features

  • Zero-auth install: workos install auto-provisions an unclaimed environment when no credentials exist
  • Claim flow: workos claim links an unclaimed environment to a WorkOS account via nonce-based browser auth + polling
  • Unclaimed warning: Non-blocking stderr warning on management commands, once-per-session dedup, JSON-mode suppression
  • Debug command: Hidden workos debug with subcommands (credentials, config, proxy, env, reset) for developer diagnostics
  • Credential proxy: Refactored proxy helpers, gateway auth support for unclaimed environments

What was tested

Automated

  • 1123 tests passing (~93 new across 6 spec files)
  • pnpm typecheck — PASS
  • pnpm build — PASS
  • New test coverage:
    • unclaimed-env-api.spec.ts — 20 tests (provisioning, claim initiation, claim polling, error handling)
    • unclaimed-env-provision.spec.ts — 10 tests (happy path, API failure fallback, config store writes)
    • claim.spec.ts — 12 tests (nonce generation, browser open, polling, timeout, JSON mode)
    • unclaimed-warning.spec.ts — 9 tests (warning display, dedup, JSON suppression, claimed detection)
    • debug.spec.ts — 31 tests (all subcommands, JSON mode, masking, error handling)
    • config-store.spec.ts — +11 tests (unclaimed type, claimToken field, type guard)

Manual

Full code review of all 25 changed files. Each acceptance criterion verified against source code and tests:

  • Unclaimed provisioning is non-fatal (try/catch fallback to login flow)
  • Claim flow: nonce generation, browser open, 5-min polling with transient error resilience
  • Unclaimed warning: stderr-only, once-per-session dedup, JSON suppression, lazy claim detection, never-throw guarantee
  • Env list shows (unclaimed) label with hint to run workos claim
  • Management commands wired with maybeWarnUnclaimed() across command handlers

Key design decisions

  • Non-fatal provisioning: tryUnclaimedProvision() catches all errors and returns false, allowing seamless fallback to the existing ensureAuthenticated() login flow
  • Once-per-session warning: maybeWarnUnclaimed() uses a module-level flag to prevent repeated warnings across multiple commands in a session
  • JSON mode awareness: Warnings go to stderr only in human output mode; JSON output stays clean
  • Claim polling: 5-minute timeout with 2-second intervals, resilient to transient network errors during polling
  • Hidden debug command: Registered with false description in yargs so it doesn't appear in --help output — developer-only tool

Files changed (25)

File Change
src/lib/unclaimed-env-api.ts New — API client for unclaimed env provisioning and claim
src/lib/unclaimed-env-api.spec.ts New — 20 tests
src/lib/unclaimed-env-provision.ts New — tryUnclaimedProvision() helper
src/lib/unclaimed-env-provision.spec.ts New — 10 tests
src/lib/unclaimed-warning.ts New — warning module with claim detection
src/lib/unclaimed-warning.spec.ts New — 9 tests
src/commands/claim.ts New — workos claim command
src/commands/claim.spec.ts New — 12 tests
src/commands/debug.ts New — hidden workos debug introspection command
src/commands/debug.spec.ts New — 31 tests
src/utils/box.ts New — boxed message utility for provisioning success
src/lib/config-store.ts Extended — unclaimed type, claimToken field
src/lib/config-store.spec.ts Extended — +11 tests
src/lib/credential-proxy.ts Refactored — deduped proxy helpers, gateway auth for unclaimed envs
src/lib/agent-interface.ts Extended — expanded safe command allowlist
src/commands/env.ts Extended — unclaimed label in env list
src/lib/env-writer.ts Extended — CLAIM_TOKEN env var support
src/lib/run-with-core.ts Extended — maybeWarnUnclaimed() wiring
src/bin.ts Extended — credential resolution, claim + debug command registration, unclaimed warning middleware
src/utils/help-json.ts Extended — claim command in help registry
README.md Extended — unclaimed environments section, claim command
.gitignore Extended
package.json Version bump
.release-please-manifest.json Version bump
CHANGELOG.md Removed stale entries

Follow-ups

  • env.spec.ts lacks tests for unclaimed label display in runEnvList
  • help-json.spec.ts does not explicitly verify claim command registration
  • Duplicate generateCookiePassword exists in unclaimed-env-api.ts and env-writer.ts — consider extracting to a shared utility

Screenshots

Unclaimed environment created
capture_20260310_221758

Warning about taking actions on an unclaimed environment
capture_20260310_215811

Claim an environment
capture_20260313_070103

Phase 1 core infrastructure for one-shot mode:
- One-shot provisioning API client (provisionOneShotEnvironment)
- Claim nonce API client (createClaimNonce)
- Cookie password generator (generateCookiePassword)
- OneShotApiError with status code, timeout, rate limit handling
- Config store: add unclaimed environment type and claimToken field
- isUnclaimedEnvironment() helper
- 30 new tests covering all API scenarios and config round-trips
Add warnIfUnclaimed() module that shows a non-blocking stderr warning
when management commands run against an unclaimed environment. Lazily
checks claimed status via createClaimNonce() once per session and
auto-upgrades config when claimed. Wired into all management command
handlers in bin.ts. Updated env list to show (unclaimed) label.
Copy link
Member Author

@nicknisi nicknisi left a comment

Choose a reason for hiding this comment

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

Code Review Findings

Warnings

  1. Missing test coverage for unclaimed label displayenv.spec.ts lacks tests for the (unclaimed) label in runEnvList (src/commands/env.ts:221-237). Consider adding tests that verify the label renders correctly for unclaimed environments.

  2. Missing test for claim command registrationhelp-json.spec.ts does not explicitly verify the claim command appears in the help registry (src/utils/help-json.ts:1037-1051). A targeted assertion would prevent silent regressions.

  3. Duplicate generateCookiePassword — The same function exists in both src/lib/one-shot-api.ts:196 and src/lib/env-writer.ts:45. Consider extracting to a shared utility module to reduce duplication.

Info

  1. False positive in .case-testedfail_indicators: 1 is a grep heuristic false positive; confirmed 1098/1098 tests passing.

  2. Pre-existing large filesrun-with-core.ts (541 lines) and help-json.ts (1259 lines) were already above the 300-line threshold; changes in this PR are minimal additions.

Automated review by case/reviewer agent

@nicknisi nicknisi changed the title feat(one-shot): add zero-auth install flow with claim command feat: add zero-auth install flow with claim command Mar 11, 2026
@nicknisi nicknisi marked this pull request as draft March 11, 2026 03:16
nicknisi added 13 commits March 10, 2026 22:18
- unclaimed-env-api: treat HTTP 409 as alreadyClaimed (not generic error)
- claim command: detect 401 in polling loop, clean up and exit
- unclaimed-warning: re-add lazy claim check to detect external claims,
  handle 401 by removing stale claim token
Start a lightweight proxy that injects x-workos-claim-token and
x-workos-client-id headers when the active environment is unclaimed.
This allows the installer's AI agent to authenticate with the LLM
gateway without OAuth tokens.
Subcommands: state (dump raw credentials/config/storage), reset (clear
auth state), simulate (write real state for edge-case testing), token
(decode JWT claims). Hidden from --help, supports --json output mode.
…ddleware

- Extract HOP_BY_HOP_HEADERS, filterHeaders(), buildUpstreamPath() in
  credential-proxy.ts — eliminates duplicate header/URL logic between
  startCredentialProxy and startClaimTokenProxy
- Replace 82 individual maybeWarnUnclaimed() calls in bin.ts with a
  single yargs middleware (excludes auth/claim/install/debug/env/skills/doctor)
- Add change-detection guard in markEnvironmentClaimed() to skip
  redundant filesystem writes on already-claimed environments
- saveConfig() now verifies keyring writes with immediate read-back,
  falling back to file if the keyring entry isn't readable
- Provisioning verifies config persisted after save
- debug state shows separate Storage sections for credentials and config,
  each with their own keyring/file diagnostics
- markEnvironmentClaimed() renames env key from 'unclaimed' to 'sandbox'
- Claim success now prompts user to run `workos auth login`
@nicknisi nicknisi marked this pull request as ready for review March 13, 2026 12:02
…imed flow

- Split EnvironmentConfig into discriminated union (ClaimedEnvironmentConfig |
  UnclaimedEnvironmentConfig) with required clientId/claimToken on unclaimed
  variant, eliminating 4 redundant null checks across the codebase
- Make isUnclaimedEnvironment a proper type predicate (env is
  UnclaimedEnvironmentConfig)
- Refactor markEnvironmentClaimed to construct new object instead of mutating
  + deleting fields
- Add logError to outer catch in unclaimed-warning (never-throw preserved)
- Surface user-facing message when browser open fails in claim command
- Differentiate API vs filesystem errors in tryProvisionUnclaimedEnv
- Return false on config read-back failure instead of continuing as success
- Add configSource to debug state JSON output for parity with human mode
- Use exitWithError in claim command for proper exit code 1 on failures
- Remove redundant --json option on claim command (inherited from global)
- Track consecutive poll failures in claim command and update spinner
  message after 3+ failures to surface connection issues to user
- Fix credential-proxy module comment ("from file" -> "into upstream
  requests") and soften claim token expiry assumption
- Remove vestigial comment in debug.ts that described old approach
- Extract MockUnclaimedEnvApiError to shared test helper to deduplicate
  identical classes in claim.spec.ts and unclaimed-warning.spec.ts
- Extract resolveInstallCredentials from bin.ts to standalone module
  with 10 unit tests covering the full credential resolution priority
  chain (env var > flag > unclaimed > OAuth > provision > login)
- Reorder .env.local write before config save to prevent orphaned config
  entries on filesystem failure
- Add MAX_CONSECUTIVE_FAILURES (10) cap to claim polling loop for early
  exit instead of burning full 5-minute timeout
- Prevent markEnvironmentClaimed from overwriting existing sandbox env
- Add logging for non-401 errors in warnIfUnclaimed claim check
- Log browser open errors in claim command for diagnostics
- Wrap resolveInstallCredentials in try-catch with contextual logging
- Replace || with ?? for camelCase/snake_case API field resolution
- Add dashboard and default command to middleware exclusion list
- Correct RFC citation (2616 → 7230 §6.1) for hop-by-hop headers
- Add 9 tests: 401 poll auto-claim, 401 warning promotion, 409 conflict,
  consecutive failure cap, spinner message, browser open failure, config
  read-back failure, non-unclaimed no-op, sandbox key collision
- Fix exitWithError mock to throw (matches debug.spec.ts pattern)
- Add message method to spinner mock
@nicknisi nicknisi merged commit d1b0039 into main Mar 13, 2026
5 checks passed
@nicknisi nicknisi deleted the feat/one-shot-mode branch March 13, 2026 16:30
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