yandex-cli 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.
- yandex_cli-0.1.0.dist-info/METADATA +240 -0
- yandex_cli-0.1.0.dist-info/RECORD +115 -0
- yandex_cli-0.1.0.dist-info/WHEEL +4 -0
- yandex_cli-0.1.0.dist-info/entry_points.txt +3 -0
- yandex_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- ycli/__init__.py +13 -0
- ycli/cli.py +58 -0
- ycli/log.py +27 -0
- ycli/mcp.py +31 -0
- ycli/yandex/__init__.py +0 -0
- ycli/yandex/base.py +67 -0
- ycli/yandex/forms/__init__.py +1 -0
- ycli/yandex/forms/_base.py +17 -0
- ycli/yandex/forms/_clideps.py +20 -0
- ycli/yandex/forms/_deps.py +10 -0
- ycli/yandex/forms/_models.py +18 -0
- ycli/yandex/forms/answers/__init__.py +1 -0
- ycli/yandex/forms/answers/cli.py +25 -0
- ycli/yandex/forms/answers/client.py +57 -0
- ycli/yandex/forms/answers/mcp.py +15 -0
- ycli/yandex/forms/answers/models.py +54 -0
- ycli/yandex/forms/cli.py +16 -0
- ycli/yandex/forms/client.py +29 -0
- ycli/yandex/forms/mcp.py +13 -0
- ycli/yandex/forms/me/__init__.py +1 -0
- ycli/yandex/forms/me/cli.py +19 -0
- ycli/yandex/forms/me/client.py +25 -0
- ycli/yandex/forms/me/mcp.py +20 -0
- ycli/yandex/forms/me/models.py +18 -0
- ycli/yandex/forms/questions/__init__.py +1 -0
- ycli/yandex/forms/questions/cli.py +25 -0
- ycli/yandex/forms/questions/client.py +24 -0
- ycli/yandex/forms/questions/mcp.py +15 -0
- ycli/yandex/forms/questions/models.py +46 -0
- ycli/yandex/forms/surveys/__init__.py +1 -0
- ycli/yandex/forms/surveys/cli.py +26 -0
- ycli/yandex/forms/surveys/client.py +36 -0
- ycli/yandex/forms/surveys/mcp.py +26 -0
- ycli/yandex/forms/surveys/models.py +44 -0
- ycli/yandex/tracker/__init__.py +1 -0
- ycli/yandex/tracker/_base.py +14 -0
- ycli/yandex/tracker/_clideps.py +46 -0
- ycli/yandex/tracker/_deps.py +10 -0
- ycli/yandex/tracker/_models.py +48 -0
- ycli/yandex/tracker/changelog/__init__.py +1 -0
- ycli/yandex/tracker/changelog/cli.py +25 -0
- ycli/yandex/tracker/changelog/client.py +28 -0
- ycli/yandex/tracker/changelog/mcp.py +15 -0
- ycli/yandex/tracker/changelog/models.py +58 -0
- ycli/yandex/tracker/cli.py +26 -0
- ycli/yandex/tracker/client.py +39 -0
- ycli/yandex/tracker/comments/__init__.py +1 -0
- ycli/yandex/tracker/comments/cli.py +28 -0
- ycli/yandex/tracker/comments/client.py +37 -0
- ycli/yandex/tracker/comments/mcp.py +15 -0
- ycli/yandex/tracker/comments/models.py +34 -0
- ycli/yandex/tracker/issues/__init__.py +1 -0
- ycli/yandex/tracker/issues/cli.py +132 -0
- ycli/yandex/tracker/issues/client.py +92 -0
- ycli/yandex/tracker/issues/mcp.py +53 -0
- ycli/yandex/tracker/issues/models.py +73 -0
- ycli/yandex/tracker/issuetypes/__init__.py +1 -0
- ycli/yandex/tracker/issuetypes/cli.py +19 -0
- ycli/yandex/tracker/issuetypes/client.py +24 -0
- ycli/yandex/tracker/issuetypes/mcp.py +15 -0
- ycli/yandex/tracker/issuetypes/models.py +27 -0
- ycli/yandex/tracker/links/__init__.py +1 -0
- ycli/yandex/tracker/links/cli.py +43 -0
- ycli/yandex/tracker/links/client.py +37 -0
- ycli/yandex/tracker/links/mcp.py +15 -0
- ycli/yandex/tracker/links/models.py +56 -0
- ycli/yandex/tracker/linktypes/__init__.py +1 -0
- ycli/yandex/tracker/linktypes/cli.py +19 -0
- ycli/yandex/tracker/linktypes/client.py +24 -0
- ycli/yandex/tracker/linktypes/mcp.py +15 -0
- ycli/yandex/tracker/linktypes/models.py +28 -0
- ycli/yandex/tracker/mcp.py +23 -0
- ycli/yandex/tracker/priorities/__init__.py +1 -0
- ycli/yandex/tracker/priorities/cli.py +19 -0
- ycli/yandex/tracker/priorities/client.py +24 -0
- ycli/yandex/tracker/priorities/mcp.py +15 -0
- ycli/yandex/tracker/priorities/models.py +27 -0
- ycli/yandex/tracker/transitions/__init__.py +1 -0
- ycli/yandex/tracker/transitions/cli.py +34 -0
- ycli/yandex/tracker/transitions/client.py +52 -0
- ycli/yandex/tracker/transitions/mcp.py +15 -0
- ycli/yandex/tracker/transitions/models.py +27 -0
- ycli/yandex/tracker/worklog/__init__.py +1 -0
- ycli/yandex/tracker/worklog/cli.py +21 -0
- ycli/yandex/tracker/worklog/client.py +24 -0
- ycli/yandex/tracker/worklog/mcp.py +15 -0
- ycli/yandex/tracker/worklog/models.py +36 -0
- ycli/yandex/transport.py +116 -0
- ycli/yandex/wiki/__init__.py +1 -0
- ycli/yandex/wiki/_base.py +8 -0
- ycli/yandex/wiki/_clideps.py +20 -0
- ycli/yandex/wiki/_deps.py +10 -0
- ycli/yandex/wiki/attachments/__init__.py +0 -0
- ycli/yandex/wiki/attachments/cli.py +20 -0
- ycli/yandex/wiki/attachments/client.py +29 -0
- ycli/yandex/wiki/attachments/mcp.py +15 -0
- ycli/yandex/wiki/attachments/models.py +32 -0
- ycli/yandex/wiki/cli.py +14 -0
- ycli/yandex/wiki/client.py +27 -0
- ycli/yandex/wiki/comments/__init__.py +0 -0
- ycli/yandex/wiki/comments/cli.py +20 -0
- ycli/yandex/wiki/comments/client.py +29 -0
- ycli/yandex/wiki/comments/mcp.py +15 -0
- ycli/yandex/wiki/comments/models.py +40 -0
- ycli/yandex/wiki/mcp.py +11 -0
- ycli/yandex/wiki/pages/__init__.py +0 -0
- ycli/yandex/wiki/pages/cli.py +64 -0
- ycli/yandex/wiki/pages/client.py +74 -0
- ycli/yandex/wiki/pages/mcp.py +32 -0
- ycli/yandex/wiki/pages/models.py +82 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yandex-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interact with Yandex 360 services (Wiki, Tracker, Forms, …) from a CLI, an MCP server, or a Python SDK.
|
|
5
|
+
Project-URL: Homepage, https://github.com/bim-ba/ycli
|
|
6
|
+
Project-URL: Repository, https://github.com/bim-ba/ycli
|
|
7
|
+
Project-URL: Issues, https://github.com/bim-ba/ycli/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/bim-ba/ycli/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Sava Znatnov <careless.sava@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cli,fastmcp,forms,mcp,sdk,tracker,typer,wiki,yandex,yandex-360
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Requires-Dist: loguru>=0.7.3
|
|
24
|
+
Requires-Dist: pydantic>=2.13.4
|
|
25
|
+
Requires-Dist: requests>=2.34.2
|
|
26
|
+
Requires-Dist: typer>=0.26.8
|
|
27
|
+
Requires-Dist: uplink>=0.10.0
|
|
28
|
+
Provides-Extra: mcp
|
|
29
|
+
Requires-Dist: fastmcp>=3.4.2; extra == 'mcp'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
|
|
34
|
+
# ycli
|
|
35
|
+
|
|
36
|
+
**One Yandex 360 toolkit — four ways to use it.**
|
|
37
|
+
Drive **Tracker**, **Wiki**, and **Forms** from a CLI, an MCP server, a Python SDK,
|
|
38
|
+
or a Claude Code plugin. Built for AI agents first — pleasant for humans too.
|
|
39
|
+
|
|
40
|
+
[](https://github.com/bim-ba/ycli/actions/workflows/ci.yml)
|
|
41
|
+
[](https://github.com/bim-ba/ycli)
|
|
42
|
+
[](https://www.python.org/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
[](https://modelcontextprotocol.io/)
|
|
45
|
+
[](plugins/yandex-360/)
|
|
46
|
+
|
|
47
|
+
<img src="https://raw.githubusercontent.com/bim-ba/ycli/main/docs/assets/demo.gif" alt="ycli in action" width="760">
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
## Why ycli
|
|
52
|
+
|
|
53
|
+
- 🧩 **One SDK, four surfaces** — write logic once, use it as a CLI, an MCP server, a Python
|
|
54
|
+
library, or a Claude Code plugin.
|
|
55
|
+
- 🤖 **Agent-native** — the MCP server exposes read-only `tracker_*`, `wiki_*`, `forms_*`
|
|
56
|
+
tools so agents explore safely; writes stay in the CLI/SDK.
|
|
57
|
+
- 🛡️ **Trustworthy** — typed pydantic models, the real Yandex API quirks handled for you,
|
|
58
|
+
and a test suite kept at **100% coverage**.
|
|
59
|
+
- ⚡ **Zero-friction start** — `uv add yandex-cli`, two env vars, go.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv add yandex-cli # CLI + Python SDK
|
|
65
|
+
uv add 'yandex-cli[mcp]' # …plus the MCP server (`ycli mcp`)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run it without installing, or install it as a standalone tool:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uvx yandex-cli --help # one-off, no install
|
|
72
|
+
uv tool install yandex-cli # persistent CLI
|
|
73
|
+
uv tool install 'yandex-cli[mcp]' # …with the MCP server
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`pip install yandex-cli` works too. The CLI ships as both `yandex-cli` and the short `ycli`.
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
Pick the surface that fits how you work.
|
|
81
|
+
|
|
82
|
+
<details open>
|
|
83
|
+
<summary><b>CLI</b></summary>
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv add yandex-cli
|
|
87
|
+
ycli --help
|
|
88
|
+
ycli tracker issues get TRACKER-1
|
|
89
|
+
ycli wiki pages get onboarding
|
|
90
|
+
```
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
<details>
|
|
94
|
+
<summary><b>MCP server</b> (read-only)</summary>
|
|
95
|
+
|
|
96
|
+
Run it over stdio (needs the `mcp` extra):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
ycli mcp
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Point an MCP client at it — no prior install needed via `uvx` (tools are namespaced
|
|
103
|
+
`tracker_*`, `wiki_*`, `forms_*`):
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"yandex": {
|
|
109
|
+
"command": "uvx",
|
|
110
|
+
"args": ["--from", "yandex-cli[mcp]", "ycli", "mcp"],
|
|
111
|
+
"env": {
|
|
112
|
+
"YANDEX_ID_OAUTH_TOKEN": "...",
|
|
113
|
+
"YANDEX_ID_ORGANIZATION_ID": "..."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
</details>
|
|
120
|
+
|
|
121
|
+
<details>
|
|
122
|
+
<summary><b>Python SDK</b></summary>
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from ycli.yandex.tracker.client import TrackerClient
|
|
126
|
+
|
|
127
|
+
tracker = TrackerClient.from_env()
|
|
128
|
+
issue = tracker.issues.get("TRACKER-1")
|
|
129
|
+
print(issue.summary)
|
|
130
|
+
```
|
|
131
|
+
</details>
|
|
132
|
+
|
|
133
|
+
<details>
|
|
134
|
+
<summary><b>Claude Code plugin</b></summary>
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
/plugin marketplace add bim-ba/ycli
|
|
138
|
+
/plugin install yandex-360@ycli
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Teaches an agent to drive Yandex 360 through `ycli` — including the real API quirks.
|
|
142
|
+
See [`plugins/yandex-360/`](plugins/yandex-360/).
|
|
143
|
+
</details>
|
|
144
|
+
|
|
145
|
+
## Skills (Claude Code plugin)
|
|
146
|
+
|
|
147
|
+
| Skill | Use for |
|
|
148
|
+
|-------|---------|
|
|
149
|
+
| `yandex-360` | Entry point — install + auth, pick a surface (CLI/MCP/SDK), route to a domain |
|
|
150
|
+
| `yandex-360-tracker` | Issues, epics, comments, transitions, links, worklog, changelog |
|
|
151
|
+
| `yandex-360-wiki` | Wiki pages, page tree, comments, attachments, YFM authoring |
|
|
152
|
+
| `yandex-360-forms` | Forms, questions/schema, responses, pagination |
|
|
153
|
+
|
|
154
|
+
The skills encode the read/write commands **and** the gnarly Yandex API quirks
|
|
155
|
+
(epic-vs-parent, transition discovery, permanent wiki slugs, `fields=` rules, Forms
|
|
156
|
+
host/header traps, answers pagination).
|
|
157
|
+
|
|
158
|
+
## What's covered
|
|
159
|
+
|
|
160
|
+
Reads ship across **SDK + CLI + MCP**; writes across **SDK + CLI** only (the MCP server is
|
|
161
|
+
read-only by design).
|
|
162
|
+
|
|
163
|
+
### Tracker
|
|
164
|
+
|
|
165
|
+
| Resource | Operations | SDK | CLI | MCP |
|
|
166
|
+
|----------|-----------|:---:|:---:|:---:|
|
|
167
|
+
| issues | get · full · search · list · count | ✅ | ✅ | ✅ |
|
|
168
|
+
| issues | create · update | ✅ | ✅ | — |
|
|
169
|
+
| comments | list | ✅ | ✅ | ✅ |
|
|
170
|
+
| comments | add | ✅ | ✅ | — |
|
|
171
|
+
| links | list | ✅ | ✅ | ✅ |
|
|
172
|
+
| links | add | ✅ | ✅ | — |
|
|
173
|
+
| transitions | list | ✅ | ✅ | ✅ |
|
|
174
|
+
| transitions | execute | ✅ | ✅ | — |
|
|
175
|
+
| worklog · changelog · priorities · issuetypes · linktypes | list | ✅ | ✅ | ✅ |
|
|
176
|
+
|
|
177
|
+
### Wiki
|
|
178
|
+
|
|
179
|
+
| Resource | Operations | SDK | CLI | MCP |
|
|
180
|
+
|----------|-----------|:---:|:---:|:---:|
|
|
181
|
+
| pages | get · descendants | ✅ | ✅ | ✅ |
|
|
182
|
+
| pages | meta (metadata-only) | — | — | ✅ |
|
|
183
|
+
| pages | create · update | ✅ | ✅ | — |
|
|
184
|
+
| comments | list | ✅ | ✅ | ✅ |
|
|
185
|
+
| attachments | list | ✅ | ✅ | ✅ |
|
|
186
|
+
|
|
187
|
+
### Forms (read-only today)
|
|
188
|
+
|
|
189
|
+
| Resource | Operations | SDK | CLI | MCP |
|
|
190
|
+
|----------|-----------|:---:|:---:|:---:|
|
|
191
|
+
| me | get (whoami) | ✅ | ✅ | ✅ |
|
|
192
|
+
| surveys | list · get | ✅ | ✅ | ✅ |
|
|
193
|
+
| questions | list | ✅ | ✅ | ✅ |
|
|
194
|
+
| answers | list (drains all pages) | ✅ | ✅ | ✅ |
|
|
195
|
+
|
|
196
|
+
> **Mail and more — coming.** See [`docs/api-coverage.md`](docs/api-coverage.md) for the full
|
|
197
|
+
> gap analysis and prioritized roadmap.
|
|
198
|
+
|
|
199
|
+
## Configure
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
cp .env.example .env
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
YANDEX_ID_OAUTH_TOKEN=... # get one at https://oauth.yandex.ru/
|
|
207
|
+
YANDEX_ID_ORGANIZATION_ID=... # from the Yandex 360 admin panel
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Header casing differs per service (Tracker `X-Org-ID`, Wiki/Forms `X-Org-Id`) — ycli
|
|
211
|
+
handles it for you.
|
|
212
|
+
|
|
213
|
+
## Project layout
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
src/ycli/
|
|
217
|
+
├── cli.py # root Typer CLI → `ycli` / `yandex-cli`
|
|
218
|
+
├── mcp.py # root FastMCP server → `ycli mcp` (read-only, `[mcp]` extra)
|
|
219
|
+
├── log.py # central loguru config
|
|
220
|
+
└── yandex/
|
|
221
|
+
├── tracker/ # per-domain SDK …
|
|
222
|
+
├── wiki/ # each resource group has:
|
|
223
|
+
└── forms/ # client.py · cli.py · mcp.py · models.py
|
|
224
|
+
plugins/yandex-360/ # distributable Claude Code plugin (skills + instructions)
|
|
225
|
+
docs/references/ # vendored Yandex API reference docs
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
uv sync --all-extras # --all-extras pulls in the `mcp` extra the tests exercise
|
|
232
|
+
uv run pytest # 100% coverage gate; HTTP stubbed with `responses` (no live network)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for conventions. Contributions welcome — the
|
|
236
|
+
[coverage roadmap](docs/api-coverage.md) is a good place to find a first issue.
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
[MIT](LICENSE) © 2026 Sava Znatnov
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
ycli/__init__.py,sha256=9AKPqZVknGZpy0-62HszaqsaCNCervq6OSoW35Kr5do,510
|
|
2
|
+
ycli/cli.py,sha256=tdslQlHGXXJexMjIHCE83MiPADUHjukCsKJrLTbNDGA,1588
|
|
3
|
+
ycli/log.py,sha256=C15uskRhYI6hJeV1sBKWPVuwcCzznOLxBhj_ZhFVWwY,823
|
|
4
|
+
ycli/mcp.py,sha256=STQpwzqpWj7dNyKKv3Bpm2YqOCgIo0xAfnL4_ZXslTo,942
|
|
5
|
+
ycli/yandex/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
ycli/yandex/base.py,sha256=-n3QptRL3qXiiV9RMug91c82Vov1dFB6NTFrOSPdSBs,2672
|
|
7
|
+
ycli/yandex/transport.py,sha256=LQVznvfAfVpTK6uGf_xXnOBMCLWtebqEUJIpIp2m-UY,4233
|
|
8
|
+
ycli/yandex/forms/__init__.py,sha256=3H1-e98MG9NcDuVfLlaU0STglMk_XpGRv6hqWR_Tgb4,97
|
|
9
|
+
ycli/yandex/forms/_base.py,sha256=tWs278TeEMarqFLqw52jEvaYEmwQuu7UHj4TakqxiOQ,652
|
|
10
|
+
ycli/yandex/forms/_clideps.py,sha256=b-l0S_rxmFGqgwQvc7RfKIPvaJ74LGs8RV8pX70uveY,593
|
|
11
|
+
ycli/yandex/forms/_deps.py,sha256=cu2SSzp94-QqY0VBRNmuW4EU1YbVegtgvF3eQa5clKQ,380
|
|
12
|
+
ycli/yandex/forms/_models.py,sha256=VaMPJBTx6v6C3rUjdzuybtb-60E1lmyCoI8-l-Q-UFM,550
|
|
13
|
+
ycli/yandex/forms/cli.py,sha256=iD-_dDcLp05LGwkdJsJHKy-MFnrHF9eghEoFOQA_rz4,567
|
|
14
|
+
ycli/yandex/forms/client.py,sha256=Q50OgsYPh6ZxxC6yYu1YNNgNid2icL9Wc5OIq1ILlD8,1053
|
|
15
|
+
ycli/yandex/forms/mcp.py,sha256=EZsZ_pyxUBAHTfaOIwyyu8X9TcEr0W9V6K1AwiO3Bs0,465
|
|
16
|
+
ycli/yandex/forms/answers/__init__.py,sha256=dNqjHD3dY1I1J5RxxtRYF2PpBjWHsQSdXwBdNmWb0G8,52
|
|
17
|
+
ycli/yandex/forms/answers/cli.py,sha256=XHXVa-kY2ffx3GnWihHSFh8MTPfW5P0DIw4aCc8_sGQ,757
|
|
18
|
+
ycli/yandex/forms/answers/client.py,sha256=KTIN2TFy8TuSctoL0eM1yrJFkwonqwjpEdtiP18x0g0,2426
|
|
19
|
+
ycli/yandex/forms/answers/mcp.py,sha256=nuk7NtE98N2IpGw_8tlCEBxlsktMGjS73_UqsBWjEBs,592
|
|
20
|
+
ycli/yandex/forms/answers/models.py,sha256=JPnoJk57NgJs2pKZvbnVTpmJ_m-xvAFk5iOFIo385Ys,1522
|
|
21
|
+
ycli/yandex/forms/me/__init__.py,sha256=_TMNel8tYOoQat4LN3wf5M6LY106HuWOREM1SCL9iWY,40
|
|
22
|
+
ycli/yandex/forms/me/cli.py,sha256=Onhp_QeKJlULn9UkEhobC8hWxxwOM0dqR4hovJtmJXY,534
|
|
23
|
+
ycli/yandex/forms/me/client.py,sha256=hqOn7L3RLYGc95tWaY5HME6j1JBdUU8wmCLTO-YWFTI,767
|
|
24
|
+
ycli/yandex/forms/me/mcp.py,sha256=bO2hC6XNBeqO1luALzFJyC3XnI7cgW70KJdWqcSmOD4,847
|
|
25
|
+
ycli/yandex/forms/me/models.py,sha256=hH7N1RPuNTyycFXDtmbqOuddSk74p0VM4jx0oaIKOX4,479
|
|
26
|
+
ycli/yandex/forms/questions/__init__.py,sha256=6aCxCxcJ_tQU6htwLjiiPySePzLi0Rhb86j7nOmNUls,54
|
|
27
|
+
ycli/yandex/forms/questions/cli.py,sha256=b2pIMepNYH3IRU3c_L0PpN9heSCriLnODs8os60kkVo,737
|
|
28
|
+
ycli/yandex/forms/questions/client.py,sha256=xlxFll9DWUsRVn66KIipExR0QQ7uoBv8BHVefo9FMk4,903
|
|
29
|
+
ycli/yandex/forms/questions/mcp.py,sha256=aswfFs0k9nr5bg_Jz4D4j57LsBRK1QYbxYCfl03rF_0,598
|
|
30
|
+
ycli/yandex/forms/questions/models.py,sha256=20VF_sbo3jKJC_DZ66YXWBHJLpWCKCUFpOdBU8noBtA,1261
|
|
31
|
+
ycli/yandex/forms/surveys/__init__.py,sha256=sCdpP8bV3NmceWZu9E6lV9CBJY83wIifywUVNN5Nz58,39
|
|
32
|
+
ycli/yandex/forms/surveys/cli.py,sha256=Oc0nHrcV44HBpZbiK8mH2P_QNZfQx5hNe9Cmlxa2O4I,771
|
|
33
|
+
ycli/yandex/forms/surveys/client.py,sha256=0m-NKj7s0sBT_ad86lG566FuGaKGGsWHXpm3AwSV8PU,1267
|
|
34
|
+
ycli/yandex/forms/surveys/mcp.py,sha256=wTlUGu3KjI8bc-mjKYSm0XOSUBmq-LRtR2mi3dWfsyo,1105
|
|
35
|
+
ycli/yandex/forms/surveys/models.py,sha256=W4f11gTy90mMQ1DUBBCb3yuFrOnzLrbIXbfmQJ8uFgE,1245
|
|
36
|
+
ycli/yandex/tracker/__init__.py,sha256=KoKKmC2z4XdviENgX-8tuswWrj1mwTYptPbB5q-Dcnc,87
|
|
37
|
+
ycli/yandex/tracker/_base.py,sha256=JCm6wLjlbxr8KmbLbYFg1wLM4uF2axTyuT-9_pLyTrY,520
|
|
38
|
+
ycli/yandex/tracker/_clideps.py,sha256=6JQgnOxBbIsWJrTpEvfEunYJGTZ_NqQJp9tV4h3ORGU,1502
|
|
39
|
+
ycli/yandex/tracker/_deps.py,sha256=jPJoguPcyUWiHbUWdFSkNKgXDmQqKdb0aTm9INePLAE,400
|
|
40
|
+
ycli/yandex/tracker/_models.py,sha256=nFv1mnHkiwQD8Otp4qd33hETdQUdaiBqVuZmaMyGb74,1249
|
|
41
|
+
ycli/yandex/tracker/cli.py,sha256=c4C-hiecZK1gV-oaWAuygWOSf1B-cCGV3bKjBTqxKR4,1082
|
|
42
|
+
ycli/yandex/tracker/client.py,sha256=EndiXpxwxQM8BCHrwZuSgcSLtr0nFaYnJtVNgmFWNwQ,1731
|
|
43
|
+
ycli/yandex/tracker/mcp.py,sha256=52C8SRrRd_v9YVLKmgkwdDfnjOJpuu_mSM-4w-AlR7U,952
|
|
44
|
+
ycli/yandex/tracker/changelog/__init__.py,sha256=y7D6KKt27ipqIQBG04bMLsvFLb0MyX5Scz_3Kk7CqQY,56
|
|
45
|
+
ycli/yandex/tracker/changelog/cli.py,sha256=K1b7m6AdDTtCUi-NFdFWLblNfxXOXX6pMtZ1aWI9qgk,725
|
|
46
|
+
ycli/yandex/tracker/changelog/client.py,sha256=BofpYqh8_42mRu4T-8az8o08u4jOUimOuGndH8rywcg,1014
|
|
47
|
+
ycli/yandex/tracker/changelog/mcp.py,sha256=LZIhSsp5TZ8VKYa6rWf00mN-iBiYw0vbxDxKPdRELW8,584
|
|
48
|
+
ycli/yandex/tracker/changelog/models.py,sha256=gtFnDJIIRqzwc7qZyY1tHqz8HnpkfShzVaKT9QWG9sA,1763
|
|
49
|
+
ycli/yandex/tracker/comments/__init__.py,sha256=aG_cyIWMy3k34S0q8CBnX9LSoFe8D9NMhazhxU1wZRE,55
|
|
50
|
+
ycli/yandex/tracker/comments/cli.py,sha256=RtxoMpK5to39lSUmxzg3F-Z8Q_gDPpVH0T086jYWJt4,848
|
|
51
|
+
ycli/yandex/tracker/comments/client.py,sha256=jRTKygQEL7TSgptNN8YO6aJs2zIqYiVUsg-p3hLf5fg,1384
|
|
52
|
+
ycli/yandex/tracker/comments/mcp.py,sha256=b7T_dMlflOxCk4MilYjYbA7fosB5ROPeT3pZWc23MiY,563
|
|
53
|
+
ycli/yandex/tracker/comments/models.py,sha256=_EZ8YjyR8KMa9H4SLlGw8HfNC61pCZv0GcCgilEx2GU,1010
|
|
54
|
+
ycli/yandex/tracker/issues/__init__.py,sha256=b6X_MBfw3hgUxvbSbwEJQeo4PGz-6CFQfMWZu9F72G4,40
|
|
55
|
+
ycli/yandex/tracker/issues/cli.py,sha256=a5IPdVcru0Rznz58ok5OAfspT7vrV7hfFMRtIFYJlI0,5176
|
|
56
|
+
ycli/yandex/tracker/issues/client.py,sha256=fJ_DHtTaFvdqkqO28JzTcDSENOQUVXzde4XtreaCcsY,3519
|
|
57
|
+
ycli/yandex/tracker/issues/mcp.py,sha256=N3-9K078yuW8qhI-rmjOekILQqSa09noghBTSjh9WHs,2051
|
|
58
|
+
ycli/yandex/tracker/issues/models.py,sha256=SUuYDPrCjIm-bbgiqRItdbiEc-M085WFobDlvDSxs7k,2190
|
|
59
|
+
ycli/yandex/tracker/issuetypes/__init__.py,sha256=z33PeF-LM5Uj8FLOyUgVx8mTS08IqjMol6E8JeTjVNI,44
|
|
60
|
+
ycli/yandex/tracker/issuetypes/cli.py,sha256=5P-QSBPSIWncKkz-si_k3vu-89yVjKP2sXtT-jpBnHU,480
|
|
61
|
+
ycli/yandex/tracker/issuetypes/client.py,sha256=SSq4TwVNrv_20YiuIPurMlBTnMuOJmojpUt-ZIftoy8,773
|
|
62
|
+
ycli/yandex/tracker/issuetypes/mcp.py,sha256=ekGcf2mX87XDZHze67NG7X8nbmab4inl4I7DRb-5MGs,576
|
|
63
|
+
ycli/yandex/tracker/issuetypes/models.py,sha256=_8jGU_4tPLqBNLLIoeHwXlhfBLY_s6z60Q1ySPu4hD8,649
|
|
64
|
+
ycli/yandex/tracker/links/__init__.py,sha256=0KN1uWLFmXXA7ITTYVZdPQhWMU7wcRsCBe6WS84Zbpk,52
|
|
65
|
+
ycli/yandex/tracker/links/cli.py,sha256=TBz07drv1NG_1HpBWbBTcvOi-rcEQMCJpqhb3st6vbw,1325
|
|
66
|
+
ycli/yandex/tracker/links/client.py,sha256=tnGsLZ2JvbtsPyJsjblSCGsmaob1rWc3F3r2-s2LdcU,1396
|
|
67
|
+
ycli/yandex/tracker/links/mcp.py,sha256=YQgpeVmkO5EEtWGbK5crP6O_Qoqj2NVg1Df8ORiYkUM,572
|
|
68
|
+
ycli/yandex/tracker/links/models.py,sha256=NuXj1mlFbceZDm28cc2N254kG53OOKsRZ3Z7M6HbtJ4,1516
|
|
69
|
+
ycli/yandex/tracker/linktypes/__init__.py,sha256=a2HGuqI-f9_6cKYsFeb_c37ramvnK4OhthnaRLBekNE,43
|
|
70
|
+
ycli/yandex/tracker/linktypes/cli.py,sha256=Wsc3nv1-f9El_r70cFIRXC2CCPyNseVRN5lfxIqHQdI,474
|
|
71
|
+
ycli/yandex/tracker/linktypes/client.py,sha256=TplWV3T1jiY8BUQY9pb8Ldq4kE4ial2kxGqtCrZN8u8,766
|
|
72
|
+
ycli/yandex/tracker/linktypes/mcp.py,sha256=vl7ajmWZE1qPhetAxXBq3sG2pc8G9NjsJRGKfCTft0c,580
|
|
73
|
+
ycli/yandex/tracker/linktypes/models.py,sha256=85e1O2RVrr3qiO-VPvevHiZFT2bA4-SMgFiO0TJ85ss,688
|
|
74
|
+
ycli/yandex/tracker/priorities/__init__.py,sha256=fXwqCURcaJklbW3vsnVQjSVUJSMyBrrbjY5rt2k22X4,44
|
|
75
|
+
ycli/yandex/tracker/priorities/cli.py,sha256=93fw5KVuoIb98q42lIzZtXBx7wNY2ZpJsfWzzOOFNwc,477
|
|
76
|
+
ycli/yandex/tracker/priorities/client.py,sha256=QvA1BQOsNhPWz5HA-HcX7yvPHAL16YQjJlqnbQnQQ2U,771
|
|
77
|
+
ycli/yandex/tracker/priorities/mcp.py,sha256=Lg6s4wSC0YBKqn5c33-TV7R4oIh9vG_VH0M8Epksls8,575
|
|
78
|
+
ycli/yandex/tracker/priorities/models.py,sha256=gJnTHKCDkSI2qPPBMdTSpXujfBcQVtlE-PVRW7eW4gU,648
|
|
79
|
+
ycli/yandex/tracker/transitions/__init__.py,sha256=xkcIidfS5M4Ee_7lGhb8pOp09pVIwPJRZlWo0RbIK-c,58
|
|
80
|
+
ycli/yandex/tracker/transitions/cli.py,sha256=TM9jAqsfD5rWYebqYq804fV7gPdANkEaorpB7ctQ7ms,1164
|
|
81
|
+
ycli/yandex/tracker/transitions/client.py,sha256=4PJZ-In_oqaQVu52-RIgTcm_KhOQfmii-pgaMwZnd8E,2276
|
|
82
|
+
ycli/yandex/tracker/transitions/mcp.py,sha256=mB-sB9sLJdubHknNgTsbWjlM2qVIazsBTmqG2k1SUTA,603
|
|
83
|
+
ycli/yandex/tracker/transitions/models.py,sha256=bOeOgUg1xBaCNmh0goAydqBH-ZgvnC9hDPOB1jblonc,683
|
|
84
|
+
ycli/yandex/tracker/worklog/__init__.py,sha256=xqxT7vujOEX74DU6FY5g8U8pj_6dbkDKl2WSOTFaztA,54
|
|
85
|
+
ycli/yandex/tracker/worklog/cli.py,sha256=NTwZHEgLRdrH5joExsLZEpGVQrtR0ax59IXaug4e0_o,593
|
|
86
|
+
ycli/yandex/tracker/worklog/client.py,sha256=YzKzMx0PS8VyJ3MHLAlMzD8GL91rH9gRJMireRhT020,830
|
|
87
|
+
ycli/yandex/tracker/worklog/mcp.py,sha256=6nx_hr55ocEo3hnecFwo0qFWwlqpE4tgs5c4WFjVo08,573
|
|
88
|
+
ycli/yandex/tracker/worklog/models.py,sha256=a1RAwAo6u9XTIhXHS3TyJ7_e2QdUPeEo0sykMhsn6Ws,1088
|
|
89
|
+
ycli/yandex/wiki/__init__.py,sha256=uKh1kboi42K9VPB2yZbGIwB6Hq9AL3r1-HU-ppoRzJk,94
|
|
90
|
+
ycli/yandex/wiki/_base.py,sha256=L0DeYDZt7xqhcx6nlSzs_1uDcIp2H1LVyDC0ijclj1g,252
|
|
91
|
+
ycli/yandex/wiki/_clideps.py,sha256=B_hSr5ShMoh79nUjwHJNV6jrqcdVX0jowByacF0yRn8,648
|
|
92
|
+
ycli/yandex/wiki/_deps.py,sha256=cC1CGAAgeyUGYPgMH4ToPQYAlcUTaWGxyurZ7UC2Kec,370
|
|
93
|
+
ycli/yandex/wiki/cli.py,sha256=dBzttiXZVFfriK3EgHDCY1uCZhI2BT7S-Xuqmz4SAO4,490
|
|
94
|
+
ycli/yandex/wiki/client.py,sha256=TJMle-VPZ9mppFFoSx5J1OggrhG-fQc76jax_soc3xk,971
|
|
95
|
+
ycli/yandex/wiki/mcp.py,sha256=r3YsbDXJiYiku_M745LM35SX7HhpRctNAGU3e2v_G74,381
|
|
96
|
+
ycli/yandex/wiki/attachments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
97
|
+
ycli/yandex/wiki/attachments/cli.py,sha256=szl5jw3hdfiECUk4rhrGYYmZn9cp6h2-0_2JEAppecw,599
|
|
98
|
+
ycli/yandex/wiki/attachments/client.py,sha256=c3P60xhC9CxuvE8Wysl4r-YGQ_xK29LthBdtNYcGinQ,990
|
|
99
|
+
ycli/yandex/wiki/attachments/mcp.py,sha256=epzNdbc9mroMB7NG8O4guH8kKMPEikANZ4C9-JDh2dI,569
|
|
100
|
+
ycli/yandex/wiki/attachments/models.py,sha256=TEd88ADuaNPNX-4o43JTcDak6T4K7ID2h7mIt_vNEyY,872
|
|
101
|
+
ycli/yandex/wiki/comments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
|
+
ycli/yandex/wiki/comments/cli.py,sha256=JmPPb7aye9JyizsDG9x85XavuXZBE9ShqHZpIAQMRHA,581
|
|
103
|
+
ycli/yandex/wiki/comments/client.py,sha256=drLrjU0dQyZ5ZRCKvtjFmfFFDQJbhZL925VdTif4AfE,982
|
|
104
|
+
ycli/yandex/wiki/comments/mcp.py,sha256=rckwlf2wo4ZltZ2VykGRMvLOcOn6iiELyJq8jB1AFjw,545
|
|
105
|
+
ycli/yandex/wiki/comments/models.py,sha256=iAcBh20KhHV5pUPBsdOtfKVZmLcRuc5zM-wtNdR7pTc,1046
|
|
106
|
+
ycli/yandex/wiki/pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
107
|
+
ycli/yandex/wiki/pages/cli.py,sha256=2q_HdtyEyElcBfcoYtdSLLtqckKxUg05ol9ccU9OucE,2329
|
|
108
|
+
ycli/yandex/wiki/pages/client.py,sha256=VeL3w3NwqGUZuDY7hNZTYyFeILd1NpCBW8GTcnBC_uc,2858
|
|
109
|
+
ycli/yandex/wiki/pages/mcp.py,sha256=K27u4tTDJ9L0-ZnTViiHWKkp5xvY8voS1wANumznnNk,1241
|
|
110
|
+
ycli/yandex/wiki/pages/models.py,sha256=d4oW4M1efuCOUsUkhP0W7cwXYhsQGTWS_FS1I0LisOY,2270
|
|
111
|
+
yandex_cli-0.1.0.dist-info/METADATA,sha256=_fB2wMJyWwOg0JkPIa8ElzaHU17x4nNChjY-sLVqAzg,8056
|
|
112
|
+
yandex_cli-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
113
|
+
yandex_cli-0.1.0.dist-info/entry_points.txt,sha256=dHDIa0h--BaZ4vEOsj6cO0XRrwXK2op7WrFXswOnpTU,66
|
|
114
|
+
yandex_cli-0.1.0.dist-info/licenses/LICENSE,sha256=MO2MsGmmpbkFPOptoyGxPnyKE8ZiBdHTbt35RCxx1Zg,1069
|
|
115
|
+
yandex_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sava Znatnov
|
|
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.
|
ycli/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""ycli — interact with Yandex 360 services (Wiki, Tracker, Forms, …).
|
|
2
|
+
|
|
3
|
+
One codebase, many surfaces: a Typer CLI (``ycli``), a FastMCP server (``ycli mcp``),
|
|
4
|
+
and an importable Python SDK under ``ycli.yandex``. Distributed on PyPI as ``yandex-cli``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import version
|
|
10
|
+
|
|
11
|
+
# Single source of truth: the version declared in pyproject.toml (read from installed
|
|
12
|
+
# metadata under the distribution name `yandex-cli`).
|
|
13
|
+
__version__ = version("yandex-cli")
|
ycli/cli.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""``ycli`` root CLI — mounts each domain's sub-app. Domain logic lives in <domain>/cli.py.
|
|
2
|
+
|
|
3
|
+
Run a subcommand directly: ``uv run ycli wiki pages get <slug>`` (or ``python -m ycli.cli``).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from ycli.log import configure
|
|
11
|
+
from ycli.yandex.forms.cli import app as forms_app
|
|
12
|
+
from ycli.yandex.tracker.cli import app as tracker_app
|
|
13
|
+
from ycli.yandex.wiki.cli import app as wiki_app
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(
|
|
16
|
+
name="ycli",
|
|
17
|
+
help="ycli — Yandex 360 API SDK CLI.",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
pretty_exceptions_show_locals=False,
|
|
20
|
+
add_completion=False,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.callback()
|
|
25
|
+
def _main() -> None:
|
|
26
|
+
"""Configure logging once before any subcommand runs."""
|
|
27
|
+
configure()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
app.add_typer(wiki_app)
|
|
31
|
+
app.add_typer(tracker_app)
|
|
32
|
+
app.add_typer(forms_app)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command(name="mcp")
|
|
36
|
+
def mcp() -> None:
|
|
37
|
+
"""Run the read-only MCP server over stdio (requires the ``mcp`` extra).
|
|
38
|
+
|
|
39
|
+
Tools are namespaced ``wiki_*``, ``tracker_*``, ``forms_*``. Point an MCP client
|
|
40
|
+
at ``ycli mcp``.
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
from ycli.mcp import main as run_server
|
|
44
|
+
except ModuleNotFoundError as exc: # pragma: no cover - only without the 'mcp' extra
|
|
45
|
+
raise typer.BadParameter(
|
|
46
|
+
"The MCP server requires the 'mcp' extra. Install it with: "
|
|
47
|
+
"uv add 'yandex-cli[mcp]' (or: uv tool install 'yandex-cli[mcp]')."
|
|
48
|
+
) from exc
|
|
49
|
+
run_server()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main() -> None: # pragma: no cover
|
|
53
|
+
"""Console-script entry point (``ycli`` / ``yandex-cli``)."""
|
|
54
|
+
app()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__": # pragma: no cover
|
|
58
|
+
main()
|
ycli/log.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Central loguru configuration — one sink to stderr, idempotent.
|
|
2
|
+
|
|
3
|
+
``configure()`` always removes existing sinks and installs exactly one stderr sink,
|
|
4
|
+
so calling it more than once never stacks duplicate output and always rebinds to the
|
|
5
|
+
current ``sys.stderr`` (important under pytest's ``capsys``). stdout stays clean for
|
|
6
|
+
machine-readable command output.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from ycli.log import configure
|
|
10
|
+
>>> configure() # doctest: +SKIP
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from loguru import logger
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def configure(level: str = "INFO") -> None:
|
|
21
|
+
"""Install a single stderr sink at ``level`` (idempotent — safe to call repeatedly).
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> configure("DEBUG") # doctest: +SKIP
|
|
25
|
+
"""
|
|
26
|
+
logger.remove()
|
|
27
|
+
logger.add(sys.stderr, level=level, backtrace=False, diagnose=False)
|
ycli/mcp.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Root Yandex 360 FastMCP server — mounts the per-domain subservers.
|
|
2
|
+
|
|
3
|
+
Run over stdio for LLM-agent clients: ``ycli mcp`` (or ``python -m ycli.mcp``).
|
|
4
|
+
Tools are namespaced per domain: ``wiki_*``, ``tracker_*``, ``forms_*``. Reads-only.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
|
|
9
|
+
from ycli.log import configure
|
|
10
|
+
from ycli.yandex.forms.mcp import mcp as forms_mcp
|
|
11
|
+
from ycli.yandex.tracker.mcp import mcp as tracker_mcp
|
|
12
|
+
from ycli.yandex.wiki.mcp import mcp as wiki_mcp
|
|
13
|
+
|
|
14
|
+
mcp = FastMCP("yandex")
|
|
15
|
+
mcp.mount(wiki_mcp, namespace="wiki")
|
|
16
|
+
mcp.mount(tracker_mcp, namespace="tracker")
|
|
17
|
+
mcp.mount(forms_mcp, namespace="forms")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main() -> None: # pragma: no cover
|
|
21
|
+
"""Run the root server over stdio (the console-script entry point).
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> main() # doctest: +SKIP
|
|
25
|
+
"""
|
|
26
|
+
configure() # match the CLI: single stderr sink, stdout stays clean for the protocol
|
|
27
|
+
mcp.run()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__": # pragma: no cover
|
|
31
|
+
main()
|
ycli/yandex/__init__.py
ADDED
|
File without changes
|
ycli/yandex/base.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Shared base for every Yandex resource client (uplink.Consumer).
|
|
2
|
+
|
|
3
|
+
Holds the two things every resource repeats: a required-``session`` constructor (DI —
|
|
4
|
+
the client takes a configured ``requests.Session``, never reaches into the env) and a
|
|
5
|
+
``from_env()`` classmethod. The per-API base URL is a ``base_url`` ClassVar set by a
|
|
6
|
+
per-domain base (e.g. ``WikiResource``); resource clients inherit it.
|
|
7
|
+
|
|
8
|
+
uplink's ``ConsumerMeta`` collects decorated request methods from the leaf subclass, so
|
|
9
|
+
an intermediate base with no decorated methods is fine.
|
|
10
|
+
|
|
11
|
+
NOTE: no ``from __future__ import annotations`` — uplink reads method annotations eagerly.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from ycli.yandex.wiki.pages.client import PagesClient
|
|
15
|
+
>>> PagesClient.from_env() # doctest: +SKIP
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from typing import ClassVar, Self
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
import uplink
|
|
23
|
+
|
|
24
|
+
from ycli.yandex.transport import Transport
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def session_from_env() -> requests.Session:
|
|
28
|
+
"""Build an authed session from ``$YANDEX_ID_OAUTH_TOKEN`` / ``$YANDEX_ID_ORGANIZATION_ID``.
|
|
29
|
+
|
|
30
|
+
Raises ``ValueError`` (naming the missing variable) when either is absent/empty.
|
|
31
|
+
Credential resolution lives here, NOT in ``Transport`` (which stays env-free).
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> session_from_env() # doctest: +SKIP
|
|
35
|
+
"""
|
|
36
|
+
token = os.environ.get("YANDEX_ID_OAUTH_TOKEN", "")
|
|
37
|
+
org_id = os.environ.get("YANDEX_ID_ORGANIZATION_ID", "")
|
|
38
|
+
if not token:
|
|
39
|
+
raise ValueError("YANDEX_ID_OAUTH_TOKEN is empty — set it in the environment")
|
|
40
|
+
if not org_id:
|
|
41
|
+
raise ValueError("YANDEX_ID_ORGANIZATION_ID is empty — set it in the environment")
|
|
42
|
+
return Transport.session(token=token, org_id=org_id)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BaseYandex(uplink.Consumer):
|
|
46
|
+
"""Required-``session`` DI + ``from_env`` env resolution + ``base_url`` classvar."""
|
|
47
|
+
|
|
48
|
+
base_url: ClassVar[str]
|
|
49
|
+
|
|
50
|
+
def __init__(self, *, session: requests.Session) -> None:
|
|
51
|
+
# uplink joins paths with urljoin; a base without a trailing slash drops the
|
|
52
|
+
# last segment (".../v1" + "pages" -> ".../pages"). Normalize so a base_url
|
|
53
|
+
# with no trailing slash is safe.
|
|
54
|
+
base = self.base_url.rstrip("/") + "/"
|
|
55
|
+
# Retain the raw requests.Session before super().__init__ wraps it — uplink's
|
|
56
|
+
# own ``self.session`` is a wrapper that does not expose the injected headers.
|
|
57
|
+
self._session: requests.Session = session
|
|
58
|
+
super().__init__(base_url=base, client=session)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_env(cls) -> Self:
|
|
62
|
+
"""Build a client from ``$YANDEX_ID_*`` (raises on a missing var).
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> BaseYandex.from_env() # doctest: +SKIP
|
|
66
|
+
"""
|
|
67
|
+
return cls(session=session_from_env())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Yandex Forms domain — per-resource clients (me/surveys/questions/answers), CLI, and MCP."""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Per-domain base — carries the Forms API base_url; resource clients inherit it.
|
|
2
|
+
|
|
3
|
+
Forms lives on its OWN host (``api.forms.yandex.net``), distinct from Tracker/Wiki —
|
|
4
|
+
this base is the single place that fact is encoded.
|
|
5
|
+
|
|
6
|
+
NOTE: no ``from __future__ import annotations`` — resource clients subclass this and
|
|
7
|
+
uplink reads their method annotations eagerly. Keep this module annotation-eager too.
|
|
8
|
+
"""
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
|
|
11
|
+
from ycli.yandex.base import BaseYandex
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FormsResource(BaseYandex):
|
|
15
|
+
"""Base for every Forms resource client (inherits session DI + from_env)."""
|
|
16
|
+
|
|
17
|
+
base_url: ClassVar[str] = "https://api.forms.yandex.net/v1"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Lazy Typer DI for the forms CLI."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from ycli.yandex.forms.client import FormsClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def forms_client(ctx: typer.Context) -> FormsClient:
|
|
10
|
+
"""Return the request-scoped FormsClient, building it from env on first access.
|
|
11
|
+
|
|
12
|
+
Lazy so ``--help`` (which never runs a command body) needs no creds; cached on
|
|
13
|
+
``ctx.obj`` so multiple accesses within one invocation share the session.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> forms_client(ctx) # doctest: +SKIP
|
|
17
|
+
"""
|
|
18
|
+
if ctx.obj is None:
|
|
19
|
+
ctx.obj = FormsClient.from_env()
|
|
20
|
+
return ctx.obj
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""FastMCP dependency provider for the forms subserver — builds a FormsClient per call."""
|
|
2
|
+
from ycli.yandex.forms.client import FormsClient
|
|
3
|
+
|
|
4
|
+
RO: dict[str, bool] = {"readOnlyHint": True}
|
|
5
|
+
TAGS: set[str] = {"forms"}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def forms_client() -> FormsClient:
|
|
9
|
+
"""Provide an env-built FormsClient to forms MCP tools (FastMCP caches within a call)."""
|
|
10
|
+
return FormsClient.from_env()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Shared pydantic base for Forms resources — lenient model config.
|
|
2
|
+
|
|
3
|
+
``_Lenient`` ignores extra fields (the API returns more than we project) and accepts
|
|
4
|
+
population by name OR alias.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> _Lenient.model_validate({"unknown": 1}).model_config["extra"]
|
|
8
|
+
'ignore'
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _Lenient(BaseModel):
|
|
16
|
+
"""Base model: extra fields silently ignored, population by name OR alias allowed."""
|
|
17
|
+
|
|
18
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|