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,569 @@
|
|
|
1
|
+
"""Launch contract — the FROZEN type surface of the command-line layer.
|
|
2
|
+
|
|
3
|
+
This module is the single typed seam between a raw ``argv`` and a fully
|
|
4
|
+
configured run. It owns the *application* command line: the declarative flag
|
|
5
|
+
table vocabulary, the parsed :class:`Invocation` those flags fold into, the
|
|
6
|
+
gathered ``@file`` :class:`Attachments`, and the typed :class:`CredentialFault`
|
|
7
|
+
the sign-in surface raises. It declares *only* shapes plus a few inert, pure
|
|
8
|
+
helpers — no parsing, no terminal I/O, no widgets. The reader
|
|
9
|
+
(:func:`~.invocation.read_invocation`), the usage renderer
|
|
10
|
+
(:func:`~.invocation.render_usage`), the attachment gatherer
|
|
11
|
+
(:func:`~.invocation.gather_attachments`), the model-catalog printer
|
|
12
|
+
(:func:`~.catalog.print_model_catalog`), the resume picker
|
|
13
|
+
(:func:`~.pickers.pick_resume_target`), the settings browser
|
|
14
|
+
(:func:`~.pickers.browse_settings`), and the credential command
|
|
15
|
+
(:func:`~.credentials.run_credential_command`) are each written against the
|
|
16
|
+
names declared here, so the file is intentionally small, append-mostly, and
|
|
17
|
+
stable.
|
|
18
|
+
|
|
19
|
+
Design stance (TS ``src/launch/contract.ts``, ported):
|
|
20
|
+
|
|
21
|
+
- There is exactly **one declarative flag table**
|
|
22
|
+
(:data:`~.invocation.flags.FLAG_SPECS`). The reader walks it to bind tokens;
|
|
23
|
+
the usage renderer generates the help text *from the same table*. Help and
|
|
24
|
+
parsing cannot drift, because there is no second hand-maintained help string
|
|
25
|
+
to drift against.
|
|
26
|
+
- :class:`Invocation` is a superset of the boot-layer minimal invocation: it
|
|
27
|
+
keeps ``mode`` / ``prompt`` / ``flags`` / ``positionals`` and adds the
|
|
28
|
+
resolved, strongly-typed launch fields the runtime reads.
|
|
29
|
+
- Failures are typed unions (:class:`CredentialFault`,
|
|
30
|
+
:class:`~.invocation.attachments.AttachmentError`), never string sentinels.
|
|
31
|
+
- The credential vault (:class:`AuthVault`) is an app-owned **Protocol** here:
|
|
32
|
+
the framework publishes no credential-store type, and the concrete
|
|
33
|
+
multi-account disk vault lands with the boot layer. The credential command
|
|
34
|
+
compiles and is unit-tested against an in-memory stand-in.
|
|
35
|
+
|
|
36
|
+
Port notes
|
|
37
|
+
----------
|
|
38
|
+
- TS optional-absent fields become explicit ``None`` defaults; TS readonly
|
|
39
|
+
arrays become tuples on the frozen dataclasses.
|
|
40
|
+
- ``OAuthCredentials`` was imported from ``indusagi/ai`` in TS. The Python
|
|
41
|
+
framework has no provider-object OAuth surface (its primitives live in
|
|
42
|
+
:mod:`indusagi.llmgateway.credentials.oauth` and speak ``OAuthTokens``), so
|
|
43
|
+
the credential shape the *vault* stores is declared here, app-owned, with
|
|
44
|
+
the TS field names (``access`` / ``refresh`` / ``expires``). The launch
|
|
45
|
+
OAuth adapter (:mod:`.oauth`) maps framework tokens into this shape.
|
|
46
|
+
- Field names are the mechanical snake_case renames of the TS members
|
|
47
|
+
(``appendSystem`` → ``append_system``, ``noTools`` → ``no_tools``); the
|
|
48
|
+
loose flag bag keeps the TS canonical *flag-name* keys verbatim
|
|
49
|
+
(``"append-system"``, ``"no-tools"``).
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
55
|
+
from dataclasses import dataclass
|
|
56
|
+
from typing import Any, Final, Literal, Protocol, TypeAlias
|
|
57
|
+
|
|
58
|
+
from indusagi.agent import SessionInfo
|
|
59
|
+
from indusagi.ai import ImageContent, ThinkingLevel
|
|
60
|
+
from indusagi.shell_app import Settings
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"AttachmentOptions",
|
|
64
|
+
"Attachments",
|
|
65
|
+
"AuthVault",
|
|
66
|
+
"CatalogFilter",
|
|
67
|
+
"CredentialFault",
|
|
68
|
+
"CredentialFaultKind",
|
|
69
|
+
"CredentialVerb",
|
|
70
|
+
"FlagDefault",
|
|
71
|
+
"FlagKind",
|
|
72
|
+
"FlagSpec",
|
|
73
|
+
"FlagValue",
|
|
74
|
+
"ImageContent",
|
|
75
|
+
"Invocation",
|
|
76
|
+
"OAuthCredentials",
|
|
77
|
+
"OutputMode",
|
|
78
|
+
"ProviderEntry",
|
|
79
|
+
"ResumeFault",
|
|
80
|
+
"ResumeRef",
|
|
81
|
+
"SessionLoader",
|
|
82
|
+
"Settings",
|
|
83
|
+
"SettingsBrowseOptions",
|
|
84
|
+
"THINKING_EFFORTS",
|
|
85
|
+
"TOOL_NAMES",
|
|
86
|
+
"ThinkingEffort",
|
|
87
|
+
"ThinkingLevel",
|
|
88
|
+
"ToolName",
|
|
89
|
+
"credential_fault",
|
|
90
|
+
"is_output_mode",
|
|
91
|
+
"is_thinking_effort",
|
|
92
|
+
"is_tool_name",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Re-exported framework vocabulary
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
#: The launch-local alias for the framework session descriptor surfaced by the
|
|
101
|
+
#: resume picker (TS aliased ``SessionInfo as ResumeRef``).
|
|
102
|
+
ResumeRef: TypeAlias = SessionInfo
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Output modes
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
#: The three terminal output modes the launch layer can resolve to. These map
|
|
110
|
+
#: one-to-one onto the boot runner ids the orchestrator dispatches on:
|
|
111
|
+
#:
|
|
112
|
+
#: - ``text`` — interactive terminal or a single human-readable answer.
|
|
113
|
+
#: - ``json`` — a single non-interactive request whose result is structured.
|
|
114
|
+
#: - ``rpc`` — the headless line protocol for a driving parent process.
|
|
115
|
+
#:
|
|
116
|
+
#: ``text`` is the interactive default; ``json`` is selected by ``--print``
|
|
117
|
+
#: (without the interactive override), and ``rpc`` by ``--json`` / ``--rpc``.
|
|
118
|
+
OutputMode: TypeAlias = Literal["text", "json", "rpc"]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_output_mode(value: str) -> bool:
|
|
122
|
+
"""Narrow an arbitrary string to an :data:`OutputMode`."""
|
|
123
|
+
return value == "text" or value == "json" or value == "rpc"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Reasoning effort
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
#: One reasoning-effort rung from :data:`THINKING_EFFORTS`.
|
|
131
|
+
ThinkingEffort: TypeAlias = Literal["off", "minimal", "low", "medium", "high", "xhigh"]
|
|
132
|
+
|
|
133
|
+
#: The ordered reasoning-effort vocabulary accepted by ``--thinking``. ``off``
|
|
134
|
+
#: disables extended reasoning entirely; the remaining rungs ascend in effort.
|
|
135
|
+
#: Ordered so the usage text can enumerate it and :func:`is_thinking_effort`
|
|
136
|
+
#: can validate against it without a second list. It is a superset-compatible
|
|
137
|
+
#: widening of the framework :data:`~indusagi.ai.ThinkingLevel` (which omits
|
|
138
|
+
#: ``off``); callers that hand a level to the framework drop ``off``.
|
|
139
|
+
THINKING_EFFORTS: Final[tuple[ThinkingEffort, ...]] = (
|
|
140
|
+
"off",
|
|
141
|
+
"minimal",
|
|
142
|
+
"low",
|
|
143
|
+
"medium",
|
|
144
|
+
"high",
|
|
145
|
+
"xhigh",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def is_thinking_effort(value: str) -> bool:
|
|
150
|
+
"""Pure membership test against :data:`THINKING_EFFORTS`; the model
|
|
151
|
+
resolver reuses it to parse the ``model:effort`` shorthand without
|
|
152
|
+
importing the parser."""
|
|
153
|
+
return value in THINKING_EFFORTS
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# Tool roster
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
#: One built-in tool identifier from :data:`TOOL_NAMES`.
|
|
161
|
+
ToolName: TypeAlias = Literal[
|
|
162
|
+
"read",
|
|
163
|
+
"write",
|
|
164
|
+
"edit",
|
|
165
|
+
"bash",
|
|
166
|
+
"grep",
|
|
167
|
+
"find",
|
|
168
|
+
"ls",
|
|
169
|
+
"task",
|
|
170
|
+
"todo_read",
|
|
171
|
+
"todo_write",
|
|
172
|
+
"web_fetch",
|
|
173
|
+
"web_search",
|
|
174
|
+
"composio",
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
#: The closed roster of built-in tool names the ``--tools`` / ``--no-tools``
|
|
178
|
+
#: flags select against. A literal tuple (rather than the live tool map) so
|
|
179
|
+
#: the launch layer can validate a ``--tools`` list before any tool module is
|
|
180
|
+
#: constructed.
|
|
181
|
+
TOOL_NAMES: Final[tuple[ToolName, ...]] = (
|
|
182
|
+
"read",
|
|
183
|
+
"write",
|
|
184
|
+
"edit",
|
|
185
|
+
"bash",
|
|
186
|
+
"grep",
|
|
187
|
+
"find",
|
|
188
|
+
"ls",
|
|
189
|
+
"task",
|
|
190
|
+
"todo_read",
|
|
191
|
+
"todo_write",
|
|
192
|
+
"web_fetch",
|
|
193
|
+
"web_search",
|
|
194
|
+
"composio",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def is_tool_name(value: str) -> bool:
|
|
199
|
+
"""Narrow an arbitrary string to a known :data:`ToolName`."""
|
|
200
|
+
return value in TOOL_NAMES
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
# Declarative flag table
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
#: The value vocabulary a flag binds to its target field.
|
|
208
|
+
#:
|
|
209
|
+
#: - ``boolean`` — a bare switch; presence sets it true (e.g. ``--print``).
|
|
210
|
+
#: - ``string`` — consumes the following token as text (e.g. ``--model``).
|
|
211
|
+
#: - ``number`` — consumes the following token and coerces to a number.
|
|
212
|
+
#: - ``list`` — accumulates; either repeated or one comma-separated token
|
|
213
|
+
#: (e.g. ``--mcp a --mcp b``, or ``--tools read,bash``).
|
|
214
|
+
FlagKind: TypeAlias = Literal["boolean", "string", "number", "list"]
|
|
215
|
+
|
|
216
|
+
#: The default value attached to a :class:`FlagSpec`, narrowed by
|
|
217
|
+
#: :data:`FlagKind`: a boolean default for switches, a string for value flags,
|
|
218
|
+
#: a number for numeric flags, and a string tuple for lists.
|
|
219
|
+
FlagDefault: TypeAlias = bool | str | float | tuple[str, ...]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True, slots=True)
|
|
223
|
+
class FlagSpec:
|
|
224
|
+
"""One row of the single declarative flag table.
|
|
225
|
+
|
|
226
|
+
Every recognised option is described here exactly once. The reader indexes
|
|
227
|
+
the table by :attr:`name` and :attr:`aliases` to bind tokens to
|
|
228
|
+
:attr:`Invocation.flags`; the usage generator walks the same rows to render
|
|
229
|
+
the option reference. There is no second source of truth for either side.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# Canonical long spelling, leading dashes included (e.g. "--model"). This
|
|
233
|
+
# is also the key the parsed value lands under in Invocation.flags, sans
|
|
234
|
+
# the leading dashes.
|
|
235
|
+
name: str
|
|
236
|
+
# The value vocabulary this flag binds (see FlagKind).
|
|
237
|
+
kind: FlagKind
|
|
238
|
+
# One-line description rendered verbatim in the generated usage text.
|
|
239
|
+
describe: str
|
|
240
|
+
# Accepted alternate spellings — short forms ("-m") and synonyms ("--rpc"
|
|
241
|
+
# for "--json"). All alias hits normalise to `name`.
|
|
242
|
+
aliases: tuple[str, ...] = ()
|
|
243
|
+
# Optional default folded into Invocation.flags when the flag is absent.
|
|
244
|
+
# Its runtime type must match `kind`.
|
|
245
|
+
default: FlagDefault | None = None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
# Invocation
|
|
250
|
+
# ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
#: The runtime value a parsed flag can carry in :attr:`Invocation.flags`: a
|
|
253
|
+
#: boolean switch, a scalar value, a numeric value, or an accumulated list.
|
|
254
|
+
FlagValue: TypeAlias = bool | str | float | list[str]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass(frozen=True, slots=True)
|
|
258
|
+
class Invocation:
|
|
259
|
+
"""The fully parsed command line — the launch layer's enrichment of the
|
|
260
|
+
thin boot routing shape into the complete, strongly-typed surface the
|
|
261
|
+
runtime reads.
|
|
262
|
+
|
|
263
|
+
The first four members (:attr:`mode`, :attr:`prompt`, :attr:`flags`,
|
|
264
|
+
:attr:`positionals`) are the superset of the boot-layer minimal invocation
|
|
265
|
+
(``positionals`` is the renamed ``rest``); everything below is the
|
|
266
|
+
resolved launch configuration. :attr:`flags` retains the loosely-typed bag
|
|
267
|
+
for extension flags and round-tripping, while the named fields give
|
|
268
|
+
consumers a precise, pre-coerced view of the options that matter to the
|
|
269
|
+
runtime. Treat every field as read-only.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
# Resolved terminal output mode; selects the boot runner.
|
|
273
|
+
mode: OutputMode
|
|
274
|
+
# All parsed switches, keyed by canonical flag name (extension-flag escape
|
|
275
|
+
# hatch).
|
|
276
|
+
flags: Mapping[str, FlagValue]
|
|
277
|
+
# Positional tokens not consumed as flags (the boot layer's `rest`).
|
|
278
|
+
positionals: tuple[str, ...]
|
|
279
|
+
# First user message assembled from positionals, stdin, and attachments.
|
|
280
|
+
prompt: str | None = None
|
|
281
|
+
# `@file` arguments expanded to inline prose plus base64 media, if any.
|
|
282
|
+
attachments: Attachments | None = None
|
|
283
|
+
# Explicit model selector (--model / -m), provider-qualified or bare.
|
|
284
|
+
model: str | None = None
|
|
285
|
+
# Named credential account to authenticate the run with (--account).
|
|
286
|
+
account: str | None = None
|
|
287
|
+
# Working directory the run is scoped to (--cwd); None means process cwd.
|
|
288
|
+
cwd: str | None = None
|
|
289
|
+
# Replacement system prompt (--system); None keeps the built-in.
|
|
290
|
+
system: str | None = None
|
|
291
|
+
# Extra text appended after the system prompt (--append-system).
|
|
292
|
+
append_system: str | None = None
|
|
293
|
+
# Reasoning-effort rung requested via --thinking.
|
|
294
|
+
thinking: ThinkingEffort | None = None
|
|
295
|
+
# Explicit tool allow-list (--tools); None means every built-in tool.
|
|
296
|
+
tools: tuple[ToolName, ...] | None = None
|
|
297
|
+
# Disable all tools for this run (--no-tools).
|
|
298
|
+
no_tools: bool = False
|
|
299
|
+
# External MCP server endpoints to attach (--mcp, repeatable/comma-joined).
|
|
300
|
+
mcp: tuple[str, ...] = ()
|
|
301
|
+
# Run a single request and exit, printing only the result (--print / -p).
|
|
302
|
+
print: bool = False
|
|
303
|
+
# Force the interactive REPL even alongside a prompt (--interactive / -i).
|
|
304
|
+
interactive: bool = False
|
|
305
|
+
# Asking for the usage banner (--help / -h).
|
|
306
|
+
help: bool = False
|
|
307
|
+
# Asking for the version string (--version / -v).
|
|
308
|
+
version: bool = False
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
# Attachments
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@dataclass(frozen=True, slots=True)
|
|
317
|
+
class Attachments:
|
|
318
|
+
"""The result of expanding the ``@file`` arguments collected on the
|
|
319
|
+
command line.
|
|
320
|
+
|
|
321
|
+
Text files are inlined into :attr:`prose` (each wrapped in a delimited
|
|
322
|
+
block keyed by its path); image files are decoded to framework
|
|
323
|
+
:class:`~indusagi.ai.ImageContent` and collected in :attr:`media`. Both
|
|
324
|
+
are concatenated onto the first user message. An invocation with no
|
|
325
|
+
``@file`` arguments has no :class:`Attachments` at all rather than an
|
|
326
|
+
empty one.
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
# Concatenated text of every inlined file, each wrapped with its path.
|
|
330
|
+
prose: str
|
|
331
|
+
# Base64 image content decoded from every image @file argument.
|
|
332
|
+
media: tuple[ImageContent, ...]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@dataclass(frozen=True, slots=True)
|
|
336
|
+
class AttachmentOptions:
|
|
337
|
+
"""Options for :func:`~.invocation.gather_attachments`: the cwd that
|
|
338
|
+
``@file`` references are resolved against."""
|
|
339
|
+
|
|
340
|
+
# Working directory @file references are resolved relative to.
|
|
341
|
+
cwd: str
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# ---------------------------------------------------------------------------
|
|
345
|
+
# Credential command
|
|
346
|
+
# ---------------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
#: The two verbs the credential command recognises as the first positional
|
|
349
|
+
#: token. Anything else means the command does not own this invocation and the
|
|
350
|
+
#: caller proceeds to normal launch.
|
|
351
|
+
#:
|
|
352
|
+
#: - ``signin`` — validate and store a credential for a provider / account.
|
|
353
|
+
#: - ``signout`` — remove a stored credential for a provider / account.
|
|
354
|
+
CredentialVerb: TypeAlias = Literal["signin", "signout"]
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass(frozen=True, slots=True)
|
|
358
|
+
class ProviderEntry:
|
|
359
|
+
"""A provider entry in the credential directory — the facts the sign-in
|
|
360
|
+
prompts print and validate against. :attr:`env_key` is the conventional
|
|
361
|
+
environment variable :func:`indusagi.ai.get_env_api_key` reads;
|
|
362
|
+
:attr:`docs_url` is where the user obtains a key. These are external
|
|
363
|
+
provider conventions, not derived data."""
|
|
364
|
+
|
|
365
|
+
# Stable provider id matching the framework provider vocabulary.
|
|
366
|
+
id: str
|
|
367
|
+
# Human-facing provider label for menus and prompts.
|
|
368
|
+
label: str
|
|
369
|
+
# Conventional api-key environment variable for this provider.
|
|
370
|
+
env_key: str
|
|
371
|
+
# Page where a user obtains an api key for this provider.
|
|
372
|
+
docs_url: str
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
#: The closed set of failure categories the credential command can raise.
|
|
376
|
+
#: A consumer switches on :attr:`CredentialFault.kind`, never on message text:
|
|
377
|
+
#:
|
|
378
|
+
#: - ``unknown-provider`` — the named provider is not in the directory.
|
|
379
|
+
#: - ``invalid-key`` — the supplied key failed format validation.
|
|
380
|
+
#: - ``invalid-account`` — the account name failed the naming rules.
|
|
381
|
+
#: - ``name-collision`` — the account name already exists for the provider.
|
|
382
|
+
#: - ``not-found`` — sign-out targeted a credential not stored.
|
|
383
|
+
#: - ``vault`` — the underlying credential store failed.
|
|
384
|
+
#: - ``aborted`` — the user cancelled an interactive prompt.
|
|
385
|
+
CredentialFaultKind: TypeAlias = Literal[
|
|
386
|
+
"unknown-provider",
|
|
387
|
+
"invalid-key",
|
|
388
|
+
"invalid-account",
|
|
389
|
+
"name-collision",
|
|
390
|
+
"not-found",
|
|
391
|
+
"vault",
|
|
392
|
+
"aborted",
|
|
393
|
+
]
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@dataclass(frozen=True, slots=True)
|
|
397
|
+
class CredentialFault:
|
|
398
|
+
"""A typed credential failure. :attr:`kind` drives recovery; :attr:`hint`
|
|
399
|
+
carries a single actionable next step for the human (e.g. the env-var to
|
|
400
|
+
set), and :attr:`cause` preserves any wrapped error for diagnostics."""
|
|
401
|
+
|
|
402
|
+
# The failure category (the discriminant).
|
|
403
|
+
kind: CredentialFaultKind
|
|
404
|
+
# Human-readable summary of what failed.
|
|
405
|
+
message: str
|
|
406
|
+
# Optional single actionable suggestion for resolving the fault.
|
|
407
|
+
hint: str | None = None
|
|
408
|
+
# Optional wrapped underlying error.
|
|
409
|
+
cause: object | None = None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def credential_fault(
|
|
413
|
+
kind: CredentialFaultKind,
|
|
414
|
+
message: str,
|
|
415
|
+
*,
|
|
416
|
+
hint: str | None = None,
|
|
417
|
+
cause: object | None = None,
|
|
418
|
+
) -> CredentialFault:
|
|
419
|
+
"""Construct a :class:`CredentialFault`. A tiny inert helper so call sites
|
|
420
|
+
raise a well-formed typed fault without re-spelling the shape."""
|
|
421
|
+
return CredentialFault(kind=kind, message=message, hint=hint, cause=cause)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@dataclass(frozen=True, slots=True)
|
|
425
|
+
class OAuthCredentials:
|
|
426
|
+
"""Browser-sign-in credentials as the *vault* stores them (the TS
|
|
427
|
+
``indusagi/ai`` ``OAuthCredentials`` shape, app-owned in the port — see
|
|
428
|
+
the module docstring). ``expires`` is an absolute epoch-millisecond
|
|
429
|
+
deadline, matching the framework's ``OAuthTokens.expires_at``."""
|
|
430
|
+
|
|
431
|
+
# The live access token.
|
|
432
|
+
access: str
|
|
433
|
+
# The refresh token, when the provider issued one.
|
|
434
|
+
refresh: str | None = None
|
|
435
|
+
# Absolute epoch-millisecond expiry deadline, when known.
|
|
436
|
+
expires: int | None = None
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class AuthVault(Protocol):
|
|
440
|
+
"""The credential-vault surface the credential command depends on.
|
|
441
|
+
|
|
442
|
+
The framework publishes no credential-store type, so the launch contract
|
|
443
|
+
forward-declares the slice it needs: per-provider, per-account records
|
|
444
|
+
keyed by an account name. A record stores *either* an api key or a set of
|
|
445
|
+
browser-sign-in credentials; the vault refreshes an expired browser token
|
|
446
|
+
before yielding a usable key. The boot layer supplies the concrete
|
|
447
|
+
multi-account disk vault; this Protocol lets the credential command be
|
|
448
|
+
unit-tested against an in-memory stand-in before then.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
async def list_accounts(self, provider: str) -> list[str]:
|
|
452
|
+
"""Stored account names for a provider, in insertion order."""
|
|
453
|
+
...
|
|
454
|
+
|
|
455
|
+
async def default_account(self, provider: str) -> str | None:
|
|
456
|
+
"""The default account name for a provider, if one is set."""
|
|
457
|
+
...
|
|
458
|
+
|
|
459
|
+
async def put_api_key(
|
|
460
|
+
self,
|
|
461
|
+
provider: str,
|
|
462
|
+
account: str,
|
|
463
|
+
api_key: str,
|
|
464
|
+
make_default: bool = False,
|
|
465
|
+
) -> None:
|
|
466
|
+
"""Persist an api key under a provider / account, optionally as the
|
|
467
|
+
default."""
|
|
468
|
+
...
|
|
469
|
+
|
|
470
|
+
async def put_oauth(
|
|
471
|
+
self,
|
|
472
|
+
provider: str,
|
|
473
|
+
account: str,
|
|
474
|
+
credentials: OAuthCredentials,
|
|
475
|
+
make_default: bool = False,
|
|
476
|
+
) -> None:
|
|
477
|
+
"""Persist browser-sign-in credentials under a provider / account,
|
|
478
|
+
optionally as the default."""
|
|
479
|
+
...
|
|
480
|
+
|
|
481
|
+
async def auth_kind(
|
|
482
|
+
self, provider: str, account: str
|
|
483
|
+
) -> Literal["apiKey", "oauth"] | None:
|
|
484
|
+
"""Report whether a stored account holds an api key or browser-sign-in
|
|
485
|
+
credentials, or ``None`` when nothing is stored there."""
|
|
486
|
+
...
|
|
487
|
+
|
|
488
|
+
async def read_usable_key(self, provider: str, account: str) -> str | None:
|
|
489
|
+
"""Resolve a stored account to a live api-key string. An api-key
|
|
490
|
+
record yields its key verbatim; a browser-sign-in record is refreshed
|
|
491
|
+
(persisting any rotated token) before its usable key is returned.
|
|
492
|
+
Resolves ``None`` when nothing usable is stored."""
|
|
493
|
+
...
|
|
494
|
+
|
|
495
|
+
async def remove(self, provider: str, account: str | None = None) -> bool:
|
|
496
|
+
"""Remove a stored credential; resolves False when nothing was
|
|
497
|
+
removed."""
|
|
498
|
+
...
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# ---------------------------------------------------------------------------
|
|
502
|
+
# Model catalog
|
|
503
|
+
# ---------------------------------------------------------------------------
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@dataclass(frozen=True, slots=True)
|
|
507
|
+
class CatalogFilter:
|
|
508
|
+
"""The filter applied when rendering the ``--list-models`` table. Every
|
|
509
|
+
field is optional; an absent field matches everything. :attr:`search` is a
|
|
510
|
+
plain case-insensitive substring test over the provider/model identifier —
|
|
511
|
+
no fuzzy matcher and no external ranking."""
|
|
512
|
+
|
|
513
|
+
# Restrict to a single provider id.
|
|
514
|
+
provider: str | None = None
|
|
515
|
+
# Keep only models that advertise a reasoning budget.
|
|
516
|
+
thinking_only: bool | None = None
|
|
517
|
+
# Keep only models that accept image input.
|
|
518
|
+
images_only: bool | None = None
|
|
519
|
+
# Case-insensitive substring filter over the model identifier.
|
|
520
|
+
search: str | None = None
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# ---------------------------------------------------------------------------
|
|
524
|
+
# Resume picker
|
|
525
|
+
# ---------------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
#: A function that loads a set of resumable sessions. The resume picker takes
|
|
528
|
+
#: two: one for the current working directory and one for every directory,
|
|
529
|
+
#: both shaped like the framework session lister.
|
|
530
|
+
SessionLoader: TypeAlias = Callable[[], Awaitable[Sequence[ResumeRef]]]
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
@dataclass(frozen=True, slots=True)
|
|
534
|
+
class ResumeFault:
|
|
535
|
+
"""A launch-time error from the resume flow (the picker failing to mount,
|
|
536
|
+
or a session store read fault). Typed so the orchestrator can fall back
|
|
537
|
+
to a fresh session rather than crash."""
|
|
538
|
+
|
|
539
|
+
# Human-readable summary of what failed.
|
|
540
|
+
message: str
|
|
541
|
+
# Optional wrapped underlying error.
|
|
542
|
+
cause: object | None = None
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
# ---------------------------------------------------------------------------
|
|
546
|
+
# Settings browser
|
|
547
|
+
# ---------------------------------------------------------------------------
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
@dataclass(frozen=True, slots=True)
|
|
551
|
+
class SettingsBrowseOptions:
|
|
552
|
+
"""Options for :func:`~.pickers.browse_settings`: the resolved framework
|
|
553
|
+
settings, the directories of user-authored resources to enumerate, the
|
|
554
|
+
active cwd, and the profile directory. The browser renders a plain console
|
|
555
|
+
listing of these — no TUI."""
|
|
556
|
+
|
|
557
|
+
# The merged, resolved framework settings.
|
|
558
|
+
settings: Settings
|
|
559
|
+
# Resolved absolute paths of discovered resources, grouped by category.
|
|
560
|
+
resolved_paths: Mapping[str, Sequence[str]]
|
|
561
|
+
# The active working directory.
|
|
562
|
+
cwd: str
|
|
563
|
+
# The profile directory the settings were loaded from.
|
|
564
|
+
profile_dir: str
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
# Quiet "imported but unused" for the re-exported framework names: they are
|
|
568
|
+
# part of this contract's public surface (`__all__`).
|
|
569
|
+
_REEXPORTS: Final[tuple[Any, ...]] = (ImageContent, ThinkingLevel, Settings, SessionInfo)
|