induscode 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- induscode/__init__.py +56 -0
- induscode/addons/__init__.py +176 -0
- induscode/addons/contract.py +923 -0
- induscode/addons/dispatch/__init__.py +43 -0
- induscode/addons/dispatch/event_dispatcher.py +348 -0
- induscode/addons/dispatch/tool_interceptor.py +349 -0
- induscode/addons/host.py +469 -0
- induscode/addons/loader.py +314 -0
- induscode/addons/manifest.py +232 -0
- induscode/addons/surface.py +199 -0
- induscode/boot/__init__.py +108 -0
- induscode/boot/auth_vault.py +323 -0
- induscode/boot/boot.py +210 -0
- induscode/boot/contract.py +223 -0
- induscode/boot/invocation.py +117 -0
- induscode/boot/runners/__init__.py +42 -0
- induscode/boot/runners/link_runner.py +82 -0
- induscode/boot/runners/oneshot_runner.py +85 -0
- induscode/boot/runners/registry.py +46 -0
- induscode/boot/runners/repl_runner.py +340 -0
- induscode/boot/runners/session.py +549 -0
- induscode/boot/stages.py +198 -0
- induscode/boot/upgrade/__init__.py +36 -0
- induscode/boot/upgrade/apply.py +125 -0
- induscode/boot/upgrade/upgrades.py +136 -0
- induscode/briefing/__init__.py +115 -0
- induscode/briefing/compose.py +414 -0
- induscode/briefing/contract.py +528 -0
- induscode/briefing/macros.py +721 -0
- induscode/briefing/skills.py +417 -0
- induscode/capability_deck/__init__.py +233 -0
- induscode/capability_deck/bridge_ledger/__init__.py +66 -0
- induscode/capability_deck/bridge_ledger/key.py +181 -0
- induscode/capability_deck/bridge_ledger/ledger.py +276 -0
- induscode/capability_deck/bridge_ledger/network.py +336 -0
- induscode/capability_deck/builtin_bridge.py +358 -0
- induscode/capability_deck/cards/__init__.py +116 -0
- induscode/capability_deck/cards/bg_process.py +482 -0
- induscode/capability_deck/cards/memory.py +226 -0
- induscode/capability_deck/cards/saas.py +280 -0
- induscode/capability_deck/cards/task.py +256 -0
- induscode/capability_deck/cards/todo.py +312 -0
- induscode/capability_deck/contract.py +450 -0
- induscode/capability_deck/manifest.py +126 -0
- induscode/capability_deck/provision.py +217 -0
- induscode/channels/__init__.py +146 -0
- induscode/channels/contract.py +585 -0
- induscode/channels/framer.py +132 -0
- induscode/channels/link/__init__.py +50 -0
- induscode/channels/link/dialog.py +246 -0
- induscode/channels/link/driver.py +308 -0
- induscode/channels/link/server.py +217 -0
- induscode/channels/oneshot.py +178 -0
- induscode/channels/ops.py +140 -0
- induscode/channels/session_ops.py +172 -0
- induscode/conductor/__init__.py +240 -0
- induscode/conductor/catalog.py +309 -0
- induscode/conductor/conductor.py +1084 -0
- induscode/conductor/contract.py +1035 -0
- induscode/conductor/matcher.py +291 -0
- induscode/conductor/serialize.py +575 -0
- induscode/conductor/signal_hub.py +382 -0
- induscode/conductor/skill_parse.py +294 -0
- induscode/conductor/transcript_store.py +449 -0
- induscode/console/__init__.py +236 -0
- induscode/console/app.py +1677 -0
- induscode/console/components/__init__.py +62 -0
- induscode/console/components/banner.py +499 -0
- induscode/console/components/banner_sweep.py +188 -0
- induscode/console/components/emblem.py +181 -0
- induscode/console/components/status_bar.py +102 -0
- induscode/console/contract.py +836 -0
- induscode/console/input/__init__.py +107 -0
- induscode/console/input/chord.py +197 -0
- induscode/console/input/dir_reader.py +113 -0
- induscode/console/input/intents.py +258 -0
- induscode/console/input/providers.py +469 -0
- induscode/console/mount.py +137 -0
- induscode/console/overlays/__init__.py +94 -0
- induscode/console/overlays/auth.py +503 -0
- induscode/console/overlays/pickers.py +526 -0
- induscode/console/overlays/router.py +129 -0
- induscode/console/overlays/sessions.py +232 -0
- induscode/console/reducer.py +145 -0
- induscode/console/resume_picker.py +156 -0
- induscode/console/slash_commands/__init__.py +78 -0
- induscode/console/slash_commands/builtins.py +254 -0
- induscode/console/slash_commands/dynamic.py +217 -0
- induscode/console/slash_commands/integrations.py +949 -0
- induscode/console/slash_commands/transcript.py +404 -0
- induscode/console/slash_commands/workbench.py +430 -0
- induscode/console/startup.py +434 -0
- induscode/console/theme/__init__.py +44 -0
- induscode/console/theme/adapter.py +168 -0
- induscode/console/theme/palette.py +128 -0
- induscode/console/theme/resolve.py +123 -0
- induscode/console/theme/tokens.py +185 -0
- induscode/console_slash/__init__.py +111 -0
- induscode/console_slash/contract.py +185 -0
- induscode/console_slash/registry.py +140 -0
- induscode/console_slash/resolve.py +194 -0
- induscode/console_slash/shared.py +172 -0
- induscode/entry.py +108 -0
- induscode/insight/__init__.py +153 -0
- induscode/insight/collector.py +73 -0
- induscode/insight/replay.py +305 -0
- induscode/insight/wrapper.py +1115 -0
- induscode/kit/__init__.py +82 -0
- induscode/kit/clipboard_image.py +215 -0
- induscode/kit/external_editor.py +120 -0
- induscode/kit/image.py +188 -0
- induscode/kit/shell.py +89 -0
- induscode/kit/tool_fetch.py +288 -0
- induscode/launch/__init__.py +224 -0
- induscode/launch/catalog.py +310 -0
- induscode/launch/contract.py +569 -0
- induscode/launch/credentials.py +852 -0
- induscode/launch/invocation/__init__.py +39 -0
- induscode/launch/invocation/attachments.py +281 -0
- induscode/launch/invocation/flags.py +210 -0
- induscode/launch/invocation/read.py +369 -0
- induscode/launch/invocation/usage.py +110 -0
- induscode/launch/oauth.py +808 -0
- induscode/launch/packages.py +299 -0
- induscode/launch/pickers.py +291 -0
- induscode/py.typed +0 -0
- induscode/runtime_bridge/__init__.py +166 -0
- induscode/runtime_bridge/bridges/__init__.py +66 -0
- induscode/runtime_bridge/bridges/_drive.py +268 -0
- induscode/runtime_bridge/bridges/builtins.py +177 -0
- induscode/runtime_bridge/bridges/claude_cli.py +198 -0
- induscode/runtime_bridge/bridges/codex_cli.py +203 -0
- induscode/runtime_bridge/bridges/indusagi_cli.py +217 -0
- induscode/runtime_bridge/broker.py +397 -0
- induscode/runtime_bridge/contract.py +734 -0
- induscode/runtime_bridge/sink.py +351 -0
- induscode/sessions/__init__.py +25 -0
- induscode/sessions/contract.py +119 -0
- induscode/sessions/library.py +350 -0
- induscode/settings/__init__.py +47 -0
- induscode/settings/contract.py +313 -0
- induscode/settings/manager.py +268 -0
- induscode/transcript_export/__init__.py +109 -0
- induscode/transcript_export/contract.py +522 -0
- induscode/transcript_export/publish.py +455 -0
- induscode/transcript_export/sgr.py +566 -0
- induscode/transcript_export/template.py +319 -0
- induscode/transcript_export/theme_bridge.py +325 -0
- induscode/window_budget/__init__.py +76 -0
- induscode/window_budget/budget/__init__.py +26 -0
- induscode/window_budget/budget/estimate.py +273 -0
- induscode/window_budget/budget/gate.py +60 -0
- induscode/window_budget/budget/slice.py +145 -0
- induscode/window_budget/condenser.py +170 -0
- induscode/window_budget/contract.py +329 -0
- induscode/window_budget/summarize/__init__.py +33 -0
- induscode/window_budget/summarize/condense.py +212 -0
- induscode/window_budget/summarize/prompt.py +241 -0
- induscode/workspace/__init__.py +30 -0
- induscode/workspace/brand.py +96 -0
- induscode/workspace/locator.py +269 -0
- induscode-0.1.0.dist-info/METADATA +97 -0
- induscode-0.1.0.dist-info/RECORD +167 -0
- induscode-0.1.0.dist-info/WHEEL +4 -0
- induscode-0.1.0.dist-info/entry_points.txt +3 -0
- induscode-0.1.0.dist-info/licenses/CREDITS.md +22 -0
- induscode-0.1.0.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Model catalog printer — :func:`print_model_catalog`.
|
|
2
|
+
|
|
3
|
+
Renders the available models as a padded text table. The data comes from the
|
|
4
|
+
conductor's :class:`~induscode.conductor.ModelCatalog` (itself a normalized
|
|
5
|
+
view over the framework :func:`indusagi.ai.get_providers` /
|
|
6
|
+
:func:`indusagi.ai.get_models`); this module re-derives the rows it needs,
|
|
7
|
+
applies a plain case-insensitive :class:`~.contract.CatalogFilter`, sorts by
|
|
8
|
+
provider then model id, and lays the result out as aligned columns with a
|
|
9
|
+
Title-Case header and a rule row beneath it.
|
|
10
|
+
|
|
11
|
+
Filtering is a literal substring test — no fuzzy matcher, no external
|
|
12
|
+
ranking. The column set (provider, model, context window, max output,
|
|
13
|
+
thinking, images) is the established model-table shape; only the headers and
|
|
14
|
+
ordering are this module's own.
|
|
15
|
+
|
|
16
|
+
Port note: the TS module sat on the framework ``ModelRegistry``; the port
|
|
17
|
+
plan routes the printer through the conductor catalog so there is exactly one
|
|
18
|
+
catalog path in the app (plan §3 "Model catalog"). The injectable
|
|
19
|
+
:class:`CatalogModelSource` seam keeps the TS test shape: a test hands in a
|
|
20
|
+
plain provider→models map and the layout is exercised deterministically.
|
|
21
|
+
|
|
22
|
+
(Port of TS ``src/launch/catalog.ts``.)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import math
|
|
28
|
+
import sys
|
|
29
|
+
from collections.abc import Mapping, Sequence
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from typing import Any, Final, Protocol
|
|
32
|
+
|
|
33
|
+
from ..conductor import ModelCatalog
|
|
34
|
+
from .contract import CatalogFilter
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"CatalogIo",
|
|
38
|
+
"CatalogModelSource",
|
|
39
|
+
"catalog_source",
|
|
40
|
+
"default_catalog_io",
|
|
41
|
+
"print_model_catalog",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Output seam
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CatalogIo(Protocol):
|
|
51
|
+
"""The minimal sink the catalog prints onto. A test captures the lines
|
|
52
|
+
into a list; production passes a writer over stdout."""
|
|
53
|
+
|
|
54
|
+
def print(self, line: str) -> None:
|
|
55
|
+
"""Emit one line of table text."""
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class _DefaultCatalogIo:
|
|
60
|
+
"""The default :class:`CatalogIo` backed by stdout."""
|
|
61
|
+
|
|
62
|
+
def print(self, line: str) -> None:
|
|
63
|
+
sys.stdout.write(line if line.endswith("\n") else line + "\n")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def default_catalog_io() -> CatalogIo:
|
|
67
|
+
"""Build the live stdout-backed :class:`CatalogIo`."""
|
|
68
|
+
return _DefaultCatalogIo()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Row projection
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True, slots=True)
|
|
77
|
+
class _CatalogRow:
|
|
78
|
+
"""One normalized table row, derived from a framework model record."""
|
|
79
|
+
|
|
80
|
+
provider: str
|
|
81
|
+
model_id: str
|
|
82
|
+
context_window: int
|
|
83
|
+
max_output: int
|
|
84
|
+
thinking: bool
|
|
85
|
+
images: bool
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
#: The table column headers, in render order.
|
|
89
|
+
_HEADERS: Final[tuple[str, ...]] = (
|
|
90
|
+
"Provider",
|
|
91
|
+
"Model",
|
|
92
|
+
"Context",
|
|
93
|
+
"Max Output",
|
|
94
|
+
"Thinking",
|
|
95
|
+
"Images",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
#: Sentinel marking "field not present at all" on a raw record.
|
|
99
|
+
_ABSENT: Final[Any] = object()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _field_of(raw: Any, name: str) -> Any:
|
|
103
|
+
"""Read ``name`` off a raw model record — mapping key or dataclass
|
|
104
|
+
attribute — returning the absent sentinel when the field does not exist.
|
|
105
|
+
Mirrors the conductor catalog's tolerant accessor so test fakes can be
|
|
106
|
+
plain mappings."""
|
|
107
|
+
if isinstance(raw, Mapping):
|
|
108
|
+
return raw.get(name, _ABSENT)
|
|
109
|
+
return getattr(raw, name, _ABSENT)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _as_int(value: Any) -> int:
|
|
113
|
+
"""A number field as an int, 0 for anything non-numeric."""
|
|
114
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
115
|
+
return 0
|
|
116
|
+
return int(value)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _to_row(model: Any) -> _CatalogRow:
|
|
120
|
+
"""Project a framework model record down to a :class:`_CatalogRow`."""
|
|
121
|
+
raw_inputs = _field_of(model, "input")
|
|
122
|
+
inputs = (
|
|
123
|
+
list(raw_inputs)
|
|
124
|
+
if isinstance(raw_inputs, Sequence) and not isinstance(raw_inputs, (str, bytes))
|
|
125
|
+
else []
|
|
126
|
+
)
|
|
127
|
+
return _CatalogRow(
|
|
128
|
+
provider=str(_field_of(model, "provider")),
|
|
129
|
+
model_id=str(_field_of(model, "id")),
|
|
130
|
+
context_window=_as_int(_field_of(model, "contextWindow")),
|
|
131
|
+
max_output=_as_int(_field_of(model, "maxTokens")),
|
|
132
|
+
thinking=_field_of(model, "reasoning") is True,
|
|
133
|
+
images="image" in inputs,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
# Filtering & sorting
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _filter_rows(
|
|
143
|
+
rows: Sequence[_CatalogRow], filter: CatalogFilter
|
|
144
|
+
) -> list[_CatalogRow]:
|
|
145
|
+
"""Keep the rows that satisfy every set field of ``filter``. An absent
|
|
146
|
+
field matches everything; ``search`` is a case-insensitive substring test
|
|
147
|
+
over ``"provider/modelId"``."""
|
|
148
|
+
provider_needle = (
|
|
149
|
+
str(filter.provider).lower() if filter.provider is not None else None
|
|
150
|
+
)
|
|
151
|
+
search_needle = filter.search.strip().lower() if filter.search is not None else None
|
|
152
|
+
selected: list[_CatalogRow] = []
|
|
153
|
+
for row in rows:
|
|
154
|
+
if provider_needle is not None and row.provider.lower() != provider_needle:
|
|
155
|
+
continue
|
|
156
|
+
if filter.thinking_only is True and not row.thinking:
|
|
157
|
+
continue
|
|
158
|
+
if filter.images_only is True and not row.images:
|
|
159
|
+
continue
|
|
160
|
+
if search_needle:
|
|
161
|
+
haystack = f"{row.provider}/{row.model_id}".lower()
|
|
162
|
+
if search_needle not in haystack:
|
|
163
|
+
continue
|
|
164
|
+
selected.append(row)
|
|
165
|
+
return selected
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _sort_rows(rows: Sequence[_CatalogRow]) -> list[_CatalogRow]:
|
|
169
|
+
"""Order rows by provider, then by model id, both ascending."""
|
|
170
|
+
return sorted(rows, key=lambda row: (row.provider, row.model_id))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Formatting
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _format_token_count(tokens: int) -> str:
|
|
179
|
+
"""Format a token count compactly: ``128K`` / ``1.0M`` for large windows,
|
|
180
|
+
the bare number otherwise. A dash stands in for an unknown (zero)
|
|
181
|
+
count."""
|
|
182
|
+
if tokens <= 0:
|
|
183
|
+
return "-"
|
|
184
|
+
if tokens >= 1_000_000:
|
|
185
|
+
millions = tokens / 1_000_000
|
|
186
|
+
rendered = f"{millions:.0f}" if millions % 1 == 0 else f"{millions:.1f}"
|
|
187
|
+
return f"{rendered}M"
|
|
188
|
+
if tokens >= 1_000:
|
|
189
|
+
# Math.round semantics (half away from zero for positives).
|
|
190
|
+
return f"{math.floor(tokens / 1_000 + 0.5)}K"
|
|
191
|
+
return str(tokens)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _cells(row: _CatalogRow) -> tuple[str, ...]:
|
|
195
|
+
"""Render one row to its six string cells, in header order."""
|
|
196
|
+
return (
|
|
197
|
+
row.provider,
|
|
198
|
+
row.model_id,
|
|
199
|
+
_format_token_count(row.context_window),
|
|
200
|
+
_format_token_count(row.max_output),
|
|
201
|
+
"yes" if row.thinking else "no",
|
|
202
|
+
"yes" if row.images else "no",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _pad(value: str, width: int) -> str:
|
|
207
|
+
"""Pad a cell to a fixed width on the right."""
|
|
208
|
+
return value if len(value) >= width else value + " " * (width - len(value))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _layout(rows: Sequence[_CatalogRow]) -> list[str]:
|
|
212
|
+
"""Lay out a header plus body as an aligned table: compute each column
|
|
213
|
+
width from the widest cell, emit the Title-Case header, a rule row of
|
|
214
|
+
dashes, then every body row. Pure; returns the lines for the caller to
|
|
215
|
+
print."""
|
|
216
|
+
body = [_cells(row) for row in rows]
|
|
217
|
+
widths = [
|
|
218
|
+
max(len(header), *(len(row[col]) for row in body)) if body else len(header)
|
|
219
|
+
for col, header in enumerate(_HEADERS)
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
def render(cells: Sequence[str]) -> str:
|
|
223
|
+
return " ".join(_pad(cell, widths[col]) for col, cell in enumerate(cells)).rstrip()
|
|
224
|
+
|
|
225
|
+
rule = " ".join("-" * width for width in widths)
|
|
226
|
+
return [render(_HEADERS), rule, *(render(row) for row in body)]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
# Data source
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class CatalogModelSource(Protocol):
|
|
235
|
+
"""The seam the catalog sources its raw models from. Defaults to the
|
|
236
|
+
conductor :class:`~induscode.conductor.ModelCatalog`; tests inject a
|
|
237
|
+
stand-in to drive the layout deterministically."""
|
|
238
|
+
|
|
239
|
+
def providers(self) -> Sequence[str]:
|
|
240
|
+
"""Enumerate the providers to scan."""
|
|
241
|
+
...
|
|
242
|
+
|
|
243
|
+
def models(self, provider: str) -> Sequence[Any]:
|
|
244
|
+
"""Pull every model for one provider."""
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class _ModelCatalogSource:
|
|
249
|
+
"""A :class:`CatalogModelSource` over a conductor
|
|
250
|
+
:class:`~induscode.conductor.ModelCatalog`: providers come from the
|
|
251
|
+
catalog's grouping and each card contributes its retained framework model
|
|
252
|
+
record."""
|
|
253
|
+
|
|
254
|
+
def __init__(self, catalog: ModelCatalog) -> None:
|
|
255
|
+
self._catalog = catalog
|
|
256
|
+
|
|
257
|
+
def providers(self) -> Sequence[str]:
|
|
258
|
+
return self._catalog.providers()
|
|
259
|
+
|
|
260
|
+
def models(self, provider: str) -> Sequence[Any]:
|
|
261
|
+
return [card.model for card in self._catalog.by_provider(provider)]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def catalog_source(catalog: ModelCatalog | None = None) -> CatalogModelSource:
|
|
265
|
+
"""Build a :class:`CatalogModelSource` over a conductor model catalog
|
|
266
|
+
(a fresh one over the live framework registry when none is given)."""
|
|
267
|
+
return _ModelCatalogSource(catalog if catalog is not None else ModelCatalog())
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
# Public entry
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def print_model_catalog(
|
|
276
|
+
io: CatalogIo | None = None,
|
|
277
|
+
filter: CatalogFilter | None = None,
|
|
278
|
+
source: CatalogModelSource | None = None,
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Print the model catalog table.
|
|
281
|
+
|
|
282
|
+
Pulls every model from the source, applies ``filter``, sorts, and writes
|
|
283
|
+
the aligned table through ``io``. When the filter matches nothing, a
|
|
284
|
+
single explanatory line is printed instead of an empty table.
|
|
285
|
+
|
|
286
|
+
:param io: the line sink (defaults to stdout)
|
|
287
|
+
:param filter: optional provider / capability / substring narrowing
|
|
288
|
+
:param source: optional model source (defaults to the conductor catalog
|
|
289
|
+
over the live framework registry)
|
|
290
|
+
"""
|
|
291
|
+
live_io = io if io is not None else default_catalog_io()
|
|
292
|
+
live_filter = filter if filter is not None else CatalogFilter()
|
|
293
|
+
live_source = source if source is not None else catalog_source()
|
|
294
|
+
|
|
295
|
+
all_rows: list[_CatalogRow] = []
|
|
296
|
+
for provider in live_source.providers():
|
|
297
|
+
try:
|
|
298
|
+
models = live_source.models(str(provider))
|
|
299
|
+
except Exception:
|
|
300
|
+
continue
|
|
301
|
+
for model in models:
|
|
302
|
+
all_rows.append(_to_row(model))
|
|
303
|
+
|
|
304
|
+
selected = _sort_rows(_filter_rows(all_rows, live_filter))
|
|
305
|
+
if not selected:
|
|
306
|
+
live_io.print("No models match the given filter.")
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
for line in _layout(selected):
|
|
310
|
+
live_io.print(line)
|