Vendor files
vendor_files
¶
Pydantic models describing the credential files we read from third-party CLIs.
These files are owned by Anthropic, OpenAI, GitHub, GitLab, and a handful of
OpenAI-compatible providers — not by us. Every model in this module
therefore uses extra="ignore" (Pydantic's default), and only the fields
we actually consume have any guarantees. Vendor adds a new key, prunes a
side-field, renames an internal-only block? Best-effort: we keep working
as long as the fields we read still hold their shape.
A single failure mode is loud: if a vendor renames or retypes a field we
depend on (e.g. claudeAiOauth.accessToken or tokens.access_token),
the model raises ValidationError — pointing at
the exact field, in the exact file. Callers translate that into a clear
"vendor file format may have changed" surface.
For fields where we tolerate absence (most of them), the field is declared
optional with a default; the extractor checks truthiness rather than
is not None.
The file-loading helpers in this module (load_vendor_json,
load_vendor_yaml)
distinguish between "file is absent / unreadable / not a dict at the top
level" (silent fallback) and "file present but structure broke our
contract" (loud). See those docstrings for the exact rules.
JsTimestamp = Annotated[float | None, BeforeValidator(_normalize_js_timestamp)]
module-attribute
¶
POSIX-seconds timestamp coerced from a JS Date.now() ms value.
__all__ = ['JsTimestamp', 'RawApiKeyJsonFile', 'RawClaudeCredentialsFile', 'RawClaudeOauthBlock', 'RawCodexAuthFile', 'RawCodexTokensBlock', 'RawGhHostBlock', 'RawGhHostsFile', 'RawGlabConfigFile', 'RawGlabHostBlock', 'load_vendor_json', 'load_vendor_yaml', 'warn_drift']
module-attribute
¶
RawClaudeOauthBlock
¶
Bases: _VendorFile
.credentials.json → claudeAiOauth — Claude OAuth state block.
Typed fields are the ones we actually inspect: accessToken /
refreshToken go into HTTP headers, expiresAt drives the refresh
timer. Everything else is pass-through metadata stored in the output
credential dict — declared as :data:typing.Any to avoid coupling to
a vendor-side shape we never look at.
accessToken = ''
class-attribute
instance-attribute
¶
refreshToken = ''
class-attribute
instance-attribute
¶
expiresAt = None
class-attribute
instance-attribute
¶
scopes = ''
class-attribute
instance-attribute
¶
subscriptionType = None
class-attribute
instance-attribute
¶
rateLimitTier = None
class-attribute
instance-attribute
¶
RawClaudeCredentialsFile
¶
Bases: _VendorFile
Top-level shape of Claude Code's .credentials.json.
The OAuth block is optional — the file may exist without it (e.g. when the user authenticated via API key only).
claudeAiOauth = None
class-attribute
instance-attribute
¶
RawCodexTokensBlock
¶
Bases: _VendorFile
auth.json → tokens — Codex OAuth token block.
access_token and refresh_token go into HTTP headers; id_token
is parsed as a JWT in the synthetic-auth-file writer. account_id is
pass-through metadata, declared as :data:typing.Any.
RawCodexAuthFile
¶
RawApiKeyJsonFile
¶
Bases: _VendorFile
{"api_key": "..."}-shaped JSON config.
Used by Claude's config.json (API-key fallback path) and by the
OpenAI-compatible providers (blablador, kisski).
api_key = ''
class-attribute
instance-attribute
¶
RawGhHostBlock
¶
Bases: _VendorFile
hosts.yml → <host> — one entry in gh's per-host config.
oauth_token = ''
class-attribute
instance-attribute
¶
RawGhHostsFile
¶
Bases: RootModel[dict[str, RawGhHostBlock]]
Top-level shape of gh's hosts.yml.
The YAML is a bare dict keyed by host name (github.com,
ghe.example.com, …) — no wrapper section. RootModel lets us
validate it without inventing a synthetic outer key.
RawGlabHostBlock
¶
Bases: _VendorFile
config.yml → hosts.<host> — one entry in glab's per-host config.
token = ''
class-attribute
instance-attribute
¶
RawGlabConfigFile
¶
Bases: _VendorFile
Top-level shape of glab's config.yml — has a hosts: map.
hosts = Field(default_factory=dict)
class-attribute
instance-attribute
¶
load_vendor_json(model, path)
¶
Read a JSON vendor file from path and validate against model.
Returns None only for "the file isn't there" cases — missing,
unreadable, or unparseable as JSON. When the JSON parses but the
structure doesn't match model (wrong root type, missing nested
field, wrong field type), raises ValidationError
— a ValueError subclass — so the caller surfaces "your credential
file is in a shape we don't recognize" rather than silently falling
through to a generic "not found" path. Callers that want a
fall-through anyway (e.g. Claude tries OAuth then API key) catch
ValidationError explicitly.
Source code in src/terok_executor/credentials/vendor_files.py
load_vendor_yaml(model, path)
¶
Read a YAML vendor file from path and validate against model.
Same fallback / loud-fail rules as load_vendor_json.
Uses ruamel.yaml's safe loader; RootModel subclasses (like
RawGhHostsFile)
accept the parsed dict as their root value.
Source code in src/terok_executor/credentials/vendor_files.py
warn_drift(path, exc)
¶
Print a stderr breadcrumb when a vendor file fails validation.
Extractors with their own fallback path (e.g. Claude tries OAuth then
API key) catch ValidationError silently.
Without this breadcrumb, a vendor renaming a field we depend on would
surface only as a generic "no creds found" with no diagnostic trail.
Pydantic's default str(exc) includes the offending input_value
— which can be a credential — so the breadcrumb deliberately renders
only the field path(s) from exc.errors(), never the input value.