claudewheel v0.20.1 /claudewheel.wizard
Edit
On this page

Form-style TUI wizard for creating new Claude Code profiles.

#claudewheel.wizard

#claudewheel.wizard

Interactive form wizard for creating and configuring new profiles.

#WizardResult

Collected values from a completed profile wizard run.

#_validate_name

python
def _validate_name(name: str, existing_profiles: list[str]) -> str | None

Return error message, or None if valid.

#_build_fields

python
def _build_fields(existing_profiles: list[str]) -> list[FormField]

Build the ordered list of wizard form fields.

#_build_result

python
def _build_result(values: dict[str, object]) -> WizardResult

Construct a WizardResult from submitted form values.

#_cancelled_result

python
def _cancelled_result() -> WizardResult

Return a cancelled WizardResult with zero-value fields.

#run_profile_wizard

python
def run_profile_wizard(existing_profiles: list[str], theme, terminal) -> WizardResult

Run the profile creation form and return the user's choices.

The form renders on terminal with theme colors via the ui widget layer: fullscreen, borrowed when the terminal is already raw.

#_load_shared_settings

python
def _load_shared_settings() -> dict

Load shared-settings.json, falling back to canonical defaults if missing.

#_set_onboarding_flag

python
def _set_onboarding_flag(config_dir: str) -> None

Merge hasCompletedOnboarding: true into {config_dir}/.claude.json.

Claude Code gates on this flag in interactive mode -- it is normally set by CC's own OAuth success handler, but claudewheel bypasses that when injecting tokens via CLAUDE_CODE_OAUTH_TOKEN. Read-merge-write preserves any other metadata CC has already written (machineID, cachedGrowthBookFeatures, etc.). Corrupt or missing files are handled gracefully. If the config directory itself doesn't exist, this is a no-op (nothing to write to).

#create_profile

python
def create_profile(result: WizardResult, cfg: ConfigManager) -> list[str]

Execute the profile creation based on wizard results.

Returns the summary lines describing what was created; presentation is the caller's job (the TUI shows a fullscreen page, the CLI prints them).

#_find_claude_binary

python
def _find_claude_binary() -> str | None

Locate the Claude Code binary. Returns the path or None.

#run_auth_flow

python
def run_auth_flow(config_dir: str, profile_name: str, theme, terminal, skip_label: str='Skip for now') -> str

Prompt the user to set up authentication for a newly created profile.

Presents a selection form with three choices: session login (browser-based), long-lived token, or skip. After picking a method, a second form asks which browser to open the auth URL in (or to suppress browser opening and copy the URL manually). The browser chosen in the last successful auth is remembered in state.json and pre-focused on the next run. Returns one of:

  • "authenticated" -- auth was set up successfully (tokens: validated

against the API before saving)

  • "unverified" -- a token was saved WITHOUT validation (the probe

was unreachable or inconclusive and the user explicitly chose to save)

  • "skip" -- the user explicitly chose to skip
  • "cancel" -- the user cancelled a form (Esc/Ctrl-C)
  • "failed" -- auth was attempted but did not complete

This function is safe to call after create_profile() -- auth failure never prevents profile creation. The forms render on terminal with theme colors; a terminal that is already raw is borrowed (the forms render as pages in the existing screen).

#_apply_browser_env

python
def _apply_browser_env(env: dict[str, str], browser: str) -> None

Set BROWSER in env from the browser-form selection.

browser is either a browser binary path or "copy". Claude Code spawns $BROWSER ; BROWSER=false makes that fail, so claude falls back to printing the URL for manual copying.

#_capture_tier_from_credentials

python
def _capture_tier_from_credentials(credentials: Path, profile_name: str) -> None

Read rateLimitTier/subscriptionType from .credentials.json and store in tokens.json.

Best-effort: silently skips if the credentials file cannot be parsed or the expected fields are absent (older Claude Code versions omit them).

#_auth_session_login

python
def _auth_session_login(config_dir: str, profile_name: str, browser: str, terminal) -> bool

Run claude auth login with CLAUDE_CONFIG_DIR and BROWSER set.

The whole body runs inside terminal.cooked() so the claude subprocess and all prints see a real cooked terminal; raw mode (and the alt screen, if any) is restored on exit.

#_read_pasted_token

python
def _read_pasted_token(prompt: str) -> str | None

Read a manually pasted token from input().

Removes ALL whitespace, including linebreaks and spaces embedded by line-wrapped terminal copies. Returns None on EOF/Ctrl-C; an empty string when nothing (or only whitespace) was entered.

#_capture_setup_token

python
def _capture_setup_token(config_dir: str, browser: str) -> str | None

Run claude setup-token under a PTY and scrape the token.

Must run inside a cooked window: run_under_pty proxies the real terminal (it sets its own raw mode internally and restores it). The user interacts with setup-token normally while ALL output is captured; the token is then extracted from the capture -- no paste needed. Returns the token, or None on failure.

If extraction fails, that is a hard, explicit situation: the user is told the scrape failed and offered a manual paste as a clearly labeled recovery step -- not a silent fallback. Empty input aborts.

#_save_token

python
def _save_token(profile_name: str, token: str) -> bool

Write the token via add_token; print and return False on OSError.

#_auth_long_lived_token

python
def _auth_long_lived_token(config_dir: str, profile_name: str, browser: str, theme, terminal) -> str

Run claude setup-token, scrape the token, validate it, save it.

Every token (scraped or manually recovered) is probed against the API BEFORE being saved. Returns one of:

  • "authenticated" -- the probe returned VALID and the token was saved
  • "unverified" -- the probe was UNREACHABLE/INDETERMINATE and the

user explicitly chose to save the unvalidated token

  • "failed" -- anything else. A token the API rejects (401) is NEVER

saved: one manual re-paste is offered (the scrape may have picked a stale frame), then the flow fails hard.