Use this skill when updating an existing cyhole Interaction to reflect API changes. Trigger whenever the user says "update [API name] interaction", "[API name] API changed", "add endpoint to [API name]", "remove deprecated endpoint from [API name]", "[API name] now requires an API key", "sync [API name] with latest docs", or similar requests to evolve an already-implemented Interaction. Also trigger when the user provides a new documentation URL for an API that is already in cyhole. Do NOT skip this skill even for "small" changes like adding one endpoint or updating one schema — conventions must be applied consistently.
التثبيت
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
Use this skill when updating an existing cyhole Interaction to reflect API changes. Trigger whenever the user says "update [API name] interaction", "[API name] API changed", "add endpoint to [API name]", "remove deprecated endpoint from [API name]", "[API name] now requires an API key", "sync [API name] with latest docs", or similar requests to evolve an already-implemented Interaction. Also trigger when the user provides a new documentation URL for an API that is already in cyhole. Do NOT skip this skill even for "small" changes like adding one endpoint or updating one schema — conventions must be applied consistently.
cyhole Interaction Update Skill
This skill guides the full update of an existing Interaction in the cyhole library when
API changes occur — new endpoints, deprecated endpoints, schema changes, authentication
changes, or structural reorganisation.
Step 0: Gather Requirements
Before touching code, collect (ask if not provided):
Interaction name — the existing lowercase identifier (e.g. jupiter, birdeye).
API documentation URL — required. Fetch it to understand the current API surface.
Release version — the cyhole version this update ships in (e.g. 0.4.0). Used to
populate index.md endpoint table entries.
Changes summary — if the user already knows what changed (new endpoint X, deprecated
endpoint Y, auth key added, etc.) accept their description. Otherwise detect changes in
Step 2.
Step 1: Audit the Current Implementation
Read the existing interaction to build a baseline inventory:
# list source filesls src/cyhole/{name}/
# list all public methods (current endpoint set)
grep -n "def " src/cyhole/{name}/client.py
# read the endpoints table for deprecation/release history# (look at docs/interactions/{name}/index.md, section ## Endpoints)
Document:
All currently implemented endpoints (method name, HTTP type)
Whether authentication is already wired (api_key in __init__, headers)
Current base URL (self.url_api)
Step 2: Research the New API State
Fetch the provided documentation URL with WebFetch. For every endpoint in the docs extract:
HTTP method and path
Required and optional parameters (types, allowed values, defaults)
An endpoint is modified when its parameters or response shape changes.
Update schema.py:
Add new response fields. Use str | None = None for optional fields added mid-lifecycle.
Remove fields that no longer appear in the API response (confirm they are truly gone).
Update field types if the API changed them.
Add new param enums to param.py for any new fixed-value parameters.
Preserve all docstrings and add descriptions for any new fields — explain what the
field represents, its units, and any edge cases (e.g. None when not applicable).
cyhole is a user-facing library, so this functional description is what end-users
actually consume in the docs site — it is not optional.
Field-level docs go under the Google-style Attributes: section, neverParameters:.
Griffe interprets Parameters: as a function/__init__ signature and emits
"Parameter X does not appear in the function signature" warnings that abort
mkdocs build --strict. If you encounter an existing class that uses Parameters:
for its fields, fix it as part of this update.
Update interaction.py:
Add or remove parameters from the private method signature (maintain the overload pattern).
Update the params / body dict to include new params or drop removed ones.
If a new param has fixed allowed values, use the new {Name}{ParamName} enum from param.py.
Update the docstring Parameters and Returns sections to reflect the change.
Update client.py:
Mirror every signature change in both {Name}Client.{method_name} and
{Name}AsyncClient.{method_name}.
The client methods must match the private method's public-facing signature exactly.
Update tests and mock files:
If new params were added, add representative calls in the test.
If the response shape changed, update the mock JSON file to match the new schema
(all Pydantic model fields must be present in the fixture).
Re-run tests to confirm mock responses deserialise correctly.
Update docs/interactions/{name}/index.md:
No table change needed for a modified endpoint (release version and Deprecated column stay).
4c — New Endpoint
A new endpoint follows the same conventions as creating an endpoint in a brand-new
Interaction. Apply these steps:
Every field must have a concise docstring explaining what it represents, its units, and
when it is None.
Use Field(alias="originalName") when JSON keys differ from Python naming style.
Nest sub-schemas for complex structures; name them descriptively without a Response suffix.
For endpoints with more than ~3 meaningful inputs, define a Pydantic model and accept it
as a single argument: Post{EndpointName}Body for POST endpoints,
Get{EndpointName}Query for GETs with many filters. Serialise to the request with
model_dump(exclude_none = True) so unset filters are not sent. Example: Birdeye's
GetV3TokenListQuery covering 57 optional filter params.
For sibling .../single and .../multiple endpoints whose only delta is input cardinality,
define distinct response schemas (…Response + …MultipleResponse) and consolidate
them under a single polymorphic interaction method (see the interaction.py block below).
If schema.py is already large or about to grow further, consider splitting into a
schema/ sub-package per CLAUDE.md → "Scaling patterns for large interactions" as a
standalone REF: commit before adding the new endpoints.
Document each enum member: what it means in the API context.
interaction.py — add the overload + implementation:
@overloaddef_{request_type}_{endpoint_name}(self, sync: Literal[True], ...) -> {Response}Model: ...
@overloaddef_{request_type}_{endpoint_name}(self, sync: Literal[False], ...) -> Coroutine[None, None, {Response}Model]: ...
def_{request_type}_{endpoint_name}(self, sync: bool, ...) -> {Response}Model | Coroutine[None, None, {Response}Model]:
"""
This function refers to the **{EndpointName}** API endpoint.
[One-or-two-sentence functional description: what this endpoint returns, what it is
useful for, and any important caveats — e.g. pagination defaults, rate limits, units
of returned values. Do NOT just restate the endpoint name. This docstring is what
end-users see in the mkdocs site since public client methods are intentionally thin
wrappers that link here.]
Parameters:
sync: if True run synchronously, else return a coroutine.
...: [each param with type, description, units/default, and valid values
or enum reference]
Returns:
{Response}Model: [description of the response, calling out the key fields
a caller is most likely to use]
Raises:
{Name}Exception: if the API returns an error.
"""
url = self.url_api + "endpoint/path"returnself.api_return_model(sync, RequestType.{TYPE}.value, url, {Response}Model, params=params)
Always use self.api_return_model(sync, type, url, ResponseModel, **kwargs) for the dispatch — never write the manual if sync: ... else: async def async_request(): ... ladder. The helper forwards every keyword (params=, json=, headers=) to client.api().
Consolidated single/multiple endpoint pairs. When the API exposes a .../single and .../multiple pair whose only delta is input cardinality, expose them as one polymorphic method with four @overload declarations narrowing the return type on sync × cardinality. Routing happens inside the method body by isinstance(address, str):
This applies even when the two endpoints use different HTTP verbs (GET single + POST batch). The sync and async client wrappers must mirror the polymorphic overloads so callers get a narrowed return type.
client.py — add sync and async methods:
def {endpoint_name}(self, ...) -> {Response}Model:
"""
Call the {Name}'s {RequestType} **[{EndpointName}]({doc_url})** API endpoint for synchronous logic.
All the API endpoint details are available on [`{Name}._{method_name}`][cyhole.{name}.interaction.{Name}._{method_name}].
"""returnself._interaction._{method_name}(True, ...)
asyncdef {endpoint_name}(self, ...) -> {Response}Model:
"""
Call the {Name}'s {RequestType} **[{EndpointName}]({doc_url})** API endpoint for asynchronous logic.
All the API endpoint details are available on [`{Name}._{method_name}`][cyhole.{name}.interaction.{Name}._{method_name}].
"""returnawaitself._interaction._{method_name}(False, ...)
Update self.url_api in interaction.py.__init__ and verify all endpoint path strings
are still correct relative to the new base.
Step 5: Run Tests, Lint, and Docs Build
These three checks are mandatory — do not skip any of them, and do not declare the task done until all three are clean.
# 1. Tests must pass with mock responses
pytest tests/test_{name}.py -v
# 2. Lint must be clean for the modified code
ruff check src/
# 3. Docs build must succeed in strict mode (zero WARNINGs, zero ERRORs)
mkdocs build --strict
Fix every issue before proceeding. Common failures:
ruff flags: imports left behind by removing a deprecated endpoint (schemas, enums, exceptions no longer referenced); unused helper variables.
mkdocs --strict failures:
Pydantic class docstrings using Parameters: instead of Attributes: for fields.
Cross-reference links in client.py or index.md pointing to a method that was deprecated/renamed.
index.md endpoint table row still linking to a now-removed private method anchor.
Step 6: Final Checklist
Change list fully processed — no skipped items
Deprecated: method gone from interaction.py, client.py, schema.py, param.py, tests, mock files; index.md row updated (no link, deprecation version filled)
New: two @overload + implementation in interaction.py; sync + async in client.py; response model + sub-schemas in schema.py; param enums in param.py if needed; mock JSON created; _sync + _async tests added; index.md row added
Auth added: api_key param in __init__, header injection, config.py + test.default.ini updated, test class updated, docs updated
All Pydantic models have docstrings, and field-level docs use Attributes: (never Parameters:)
Every documented field describes meaning, units, and None conditions
Every new or modified private endpoint method (_{verb}_{name}) has a true functional description (what the endpoint returns, why a user would call it), not just a name restatement
All param enum members have docstrings
Overload pattern correct on every private method (two @overload + implementation; four for consolidated single/multiple endpoints)
api_return_model used — no raw client.api() and no manual if sync: ... else: async def async_request(): ... ladder in interaction.py
Endpoints with >3 inputs accept a Post{Name}Body / Get{Name}Query Pydantic model rather than enumerating params; the body/query is serialised via model_dump(exclude_none = True)
Sibling .../single and .../multiple endpoints consolidated under one polymorphic method with address: str | list[str]; sync + async clients mirror the overloads
If schema.py was split into a sub-package, the split happens in a standalone REF: commit before any new-endpoint commit; schema/__init__.py re-exports every public name so existing imports keep working
ruff check src/ is clean
mkdocs build --strict runs with zero WARNINGs and zero ERRORs