polyglav 0.37.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.
Files changed (163) hide show
  1. polyglav-0.37.0/LICENSE +21 -0
  2. polyglav-0.37.0/PKG-INFO +79 -0
  3. polyglav-0.37.0/README.md +64 -0
  4. polyglav-0.37.0/plugins/__init__.py +4 -0
  5. polyglav-0.37.0/plugins/polyglav-core-anthropic/manifest.json +13 -0
  6. polyglav-0.37.0/plugins/polyglav-core-anthropic/src/plugin.py +24 -0
  7. polyglav-0.37.0/plugins/polyglav-core-anthropic/tests/test_anthropic_provider.py +53 -0
  8. polyglav-0.37.0/plugins/polyglav-core-dev/manifest.json +17 -0
  9. polyglav-0.37.0/plugins/polyglav-core-dev/src/plugin.py +234 -0
  10. polyglav-0.37.0/plugins/polyglav-core-dev/tests/test_tools.py +200 -0
  11. polyglav-0.37.0/plugins/polyglav-core-edit/manifest.json +15 -0
  12. polyglav-0.37.0/plugins/polyglav-core-edit/src/plugin.py +103 -0
  13. polyglav-0.37.0/plugins/polyglav-core-edit/tests/test_tools.py +127 -0
  14. polyglav-0.37.0/plugins/polyglav-core-eval/manifest.json +20 -0
  15. polyglav-0.37.0/plugins/polyglav-core-eval/src/plugin.py +89 -0
  16. polyglav-0.37.0/plugins/polyglav-core-eval/tests/test_fixtures.py +46 -0
  17. polyglav-0.37.0/plugins/polyglav-core-exec/manifest.json +15 -0
  18. polyglav-0.37.0/plugins/polyglav-core-exec/src/plugin.py +127 -0
  19. polyglav-0.37.0/plugins/polyglav-core-exec/tests/test_tools.py +157 -0
  20. polyglav-0.37.0/plugins/polyglav-core-fs/manifest.json +19 -0
  21. polyglav-0.37.0/plugins/polyglav-core-fs/src/plugin.py +357 -0
  22. polyglav-0.37.0/plugins/polyglav-core-fs/tests/test_tools.py +335 -0
  23. polyglav-0.37.0/plugins/polyglav-core-git/manifest.json +15 -0
  24. polyglav-0.37.0/plugins/polyglav-core-git/src/plugin.py +203 -0
  25. polyglav-0.37.0/plugins/polyglav-core-git/tests/test_tools.py +220 -0
  26. polyglav-0.37.0/plugins/polyglav-core-groq/manifest.json +13 -0
  27. polyglav-0.37.0/plugins/polyglav-core-groq/src/plugin.py +11 -0
  28. polyglav-0.37.0/plugins/polyglav-core-groq/tests/test_groq_provider.py +36 -0
  29. polyglav-0.37.0/plugins/polyglav-core-mcp/manifest.json +19 -0
  30. polyglav-0.37.0/plugins/polyglav-core-mcp/src/client.py +260 -0
  31. polyglav-0.37.0/plugins/polyglav-core-mcp/src/client_http.py +141 -0
  32. polyglav-0.37.0/plugins/polyglav-core-mcp/src/client_stdio.py +176 -0
  33. polyglav-0.37.0/plugins/polyglav-core-mcp/src/jsonrpc.py +128 -0
  34. polyglav-0.37.0/plugins/polyglav-core-mcp/src/plugin.py +248 -0
  35. polyglav-0.37.0/plugins/polyglav-core-mcp/src/resources.py +31 -0
  36. polyglav-0.37.0/plugins/polyglav-core-mcp/src/server.py +184 -0
  37. polyglav-0.37.0/plugins/polyglav-core-mcp/tests/test_mcp.py +423 -0
  38. polyglav-0.37.0/plugins/polyglav-core-ollama/manifest.json +13 -0
  39. polyglav-0.37.0/plugins/polyglav-core-ollama/src/plugin.py +20 -0
  40. polyglav-0.37.0/plugins/polyglav-core-ollama/tests/test_ollama_provider.py +146 -0
  41. polyglav-0.37.0/plugins/polyglav-core-openai/manifest.json +13 -0
  42. polyglav-0.37.0/plugins/polyglav-core-openai/src/plugin.py +21 -0
  43. polyglav-0.37.0/plugins/polyglav-core-openai/tests/test_openai_provider.py +61 -0
  44. polyglav-0.37.0/plugins/polyglav-core-opencode/manifest.json +13 -0
  45. polyglav-0.37.0/plugins/polyglav-core-opencode/src/opencode.py +14 -0
  46. polyglav-0.37.0/plugins/polyglav-core-opencode/src/opencode_base.py +19 -0
  47. polyglav-0.37.0/plugins/polyglav-core-opencode/src/opencode_go.py +14 -0
  48. polyglav-0.37.0/plugins/polyglav-core-opencode/src/plugin.py +7 -0
  49. polyglav-0.37.0/plugins/polyglav-core-opencode/tests/test_opencode_providers.py +88 -0
  50. polyglav-0.37.0/plugins/polyglav-core-web/manifest.json +16 -0
  51. polyglav-0.37.0/plugins/polyglav-core-web/src/display.py +25 -0
  52. polyglav-0.37.0/plugins/polyglav-core-web/src/plugin.py +186 -0
  53. polyglav-0.37.0/plugins/polyglav-core-web/src/search.py +68 -0
  54. polyglav-0.37.0/plugins/polyglav-core-web/tests/test_tools.py +236 -0
  55. polyglav-0.37.0/plugins/polyglav-core-webhook/manifest.json +14 -0
  56. polyglav-0.37.0/plugins/polyglav-core-webhook/src/plugin.py +28 -0
  57. polyglav-0.37.0/plugins/polyglav-core-webhook/tests/test_webhook.py +67 -0
  58. polyglav-0.37.0/pyproject.toml +39 -0
  59. polyglav-0.37.0/setup.cfg +4 -0
  60. polyglav-0.37.0/src/polyglav/__init__.py +8 -0
  61. polyglav-0.37.0/src/polyglav/__main__.py +5 -0
  62. polyglav-0.37.0/src/polyglav/asks.py +141 -0
  63. polyglav-0.37.0/src/polyglav/bundled_roles.json +127 -0
  64. polyglav-0.37.0/src/polyglav/bundled_teams.json +54 -0
  65. polyglav-0.37.0/src/polyglav/chat.py +332 -0
  66. polyglav-0.37.0/src/polyglav/cli.py +796 -0
  67. polyglav-0.37.0/src/polyglav/commands/__init__.py +0 -0
  68. polyglav-0.37.0/src/polyglav/commands/builtins.py +1790 -0
  69. polyglav-0.37.0/src/polyglav/commands/registry.py +46 -0
  70. polyglav-0.37.0/src/polyglav/config.py +238 -0
  71. polyglav-0.37.0/src/polyglav/engine.py +1638 -0
  72. polyglav-0.37.0/src/polyglav/eval.py +303 -0
  73. polyglav-0.37.0/src/polyglav/fleet.py +597 -0
  74. polyglav-0.37.0/src/polyglav/focus.py +96 -0
  75. polyglav-0.37.0/src/polyglav/jobs.py +631 -0
  76. polyglav-0.37.0/src/polyglav/main.py +342 -0
  77. polyglav-0.37.0/src/polyglav/memory.py +86 -0
  78. polyglav-0.37.0/src/polyglav/models.py +96 -0
  79. polyglav-0.37.0/src/polyglav/modes.py +75 -0
  80. polyglav-0.37.0/src/polyglav/plugins/__init__.py +3 -0
  81. polyglav-0.37.0/src/polyglav/plugins/manager.py +477 -0
  82. polyglav-0.37.0/src/polyglav/providers/__init__.py +35 -0
  83. polyglav-0.37.0/src/polyglav/providers/base.py +237 -0
  84. polyglav-0.37.0/src/polyglav/providers/registry.py +129 -0
  85. polyglav-0.37.0/src/polyglav/roles.py +203 -0
  86. polyglav-0.37.0/src/polyglav/runs.py +114 -0
  87. polyglav-0.37.0/src/polyglav/scheduler.py +321 -0
  88. polyglav-0.37.0/src/polyglav/server.py +137 -0
  89. polyglav-0.37.0/src/polyglav/sessions/__init__.py +0 -0
  90. polyglav-0.37.0/src/polyglav/sessions/manager.py +277 -0
  91. polyglav-0.37.0/src/polyglav/sessions/render.py +329 -0
  92. polyglav-0.37.0/src/polyglav/sessions/turns.py +187 -0
  93. polyglav-0.37.0/src/polyglav/skills.py +149 -0
  94. polyglav-0.37.0/src/polyglav/teams.py +182 -0
  95. polyglav-0.37.0/src/polyglav/tools/__init__.py +0 -0
  96. polyglav-0.37.0/src/polyglav/tools/ask.py +236 -0
  97. polyglav-0.37.0/src/polyglav/tools/catalog.py +283 -0
  98. polyglav-0.37.0/src/polyglav/tools/delegate.py +182 -0
  99. polyglav-0.37.0/src/polyglav/tools/handoff.py +100 -0
  100. polyglav-0.37.0/src/polyglav/tools/policy.py +100 -0
  101. polyglav-0.37.0/src/polyglav/tools/registry.py +266 -0
  102. polyglav-0.37.0/src/polyglav/tools/team.py +154 -0
  103. polyglav-0.37.0/src/polyglav/ui.py +782 -0
  104. polyglav-0.37.0/src/polyglav/utils/__init__.py +0 -0
  105. polyglav-0.37.0/src/polyglav/utils/http.py +54 -0
  106. polyglav-0.37.0/src/polyglav.egg-info/PKG-INFO +79 -0
  107. polyglav-0.37.0/src/polyglav.egg-info/SOURCES.txt +161 -0
  108. polyglav-0.37.0/src/polyglav.egg-info/dependency_links.txt +1 -0
  109. polyglav-0.37.0/src/polyglav.egg-info/entry_points.txt +2 -0
  110. polyglav-0.37.0/src/polyglav.egg-info/top_level.txt +1 -0
  111. polyglav-0.37.0/tests/test_agent_loop.py +354 -0
  112. polyglav-0.37.0/tests/test_ask.py +149 -0
  113. polyglav-0.37.0/tests/test_asks.py +333 -0
  114. polyglav-0.37.0/tests/test_assistant.py +94 -0
  115. polyglav-0.37.0/tests/test_autonomous_supervisor.py +150 -0
  116. polyglav-0.37.0/tests/test_bundled_plugins.py +121 -0
  117. polyglav-0.37.0/tests/test_catalog.py +147 -0
  118. polyglav-0.37.0/tests/test_cli.py +585 -0
  119. polyglav-0.37.0/tests/test_commands.py +849 -0
  120. polyglav-0.37.0/tests/test_completion.py +78 -0
  121. polyglav-0.37.0/tests/test_composer.py +63 -0
  122. polyglav-0.37.0/tests/test_config.py +429 -0
  123. polyglav-0.37.0/tests/test_delegate.py +263 -0
  124. polyglav-0.37.0/tests/test_delegation_permissions.py +340 -0
  125. polyglav-0.37.0/tests/test_engine.py +808 -0
  126. polyglav-0.37.0/tests/test_eval.py +303 -0
  127. polyglav-0.37.0/tests/test_fleet.py +508 -0
  128. polyglav-0.37.0/tests/test_focus.py +251 -0
  129. polyglav-0.37.0/tests/test_focus_on_delegate.py +119 -0
  130. polyglav-0.37.0/tests/test_handoff.py +185 -0
  131. polyglav-0.37.0/tests/test_history.py +231 -0
  132. polyglav-0.37.0/tests/test_http.py +120 -0
  133. polyglav-0.37.0/tests/test_jobs.py +1071 -0
  134. polyglav-0.37.0/tests/test_memory.py +135 -0
  135. polyglav-0.37.0/tests/test_models.py +120 -0
  136. polyglav-0.37.0/tests/test_modes.py +205 -0
  137. polyglav-0.37.0/tests/test_plugin_suites.py +13 -0
  138. polyglav-0.37.0/tests/test_plugins.py +649 -0
  139. polyglav-0.37.0/tests/test_print.py +173 -0
  140. polyglav-0.37.0/tests/test_provider_session.py +54 -0
  141. polyglav-0.37.0/tests/test_providers.py +498 -0
  142. polyglav-0.37.0/tests/test_providers_registry.py +164 -0
  143. polyglav-0.37.0/tests/test_repl_input.py +160 -0
  144. polyglav-0.37.0/tests/test_roles.py +360 -0
  145. polyglav-0.37.0/tests/test_run_buffer.py +121 -0
  146. polyglav-0.37.0/tests/test_run_resume.py +173 -0
  147. polyglav-0.37.0/tests/test_runs.py +102 -0
  148. polyglav-0.37.0/tests/test_server.py +77 -0
  149. polyglav-0.37.0/tests/test_session_id.py +150 -0
  150. polyglav-0.37.0/tests/test_session_log.py +487 -0
  151. polyglav-0.37.0/tests/test_session_render.py +375 -0
  152. polyglav-0.37.0/tests/test_skills.py +259 -0
  153. polyglav-0.37.0/tests/test_subagent.py +242 -0
  154. polyglav-0.37.0/tests/test_team_loop.py +173 -0
  155. polyglav-0.37.0/tests/test_team_run.py +237 -0
  156. polyglav-0.37.0/tests/test_team_tool.py +184 -0
  157. polyglav-0.37.0/tests/test_teams.py +323 -0
  158. polyglav-0.37.0/tests/test_tool_calling.py +145 -0
  159. polyglav-0.37.0/tests/test_tool_policy.py +137 -0
  160. polyglav-0.37.0/tests/test_tool_registry.py +319 -0
  161. polyglav-0.37.0/tests/test_turns.py +197 -0
  162. polyglav-0.37.0/tests/test_ui.py +654 -0
  163. polyglav-0.37.0/tests/test_unattended.py +265 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Evgenij Myasnikov
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.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: polyglav
3
+ Version: 0.37.0
4
+ Summary: A lightweight agentic tooling core with API, CLI and REPL interfaces
5
+ Author: Evgenij Myasnikov
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/polyglav/polyglav
8
+ Project-URL: Issues, https://github.com/polyglav/polyglav/issues
9
+ Project-URL: Changelog, https://github.com/polyglav/polyglav/blob/main/CHANGELOG.md
10
+ Keywords: ai,ai-agents,api,assistant,cli,cloud,local,python,repl
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
15
+
16
+ # Polyglav
17
+
18
+ <p align="center"><img src="polyglav.svg" alt="Polyglav terminal session"></p>
19
+
20
+ **One agent, many heads**
21
+
22
+ <p>
23
+ <a href="https://pypi.org/project/polyglav/"><img src="https://img.shields.io/pypi/v/polyglav" alt="PyPI version"></a>
24
+ <a href="https://polyglav.github.io/polyglav/"><img src="https://img.shields.io/badge/docs-site-blue" alt="Site"></a>
25
+ <img src="https://img.shields.io/badge/python-%3E%3D3.10-blue" alt="Python >=3.10">
26
+ <img src="https://img.shields.io/github/actions/workflow/status/polyglav/polyglav/ci.yml?branch=main" alt="CI">
27
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
28
+ <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License">
29
+ </p>
30
+
31
+ Polyglav is an AI agent that lives in your terminal. You describe an outcome once, it breaks the job into steps, brings in specialist agents for each one, and lets them work through it in order, handing results from one to the next as they finish. You stay at a single prompt the whole time. The name says it: poly means many, glav means heads.
32
+
33
+ ## Why Polyglav
34
+
35
+ - **One prompt, many heads** - ask for an outcome, and Polyglav splits it across specialists that run one after another in the same process. As soon as one delivers, the next continues. No windows to switch and no babysitting.
36
+ - **It actually gets work done** - it reads and writes files, searches the web, runs commands, edits code, and commits, all behind permissions you control.
37
+ - **Grows with you** - reusable roles and skills, scheduled jobs that run unattended, and a supervisor for many agents at once.
38
+ - **Your machine, your keys** - config and complete session logs stay on your disk. Bring your own provider key, or run fully local.
39
+ - **Safe by default** - every tool is permission controlled. Anything outside your project asks first, and every action is on the record.
40
+ - **Zero dependencies** - pure Python standard library. Nothing to audit, no supply chain, no lockfile churn.
41
+ - **At home in the terminal** - a fast streaming REPL with dimmed thinking, markdown rendering, command completion, and history. No browser and no account.
42
+ - **Many providers, any model** - Ollama, OpenAI, Groq, Anthropic, OpenCode Zen/Go, or any OpenAI-compatible endpoint, picked automatically from the address.
43
+
44
+ ## What you can do with it
45
+
46
+ - **Ship a change end to end** - ask for a feature and let a planner, a programmer, and a reviewer work through it in order.
47
+ - **Keep a project tidy** - schedule a job that refreshes docs, updates a changelog, or summarizes logs on a cron.
48
+ - **Research and write** - point it at a folder of notes or papers and ask for a summary with sources.
49
+ - **Run many agents** - give each folder its own agent and watch them all from one place.
50
+ - **Automate with the API** - drive the same loop from scripts, CI, or another program.
51
+
52
+ ## How it works
53
+
54
+ 1. **Ask** - describe an outcome to the assistant in one sentence.
55
+ 2. **Compose** - for bigger work it designs a small team: the specialists and the order they run in.
56
+ 3. **Run** - stages run one after another, each with its own tools and permissions.
57
+ 4. **Hand off** - as the work moves, each agent passes what it did to the next.
58
+ 5. **Report** - watch progress, jump into any agent, or come back to a summary.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pipx install polyglav
64
+ polyglav
65
+ ```
66
+
67
+ For source installs, the REPL, the headless CLI, the HTTP API, Docker, plugins, and the full command reference, see [INSTALL.md](INSTALL.md).
68
+
69
+ ## Learn more
70
+
71
+ - [AGENTS.md](AGENTS.md) - architecture and conventions
72
+ - [CONTRIBUTING.md](CONTRIBUTING.md) - the contribution workflow
73
+ - [INSTALL.md](INSTALL.md) - install, run, and command reference
74
+ - [VISION.md](VISION.md) - what Polyglav is and where it is going
75
+ - [docs/index.md](docs/index.md) - full documentation
76
+
77
+ ## License
78
+
79
+ [MIT](LICENSE)
@@ -0,0 +1,64 @@
1
+ # Polyglav
2
+
3
+ <p align="center"><img src="polyglav.svg" alt="Polyglav terminal session"></p>
4
+
5
+ **One agent, many heads**
6
+
7
+ <p>
8
+ <a href="https://pypi.org/project/polyglav/"><img src="https://img.shields.io/pypi/v/polyglav" alt="PyPI version"></a>
9
+ <a href="https://polyglav.github.io/polyglav/"><img src="https://img.shields.io/badge/docs-site-blue" alt="Site"></a>
10
+ <img src="https://img.shields.io/badge/python-%3E%3D3.10-blue" alt="Python >=3.10">
11
+ <img src="https://img.shields.io/github/actions/workflow/status/polyglav/polyglav/ci.yml?branch=main" alt="CI">
12
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
13
+ <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License">
14
+ </p>
15
+
16
+ Polyglav is an AI agent that lives in your terminal. You describe an outcome once, it breaks the job into steps, brings in specialist agents for each one, and lets them work through it in order, handing results from one to the next as they finish. You stay at a single prompt the whole time. The name says it: poly means many, glav means heads.
17
+
18
+ ## Why Polyglav
19
+
20
+ - **One prompt, many heads** - ask for an outcome, and Polyglav splits it across specialists that run one after another in the same process. As soon as one delivers, the next continues. No windows to switch and no babysitting.
21
+ - **It actually gets work done** - it reads and writes files, searches the web, runs commands, edits code, and commits, all behind permissions you control.
22
+ - **Grows with you** - reusable roles and skills, scheduled jobs that run unattended, and a supervisor for many agents at once.
23
+ - **Your machine, your keys** - config and complete session logs stay on your disk. Bring your own provider key, or run fully local.
24
+ - **Safe by default** - every tool is permission controlled. Anything outside your project asks first, and every action is on the record.
25
+ - **Zero dependencies** - pure Python standard library. Nothing to audit, no supply chain, no lockfile churn.
26
+ - **At home in the terminal** - a fast streaming REPL with dimmed thinking, markdown rendering, command completion, and history. No browser and no account.
27
+ - **Many providers, any model** - Ollama, OpenAI, Groq, Anthropic, OpenCode Zen/Go, or any OpenAI-compatible endpoint, picked automatically from the address.
28
+
29
+ ## What you can do with it
30
+
31
+ - **Ship a change end to end** - ask for a feature and let a planner, a programmer, and a reviewer work through it in order.
32
+ - **Keep a project tidy** - schedule a job that refreshes docs, updates a changelog, or summarizes logs on a cron.
33
+ - **Research and write** - point it at a folder of notes or papers and ask for a summary with sources.
34
+ - **Run many agents** - give each folder its own agent and watch them all from one place.
35
+ - **Automate with the API** - drive the same loop from scripts, CI, or another program.
36
+
37
+ ## How it works
38
+
39
+ 1. **Ask** - describe an outcome to the assistant in one sentence.
40
+ 2. **Compose** - for bigger work it designs a small team: the specialists and the order they run in.
41
+ 3. **Run** - stages run one after another, each with its own tools and permissions.
42
+ 4. **Hand off** - as the work moves, each agent passes what it did to the next.
43
+ 5. **Report** - watch progress, jump into any agent, or come back to a summary.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pipx install polyglav
49
+ polyglav
50
+ ```
51
+
52
+ For source installs, the REPL, the headless CLI, the HTTP API, Docker, plugins, and the full command reference, see [INSTALL.md](INSTALL.md).
53
+
54
+ ## Learn more
55
+
56
+ - [AGENTS.md](AGENTS.md) - architecture and conventions
57
+ - [CONTRIBUTING.md](CONTRIBUTING.md) - the contribution workflow
58
+ - [INSTALL.md](INSTALL.md) - install, run, and command reference
59
+ - [VISION.md](VISION.md) - what Polyglav is and where it is going
60
+ - [docs/index.md](docs/index.md) - full documentation
61
+
62
+ ## License
63
+
64
+ [MIT](LICENSE)
@@ -0,0 +1,4 @@
1
+ """Bundled plugins shipped with polyglav. Each subdirectory here is a plugin
2
+ directory (manifest.json + entry module), discovered by
3
+ polyglav.plugins.manager.PluginManager.bundled_dir.
4
+ """
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "polyglav-core-anthropic",
3
+ "version": "0.1.0",
4
+ "description": "Anthropic provider - bundled with polyglav",
5
+ "polyglav_version": ">=0.28.0,<1.0",
6
+ "python": ">=3.10",
7
+ "entry": "src/plugin.py",
8
+ "provides": {
9
+ "tools": [],
10
+ "providers": ["anthropic"],
11
+ "commands": []
12
+ }
13
+ }
@@ -0,0 +1,24 @@
1
+ from polyglav.providers.base import OpenAICompatibleProvider
2
+
3
+
4
+ class AnthropicProvider(OpenAICompatibleProvider):
5
+ DEFAULT_BASE_URL = 'https://api.anthropic.com/v1'
6
+ DEFAULT_MODEL = 'claude-sonnet-4-20250514'
7
+ HOST_PATTERNS = ('anthropic.com',)
8
+
9
+ _BUDGET = {'low': 1024, 'medium': 2048, 'high': 4096}
10
+
11
+ def _reasoning_payload(self, payload: dict) -> dict:
12
+ if self._reasoning_off(self.reasoning):
13
+ payload['thinking'] = {'type': 'disabled'}
14
+ return payload
15
+ effort = self.reasoning
16
+ if effort is True or effort in ('on', 'auto'):
17
+ effort = 'medium'
18
+ budget = self._BUDGET.get(effort, 2048)
19
+ payload['thinking'] = {'type': 'enabled', 'budget_tokens': budget}
20
+ return payload
21
+
22
+
23
+ def register_providers(providers):
24
+ providers['anthropic'] = AnthropicProvider
@@ -0,0 +1,53 @@
1
+ import importlib.util
2
+ import sys
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ SRC = Path(__file__).resolve().parents[1] / 'src'
7
+ if str(SRC) not in sys.path:
8
+ sys.path.insert(0, str(SRC))
9
+
10
+
11
+ def _load_plugin():
12
+ name = 'polyglav_anthropic_provider_plugin'
13
+ spec = importlib.util.spec_from_file_location(name, str(SRC / 'plugin.py'))
14
+ module = importlib.util.module_from_spec(spec)
15
+ sys.modules[name] = module
16
+ spec.loader.exec_module(module)
17
+ return module
18
+
19
+
20
+ AnthropicProvider = _load_plugin().AnthropicProvider
21
+
22
+
23
+ class TestAnthropicProvider(unittest.TestCase):
24
+
25
+ def test_defaults(self):
26
+ p = AnthropicProvider()
27
+ self.assertEqual(p.base_url, 'https://api.anthropic.com/v1')
28
+ self.assertEqual(p.model, 'claude-sonnet-4-20250514')
29
+ self.assertEqual(p._endpoint(), 'https://api.anthropic.com/v1/chat/completions')
30
+
31
+ def test_host_patterns(self):
32
+ self.assertIn('anthropic.com', AnthropicProvider.HOST_PATTERNS)
33
+
34
+ def test_reasoning_off(self):
35
+ p = AnthropicProvider(reasoning=False)
36
+ payload = p._payload([{'role': 'user', 'content': 'hi'}])
37
+ self.assertEqual(payload['thinking'], {'type': 'disabled'})
38
+
39
+ def test_reasoning_auto_maps_to_medium(self):
40
+ p = AnthropicProvider(reasoning='auto')
41
+ payload = p._payload([{'role': 'user', 'content': 'hi'}])
42
+ self.assertEqual(payload['thinking'],
43
+ {'type': 'enabled', 'budget_tokens': 2048})
44
+
45
+ def test_reasoning_high_budget(self):
46
+ p = AnthropicProvider(reasoning='high')
47
+ payload = p._payload([{'role': 'user', 'content': 'hi'}])
48
+ self.assertEqual(payload['thinking'],
49
+ {'type': 'enabled', 'budget_tokens': 4096})
50
+
51
+
52
+ if __name__ == '__main__':
53
+ unittest.main()
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "polyglav-core-dev",
3
+ "version": "0.1.0",
4
+ "description": "Developer workflow wrappers: code_test, code_lint, code_format - bundled with polyglav",
5
+ "polyglav_version": ">=0.13.0,<1.0",
6
+ "python": ">=3.10",
7
+ "entry": "src/plugin.py",
8
+ "provides": {
9
+ "tools": [
10
+ "code_test",
11
+ "code_lint",
12
+ "code_format"
13
+ ],
14
+ "providers": [],
15
+ "commands": []
16
+ }
17
+ }
@@ -0,0 +1,234 @@
1
+ import shlex
2
+ import subprocess
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ MAX_TIMEOUT = 600
7
+ DEFAULT_TIMEOUT = 300
8
+
9
+ DEFAULTS = {
10
+ 'test': 'python -m unittest discover',
11
+ 'lint': 'ruff check .',
12
+ 'format': 'ruff format .',
13
+ }
14
+
15
+
16
+ def _resolve_interpreter(argv: list[str]) -> list[str]:
17
+ if argv and argv[0] in ('python', 'python3'):
18
+ return [sys.executable] + argv[1:]
19
+ return argv
20
+
21
+
22
+ def _clamp_timeout(timeout) -> int:
23
+ try:
24
+ return max(1, min(int(timeout), MAX_TIMEOUT))
25
+ except (TypeError, ValueError):
26
+ return DEFAULT_TIMEOUT
27
+
28
+
29
+ def _cap(config=None) -> int:
30
+ if config is None:
31
+ return 0
32
+ try:
33
+ return max(0, int(config.get('tool_max_result_chars', 0)))
34
+ except (TypeError, ValueError):
35
+ return 0
36
+
37
+
38
+ def _truncate(text: str, max_chars: int = 0) -> str:
39
+ if max_chars > 0 and len(text) > max_chars:
40
+ return text[:max_chars].rsplit('\n', 1)[0] + '\n... (truncated)'
41
+ return text
42
+
43
+
44
+ def _is_failure(result: str) -> bool:
45
+ for line in result.splitlines():
46
+ if line.startswith('exit '):
47
+ try:
48
+ return int(line[5:].strip()) != 0
49
+ except ValueError:
50
+ return False
51
+ return False
52
+
53
+
54
+ def _cmd(config, key: str, default: str) -> list[str]:
55
+ raw = ''
56
+ if config is not None:
57
+ raw = str(config.get(key) or '')
58
+ if not raw.strip():
59
+ raw = default
60
+ try:
61
+ argv = shlex.split(raw)
62
+ except ValueError:
63
+ argv = raw.split()
64
+ argv = argv or [default]
65
+ return _resolve_interpreter(argv)
66
+
67
+
68
+ def _is_discover(argv: list[str]) -> bool:
69
+ return (len(argv) >= 4 and argv[1] == '-m' and argv[2] == 'unittest'
70
+ and argv[3] == 'discover')
71
+
72
+
73
+ def _test_argv(config, target: str) -> list[str]:
74
+ argv = _cmd(config, 'dev.test_cmd', DEFAULTS['test'])
75
+ if not target:
76
+ return argv
77
+ if _is_discover(argv):
78
+ return [argv[0], '-m', 'unittest', target]
79
+ return argv + [target]
80
+
81
+
82
+ def _run(argv: list[str], cwd: str | None, config,
83
+ timeout: int, target: str = '') -> str:
84
+ if cwd and not Path(cwd).is_dir():
85
+ return f'Error: cwd not found: {cwd}'
86
+ timeout = _clamp_timeout(timeout)
87
+ try:
88
+ proc = subprocess.run(
89
+ argv, capture_output=True, text=True, cwd=cwd, timeout=timeout,
90
+ )
91
+ except FileNotFoundError:
92
+ return f'Error: command not found: {argv[0]}'
93
+ except subprocess.TimeoutExpired as e:
94
+ out = e.stdout or ''
95
+ return f'Error: command timed out after {timeout}s' + (
96
+ f'\n{_truncate(out, _cap(config))}' if out else '')
97
+ except OSError as e:
98
+ return f'Error running command: {e}'
99
+ stdout = proc.stdout or ''
100
+ stderr = proc.stderr or ''
101
+ body = stdout if not stderr or stderr == stdout else f'{stdout}\n{stderr}'
102
+ lines = [f'$ {" ".join(argv)}']
103
+ if cwd:
104
+ lines.append(f'[cwd: {cwd}]')
105
+ lines.append(f'exit {proc.returncode}')
106
+ no_tests = ('NO TESTS RAN' in body or 'Ran 0 tests' in body
107
+ or proc.returncode == 5)
108
+ if target and no_tests:
109
+ lines.append(
110
+ f'Error: no tests matched "{target}" '
111
+ f'(unittest exit {proc.returncode})')
112
+ if body:
113
+ lines.append(_truncate(body, _cap(config)))
114
+ return '\n'.join(lines)
115
+
116
+
117
+ def register_tools(registry):
118
+ @registry.register(
119
+ name='code_test',
120
+ description='Run the project test suite. Uses the dev.test_cmd config key (default "python -m unittest discover"); pass target to run a specific test module or path. Reports the exit code and output. Use to verify code changes.',
121
+ parameters={
122
+ 'type': 'object',
123
+ 'properties': {
124
+ 'target': {
125
+ 'type': 'string',
126
+ 'description': 'Optional test module, class, or path to run instead of the whole suite',
127
+ },
128
+ 'cwd': {
129
+ 'type': 'string',
130
+ 'description': 'Working directory for the command (defaults to the current one)',
131
+ },
132
+ 'timeout': {
133
+ 'type': 'integer',
134
+ 'description': 'Timeout in seconds before the command is killed (capped at 600)',
135
+ },
136
+ },
137
+ 'required': [],
138
+ },
139
+ category='exec',
140
+ permission='bash',
141
+ path_arg='cwd',
142
+ key_arg='target',
143
+ short='Run the project test suite',
144
+ echo=True,
145
+ glyph='$',
146
+ verb='Test',
147
+ error=_is_failure,
148
+ aliases=['run_tests', 'test_suite'],
149
+ param_aliases={'test': 'target', 'path': 'target', 'tests': 'target'},
150
+ )
151
+ def code_test(target: str = '', cwd: str | None = None,
152
+ timeout: int = DEFAULT_TIMEOUT, _config=None) -> str:
153
+ argv = _test_argv(_config, target)
154
+ return _run(argv, cwd, _config, timeout, target=target)
155
+
156
+ @registry.register(
157
+ name='code_lint',
158
+ description='Run the project linter. Uses the dev.lint_cmd config key (default "ruff check ."); pass target to lint a specific path. Reports the exit code and findings. Use to check code style before finishing.',
159
+ parameters={
160
+ 'type': 'object',
161
+ 'properties': {
162
+ 'target': {
163
+ 'type': 'string',
164
+ 'description': 'Optional path to lint instead of the whole project',
165
+ },
166
+ 'cwd': {
167
+ 'type': 'string',
168
+ 'description': 'Working directory for the command (defaults to the current one)',
169
+ },
170
+ 'timeout': {
171
+ 'type': 'integer',
172
+ 'description': 'Timeout in seconds before the command is killed (capped at 600)',
173
+ },
174
+ },
175
+ 'required': [],
176
+ },
177
+ category='exec',
178
+ permission='bash',
179
+ path_arg='cwd',
180
+ key_arg='target',
181
+ short='Run the project linter',
182
+ echo=True,
183
+ glyph='$',
184
+ verb='Lint',
185
+ error=_is_failure,
186
+ aliases=['run_lint', 'lint_check'],
187
+ param_aliases={'path': 'target'},
188
+ )
189
+ def code_lint(target: str = '', cwd: str | None = None,
190
+ timeout: int = DEFAULT_TIMEOUT, _config=None) -> str:
191
+ argv = _cmd(_config, 'dev.lint_cmd', DEFAULTS['lint'])
192
+ if target:
193
+ argv = argv + [target]
194
+ return _run(argv, cwd, _config, timeout)
195
+
196
+ @registry.register(
197
+ name='code_format',
198
+ description='Run the project formatter. Uses the dev.format_cmd config key (default "ruff format ."); pass target to format a specific path. Reports the exit code and output. Use to format code before finishing.',
199
+ parameters={
200
+ 'type': 'object',
201
+ 'properties': {
202
+ 'target': {
203
+ 'type': 'string',
204
+ 'description': 'Optional path to format instead of the whole project',
205
+ },
206
+ 'cwd': {
207
+ 'type': 'string',
208
+ 'description': 'Working directory for the command (defaults to the current one)',
209
+ },
210
+ 'timeout': {
211
+ 'type': 'integer',
212
+ 'description': 'Timeout in seconds before the command is killed (capped at 600)',
213
+ },
214
+ },
215
+ 'required': [],
216
+ },
217
+ category='exec',
218
+ permission='bash',
219
+ path_arg='cwd',
220
+ key_arg='target',
221
+ short='Run the project formatter',
222
+ echo=True,
223
+ glyph='$',
224
+ verb='Format',
225
+ error=_is_failure,
226
+ aliases=['run_format', 'format_code'],
227
+ param_aliases={'path': 'target'},
228
+ )
229
+ def code_format(target: str = '', cwd: str | None = None,
230
+ timeout: int = DEFAULT_TIMEOUT, _config=None) -> str:
231
+ argv = _cmd(_config, 'dev.format_cmd', DEFAULTS['format'])
232
+ if target:
233
+ argv = argv + [target]
234
+ return _run(argv, cwd, _config, timeout)