claudewheel v0.24.2 /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.

#_real_config_dir

python
def _real_config_dir(ws: 'Workspace', name: str) -> str

The FUNCTIONAL config dir: a real absolute path string for name.

Derived from the live workspace (ws.profiles.path_for), so it honors a relocated workspace root. This is the value stored in WizardResult.config_dir and consumed by the auth flow -- it must be a real path, never a ~ literal (auth expanduser's it, and expanduser on an absolute path is a no-op).

#_display_config_dir

python
def _display_config_dir(ws: 'Workspace', name: str) -> str

Cosmetic rendering of the config dir for readonly form fields/messages.

Shows the REAL path, home-compressed to ~/... only when it actually lives under the user's home (purely for readability). Never used functionally -- WizardResult.config_dir always carries the real path.

#_validate_name

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

Return error message, or None if valid.

#_build_fields

python
def _build_fields(ws: 'Workspace', existing_profiles: list[str]) -> list[FormField]

Build the ordered list of wizard form fields.

#_build_result

python
def _build_result(ws: 'Workspace', 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(ws: 'Workspace', existing_profiles: list[str], theme: ThemeColors, terminal: 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(ws: 'Workspace') -> dict[str, Any]

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(ws: 'Workspace', result: WizardResult, previewing: bool=False) -> list[str]

Execute the profile creation based on wizard results.

Assembles the final settings dict (clone/defaults/checkbox overrides/hooks) then delegates all durable mechanics to :meth:ProfileStore.create -- atomic settings.json write, onboarding flag, shared-store symlinks, and options.json (pinned) registration. No config_dir metadata is persisted.

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

previewing switches the summary's leading verb to the conditional. The store's writes are recorded rather than performed under --dry-run, so a past-tense summary would claim work that did not happen. The TUI never previews and leaves the default.

#_find_claude_binary

python
def _find_claude_binary(locator: 'BinaryLocator') -> str | None

Locate the Claude Code binary via the injected locator. Returns path or None.

#run_auth_flow

python
def run_auth_flow(ws: 'Workspace', locator: 'BinaryLocator', config_dir: str, profile_name: str, theme: ThemeColors, terminal: 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 <url>; 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(ws: 'Workspace', 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(ws: 'Workspace', locator: 'BinaryLocator', config_dir: str, profile_name: str, browser: str, terminal: 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, terminal: Terminal) -> str | None

Read a manually pasted token with the terminal's masked-input primitive.

The token is never echoed in clear -- each key shows a mask glyph -- so it cannot be shoulder-surfed or captured. Removes ALL whitespace, including spaces embedded by line-wrapped terminal copies. Returns None on Ctrl-C / Esc (abort); an empty string when nothing (or only whitespace) was entered.

#_format_gate

python
def _format_gate(token: str, terminal: Terminal) -> str | None

Return a format-valid pasted token, or None after one failed re-prompt.

Runs BEFORE any network probe and never makes a network call itself. If the token has the expected sk-ant- shape it is returned as-is. Otherwise one corrected paste is requested (mirroring the 401 re-paste structure); if that is empty or still malformed, the gate hard-fails with None.

#_capture_setup_token

python
def _capture_setup_token(locator: 'BinaryLocator', config_dir: str, browser: str, terminal: Terminal) -> 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(ws: 'Workspace', profile_name: str, token: str, expiry: TokenExpiryDisposition) -> bool

Write the token via the workspace TokenStore; print + return False on OSError.

expiry records how the token's lifetime is stored: setup-tokens are TTL (365 days); externally-pasted tokens are UNKNOWN (no assumed expiry).

#_auth_paste_token

python
def _auth_paste_token(ws: 'Workspace', config_dir: str, profile_name: str, theme: ThemeColors, terminal: Terminal) -> str

Prompt the user to paste a token directly, validate it, save it.

Skips the browser selection and PTY capture entirely -- the user pastes a token they already have. Validation and outcome handling mirror _auth_long_lived_token. 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 (cancelled, rejected, save error)

#_auth_long_lived_token

python
def _auth_long_lived_token(ws: 'Workspace', locator: 'BinaryLocator', config_dir: str, profile_name: str, browser: str, theme: ThemeColors, terminal: 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.

Search