hermes-tweet 0.1.0__tar.gz

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.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xquik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,4 @@
1
+ include plugin.yaml
2
+ recursive-include hermes_tweet *.yaml *.json
3
+ recursive-include hermes_tweet/skills *.md
4
+
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermes-tweet
3
+ Version: 0.1.0
4
+ Summary: Native Hermes Agent plugin for X automation through Xquik
5
+ Author: Xquik
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://xquik.com
8
+ Project-URL: Documentation, https://docs.xquik.com
9
+ Project-URL: Repository, https://github.com/Xquik-dev/hermes-tweet
10
+ Project-URL: Issues, https://github.com/Xquik-dev/hermes-tweet/issues
11
+ Keywords: hermes-agent,hermes-plugin,twitter,x,xquik,tweet,automation,social-media
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Plugins
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Communications
20
+ Classifier: Topic :: Internet
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: httpx<0.29,>=0.27
26
+ Provides-Extra: dev
27
+ Requires-Dist: bandit[toml]>=1.9.2; extra == "dev"
28
+ Requires-Dist: basedpyright>=1.36.0; extra == "dev"
29
+ Requires-Dist: build>=1.3.0; extra == "dev"
30
+ Requires-Dist: pip-audit>=2.9.0; extra == "dev"
31
+ Requires-Dist: PyYAML>=6.0; extra == "dev"
32
+ Requires-Dist: pytest>=8.2; extra == "dev"
33
+ Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
34
+ Requires-Dist: ruff>=0.14.0; extra == "dev"
35
+ Requires-Dist: twine>=6.2.0; extra == "dev"
36
+ Requires-Dist: types-PyYAML>=6.0.12; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # Hermes Tweet
40
+
41
+ [![CI](https://github.com/Xquik-dev/hermes-tweet/actions/workflows/ci.yml/badge.svg)](https://github.com/Xquik-dev/hermes-tweet/actions/workflows/ci.yml)
42
+ [![PyPI](https://img.shields.io/pypi/v/hermes-tweet.svg)](https://pypi.org/project/hermes-tweet/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/hermes-tweet.svg)](https://pypi.org/project/hermes-tweet/)
44
+ [![Apify Actor](https://apify.com/actor-badge?actor=xquik/x-tweet-scraper)](https://apify.com/xquik/x-tweet-scraper)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
46
+
47
+ Native [Hermes Agent](https://github.com/NousResearch/hermes-agent) plugin for
48
+ X automation through [Xquik](https://xquik.com).
49
+
50
+ Hermes Tweet brings X search, account reads, tweet posting, replies, likes,
51
+ retweets, follows, DMs, monitors, webhooks, draws, extraction jobs, media, and
52
+ trend reads into Hermes as structured tools.
53
+
54
+ Use it when you need a Hermes Agent Twitter plugin, Hermes X automation, social
55
+ media automation for agents, or a native Hermes toolset for X/Twitter.
56
+
57
+ ## Highlights
58
+
59
+ - Native Hermes plugin with `plugin.yaml` and pip entry point.
60
+ - 99 agent-callable Xquik endpoints generated from OpenAPI.
61
+ - 32 MPP-tagged read endpoints in the bundled catalog.
62
+ - Read and action tools are split for least-privilege operation.
63
+ - Action endpoints are disabled by default.
64
+ - Bundled Hermes skill for agent-facing usage guidance.
65
+ - Slash commands for account status and trends.
66
+ - Strict CI with formatting, linting, type checking, tests, coverage, security
67
+ scan, dependency audit, and package build checks.
68
+
69
+ ## Install
70
+
71
+ From a local checkout:
72
+
73
+ ```bash
74
+ pip install -e .
75
+ hermes plugins enable hermes-tweet
76
+ ```
77
+
78
+ For public distribution:
79
+
80
+ ```bash
81
+ pip install hermes-tweet
82
+ hermes plugins enable hermes-tweet
83
+ ```
84
+
85
+ ## Configure
86
+
87
+ Create an API key in the Xquik dashboard, then set:
88
+
89
+ ```bash
90
+ export XQUIK_API_KEY="xq_..."
91
+ ```
92
+
93
+ Optional settings:
94
+
95
+ ```bash
96
+ export XQUIK_BASE_URL="https://xquik.com"
97
+ export HERMES_TWEET_ENABLE_ACTIONS="false"
98
+ ```
99
+
100
+ Action endpoints are disabled unless `HERMES_TWEET_ENABLE_ACTIONS=true`.
101
+
102
+ ## Security Model
103
+
104
+ Hermes Tweet never accepts credentials through tool arguments. Auth is read from
105
+ environment variables and injected by the plugin at request time.
106
+
107
+ The plugin blocks dashboard-only admin, billing, credit top-up, support-ticket,
108
+ API-key, and account re-authentication endpoints from the catalog. Private reads
109
+ and write-like endpoints go through `tweet_action`, which is hidden unless
110
+ `HERMES_TWEET_ENABLE_ACTIONS=true`.
111
+
112
+ ## Tools
113
+
114
+ | Tool | Purpose |
115
+ | --- | --- |
116
+ | `tweet_explore` | Search the bundled Xquik endpoint catalog. No API call. |
117
+ | `tweet_read` | Call catalog-listed read-only endpoints. |
118
+ | `tweet_action` | Call write-like or private endpoints. Disabled by default. |
119
+
120
+ Use `tweet_explore` first, then call `tweet_read` or `tweet_action` with a
121
+ concrete `/api/v1/...` path.
122
+
123
+ ## Slash Commands
124
+
125
+ | Command | Purpose |
126
+ | --- | --- |
127
+ | `/xstatus` | Show Xquik account, subscription, and usage status. |
128
+ | `/xtrends` | Show current X trends. |
129
+
130
+ ## Development
131
+
132
+ Generate the bundled catalog from Xquik OpenAPI:
133
+
134
+ ```bash
135
+ python scripts/build_catalog.py ../xquik/openapi.yaml
136
+ ```
137
+
138
+ Run checks:
139
+
140
+ ```bash
141
+ uv run --python 3.12 --extra dev ruff format --check .
142
+ uv run --python 3.12 --extra dev ruff check .
143
+ uv run --python 3.12 --extra dev basedpyright
144
+ uv run --python 3.12 --extra dev pytest --cov=hermes_tweet --cov=tests --cov-report=term-missing --cov-fail-under=100
145
+ uv run --python 3.12 --extra dev bandit -c pyproject.toml -r hermes_tweet scripts
146
+ uv run --python 3.12 --extra dev pip-audit
147
+ uv run --python 3.12 --extra dev python -m build
148
+ uv run --python 3.12 --extra dev twine check dist/*
149
+ ```
150
+
151
+ For a single local quality gate:
152
+
153
+ ```bash
154
+ uv run --python 3.12 --extra dev ruff format --check . && \
155
+ uv run --python 3.12 --extra dev ruff check . && \
156
+ uv run --python 3.12 --extra dev basedpyright && \
157
+ uv run --python 3.12 --extra dev pytest --cov=hermes_tweet --cov=tests --cov-report=term-missing --cov-fail-under=100 && \
158
+ uv run --python 3.12 --extra dev bandit -c pyproject.toml -r hermes_tweet scripts && \
159
+ uv run --python 3.12 --extra dev pip-audit && \
160
+ uv run --python 3.12 --extra dev python -m build && \
161
+ uv run --python 3.12 --extra dev twine check dist/*
162
+ ```
163
+
164
+ ## Relationship To TweetClaw
165
+
166
+ TweetClaw is the OpenClaw-native npm plugin. Hermes Tweet is the Hermes-native
167
+ Python plugin. Both use the same Xquik API contract.
168
+
169
+ ## Public Repo Metadata
170
+
171
+ Recommended GitHub description:
172
+
173
+ > Native Hermes Agent plugin for X/Twitter automation through Xquik.
174
+
175
+ Recommended topics:
176
+
177
+ `hermes-agent`, `hermes-plugin`, `hermes`, `twitter`, `x`, `x-api`,
178
+ `x-automation`, `xquik`, `tweet`, `automation`, `social-media`,
179
+ `social-media-automation`, `ai-agent`, `mcp`, `agent-tools`, `twitter-api`,
180
+ `twitter-automation`, `x-twitter`, `apify`, `python`
@@ -0,0 +1,142 @@
1
+ # Hermes Tweet
2
+
3
+ [![CI](https://github.com/Xquik-dev/hermes-tweet/actions/workflows/ci.yml/badge.svg)](https://github.com/Xquik-dev/hermes-tweet/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/hermes-tweet.svg)](https://pypi.org/project/hermes-tweet/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/hermes-tweet.svg)](https://pypi.org/project/hermes-tweet/)
6
+ [![Apify Actor](https://apify.com/actor-badge?actor=xquik/x-tweet-scraper)](https://apify.com/xquik/x-tweet-scraper)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+
9
+ Native [Hermes Agent](https://github.com/NousResearch/hermes-agent) plugin for
10
+ X automation through [Xquik](https://xquik.com).
11
+
12
+ Hermes Tweet brings X search, account reads, tweet posting, replies, likes,
13
+ retweets, follows, DMs, monitors, webhooks, draws, extraction jobs, media, and
14
+ trend reads into Hermes as structured tools.
15
+
16
+ Use it when you need a Hermes Agent Twitter plugin, Hermes X automation, social
17
+ media automation for agents, or a native Hermes toolset for X/Twitter.
18
+
19
+ ## Highlights
20
+
21
+ - Native Hermes plugin with `plugin.yaml` and pip entry point.
22
+ - 99 agent-callable Xquik endpoints generated from OpenAPI.
23
+ - 32 MPP-tagged read endpoints in the bundled catalog.
24
+ - Read and action tools are split for least-privilege operation.
25
+ - Action endpoints are disabled by default.
26
+ - Bundled Hermes skill for agent-facing usage guidance.
27
+ - Slash commands for account status and trends.
28
+ - Strict CI with formatting, linting, type checking, tests, coverage, security
29
+ scan, dependency audit, and package build checks.
30
+
31
+ ## Install
32
+
33
+ From a local checkout:
34
+
35
+ ```bash
36
+ pip install -e .
37
+ hermes plugins enable hermes-tweet
38
+ ```
39
+
40
+ For public distribution:
41
+
42
+ ```bash
43
+ pip install hermes-tweet
44
+ hermes plugins enable hermes-tweet
45
+ ```
46
+
47
+ ## Configure
48
+
49
+ Create an API key in the Xquik dashboard, then set:
50
+
51
+ ```bash
52
+ export XQUIK_API_KEY="xq_..."
53
+ ```
54
+
55
+ Optional settings:
56
+
57
+ ```bash
58
+ export XQUIK_BASE_URL="https://xquik.com"
59
+ export HERMES_TWEET_ENABLE_ACTIONS="false"
60
+ ```
61
+
62
+ Action endpoints are disabled unless `HERMES_TWEET_ENABLE_ACTIONS=true`.
63
+
64
+ ## Security Model
65
+
66
+ Hermes Tweet never accepts credentials through tool arguments. Auth is read from
67
+ environment variables and injected by the plugin at request time.
68
+
69
+ The plugin blocks dashboard-only admin, billing, credit top-up, support-ticket,
70
+ API-key, and account re-authentication endpoints from the catalog. Private reads
71
+ and write-like endpoints go through `tweet_action`, which is hidden unless
72
+ `HERMES_TWEET_ENABLE_ACTIONS=true`.
73
+
74
+ ## Tools
75
+
76
+ | Tool | Purpose |
77
+ | --- | --- |
78
+ | `tweet_explore` | Search the bundled Xquik endpoint catalog. No API call. |
79
+ | `tweet_read` | Call catalog-listed read-only endpoints. |
80
+ | `tweet_action` | Call write-like or private endpoints. Disabled by default. |
81
+
82
+ Use `tweet_explore` first, then call `tweet_read` or `tweet_action` with a
83
+ concrete `/api/v1/...` path.
84
+
85
+ ## Slash Commands
86
+
87
+ | Command | Purpose |
88
+ | --- | --- |
89
+ | `/xstatus` | Show Xquik account, subscription, and usage status. |
90
+ | `/xtrends` | Show current X trends. |
91
+
92
+ ## Development
93
+
94
+ Generate the bundled catalog from Xquik OpenAPI:
95
+
96
+ ```bash
97
+ python scripts/build_catalog.py ../xquik/openapi.yaml
98
+ ```
99
+
100
+ Run checks:
101
+
102
+ ```bash
103
+ uv run --python 3.12 --extra dev ruff format --check .
104
+ uv run --python 3.12 --extra dev ruff check .
105
+ uv run --python 3.12 --extra dev basedpyright
106
+ uv run --python 3.12 --extra dev pytest --cov=hermes_tweet --cov=tests --cov-report=term-missing --cov-fail-under=100
107
+ uv run --python 3.12 --extra dev bandit -c pyproject.toml -r hermes_tweet scripts
108
+ uv run --python 3.12 --extra dev pip-audit
109
+ uv run --python 3.12 --extra dev python -m build
110
+ uv run --python 3.12 --extra dev twine check dist/*
111
+ ```
112
+
113
+ For a single local quality gate:
114
+
115
+ ```bash
116
+ uv run --python 3.12 --extra dev ruff format --check . && \
117
+ uv run --python 3.12 --extra dev ruff check . && \
118
+ uv run --python 3.12 --extra dev basedpyright && \
119
+ uv run --python 3.12 --extra dev pytest --cov=hermes_tweet --cov=tests --cov-report=term-missing --cov-fail-under=100 && \
120
+ uv run --python 3.12 --extra dev bandit -c pyproject.toml -r hermes_tweet scripts && \
121
+ uv run --python 3.12 --extra dev pip-audit && \
122
+ uv run --python 3.12 --extra dev python -m build && \
123
+ uv run --python 3.12 --extra dev twine check dist/*
124
+ ```
125
+
126
+ ## Relationship To TweetClaw
127
+
128
+ TweetClaw is the OpenClaw-native npm plugin. Hermes Tweet is the Hermes-native
129
+ Python plugin. Both use the same Xquik API contract.
130
+
131
+ ## Public Repo Metadata
132
+
133
+ Recommended GitHub description:
134
+
135
+ > Native Hermes Agent plugin for X/Twitter automation through Xquik.
136
+
137
+ Recommended topics:
138
+
139
+ `hermes-agent`, `hermes-plugin`, `hermes`, `twitter`, `x`, `x-api`,
140
+ `x-automation`, `xquik`, `tweet`, `automation`, `social-media`,
141
+ `social-media-automation`, `ai-agent`, `mcp`, `agent-tools`, `twitter-api`,
142
+ `twitter-automation`, `x-twitter`, `apify`, `python`
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from . import schemas
9
+ from .tools import (
10
+ action_enabled,
11
+ call_action,
12
+ call_read,
13
+ check_api_available,
14
+ explore,
15
+ xstatus,
16
+ xtrends,
17
+ )
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ TOOLSET = "hermes-tweet"
22
+
23
+
24
+ def _register_bundled_skills(ctx: Any) -> None:
25
+ skills_dir = Path(__file__).parent / "skills"
26
+ for child in sorted(skills_dir.iterdir()):
27
+ skill_md = child / "SKILL.md"
28
+ if child.is_dir() and skill_md.exists():
29
+ ctx.register_skill(child.name, skill_md)
30
+
31
+
32
+ def register(ctx: Any) -> None:
33
+ ctx.register_tool(
34
+ name="tweet_explore",
35
+ toolset=TOOLSET,
36
+ schema=schemas.TWEET_EXPLORE,
37
+ handler=explore,
38
+ is_async=False,
39
+ description="Search the bundled Xquik endpoint catalog.",
40
+ emoji="🔎",
41
+ )
42
+
43
+ ctx.register_tool(
44
+ name="tweet_read",
45
+ toolset=TOOLSET,
46
+ schema=schemas.TWEET_READ,
47
+ handler=call_read,
48
+ check_fn=check_api_available,
49
+ requires_env=["XQUIK_API_KEY"],
50
+ is_async=False,
51
+ description="Call catalog-listed read-only Xquik endpoints.",
52
+ emoji="📖",
53
+ )
54
+
55
+ ctx.register_tool(
56
+ name="tweet_action",
57
+ toolset=TOOLSET,
58
+ schema=schemas.TWEET_ACTION,
59
+ handler=call_action,
60
+ check_fn=action_enabled,
61
+ requires_env=["XQUIK_API_KEY", "HERMES_TWEET_ENABLE_ACTIONS"],
62
+ is_async=False,
63
+ description="Call write-like or private Xquik endpoints.",
64
+ emoji="✍️",
65
+ )
66
+
67
+ ctx.register_command(
68
+ "xstatus",
69
+ handler=xstatus,
70
+ description="Show Xquik account and usage status",
71
+ )
72
+ ctx.register_command("xtrends", handler=xtrends, description="Show current X trends")
73
+
74
+ _register_bundled_skills(ctx)
75
+ logger.info(
76
+ "Hermes Tweet loaded with actions=%s",
77
+ os.getenv("HERMES_TWEET_ENABLE_ACTIONS", "false"),
78
+ )
79
+
80
+
81
+ __all__ = ["register"]
@@ -0,0 +1,164 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from importlib.resources import files
6
+ from typing import Any, cast
7
+
8
+ DEFAULT_LIMIT = 25
9
+ MAX_LIMIT = 100
10
+ PACKAGE_NAME = "hermes_tweet"
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Endpoint:
15
+ category: str
16
+ free: bool
17
+ method: str
18
+ path: str
19
+ summary: str
20
+ parameters: tuple[dict[str, Any], ...] = ()
21
+ response_shape: str | None = None
22
+ mpp: dict[str, str] | None = None
23
+ action: bool = False
24
+
25
+ def to_dict(self) -> dict[str, Any]:
26
+ data: dict[str, Any] = {
27
+ "category": self.category,
28
+ "free": self.free,
29
+ "method": self.method,
30
+ "path": self.path,
31
+ "summary": self.summary,
32
+ "action": self.action,
33
+ }
34
+ if self.parameters:
35
+ data["parameters"] = list(self.parameters)
36
+ if self.response_shape:
37
+ data["responseShape"] = self.response_shape
38
+ if self.mpp:
39
+ data["mpp"] = self.mpp
40
+ return data
41
+
42
+
43
+ def _load_raw() -> list[dict[str, Any]]:
44
+ text = files(PACKAGE_NAME).joinpath("catalog_data.json").read_text(encoding="utf-8")
45
+ data = cast("object", json.loads(text))
46
+ items = cast("list[object]", data if isinstance(data, list) else [])
47
+ return [cast("dict[str, Any]", item) for item in items if isinstance(item, dict)]
48
+
49
+
50
+ def _tuple_parameters(value: Any) -> tuple[dict[str, Any], ...]:
51
+ items = cast("list[object]", value if isinstance(value, list) else [])
52
+ return tuple(cast("dict[str, Any]", item) for item in items if isinstance(item, dict))
53
+
54
+
55
+ def _endpoint(item: dict[str, Any]) -> Endpoint:
56
+ return Endpoint(
57
+ action=bool(item.get("action")),
58
+ category=str(item.get("category", "unknown")),
59
+ free=bool(item.get("free", False)),
60
+ method=str(item.get("method", "GET")).upper(),
61
+ mpp=item.get("mpp") if isinstance(item.get("mpp"), dict) else None,
62
+ parameters=_tuple_parameters(item.get("parameters")),
63
+ path=str(item.get("path", "")),
64
+ response_shape=str(item["responseShape"]) if item.get("responseShape") else None,
65
+ summary=str(item.get("summary", "")),
66
+ )
67
+
68
+
69
+ ENDPOINTS: tuple[Endpoint, ...] = tuple(_endpoint(item) for item in _load_raw())
70
+
71
+
72
+ def normalize_method(method: str | None) -> str:
73
+ return (method or "GET").upper()
74
+
75
+
76
+ def normalize_limit(value: Any) -> int:
77
+ if not isinstance(value, int):
78
+ return DEFAULT_LIMIT
79
+ return min(max(value, 1), MAX_LIMIT)
80
+
81
+
82
+ def _segments(path: str) -> list[str]:
83
+ normalized = path.removesuffix("/")
84
+ return normalized.split("/")
85
+
86
+
87
+ def matches_path(template: str, concrete: str) -> bool:
88
+ if template == concrete:
89
+ return True
90
+ template_segments = _segments(template)
91
+ concrete_segments = _segments(concrete)
92
+ if len(template_segments) != len(concrete_segments):
93
+ return False
94
+ for template_segment, concrete_segment in zip(
95
+ template_segments, concrete_segments, strict=True
96
+ ):
97
+ if template_segment.startswith("{") and template_segment.endswith("}"):
98
+ if not concrete_segment:
99
+ return False
100
+ continue
101
+ if template_segment.startswith(":"):
102
+ if not concrete_segment:
103
+ return False
104
+ continue
105
+ if template_segment != concrete_segment:
106
+ return False
107
+ return True
108
+
109
+
110
+ def find_endpoint(method: str, path: str) -> Endpoint | None:
111
+ normalized = normalize_method(method)
112
+ for endpoint in ENDPOINTS:
113
+ if endpoint.method == normalized and matches_path(endpoint.path, path):
114
+ return endpoint
115
+ return None
116
+
117
+
118
+ def _matches_query(endpoint: Endpoint, query: str) -> bool:
119
+ normalized = query.lower()
120
+ haystack = " ".join(
121
+ [
122
+ endpoint.category,
123
+ endpoint.method,
124
+ endpoint.path,
125
+ endpoint.summary,
126
+ endpoint.response_shape or "",
127
+ json.dumps(list(endpoint.parameters), ensure_ascii=False),
128
+ ]
129
+ ).lower()
130
+ return normalized in haystack
131
+
132
+
133
+ def explore(args: dict[str, Any]) -> list[dict[str, Any]]:
134
+ method = normalize_method(args.get("method")) if args.get("method") else None
135
+ category = str(args.get("category", "")).strip().lower()
136
+ path = str(args.get("path", "")).strip()
137
+ query = str(args.get("query", "")).strip()
138
+ limit = normalize_limit(args.get("limit"))
139
+ include_actions = bool(args.get("include_actions", False))
140
+
141
+ endpoints = ENDPOINTS
142
+ if method:
143
+ endpoints = tuple(endpoint for endpoint in endpoints if endpoint.method == method)
144
+ if category:
145
+ endpoints = tuple(
146
+ endpoint for endpoint in endpoints if endpoint.category.lower() == category
147
+ )
148
+ if path:
149
+ endpoints = tuple(
150
+ endpoint
151
+ for endpoint in endpoints
152
+ if path in endpoint.path or matches_path(endpoint.path, path)
153
+ )
154
+ if query:
155
+ endpoints = tuple(endpoint for endpoint in endpoints if _matches_query(endpoint, query))
156
+ if "free" in args:
157
+ endpoints = tuple(endpoint for endpoint in endpoints if endpoint.free is bool(args["free"]))
158
+ if "mpp" in args:
159
+ endpoints = tuple(
160
+ endpoint for endpoint in endpoints if (endpoint.mpp is not None) is bool(args["mpp"])
161
+ )
162
+ if not include_actions:
163
+ endpoints = tuple(endpoint for endpoint in endpoints if not endpoint.action)
164
+ return [endpoint.to_dict() for endpoint in endpoints[:limit]]