Skip to content
claudewheel.segment
Edit
On this page

Segment model, SegmentBar, and option discovery logic.

#claudewheel.segment

#claudewheel.segment

Segment and SegmentBar dataclasses, option discovery, and cross-segment constraints.

#DiscoveryResult

Structured result from a discovery function.

#DiscoveryEntry

Registry entry mapping a discovery type to its function.

#_deduplicate

python
def _deduplicate(items: list[str]) -> list[str]

Remove duplicates preserving first occurrence order.

#SegmentState

Manages option collections with cache-invalidating mutation methods.

#options

python
def options(self) -> list[str]

Return the computed option list, rebuilding from collections if invalidated.

#_base_of

python
def _base_of(val: str) -> str

The model id val derives from: itself, or the id under its [1m].

#_release_date_of

python
def _release_date_of(self, val: str) -> str

The ISO release date recorded for val, or "" when unknown.

A [1m] entry is claudewheel's own spelling of a base model with the 1M context window selected, so it takes the base model's date; its own metadata is consulted only when the base carries no date.

#_sorted_by_release_date

python
def _sorted_by_release_date(self, values: list[str]) -> list[str]

Order values newest release first, without touching stored order on disk.

Pinned entries keep their stored order at the front -- pinning is an explicit statement about position. The rest are ordered by recorded release date, newest first, with a [1m] entry immediately after the base model it derives from. Entries with no recorded date follow all dated ones in their stored order, which is what the whole list looks like before the first successful refresh.

The unit being ordered is the base model, not the individual entry: a base and its [1m] variant move together, so two bases sharing a release date cannot separate one of them from its own variant. Bases sharing a date keep their stored order relative to each other.

#set_discovered

python
def set_discovered(self, vals: list[str], *, verify_fn: Callable[[str], bool] | None=None) -> None

Replace discovered values, optionally verifying old values before dropping them.

#add_pinned

python
def add_pinned(self, val: str) -> None

Add a value to the pinned collection if not already present.

#remove_pinned

python
def remove_pinned(self, val: str) -> None

Remove a value from the pinned collection, ignoring if absent.

#set_defaults

python
def set_defaults(self, vals: list[str]) -> None

Replace the defaults collection with the given values.

#add_ephemeral

python
def add_ephemeral(self, val: str) -> None

Add a value to the ephemeral collection if not already present.

#set_installed

python
def set_installed(self, vals: set[str]) -> None

Replace the installed set with the given values.

#has_installed

python
def has_installed(self) -> bool

True when at least one value is marked as installed.

#mark_installed

python
def mark_installed(self, val: str) -> None

Mark a single value as installed.

#set_metadata

python
def set_metadata(self, meta: dict[str, dict[str, Any]]) -> None

Replace all metadata with the given mapping.

#update_metadata

python
def update_metadata(self, partial: dict[str, dict[str, Any]]) -> None

Merge partial metadata into the existing metadata mapping.

#source_of

python
def source_of(self, val: str) -> str | None

Return the collection name containing val, or None if not found.

#is_installed

python
def is_installed(self, val: str) -> bool

True when val is in the installed set.

#set_authenticated

python
def set_authenticated(self, vals: set[str]) -> None

Set the authenticated values and activate auth status tracking.

#set_managed

python
def set_managed(self, vals: set[str]) -> None

Record the "managed" (externally-managed) values.

A managed value is neither authenticated nor unauthenticated: cw cannot verify its auth because Claude Code owns it (the "default" profile, ~/.claude). Managed values are excluded from the unauthenticated rendering/interception paths.

#has_auth_status

python
def has_auth_status(self) -> bool

True when auth status tracking has been activated.

#is_authenticated

python
def is_authenticated(self, val: str) -> bool

True when val is in the authenticated set.

#is_managed

python
def is_managed(self, val: str) -> bool

True when val is externally managed (e.g. the vanilla default).

#Segment

A single segment in the TUI bar with options, selection state, and search.

#options

python
def options(self) -> list[str]

Delegate to state.options for the computed option list.

#display_options

python
def display_options(self) -> list[str]

Options visible in the UI: real options + virtual "+" for creatable segments.

#selected_idx

python
def selected_idx(self) -> int

Computed index of selected_value in display_options, or -1 if unselected.

#value

python
def value(self) -> str | None

Return the selected value if it is a real option, or None otherwise.

#filtered_options

python
def filtered_options(self) -> list[str]

Return options filtered by search_buffer using fuzzy matching.

Filters against self.options (not display_options), so "+" is excluded from fuzzy search. This is intentional.

#cycle

python
def cycle(self, direction: int) -> None

Move selection up (+1) or down (-1) through display_options.

The ring has n+1 positions: [None, 0, 1, ..., n-1] where None is the blank/unselected state and n = len(display_options). With wrap=True, cycling continuously rotates through all positions including blank. With wrap=False, blank is reachable from EITHER end of the option list (UP from first OR DOWN from last), but going past blank in either direction stays at blank rather than continuing to the other end.

#is_on_plus

python
def is_on_plus(self) -> bool

True if the current selection is the '+' creation sentinel.

#select_value

python
def select_value(self, val: str) -> bool

Select an option by its string value. Returns True if found.

#_Segment_init_wrapper

python
def _Segment_init_wrapper(self: Segment, *args: Any, options: Any=None, **kwargs: Any) -> None

Translate the options= kwarg to _init_options= for backward compat.

#SegmentBar

Ordered collection of segments with focus tracking and navigation.

#focused

python
def focused(self) -> Segment

Return the currently focused segment, or raise if the bar is empty.

#move_focus

python
def move_focus(self, direction: int) -> None

Move focus left (-1) or right (+1), wrapping around the segment list.

#get_selections

python
def get_selections(self) -> dict[str, str | None]

Return a dict mapping each segment key to its selected value.

#version_sort_key

python
def version_sort_key(version: str) -> list[int]

Split a version string on '.' and convert parts to ints for numeric sorting.

#fetch_npm_versions

python
def fetch_npm_versions(state: dict[str, Any], count: int=15) -> list[str]

Fetch recent Claude Code versions from npm, with 1-hour cache in state.

#_candidate_token_profiles

python
def _candidate_token_profiles(state: dict[str, Any], ws: 'Workspace') -> list[str]

Profile names to try for a token, last-used first.

The last-used profile is the one whose token is most likely to be current, so it is asked first; every other registered profile follows in enumeration order. Profiles carrying no token of their own (the vanilla default among them) are dropped here rather than at the request.

#_models_page_url

python
def _models_page_url(after_id: str | None) -> str

The models endpoint URL for one page of the list.

#_fetch_models_with_token

python
def _fetch_models_with_token(token: str) -> list[dict[str, str]]

Read the whole model list with one token, following pagination.

Raises urllib.error.HTTPError (401 for a rejected token, 429 for a rate-limited account) and the network errors urllib raises; the caller decides what each means.

#fetch_available_models

python
def fetch_available_models(state: dict[str, Any], ws: 'Workspace') -> list[dict[str, str]]

Fetch the models the account may use, with a 1-hour cache in state.

Purely additive discovery: the answer names what the API currently serves, and callers never remove an option because it stopped appearing. Every failure is quiet -- this runs on the discovery thread, where an exception would take the refresh down with no UI to report it -- and falls back to the cached list, however stale, so an offline launch looks exactly like an online one.

Token selection walks the profiles' own stored tokens (last-used first). A 401 (this token cannot list models) or a 429 (this account is rate limited) moves on to the next token; any other HTTP status ends the refresh, and so does a network failure, because the next token would fail the same way.

#_models_to_result

python
def _models_to_result(models: list[dict[str, str]]) -> DiscoveryResult

Turn a fetched model list into discovered values plus release dates.

#_discover_directory_listing

python
def _discover_directory_listing(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Discover options from a directory of files (e.g., installed versions).

#_discover_npm_and_local

python
def _discover_npm_and_local(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Discover versions from npm registry + locally installed files.

#_discover_npm_and_local_cached

python
def _discover_npm_and_local_cached(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Fast path for npm_and_local: use cached npm versions only if warm.

#_discover_anthropic_models

python
def _discover_anthropic_models(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Discover model ids from the Anthropic API (cached, quiet on failure).

#_discover_anthropic_models_cached

python
def _discover_anthropic_models_cached(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Fast path for anthropic_models: the cached list, only while it is warm.

An empty answer costs nothing: every model ever discovered is already in options.json, which feeds the segment's defaults collection, so a cold or stale cache shows the same option list as a warm one.

#_discover_directory_scan

python
def _discover_directory_scan(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Discover directories by scanning parent directories.

Recent dirs from state are used as hints: validated (must exist on disk), emitted first in the result, and pruned back to state (stale entries removed). Static values from options.json are NOT included -- they are handled by SegmentState.defaults via the defaults collection.

#_discover_profiles

python
def _discover_profiles(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Discover Claude Code profiles via ProfileStore enumeration.

Profile identity comes solely from the store (profile dirs and the token each one carries); config_dir is never persisted, so metadata holds only auth-presence fields. A corrupt token entry raises TokenStoreError, which propagates so discovery fails loudly rather than silently omitting profiles.

#_discover_gh_accounts

python
def _discover_gh_accounts(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Discover GitHub accounts from gh CLI auth status.

#_discover_state_field

python
def _discover_state_field(config: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> DiscoveryResult

Discover options by merging state-tracked values with static defaults.

#_parse_static_values

python
def _parse_static_values(config: dict[str, Any]) -> list[str]

The plain string values listed in an options_def entry.

#model_option_requires

python
def model_option_requires() -> dict[str, dict[str, str]]

The model picker's version requirements, derived from one table.

MODEL_MIN_CLI_VERSION in defaults.py is the single place a model's minimum Claude Code version is declared. The pre-launch guard reads it to abort a launch; this turns the same table into the cross-segment constraints :func:evaluate_requires dims options with, so the picker and the guard can never disagree about which binary a model needs.

A [1m] entry is claudewheel's own spelling of a base model with the 1M context window selected, so it inherits the base model's requirement. Models absent from the table are unrestricted.

#run_slow_discovery_via_registry

python
def run_slow_discovery_via_registry(options_def: dict[str, Any], state: dict[str, Any], ws: 'Workspace') -> dict[str, DiscoveryResult]

Run only slow discovery types via the registry.

Mutates state (e.g. fetch_npm_versions writes npm_versions_cache). Callers running this in a background thread should pass a deep copy of the shared state dict and merge results back on the main thread.

#populate_segment_state

python
def populate_segment_state(seg: 'Segment', options_def_entry: dict[str, Any], state: dict[str, Any], ws: 'Workspace', *, skip_slow: bool=True) -> DiscoveryResult | None

Populate a segment's state from discovery and static config.

Looks up the discovery config, calls the registry function (its warm counterpart when the entry is slow and skip_slow is set), and writes the result to seg.state. Returns the result so the caller can persist what was discovered; None when nothing ran.

#_update_auth_from_metadata

python
def _update_auth_from_metadata(seg: 'Segment') -> None

Compute the authenticated and managed sets from segment metadata.

Ternary classification:

  • managed=True -> the value is externally managed (Claude Code owns it,

e.g. the vanilla default). cw cannot verify its auth, so it is neither authenticated NOR unauthenticated -- it goes in the managed set and is excluded from the unauthenticated (dim/intercept) paths.

  • otherwise a value is authenticated if its metadata has has_token=True

or has_credentials=True, else unauthenticated.

Auth tracking is only activated when at least one metadata entry carries any of these fields, keeping the feature invisible to segments that don't use it.

#_defaults_for

python
def _defaults_for(key: str, opt: dict[str, Any]) -> list[str]

The defaults collection for segment key.

Every segment but model takes the list shipped in DEFAULT_OPTIONS. The model list is accumulated instead: options.json holds every model the user has ever been offered -- the shipped seed plus everything discovery has since appended -- and nothing is ever removed from it, so it, not the shipped constant, is what the picker offers. The constant remains the first-run seed and the source the startup sync appends from.

The on-disk list is used unconditionally, empty included: the startup sync in AppConfigStore._migrate is the one mechanism that keeps it populated, and if that ever stops working the honest symptom is a picker with nothing in it -- not a silent switch to a different list.

#build_segment_bar

python
def build_segment_bar(cfg: 'AppConfigStore', *, skip_slow: bool=False) -> SegmentBar

Construct the segment bar from config, applying discovery and last-state restore.

#merge_slow_results

python
def merge_slow_results(bar: SegmentBar, results: dict[str, DiscoveryResult], state: dict[str, Any], options_def: dict[str, Any] | None=None) -> None

Merge background discovery results into the live segment bar.

For each segment with new options in results, update its discovered list via SegmentState, update the installed set, and restore the previous selection (falling back to last_config from state).

When options_def is provided, staleness verify callbacks from the discovery registry are wired through so values that still exist on disk are not prematurely dropped.

#evaluate_requires

python
def evaluate_requires(bar: SegmentBar, locator: 'BinaryLocator | None'=None) -> None

Recompute unavailable sets based on cross-segment requirements.

A requirement on the version segment is evaluated against the effective Claude Code version rather than the raw selection: with nothing selected, the launch runs whatever the claude symlink points at, so that binary's version is what the constraint must be satisfied by. The resolution comes from :func:claudewheel.binaries.effective_cli_version, the same call the pre-launch model-version guard makes, so the picker and the guard read one table through one resolution.

When that resolution answers None -- nothing selected and no symlink to read a version off -- a version requirement restricts nothing. The picker takes the pre-launch guard's disposition: an option is marked unavailable only on a positive determination that the effective version is below the requirement, never on an unknown one. This permissiveness is scoped to the version resolution; a requirement on an ordinary segment with no selection is unsatisfied as before.

locator names the binaries to resolve the symlink against; None uses the default locations. The symlink is resolved at most once per call -- lazily, so a bar whose options carry no version requirement never touches the filesystem -- and never once per option.

#_satisfies_constraint

python
def _satisfies_constraint(value: str | None, constraint: str) -> bool

Check if a value satisfies a version constraint like '>=2.1.110'.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search