Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7080631
refactor!: remove the Ink dashboard TUI
nicknisi Sep 23, 2026
e6efffd
fix: remove stale dashboard option and eval progress timer
nicknisi Sep 24, 2026
2727be3
chore(deps): add Ink, React, and @inkjs/ui for the full-screen installer
nicknisi Sep 24, 2026
8497cc7
feat(tui): add swappable content for the full-screen installer
nicknisi Sep 24, 2026
d340fca
refactor(ui): replace the dead dashboard flag with a UI host seam
nicknisi Sep 24, 2026
ca39d12
feat(tui): add the event-driven run model for the full-screen installer
nicknisi Sep 24, 2026
e4fed49
feat(install): add the full-screen installer
nicknisi Sep 24, 2026
f218e63
fix(install): show a question's context in the full-screen installer …
nicknisi Sep 24, 2026
e60d6fa
feat(cli): replace the lock mascot with the WorkOS logomark
nicknisi Sep 24, 2026
06ac981
feat(install): report each dashboard checklist item as it's set
nicknisi Sep 24, 2026
f77ee98
feat(tui): align the checklist with the dashboard and put it on the r…
nicknisi Sep 24, 2026
16b05fa
fix(install): leave the agent's play-by-play out of the full-screen s…
nicknisi Sep 24, 2026
f7cd528
fix(tui): don't drop or garble keys in the full-screen prompts
nicknisi Sep 24, 2026
881f552
fix(install): restore the terminal on SIGTERM and SIGHUP in the full-…
nicknisi Sep 24, 2026
8e58cc0
fix(install): print the agent-log pointer after the agent's closing line
nicknisi Sep 24, 2026
b5b4888
fix(tui): don't tick a task whose WorkOS setting still needs a look
nicknisi Sep 24, 2026
d201fd5
fix(install): address TUI validation, cancellation, and input review
nicknisi Sep 24, 2026
092b3bd
ci: diagnose smoke authentication with a direct read-only request
nicknisi Sep 25, 2026
8153045
ci: distinguish unusable smoke credentials from CLI regressions
nicknisi Sep 25, 2026
72299d7
ci: warn explicitly when smoke credentials are unavailable
nicknisi Sep 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
18 changes: 12 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,25 @@ jobs:

# Authenticated command execution against a real WorkOS staging
# environment: list plus a create → get → delete round-trip. The secret
# is a dedicated staging-environment API key; fork PRs receive no
# secrets, so this skips there instead of failing.
# is a dedicated staging-environment API key. Only a successful direct
# read-only probe enables live checks; missing/rejected credentials are
# reported as NOT RUN, while other preflight failures fail the job.
- name: Preflight smoke authentication without the CLI
id: smoke-auth-preflight
env:
WORKOS_SMOKE_API_KEY: ${{ secrets.WORKOS_SMOKE_API_KEY }}
WORKOS_SMOKE_API_URL: ${{ secrets.WORKOS_SMOKE_API_URL }}
run: sh scripts/smoke-auth-preflight.sh

- name: Smoke test authenticated commands
id: authenticated-smoke
if: steps.smoke-auth-preflight.outputs.usable == 'true'
env:
WORKOS_API_KEY: ${{ secrets.WORKOS_SMOKE_API_KEY }}
# Optional: set when the smoke key belongs to a non-default API host
# (e.g. an internal staging deployment).
WORKOS_SMOKE_API_URL: ${{ secrets.WORKOS_SMOKE_API_URL }}
run: |
if [ -z "$WORKOS_API_KEY" ]; then
echo "Skipped: WORKOS_SMOKE_API_KEY secret not available (fork PR or not yet configured)"
exit 0
fi
if [ -n "$WORKOS_SMOKE_API_URL" ]; then
export WORKOS_API_URL="$WORKOS_SMOKE_API_URL"
fi
Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ WorkOS CLI for installing AuthKit integrations and managing WorkOS resources (or

## Architecture

- Three adapters (CLI, Dashboard, Headless) subscribe to `InstallerEventEmitter` state machine events, selected by TTY detection
- Three adapters subscribe to `InstallerEventEmitter` state machine events, chosen by `selectInstallerAdapter()`: Headless in JSON mode; the full-screen TUI (`src/tui/`, Ink) for a human at an interactive terminal of at least 80×24 unless `--no-tui`; CLI otherwise. The TUI adapter wraps the CLI adapter and redirects `ui` output/prompts via `setUiHost()`, so prompt logic lives in one place
- Full-screen installer copy (tips, announcements, walkthrough, task labels) is data in `src/tui/content/installer-content.json`, validated by tests
- `OutputMode` (`human`/`json`) resolved once at startup in `bin.ts`, drives all formatting
- `installerCanUseTool()` in `agent-interface.ts` restricts Bash to safe commands only
- Config/credentials stored in system keyring with file fallback
Expand Down Expand Up @@ -103,15 +104,15 @@ All commands automatically emit a `command` telemetry event with name, duration,

**Exiting with errors:** Use `exitWithError()` or `exitWithCode()` from handlers — they throw `CliExit` which the lifecycle catches, classifies, and records.

**Skip list**: Commands in `SKIP_TELEMETRY_COMMANDS` (`command-telemetry.ts`) are excluded from command-level telemetry because they have their own session-based telemetry. Currently: `install`, `dashboard`, `root` (the default `$0` handler).
**Skip list**: Commands in `SKIP_TELEMETRY_COMMANDS` (`command-telemetry.ts`) are excluded from command-level telemetry because they have their own session-based telemetry. Currently: `install`, `root` (the default `$0` handler).

**Aliases**: if you register a command with multiple names (e.g., `['organization', 'org']`), add the alias to `src/lib/command-aliases.ts` so metrics don't fragment.

## Do / Don't

**Do:**

- Follow the adapter pattern (`CLI`, `Dashboard`, `Headless`) in `src/integrations/` when adding framework installers
- Follow the adapter pattern (`CLI`, `TUI`, `Headless`) in `src/integrations/` when adding framework installers
- Use `InstallerEventEmitter` for state machine events -- see existing adapters for examples
- Add both human and JSON output modes -- check `OutputMode` usage in `src/bin.ts`
- Follow existing command patterns in `src/commands/organization.ts` when adding resource commands
Expand Down
34 changes: 21 additions & 13 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ src/
│ ├── workos-api.ts # Generic WorkOS REST API client
│ ├── credential-proxy.ts # Token refresh proxy for long sessions
│ ├── ensure-auth.ts # Startup auth guard
│ └── adapters/ # CLI and dashboard adapters
│ └── adapters/ # CLI, full-screen (TUI), and headless adapters
├── commands/
│ ├── env.ts # workos env (add/remove/switch/list)
│ ├── organization.ts # workos organization (create/update/get/list/delete)
Expand All @@ -29,7 +29,9 @@ src/
│ ├── auth-status.ts # workos auth status
│ ├── login.ts # workos auth login
│ └── logout.ts # workos auth logout
├── dashboard/ # Ink/React TUI components
├── tui/ # Full-screen installer (Ink/React)
│ ├── content/ # Swappable tips, news, walkthrough copy (see README there)
│ └── model/ # Event-driven run model (no Ink)
├── nextjs/ # Next.js installer agent
├── react/ # React SPA installer agent
├── react-router/ # React Router installer agent
Expand Down Expand Up @@ -61,7 +63,7 @@ bun run dev

# Test installer in another project
cd /path/to/test/nextjs-app
workos dashboard
workos install

# Test management commands
workos env add sandbox sk_test_xxx
Expand Down Expand Up @@ -94,7 +96,6 @@ bun run test:watch
- **Target:** ES2022
- **Module:** NodeNext (ESM)
- **Strict mode** enabled
- **JSX:** react-jsx (for Ink/React dashboard)

## Output Mode vs Interaction Mode

Expand Down Expand Up @@ -151,18 +152,15 @@ hook) guarantees.
### Why `react-devtools-core` is a devDependency

Nothing in `src/` imports `react-devtools-core`, but it is required to
**build**, not to run. The dashboard TUI (`src/dashboard/`) uses `ink`, whose
**build**, not to run. The full-screen installer (`src/tui/`) uses `ink`, whose
reconciler does a runtime-gated `await import('./devtools.js')` that only fires
when `DEV=true`; `devtools.js` then _statically_ imports `react-devtools-core`.
`bun build --compile` follows that static import at bundle time and cannot prove
the `DEV` branch is dead, so removing the devDependency fails the compile with
`error: Could not resolve: "react-devtools-core"` (do not "clean it up"). As a
result it is also bundled into every shipped binary, costing ~742 KiB
(measured: 72,512,032 → 71,752,480 bytes when excluded). Do **not** try to trim
it with `--external react-devtools-core`: that compiles, but bun resolves the
external eagerly and the standalone binary then crashes on _every_ command
(even `--version`) with `Cannot find package 'react-devtools-core'`. The ~742 KiB
is the price of keeping the compile green and the dev-mode fallback graceful.
`error: Could not resolve: "react-devtools-core"` (do not "clean it up"). Do
**not** try to trim it with `--external react-devtools-core`: that compiles, but
bun resolves the external eagerly and the standalone binary then crashes on
_every_ command (even `--version`) with `Cannot find package 'react-devtools-core'`.

### Updating Integration Instructions

Expand Down Expand Up @@ -190,10 +188,20 @@ export function redactCredentials(obj: any): any {

**Manual testing:**

1. Run installer in a test app: `workos dashboard`
1. Run installer in a test app: `workos install` (full-screen), and `workos install --no-tui` (plain)
2. Check logs at `~/.workos/logs/workos-{timestamp}.log`
3. Verify integration works in test app

**Full-screen installer:** the view lives in `src/tui/`. Its text (tips, news,
walkthrough copy, task labels) is data in `src/tui/content/installer-content.json`;
edit that and run `bun run test src/tui/content`. The adapter
(`src/lib/adapters/tui-adapter.ts`) wraps the CLI adapter, so prompt behavior
never forks: it routes `ui` output and prompts into the view through
`setUiHost()`. Render tests draw into fake streams
(`src/tui/ink-streams.test-utils.ts`); check a real terminal at about 80×24 and
in a large window, and confirm the terminal is restored after success, failure,
and ctrl-c.

**What to test:**

- Framework detection
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,21 @@ workos install [options]
--create-pr Auto-create pull request after installation
--no-git-check Skip git dirty working tree check
--force-install Force install packages even if peer dependency checks fail
--no-tui Use plain line-by-line output instead of the full-screen installer
--debug Enable verbose logging
```

**Full-screen installer:** In an interactive terminal of at least 80×24,
`workos install` opens a full-screen view: a plain-English walkthrough of what
it's doing, a task list beside it that follows the dashboard's AuthKit
checklist, and tips and WorkOS news, with every question asked inline. Below
100×30 the task list becomes a single progress line under the walkthrough. When
it closes, your answers, the settings it made, any warnings or errors, and the
same summary a plain run prints stay in your scrollback; the agent's
step-by-step output is left to the log in `~/.workos/logs/`. Pass `--no-tui` for
plain output. Smaller terminals, piped input/output, CI and agent modes, and
`--json` always use plain or JSON output.

**Empty directories:** Running `workos install` in an empty directory scaffolds a new Next.js app with `create-next-app` (App Router, TypeScript, Tailwind, `src/`) and then wires AuthKit into it. This only happens when the directory is empty or contains nothing but VCS/editor metadata (`.git`, `.gitignore`, `LICENSE`, `.idea`, and similar). Any project file — including a `README.md` or a `package.json` — opts out, and the installer treats the directory as an existing project. Interactive runs confirm first (default yes); non-interactive/headless runs (or `--scaffold`) scaffold automatically and report `"scaffolded": true`. The package manager is resolved from how you invoked the CLI (`npm_config_user_agent`) unless you pass `--pm`.

## Examples
Expand All @@ -564,9 +576,6 @@ workos install
# Greenfield: scaffold a new Next.js app + AuthKit in an empty directory
mkdir my-app && cd my-app && workos install

# With visual dashboard (experimental)
workos dashboard

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

Expand Down Expand Up @@ -715,7 +724,7 @@ OAuth credentials are stored in the system keychain (with `~/.workos/credentials

The CLI collects anonymous usage telemetry to help improve the product:

- **Command events** -- command name, duration, success/failure, termination reason, and which flags were used (for telemetry-enabled commands; `install` and `dashboard` use session events instead)
- **Command events** -- command name, duration, success/failure, termination reason, and which flags were used (for telemetry-enabled commands; `install` uses session events instead)
- **Session events** -- framework detected, step timing, token usage (installer only)
- **Crash events** -- sanitized error type and stack trace (no secrets, truncated to 4KB)

Expand Down
22 changes: 17 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "~0.3.0",
"@anthropic-ai/sdk": "^0.124.0",
"@inkjs/ui": "^2.0.0",
"@inquirer/prompts": "^8.5.2",
"@napi-rs/keyring": "^1.2.0",
"@workos-inc/node": "^10.13.0",
Expand All @@ -39,6 +40,7 @@
"open": "^11.0.0",
"react": "^19.2.4",
"semver": "^7.7.4",
"string-width": "^8.2.2",
"uuid": "^13.0.0",
"xstate": "^5.28.0",
"yaml": "^2.8.2",
Expand Down
Loading
Loading