6The drill-down picker — lane, class, family, model, and back out of any of them.
10Plan 217 T2/T4. :class:`Navigator` walks the tree
11:mod:`oc_chat.core.lane_tree` describes — lane, free/paid, family, model,
12confirm — and :func:`run_lane_picker` drives it against a presenter and a
15The operator's requirement is reversibility, and it is met STRUCTURALLY
16rather than carefully (P4). Every state is a frozen value, every
17transition returns a NEW value, and the navigator keeps the values it
18came from on a stack. "Back" is therefore not a re-derivation that might
19disagree with what was on screen: it is the identical earlier object,
20restored. Nothing outside the navigator is touched at any point — the
21picker only ever RETURNS a :class:`LaneSelection`, and applying one is
22:mod:`oc_chat.core.lane_switch`'s job. An abandoned traversal is a no-op
23because there was never anything to undo.
27- :class:`PickerState` / :class:`Screen` / :class:`LaneSelection` — the values.
28- :class:`Navigator` — choose / act / back / abandon over a state stack.
29- :func:`render_screen` — ASCII rendering of one screen.
30- :func:`run_lane_picker` — the interactive driver.
31- :func:`lane_options_from_registry` / :func:`live_cache_from_overlay` —
32 join the live registry with the cache the 215/216 machinery wrote.
38 L2 — lifecycle events (picker entered, selection made, abandoned).
39 L3 — semantic details (level transitions, action outcomes).
44- **Nothing mutates until the final confirmation.** The confirm level is
45 never collapsed away, so a complete selection is always an explicit
46 answer. Even then this module only returns a value.
47- **Level one and the confirm level never collapse; the three middle
48 levels do.** Level one IS the picker's purpose, so a single-lane
49 install still sees which lane it has; confirm is P4's point of no
50 return. A middle level with exactly one selectable answer answers
51 itself, and ``back`` steps over the whole collapsed run as one unit,
52 because a level the operator never saw is not a level they can return
54- **Refresh is in-memory and the only network call.** It is reached only
55 by the explicit action, and the fetched list lives in the picker state
56 — it is NOT written back to the overlay. Writing it would be a disk
57 mutation inside a traversal the operator may still abandon, which is
58 exactly what P4 forbids. The persisted cache stays the wizard's to
60- **A cache that does not exist is reported, never silently filled.** The
61 "show all" escape is offered as unselectable with its reason when the
62 lane has no cached list, alongside the refresh that would create one.
63- The single-letter answers ``b`` / ``m`` / ``a`` / ``r`` are reserved at
64 every prompt, so a lane literally named ``b`` is unreachable through the
65 picker (it stays selectable by ``/lane b``). Same trade the plan-216
66 wizard documents for ``q``.
67- ASCII only: every rendered line survives a stock cp1252 console (F4).
68- **A Ctrl+C is not a "back" (plan 220 P3).** With *on_interrupt* wired —
69 the pre-session callers only — an interrupt is handed to that callback
70 and, if it returns, the same screen is re-shown unchanged. Without it
71 the in-session ``/lane`` behaviour is byte-identical to pre-220: a
72 cancel abandons. One reader, two honest meanings, chosen by the caller
73 that knows whether a session exists yet.
74- **One presenter call per screen (plan 220 P2).** Screens are emitted as
75 a single block, because every presenter opens ``show_info`` with a
76 blank line and a screen emitted row by row is a double-spaced screen.
80- oc_diagnostics (structured debug logging via _dbg)
81- oc_chat.core.lane_tree (the pure tree model this walks)
82- oc_chat.core.lane_overlay (lazy; read-only: the cached live model lists)
83- oc_chat.core.defaults (menu column widths from the shipped defaults file)
84- oc_chat.core.client_interface (lazy; ClientInterface for the live model list fetch)
85- oc_chat.core.provider_plugin (lazy; provider registry for the lane rows and the live fetch)
87from __future__
import annotations
89from dataclasses
import dataclass, replace
90from pathlib
import Path
91from typing
import Any, Callable, Mapping, Sequence
93from oc_diagnostics
import _dbg
99_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
105LEVEL_FAMILY =
"family"
107LEVEL_CONFIRM =
"confirm"
109LEVEL_ORDER: tuple[str, ...] = (
110 LEVEL_LANE, LEVEL_CLASS, LEVEL_FAMILY, LEVEL_MODEL, LEVEL_CONFIRM,
114COLLAPSIBLE_LEVELS: frozenset[str] = frozenset({LEVEL_CLASS, LEVEL_FAMILY, LEVEL_MODEL})
120ACTION_SHOW_ALL =
"show_all"
121ACTION_REFRESH =
"refresh"
125ABANDON_ANSWERS: frozenset[str] = frozenset({
"m",
"q"})
130_NAME_W = default(
"lane_picker_name_width")
131_LABEL_W = default(
"lane_picker_label_width")
134@dataclass(frozen=True)
136 """A complete, confirmed answer: which lane, which model."""
142@dataclass(frozen=True)
144 """One answered level. ``auto`` marks a level the collapse rule filled."""
151@dataclass(frozen=True)
153 """The whole traversal as one immutable value.
155 ``live`` holds refreshed model lists as a tuple of pairs rather than a
156 dict so the state stays hashable and obviously copy-free; a traversal
157 refreshes at most a handful of lanes.
160 lanes: tuple[LaneOption, ...]
161 answers: tuple[Answer, ...] = ()
162 show_all: bool =
False
163 live: tuple[tuple[str, tuple[str, ...]], ...] = ()
166 """The key answered at *level*, or ``""``."""
168 if answer.level == level:
173 """The list a refresh fetched for *lane_name* this traversal."""
174 for name, models
in self.
live:
175 if name == lane_name:
180 """The chosen lane's row, or ``None`` before level one is answered."""
184 return next((lane
for lane
in self.
lanes if lane.name == name),
None)
187@dataclass(frozen=True)
189 """One rendered level: what to show and what may be answered."""
193 choices: tuple[Choice, ...]
194 actions: tuple[Choice, ...] = ()
196 can_back: bool =
True
197 can_abandon: bool =
True
204 """The level awaiting an answer, or ``None`` when the walk is complete."""
205 for level
in LEVEL_ORDER:
206 if not state.answer_for(level):
211def full_pool(state: PickerState, option: LaneOption) -> tuple[str, ...]:
212 """The lane's live-discovered list: this traversal's refresh, else the cache."""
213 return state.live_models(option.name)
or option.cached_models
216def _base_pool(state: PickerState, option: LaneOption) -> tuple[str, ...]:
217 """Curated by default; the full live list once "show all" is engaged (P2)."""
223def pool_for(state: PickerState, level: str) -> tuple[str, ...]:
224 """The model ids still in play when *level* is being asked."""
225 option = state.lane_option()
229 if level
in (LEVEL_LANE, LEVEL_CLASS):
231 chosen_class = state.answer_for(LEVEL_CLASS)
233 ids = lane_tree.models_in_class(
234 ids, lane_free=option.free, model_class=chosen_class
236 if level == LEVEL_FAMILY:
238 chosen_family = state.answer_for(LEVEL_FAMILY)
240 ids = lane_tree.models_in_family(ids, chosen_family)
245 out: list[Choice] = []
246 for option
in state.lanes:
248 f
"{option.badge:<5} "
249 f
"verified: {(option.verification or '-'):<7}"
250 f
"{len(option.models)} curated"
256 f
"no key ({option.api_key_env_var}) - set it, or onboard the lane "
257 f
"with [5] Add provider"
262 label=option.display_label
if option.display_label != option.name
else "",
264 selectable=option.selectable,
271 option = state.lane_option()
275 out: list[Choice] = []
276 for name
in lane_tree.model_classes(ids, lane_free=option.free):
277 members = lane_tree.models_in_class(
278 ids, lane_free=option.free, model_class=name
282 label=lane_tree.CLASS_LABELS.get(name, name),
283 detail=f
"{len(members)} model(s)",
291 Choice(key=family, label=
"", detail=f
"{len(members)} model(s)")
292 for family, members
in lane_tree.group_by_family(ids)
302 Choice(key=model_id, label=
"", detail=lane_tree.vendor_detail(model_id))
303 for model_id
in pool_for(state, LEVEL_MODEL)
308 lane = state.answer_for(LEVEL_LANE)
309 model = state.answer_for(LEVEL_MODEL)
314 label=f
"switch to {lane} / {model}",
315 detail=lane_tree.vendor_detail(model),
319_CHOICE_BUILDERS: Mapping[str, Callable[[PickerState], tuple[Choice, ...]]] = {
320 LEVEL_LANE: _lane_choices,
321 LEVEL_CLASS: _class_choices,
322 LEVEL_FAMILY: _family_choices,
323 LEVEL_MODEL: _model_choices,
324 LEVEL_CONFIRM: _confirm_choices,
329 """The choices at the level awaiting an answer."""
333 return _CHOICE_BUILDERS[level](state)
337 """Answer every collapsible level that has exactly one selectable answer."""
341 if level
is None or level
not in COLLAPSIBLE_LEVELS:
343 only = lane_tree.collapse_choices(_CHOICE_BUILDERS[level](state))
347 state, answers=state.answers + (
Answer(level, only.key, auto=
True),)
349 _dbg(
"OC_CHAT.MAIN", func, f
"level {level!r} collapsed to {only.key!r}", 4)
352def advance(state: PickerState, key: str) -> PickerState |
None:
353 """Answer the current level with *key*, or ``None`` when it is not an answer."""
359 (c
for c
in _CHOICE_BUILDERS[level](state)
if c.key == key
and c.selectable),
364 _dbg(
"OC_CHAT.MAIN", func, f
"level {level!r} answered {key!r}", 3)
366 replace(state, answers=state.answers + (
Answer(level, key),))
370def _drop_levels(state: PickerState, levels: frozenset[str]) -> PickerState:
371 """Forget the answers at *levels* (used when the model pool changes)."""
373 state, answers=tuple(a
for a
in state.answers
if a.level
not in levels)
380_POOL_DEPENDENT: frozenset[str] = frozenset({LEVEL_FAMILY, LEVEL_MODEL, LEVEL_CONFIRM})
384 """Open the full live list, keeping the lane (and the class where it fits).
386 ``None`` when there is nothing to open — the lane has no cached list and
387 none has been fetched this traversal (P2: an empty cache is not an
388 implicit licence to reach the network).
390 func =
"apply_show_all"
391 option = state.lane_option()
392 if option
is None or not full_pool(state, option):
394 opened = replace(
_drop_levels(state, _POOL_DEPENDENT), show_all=
True)
397 if state.answer_for(LEVEL_CLASS)
and not pool_for(opened, LEVEL_FAMILY):
399 _dbg(
"OC_CHAT.MAIN", func,
400 f
"lane={option.name} opened {len(full_pool(state, option))} live id(s)", 3)
406 fetcher: Callable[[LaneOption], Sequence[str]],
408 """Fetch the lane's live list into the STATE — never onto disk.
410 Raises whatever *fetcher* raises; the navigator turns that into a
411 reported failure with the state untouched.
413 func =
"apply_refresh"
414 option = state.lane_option()
416 raise ValueError(
"no lane chosen yet")
417 fetched = tuple(str(model_id)
for model_id
in fetcher(option))
418 live = tuple(pair
for pair
in state.live
if pair[0] != option.name)
419 _dbg(
"OC_CHAT.MAIN", func,
420 f
"lane={option.name} refreshed to {len(fetched)} id(s)", 2)
421 return replace(state, live=live + ((option.name, fetched),))
428 """Walk the tree over a stack of states, so ``back`` restores exactly.
430 Not thread-safe and not meant to be: one navigator per picker run, on
431 the main thread, between turns.
436 lanes: Sequence[LaneOption],
438 fetcher: Callable[[LaneOption], Sequence[str]] |
None =
None,
440 func =
"Navigator.__init__"
442 self.
_stack: list[tuple[PickerState, str]] = [
448 _dbg(
"OC_CHAT.MAIN", func, f
"picker opened over {len(lanes)} lane(s)", 2)
454 """The live state. Valid even after abandoning (it is a value)."""
459 """Whether the traversal was given up, by ``back`` or by ``abandon``."""
463 """The model the walk has settled on, or ``""``."""
464 return self.
state.answer_for(LEVEL_MODEL)
467 """The confirmed selection, or ``None`` while incomplete or abandoned."""
471 lane_name=self.
state.answer_for(LEVEL_LANE),
472 model_id=self.
state.answer_for(LEVEL_MODEL),
476 """The screen to render, or ``None`` when abandoned or complete."""
486 choices=_CHOICE_BUILDERS[level](state),
487 actions=self.
_actions(state, level),
491 def _actions(self, state: PickerState, level: str) -> tuple[Choice, ...]:
492 """The pool actions, offered on every screen below the lane level.
494 Offering them only at the model level would strand the operator
495 whenever that level collapsed away, which is precisely the
496 single-model lane where "show me everything" is most wanted.
498 option = state.lane_option()
499 if option
is None or level == LEVEL_LANE:
505 label=f
"showing all {len(live)} live model(s)",
507 reason=
"already showing the full list",
510 stamp = option.models_fetched_at
or "this session"
513 label=f
"show all {len(live)} (cached {stamp})",
521 f
"no cached model list for {option.name}; "
522 f
"'{REFRESH_ANSWER}' fetches one"
528 label=
"refresh the live list",
530 reason=
"no live fetcher is wired into this picker",
534 key=ACTION_REFRESH, label=
"refresh the live list (one network call)"
536 return (show_all, refresh)
541 """Answer the current level. ``False`` leaves the state untouched."""
547 self.
_stack.append((nxt, key))
552 def act(self, action: str) -> bool:
553 """Apply a pool action. ``False`` leaves the state untouched."""
554 func =
"Navigator.act"
562 if offered
is None or not offered.selectable:
563 self.
last_error = offered.reason
if offered
is not None else (
564 f
"unknown action {action!r}"
567 if action == ACTION_SHOW_ALL:
570 self.
last_error =
"there is no live list to open"
575 except Exception
as exc:
577 _dbg(
"OC_CHAT.MAIN", func,
578 f
"EXTERNAL_ERROR: refresh failed: {type(exc).__name__}", 2,
580 self.
last_error = f
"could not refresh: {type(exc).__name__}: {exc}"
582 self.
_stack.append((nxt, action))
588 """Step back one screen. ``False`` means the walk was abandoned.
590 Stepping back off the first level IS abandoning — P4 asks for back
591 at every level, and there is nothing above level one.
593 func =
"Navigator.back"
599 _dbg(
"OC_CHAT.MAIN", func,
"backed out of the first level - abandoned", 2)
601 _, key = self.
_stack.pop()
606 """Give up the whole traversal. Nothing was ever mutated."""
607 func =
"Navigator.abandon"
609 _dbg(
"OC_CHAT.MAIN", func,
"traversal abandoned", 2)
613 """One ASCII line naming where in the tree this screen sits.
615 Plan 220 P4: every screen below level one lists models from ONE lane,
616 so the header names it — ``lane openrouter (free) / z-ai: model``. A
617 list that mixes lanes is level one, where the lane is on every row.
619 lane = state.answer_for(LEVEL_LANE)
620 chosen_class = state.answer_for(LEVEL_CLASS)
621 family = state.answer_for(LEVEL_FAMILY)
622 scope = f
"lane {lane}" if lane
else lane
623 if chosen_class
and chosen_class != lane_tree.CLASS_UNKNOWN:
624 scope = f
"{scope} ({chosen_class})"
625 if family
and family != lane_tree.NO_FAMILY:
626 scope = f
"{scope} / {family}"
627 if level == LEVEL_LANE:
628 return "choose a lane"
629 if level == LEVEL_CLASS:
630 return f
"{scope}: free or paid models?"
631 if level == LEVEL_FAMILY:
632 return f
"{scope}: model family"
633 if level == LEVEL_MODEL:
634 return f
"{scope}: model"
635 return f
"{scope}: confirm"
641def _cell(text: str, width: int) -> str:
642 """*text* padded to *width* — or given ONE separating space if it overflows.
644 Plan 220: a routed model id is routinely longer than its column
645 (``switch to openrouter / z-ai/glm-4.6``), and the bare ``:<`` form
646 then butts the next column straight onto it —
647 ``...glm-4.6vendor: z-ai``. Truncating would hide the id, so the
648 column simply gives way: one space, and the row stays readable.
650 if len(text) < width:
651 return f
"{text:<{width}}"
656 """Render *screen* as ASCII lines for a presenter to show."""
657 lines = [
"", f
" Lane picker - {screen.title}",
" " +
"-" * 60]
658 for index, choice
in enumerate(screen.choices, 1):
659 marker =
">" if choice.key == screen.highlight
else " "
660 body =
_cell(choice.key, _NAME_W) +
_cell(choice.label, _LABEL_W) + choice.detail
661 lines.append(f
" {marker} {index:>2}. {body}".rstrip())
662 if not choice.selectable
and choice.reason:
663 lines.append(f
" {choice.reason}")
664 for action
in screen.actions:
665 letter = SHOW_ALL_ANSWER
if action.key == ACTION_SHOW_ALL
else REFRESH_ANSWER
666 if action.selectable:
667 lines.append(f
" [{letter}] {action.label}")
669 lines.append(f
" [{letter}] {action.label} - {action.reason}")
670 hint = f
" Enter number - '{BACK_ANSWER}' back - 'm' main menu"
672 hint += f
" - Enter repeats {screen.highlight!r}"
678 """The one-line prompt shown under *screen*."""
679 return f
" {screen.level.capitalize()} choice: "
686 path: str | Path |
None =
None,
687) -> tuple[dict[str, tuple[str, ...]], dict[str, str |
None]]:
688 """The cached live lists the 215/216 machinery wrote, by lane name (P2).
690 Degrades to empty on a missing OR malformed overlay. The startup hook
691 ``_load_provider_overlays`` already reports a malformed overlay loudly
692 and is the right place for that; a second fatal reporter here would
693 turn a bad row into a picker that cannot open.
695 func =
"live_cache_from_overlay"
698 target = Path(path)
if path
is not None else DEFAULT_OVERLAY_PATH
700 rows = load_overlay(target)
701 except (OverlayError, OSError)
as exc:
704 _dbg(
"OC_CHAT.MAIN", func,
705 f
"VALIDATION_ERROR: {type(exc).__name__}: {exc}", 3)
707 cached = {row.name: tuple(row.cached_models)
for row
in rows}
708 stamps = {row.name: row.models_fetched_at
for row
in rows}
709 _dbg(
"OC_CHAT.MAIN", func, f
"cache for {len(cached)} overlay lane(s)", 3)
710 return cached, stamps
715 env: Mapping[str, str] |
None =
None,
716 overlay_path: str | Path |
None =
None,
717) -> tuple[LaneOption, ...]:
718 """Level-one rows for every registered provider, cache joined in."""
722 providers = [registry.get(name)
for name
in registry.names()]
723 return lane_tree.lane_options(
724 providers, env=env, cached_models=cached, fetched_at=stamps
729 """Ask the lane's own API for its live model list. THE network call.
731 Goes through the provider's ``fetch_live_models``, which is the same
732 call :meth:`ModelManager.fetch_live` makes, so a lane that works in a
733 session works here. Never called by the test suite.
735 func =
"default_fetcher"
739 provider = registry.get(option.name)
740 client = ClientInterface(provider)
741 models = tuple(str(model_id)
for model_id
in provider.fetch_live_models(client))
742 _dbg(
"OC_CHAT.MAIN", func, f
"lane={option.name} live list = {len(models)}", 2)
749def _read(driver: Any, prompt_text: str, *, interruptible: bool =
False) -> str |
None:
750 """Read one answer. ``None`` on cancel, EOF, or a non-interactive stdin.
752 With *interruptible* set (plan 220 P3 — the picker reached from the
753 opening menu, where no session exists yet) a Ctrl+C is RAISED instead
754 of folded into the abandon path, so the caller can answer it with the
755 quit confirmation and then re-show this very screen. A driver reports a
756 cancelled menu prompt by RETURNING ``None`` rather than raising, so
757 that is the interrupt too: coalescing it with "no answer" is exactly
758 the swallow TUI-02 finding 3 describes.
760 Without it the pre-220 behaviour is byte-identical, which is what keeps
761 the in-session ``/lane`` semantics ("cancel abandons, nothing else")
765 if driver
is not None:
766 raw = driver.read_user_input(
None, prompt_text=prompt_text)
768 raw = input(prompt_text)
769 except KeyboardInterrupt:
773 except (EOFError, OSError):
777 raise KeyboardInterrupt
783 """Map a typed answer to a choice key, or ``""`` when it matches none.
785 Three forms, in order: the displayed index, the key typed in full, and
786 an UNAMBIGUOUS case-insensitive prefix of one. The prefix form is what
787 makes ``y`` answer the confirm screen — the obvious keystroke there —
788 and it lets a long model id be picked by its distinctive head. It
789 resolves only when exactly one selectable key matches, so it can never
790 silently pick the wrong model.
794 (c
for c
in screen.choices
if c.key == screen.highlight
and c.selectable),
797 if highlighted
is not None:
798 return highlighted.key
799 first = next((c
for c
in screen.choices
if c.selectable),
None)
800 return first.key
if first
is not None else ""
803 if 1 <= index <= len(screen.choices):
804 return screen.choices[index - 1].key
806 exact = next((c.key
for c
in screen.choices
if c.key == raw),
"")
809 lowered = raw.lower()
811 c.key
for c
in screen.choices
812 if c.selectable
and c.key.lower().startswith(lowered)
814 return prefixed[0]
if len(prefixed) == 1
else ""
820 prompt_driver: Any =
None,
821 lanes: Sequence[LaneOption] |
None =
None,
822 env: Mapping[str, str] |
None =
None,
823 fetcher: Callable[[LaneOption], Sequence[str]] |
None =
None,
824 overlay_path: str | Path |
None =
None,
825 on_interrupt: Callable[[],
None] |
None =
None,
826) -> LaneSelection |
None:
827 """Drive the picker to a confirmed selection, or ``None``.
829 ``None`` means "nothing was chosen" in every case — stepped back off
830 the first level, escaped to the main menu, or a closed stdin. In none
831 of them has anything been mutated: applying a selection is
832 :func:`oc_chat.core.lane_switch.switch_lane`'s job, and it is only
833 ever reached with a returned value.
835 *on_interrupt* (plan 220 P3) is supplied ONLY by the pre-session
836 callers — the opening menu and its new-session path — where a Ctrl+C
837 must interrupt and confirm rather than quietly abandon. It is called
838 with the traversal untouched and either raises (the operator confirmed
839 the quit) or returns, in which case the SAME screen is re-shown:
840 "back" already means back, so an interrupt must not silently mean it
841 too. Omitted, the picker keeps its pre-220 in-session behaviour
844 func =
"run_lane_picker"
848 fetcher = default_fetcher
850 presenter.show_info(
" No provider lanes are registered.")
853 navigator =
Navigator(lanes, fetcher=fetcher)
855 screen = navigator.screen()
864 presenter.show_info(block)
868 interruptible=on_interrupt
is not None,
870 except KeyboardInterrupt:
880 if answer
in ABANDON_ANSWERS:
883 if answer == BACK_ANSWER:
884 if not navigator.back():
887 if answer
in (SHOW_ALL_ANSWER, REFRESH_ANSWER):
888 action = ACTION_SHOW_ALL
if answer == SHOW_ALL_ANSWER
else ACTION_REFRESH
889 if not navigator.act(action):
890 presenter.show_info(f
" {navigator.last_error}")
893 if not key
or not navigator.choose(key):
894 refused = next((c
for c
in screen.choices
if c.key == key),
None)
895 if refused
is not None and refused.reason:
896 presenter.show_info(f
" {refused.reason}")
898 presenter.show_info(f
" Not a choice here: {raw!r}")
900 selection = navigator.selection()
901 _dbg(
"OC_CHAT.MAIN", func,
902 f
"picker closed: {selection if selection else 'no selection'}", 2)
912 "COLLAPSIBLE_LEVELS",
934 "lane_options_from_registry",
935 "live_cache_from_overlay",
LaneSelection|None selection(self)
bool choose(self, str key)
None __init__(self, Sequence[LaneOption] lanes, *, Callable[[LaneOption], Sequence[str]]|None fetcher=None)
bool act(self, str action)
tuple[Choice,...] _actions(self, PickerState state, str level)
LaneOption|None lane_option(self)
str answer_for(self, str level)
tuple[str,...] live_models(self, str lane_name)
tuple[Choice,...] _family_choices(PickerState state)
PickerState _drop_levels(PickerState state, frozenset[str] levels)
PickerState|None advance(PickerState state, str key)
str _cell(str text, int width)
tuple[str,...] pool_for(PickerState state, str level)
tuple[LaneOption,...] lane_options_from_registry(*, Mapping[str, str]|None env=None, str|Path|None overlay_path=None)
tuple[Choice,...] _confirm_choices(PickerState state)
tuple[dict[str, tuple[str,...]], dict[str, str|None]] live_cache_from_overlay(str|Path|None path=None)
str prompt_for(Screen screen)
str _resolve_answer(Screen screen, str raw)
PickerState collapse(PickerState state)
list[str] render_screen(Screen screen)
str _title_for(PickerState state, str level)
tuple[str,...] default_fetcher(LaneOption option)
tuple[str,...] full_pool(PickerState state, LaneOption option)
LaneSelection|None run_lane_picker(Any presenter, *, Any prompt_driver=None, Sequence[LaneOption]|None lanes=None, Mapping[str, str]|None env=None, Callable[[LaneOption], Sequence[str]]|None fetcher=None, str|Path|None overlay_path=None, Callable[[], None]|None on_interrupt=None)
str|None current_level(PickerState state)
tuple[Choice,...] _model_choices(PickerState state)
tuple[str,...] _base_pool(PickerState state, LaneOption option)
tuple[Choice,...] _lane_choices(PickerState state)
str|None _read(Any driver, str prompt_text, *, bool interruptible=False)
PickerState|None apply_show_all(PickerState state)
PickerState apply_refresh(PickerState state, Callable[[LaneOption], Sequence[str]] fetcher)
tuple[Choice,...] _class_choices(PickerState state)
tuple[Choice,...] choices_for(PickerState state)