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.
Files changed (115) hide show
  1. yandex_cli-0.1.0.dist-info/METADATA +240 -0
  2. yandex_cli-0.1.0.dist-info/RECORD +115 -0
  3. yandex_cli-0.1.0.dist-info/WHEEL +4 -0
  4. yandex_cli-0.1.0.dist-info/entry_points.txt +3 -0
  5. yandex_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. ycli/__init__.py +13 -0
  7. ycli/cli.py +58 -0
  8. ycli/log.py +27 -0
  9. ycli/mcp.py +31 -0
  10. ycli/yandex/__init__.py +0 -0
  11. ycli/yandex/base.py +67 -0
  12. ycli/yandex/forms/__init__.py +1 -0
  13. ycli/yandex/forms/_base.py +17 -0
  14. ycli/yandex/forms/_clideps.py +20 -0
  15. ycli/yandex/forms/_deps.py +10 -0
  16. ycli/yandex/forms/_models.py +18 -0
  17. ycli/yandex/forms/answers/__init__.py +1 -0
  18. ycli/yandex/forms/answers/cli.py +25 -0
  19. ycli/yandex/forms/answers/client.py +57 -0
  20. ycli/yandex/forms/answers/mcp.py +15 -0
  21. ycli/yandex/forms/answers/models.py +54 -0
  22. ycli/yandex/forms/cli.py +16 -0
  23. ycli/yandex/forms/client.py +29 -0
  24. ycli/yandex/forms/mcp.py +13 -0
  25. ycli/yandex/forms/me/__init__.py +1 -0
  26. ycli/yandex/forms/me/cli.py +19 -0
  27. ycli/yandex/forms/me/client.py +25 -0
  28. ycli/yandex/forms/me/mcp.py +20 -0
  29. ycli/yandex/forms/me/models.py +18 -0
  30. ycli/yandex/forms/questions/__init__.py +1 -0
  31. ycli/yandex/forms/questions/cli.py +25 -0
  32. ycli/yandex/forms/questions/client.py +24 -0
  33. ycli/yandex/forms/questions/mcp.py +15 -0
  34. ycli/yandex/forms/questions/models.py +46 -0
  35. ycli/yandex/forms/surveys/__init__.py +1 -0
  36. ycli/yandex/forms/surveys/cli.py +26 -0
  37. ycli/yandex/forms/surveys/client.py +36 -0
  38. ycli/yandex/forms/surveys/mcp.py +26 -0
  39. ycli/yandex/forms/surveys/models.py +44 -0
  40. ycli/yandex/tracker/__init__.py +1 -0
  41. ycli/yandex/tracker/_base.py +14 -0
  42. ycli/yandex/tracker/_clideps.py +46 -0
  43. ycli/yandex/tracker/_deps.py +10 -0
  44. ycli/yandex/tracker/_models.py +48 -0
  45. ycli/yandex/tracker/changelog/__init__.py +1 -0
  46. ycli/yandex/tracker/changelog/cli.py +25 -0
  47. ycli/yandex/tracker/changelog/client.py +28 -0
  48. ycli/yandex/tracker/changelog/mcp.py +15 -0
  49. ycli/yandex/tracker/changelog/models.py +58 -0
  50. ycli/yandex/tracker/cli.py +26 -0
  51. ycli/yandex/tracker/client.py +39 -0
  52. ycli/yandex/tracker/comments/__init__.py +1 -0
  53. ycli/yandex/tracker/comments/cli.py +28 -0
  54. ycli/yandex/tracker/comments/client.py +37 -0
  55. ycli/yandex/tracker/comments/mcp.py +15 -0
  56. ycli/yandex/tracker/comments/models.py +34 -0
  57. ycli/yandex/tracker/issues/__init__.py +1 -0
  58. ycli/yandex/tracker/issues/cli.py +132 -0
  59. ycli/yandex/tracker/issues/client.py +92 -0
  60. ycli/yandex/tracker/issues/mcp.py +53 -0
  61. ycli/yandex/tracker/issues/models.py +73 -0
  62. ycli/yandex/tracker/issuetypes/__init__.py +1 -0
  63. ycli/yandex/tracker/issuetypes/cli.py +19 -0
  64. ycli/yandex/tracker/issuetypes/client.py +24 -0
  65. ycli/yandex/tracker/issuetypes/mcp.py +15 -0
  66. ycli/yandex/tracker/issuetypes/models.py +27 -0
  67. ycli/yandex/tracker/links/__init__.py +1 -0
  68. ycli/yandex/tracker/links/cli.py +43 -0
  69. ycli/yandex/tracker/links/client.py +37 -0
  70. ycli/yandex/tracker/links/mcp.py +15 -0
  71. ycli/yandex/tracker/links/models.py +56 -0
  72. ycli/yandex/tracker/linktypes/__init__.py +1 -0
  73. ycli/yandex/tracker/linktypes/cli.py +19 -0
  74. ycli/yandex/tracker/linktypes/client.py +24 -0
  75. ycli/yandex/tracker/linktypes/mcp.py +15 -0
  76. ycli/yandex/tracker/linktypes/models.py +28 -0
  77. ycli/yandex/tracker/mcp.py +23 -0
  78. ycli/yandex/tracker/priorities/__init__.py +1 -0
  79. ycli/yandex/tracker/priorities/cli.py +19 -0
  80. ycli/yandex/tracker/priorities/client.py +24 -0
  81. ycli/yandex/tracker/priorities/mcp.py +15 -0
  82. ycli/yandex/tracker/priorities/models.py +27 -0
  83. ycli/yandex/tracker/transitions/__init__.py +1 -0
  84. ycli/yandex/tracker/transitions/cli.py +34 -0
  85. ycli/yandex/tracker/transitions/client.py +52 -0
  86. ycli/yandex/tracker/transitions/mcp.py +15 -0
  87. ycli/yandex/tracker/transitions/models.py +27 -0
  88. ycli/yandex/tracker/worklog/__init__.py +1 -0
  89. ycli/yandex/tracker/worklog/cli.py +21 -0
  90. ycli/yandex/tracker/worklog/client.py +24 -0
  91. ycli/yandex/tracker/worklog/mcp.py +15 -0
  92. ycli/yandex/tracker/worklog/models.py +36 -0
  93. ycli/yandex/transport.py +116 -0
  94. ycli/yandex/wiki/__init__.py +1 -0
  95. ycli/yandex/wiki/_base.py +8 -0
  96. ycli/yandex/wiki/_clideps.py +20 -0
  97. ycli/yandex/wiki/_deps.py +10 -0
  98. ycli/yandex/wiki/attachments/__init__.py +0 -0
  99. ycli/yandex/wiki/attachments/cli.py +20 -0
  100. ycli/yandex/wiki/attachments/client.py +29 -0
  101. ycli/yandex/wiki/attachments/mcp.py +15 -0
  102. ycli/yandex/wiki/attachments/models.py +32 -0
  103. ycli/yandex/wiki/cli.py +14 -0
  104. ycli/yandex/wiki/client.py +27 -0
  105. ycli/yandex/wiki/comments/__init__.py +0 -0
  106. ycli/yandex/wiki/comments/cli.py +20 -0
  107. ycli/yandex/wiki/comments/client.py +29 -0
  108. ycli/yandex/wiki/comments/mcp.py +15 -0
  109. ycli/yandex/wiki/comments/models.py +40 -0
  110. ycli/yandex/wiki/mcp.py +11 -0
  111. ycli/yandex/wiki/pages/__init__.py +0 -0
  112. ycli/yandex/wiki/pages/cli.py +64 -0
  113. ycli/yandex/wiki/pages/client.py +74 -0
  114. ycli/yandex/wiki/pages/mcp.py +32 -0
  115. 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
+ [![CI](https://img.shields.io/github/actions/workflow/status/bim-ba/ycli/ci.yml?branch=main&style=for-the-badge)](https://github.com/bim-ba/ycli/actions/workflows/ci.yml)
41
+ [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=for-the-badge)](https://github.com/bim-ba/ycli)
42
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/)
43
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)
44
+ [![MCP](https://img.shields.io/badge/MCP-compatible-7c3aed?style=for-the-badge)](https://modelcontextprotocol.io/)
45
+ [![Claude Code](https://img.shields.io/badge/Claude%20Code-plugin-d97757?style=for-the-badge)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ yandex-cli = ycli.cli:main
3
+ ycli = ycli.cli:main
@@ -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()
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)