leanwarp 0.2.3__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.
- leanwarp-0.2.3.dist-info/METADATA +210 -0
- leanwarp-0.2.3.dist-info/RECORD +24 -0
- leanwarp-0.2.3.dist-info/WHEEL +4 -0
- leanwarp-0.2.3.dist-info/entry_points.txt +2 -0
- leanwarp-0.2.3.dist-info/licenses/LICENSE +21 -0
- leanwarp_cloud/__init__.py +25 -0
- leanwarp_cloud/_version.py +8 -0
- leanwarp_cloud/agents.py +173 -0
- leanwarp_cloud/amounts.py +20 -0
- leanwarp_cloud/cli.py +700 -0
- leanwarp_cloud/client.py +781 -0
- leanwarp_cloud/config.py +82 -0
- leanwarp_cloud/edits.py +72 -0
- leanwarp_cloud/endpoints.py +39 -0
- leanwarp_cloud/mcp_server.py +568 -0
- leanwarp_cloud/outcome.py +112 -0
- leanwarp_cloud/project.py +613 -0
- leanwarp_cloud/py.typed +0 -0
- leanwarp_cloud/resources.py +50 -0
- leanwarp_cloud/session.py +1334 -0
- leanwarp_cloud/skills/leanwarp/SKILL.md +215 -0
- leanwarp_cloud/skills/leanwarp/references/usage.md +460 -0
- leanwarp_cloud/transport.py +221 -0
- leanwarp_cloud/updates.py +144 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: leanwarp
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Check and verify Lean proofs on LeanWarp from the command line, MCP or Python
|
|
5
|
+
Project-URL: Repository, https://github.com/Isagoge-Labs/LeanWarp-SDK
|
|
6
|
+
Project-URL: Homepage, https://isagoge.in
|
|
7
|
+
Project-URL: Documentation, https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/README.md
|
|
8
|
+
Project-URL: Issues, https://github.com/Isagoge-Labs/LeanWarp-SDK/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Isagoge-Labs/LeanWarp-SDK/releases
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agents,formal-verification,lean,lean4,mathlib,mcp,theorem-proving
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: httpcore<2,>=1.0.9
|
|
25
|
+
Requires-Dist: httpx<1,>=0.28.1
|
|
26
|
+
Requires-Dist: mcp<2,>=1.28.1
|
|
27
|
+
Requires-Dist: pathspec<2,>=1.1
|
|
28
|
+
Requires-Dist: pydantic<3,>=2.12
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest<10,>=8.4; extra == 'test'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
<h1><img src="https://raw.githubusercontent.com/Isagoge-Labs/LeanWarp-SDK/main/docs/assets/leanwarp-banner.svg" alt="LeanWarp" width="960"></h1>
|
|
34
|
+
|
|
35
|
+
LeanWarp checks and verifies Lean 4 proofs on hosted workers that stay warm
|
|
36
|
+
between edits. This SDK is how you and your agents use it: the `leanwarp`
|
|
37
|
+
command, an MCP server and a Python library.
|
|
38
|
+
|
|
39
|
+
**[Get started](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/getting-started.md)** · [Agent setup](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/agents.md) ·
|
|
40
|
+
[Reference](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/src/leanwarp_cloud/skills/leanwarp/references/usage.md) ·
|
|
41
|
+
[HTTP API](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/http-api.md) · [Website](https://isagoge.in/leanwarp)
|
|
42
|
+
|
|
43
|
+
## What it does
|
|
44
|
+
|
|
45
|
+
| Operation | What you get | CLI |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Check | Lean's errors and warnings for a file; `--draft` reuses Lean's work on the unchanged part while you edit. | `leanwarp check` |
|
|
48
|
+
| Inspect | The goals and local context at a position in a proof. | `leanwarp inspect` |
|
|
49
|
+
| Try tactics | Several tactics from the same captured goal: which close it, which leave goals, which fail. The file is not edited. | `leanwarp try-tactics` |
|
|
50
|
+
| Verify | Whether a declaration proves exactly the statement you give, checked by Lean's kernel. | `leanwarp verify` |
|
|
51
|
+
| Holes | Every `sorry` and `?_` in a file, with its position, and the goals of the first 16. | `leanwarp holes` |
|
|
52
|
+
| Suggest | A tactic that closes a hole, accepted only if nothing else changes and the proof gains no unproved lemma or extra axiom. `apply` writes it into the file. | `leanwarp suggest`, `leanwarp apply` |
|
|
53
|
+
| Sorry to lemma | The proof of each theorem that still has a `sorry` moved into a new named lemma the theorem then uses; the lemma keeps its `sorry`. `apply` writes it. | `leanwarp sorry2lemma` |
|
|
54
|
+
| Declarations | The declarations a file adds, with their types and dependencies. | `leanwarp declarations` |
|
|
55
|
+
| Check files | Up to 64 files checked in one operation. | `leanwarp check-files` |
|
|
56
|
+
|
|
57
|
+
The [reference](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/src/leanwarp_cloud/skills/leanwarp/references/usage.md#commands)
|
|
58
|
+
lists every command and option; `leanwarp skill --reference` prints it.
|
|
59
|
+
|
|
60
|
+
### Coming soon
|
|
61
|
+
|
|
62
|
+
These are planned and not available yet:
|
|
63
|
+
|
|
64
|
+
- **Proof simplification**: replace a proof with a shorter one that still checks.
|
|
65
|
+
- **`have` extraction**: lift named `have` blocks into top-level lemmas.
|
|
66
|
+
- **Batch repair**: fill several holes in one operation.
|
|
67
|
+
|
|
68
|
+
## Install in two steps
|
|
69
|
+
|
|
70
|
+
**1. Install.** In a terminal, with [uv](https://docs.astral.sh/uv/getting-started/installation/):
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
uv tool install leanwarp && leanwarp setup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This installs the `leanwarp` command and its MCP server from
|
|
77
|
+
[PyPI](https://pypi.org/project/leanwarp/). `leanwarp setup` adds the LeanWarp
|
|
78
|
+
skill and MCP server to Claude Code, Codex and Cursor where they are installed,
|
|
79
|
+
and the skill to `~/.agents/skills`. It runs on Linux, macOS and WSL. You don't
|
|
80
|
+
need Lean installed: LeanWarp runs Lean and Mathlib for you.
|
|
81
|
+
|
|
82
|
+
Or paste this into your agent:
|
|
83
|
+
|
|
84
|
+
> Install LeanWarp: run `uv tool install leanwarp && leanwarp setup`, then ask me
|
|
85
|
+
> to sign in.
|
|
86
|
+
|
|
87
|
+
Already have LeanWarp 0.2 from GitHub? Its `leanwarp-sdk` tool owns the
|
|
88
|
+
`leanwarp` command, so replace it instead:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
uv tool uninstall leanwarp-sdk && uv tool install leanwarp && leanwarp setup
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**2. Sign in**, in your own terminal:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
leanwarp auth login
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
It opens the dashboard to create an API key; paste the key at the hidden prompt.
|
|
101
|
+
Never paste it into an agent conversation. Then restart your agent.
|
|
102
|
+
|
|
103
|
+
When a new release is out, results include `update_available`; run
|
|
104
|
+
`leanwarp update` to upgrade and refresh the installed skills.
|
|
105
|
+
|
|
106
|
+
## Quick start
|
|
107
|
+
|
|
108
|
+
From your Lean project or one of the [examples](https://github.com/Isagoge-Labs/LeanWarp-SDK/tree/main/examples):
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
leanwarp doctor # which environment this project runs on
|
|
112
|
+
leanwarp connect # create the project's workspace
|
|
113
|
+
leanwarp check LeanWarpExample.lean
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
If a command returns `success: null` (exit `3`), run `leanwarp wait` until it
|
|
117
|
+
finishes before submitting another operation. Then verify:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
leanwarp verify LeanWarpExample.lean \
|
|
121
|
+
--declaration add_zero_example --target '∀ n : Nat, n + 0 = n'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Wait for that operation to finish too, then run `leanwarp stop` when you are done.
|
|
125
|
+
|
|
126
|
+
Every command prints one JSON object. Results start with `success`: `true` when
|
|
127
|
+
the check passed or the proof was verified, `false` when it did not, and `null`
|
|
128
|
+
while the work is still running, in which case run `leanwarp wait`. The exit code
|
|
129
|
+
says the same thing: `0` passed, `1` did not pass, `2` the command failed, `3`
|
|
130
|
+
still running.
|
|
131
|
+
|
|
132
|
+
## Use with an agent
|
|
133
|
+
|
|
134
|
+
Give your agent the project directory and this instruction:
|
|
135
|
+
|
|
136
|
+
> Run `leanwarp skill` and follow it. Use LeanWarp to check this Lean project
|
|
137
|
+
> and verify proofs against their intended statements. Reuse the workspace
|
|
138
|
+
> while you edit, and run `leanwarp stop` when you are done.
|
|
139
|
+
|
|
140
|
+
For MCP clients, add the server `leanwarp --project /absolute/path/to/project mcp`,
|
|
141
|
+
or `uvx leanwarp --project /absolute/path/to/project mcp`
|
|
142
|
+
without installing. It uses the same key and project state as the CLI. See [agent setup](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/agents.md).
|
|
143
|
+
Keep API keys out of prompts, source files and tool arguments.
|
|
144
|
+
|
|
145
|
+
## Your project and its environment
|
|
146
|
+
|
|
147
|
+
LeanWarp uploads only your `.lean` files. Lean, Mathlib and every other
|
|
148
|
+
dependency come from an **environment**: a Lean release with Mathlib, already
|
|
149
|
+
built on LeanWarp's workers. Run `leanwarp environments` to list them.
|
|
150
|
+
`leanwarp files` lists what would upload; patterns in `.gitignore` and
|
|
151
|
+
`.leanwarpignore` leave files out.
|
|
152
|
+
|
|
153
|
+
`leanwarp connect` picks the environment for you:
|
|
154
|
+
|
|
155
|
+
- If your project's `lean-toolchain` and `lake-manifest.json` match an
|
|
156
|
+
environment, it uses that one, so results agree with your local build.
|
|
157
|
+
- If your project has neither file, as with a folder of `.lean` files, it uses
|
|
158
|
+
the only served environment, or the newest served stable Lean release when there are several.
|
|
159
|
+
- If they match no environment, it stops and lists the ones available. Run
|
|
160
|
+
`leanwarp connect --environment NAME` to check your files there anyway; they
|
|
161
|
+
then compile against that environment's Lean and Mathlib.
|
|
162
|
+
|
|
163
|
+
To start a new project, copy one of the [examples](https://github.com/Isagoge-Labs/LeanWarp-SDK/tree/main/examples), which match the
|
|
164
|
+
environments LeanWarp serves.
|
|
165
|
+
|
|
166
|
+
## Billing
|
|
167
|
+
|
|
168
|
+
You pay from prepaid credit, billed by the second while a worker runs. Run
|
|
169
|
+
`leanwarp resources` to see the published worker's starting minute price, whether
|
|
170
|
+
charges depend on usage, startup credit hold and idle timeout. LeanWarp selects
|
|
171
|
+
the worker. Use `leanwarp connect --max-spend 2` to create a workspace with a $2
|
|
172
|
+
cap, or edit an existing cap in the dashboard. Reconnecting without a cap keeps
|
|
173
|
+
the saved cap.
|
|
174
|
+
|
|
175
|
+
When the worker uses usage billing, CPU and RAM charges increase independently
|
|
176
|
+
above their reserved baselines. The starting rate is a paid minimum while the
|
|
177
|
+
worker runs, including idle time. Bursting depends on available host capacity.
|
|
178
|
+
Otherwise, the listed minute rate applies for the time the worker runs. Existing
|
|
179
|
+
workspaces keep their saved billing terms.
|
|
180
|
+
|
|
181
|
+
A worker starts with your first operation and stays warm between calls. Run
|
|
182
|
+
`leanwarp stop` when finished; closing the SDK client or your terminal does not
|
|
183
|
+
stop it. Unused held credit is released once shutdown and final usage are confirmed.
|
|
184
|
+
Stopping keeps your uploaded files and
|
|
185
|
+
completed results; the next operation reconstructs the worker. Set a spending cap
|
|
186
|
+
for each workspace in the [dashboard](https://isagoge.in/dashboard).
|
|
187
|
+
|
|
188
|
+
## Concepts
|
|
189
|
+
|
|
190
|
+
| Term | Meaning |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| Environment | A Lean release with Mathlib, prebuilt on LeanWarp, such as `lean-4.26-mathlib`. |
|
|
193
|
+
| Workspace | Your project's files on LeanWarp. One per project; creating it starts no compute. |
|
|
194
|
+
| Worker | The machine that runs Lean for a workspace. It starts on demand and stays warm. |
|
|
195
|
+
| Operation | One request, such as a check or a verification, and its result. |
|
|
196
|
+
| Revision | A numbered version of the workspace's files. Each result names the one it checked. |
|
|
197
|
+
| Receipt | The part of a verification result that records exactly what was checked. |
|
|
198
|
+
|
|
199
|
+
## Documentation
|
|
200
|
+
|
|
201
|
+
| Start here | Reference |
|
|
202
|
+
| --- | --- |
|
|
203
|
+
| [Verify your first proof](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/getting-started.md) | [Commands](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/src/leanwarp_cloud/skills/leanwarp/references/usage.md#commands) |
|
|
204
|
+
| [Set up an agent with the CLI or MCP](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/agents.md) | [Results and exit codes](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/src/leanwarp_cloud/skills/leanwarp/references/usage.md#results) |
|
|
205
|
+
| [Use the Python library](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/src/leanwarp_cloud/skills/leanwarp/references/usage.md#python) | [Recover an interrupted request](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/src/leanwarp_cloud/skills/leanwarp/references/usage.md#recovery) |
|
|
206
|
+
| [Call the HTTP API directly](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/docs/http-api.md) | [Credentials, credit and limits](https://github.com/Isagoge-Labs/LeanWarp-SDK/blob/main/src/leanwarp_cloud/skills/leanwarp/references/usage.md#credentials-credit-and-limits) |
|
|
207
|
+
|
|
208
|
+
The agent instructions ship with the package: `leanwarp skill` prints the
|
|
209
|
+
workflow and `leanwarp skill --reference` the full reference. Run
|
|
210
|
+
`leanwarp --help` or `leanwarp COMMAND --help` for arguments.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
leanwarp_cloud/__init__.py,sha256=KjZcgY1w6u6QMOpR8gf9C2AjDC5bUqoozNo0fitnCRU,721
|
|
2
|
+
leanwarp_cloud/_version.py,sha256=GLCWRRSidfCx1EAkeWvGXJTypRbCHyx5ADAZxr17P0o,295
|
|
3
|
+
leanwarp_cloud/agents.py,sha256=-C9v7XvjZoqrllU3-F7hjvrE78M4mw93HtwTon0JIgU,6571
|
|
4
|
+
leanwarp_cloud/amounts.py,sha256=VYMR3JEkpJAsb0DqE_j3nuwQTmWA1-5MYZYkXZEeZpM,806
|
|
5
|
+
leanwarp_cloud/cli.py,sha256=ugmGiIb668-POBRNB9t8WoDxH79ZShvHdjws4Ya4nto,26905
|
|
6
|
+
leanwarp_cloud/client.py,sha256=YCNFXeY0ebKuBJKPr_N_CydsxSPrLbo333apL6oS_lI,30860
|
|
7
|
+
leanwarp_cloud/config.py,sha256=nA9cLTlglm4F_Lt9UesrnAdkcFMzYw8Gvvto7QG2K_I,3037
|
|
8
|
+
leanwarp_cloud/edits.py,sha256=DtzZ0_L_ujQ1M6HH9ExDdHHs_qhHlt1XWrOZkd-mr7s,3070
|
|
9
|
+
leanwarp_cloud/endpoints.py,sha256=dfSWDmpfQjHH2nB2XiPB1sjG9EP9Jue18W4OmAlkfZM,1493
|
|
10
|
+
leanwarp_cloud/mcp_server.py,sha256=FgHncDCNFYMvX885i3Ru06FDXwBZorwGDDD52xN0ynY,23249
|
|
11
|
+
leanwarp_cloud/outcome.py,sha256=b84jKHBBkC5f3JLqa-i3G0O4G1QID2irLSWsAiLLc0w,4907
|
|
12
|
+
leanwarp_cloud/project.py,sha256=rxYNZj2mquy4vHf33g1Vdfo1nR41b0YP3OVLDox1I7A,24441
|
|
13
|
+
leanwarp_cloud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
leanwarp_cloud/resources.py,sha256=h446GgXmraDjgC1q0j_xhFdSGYC41J06Ok932V8-gD0,1712
|
|
15
|
+
leanwarp_cloud/session.py,sha256=TP_ewzLVFm9WocP9JjY1_KWVMTnNGfoK42dYrnXVz_g,59138
|
|
16
|
+
leanwarp_cloud/transport.py,sha256=t1V-J5hQo5pwyr_YwSO2IxQ0IsmSyxwBy5riBn-znlg,7695
|
|
17
|
+
leanwarp_cloud/updates.py,sha256=tjEajpdMuNTAuQmx_69nafbocTGq0Fa2cqxwOE4xABA,5464
|
|
18
|
+
leanwarp_cloud/skills/leanwarp/SKILL.md,sha256=SPS2vU2K1G7FI1QyzttrYV85uVfLyG6M7bCom5Z8ntQ,11762
|
|
19
|
+
leanwarp_cloud/skills/leanwarp/references/usage.md,sha256=fr5u_8JQuO-fQ-qXMor_SgF5R9WeB64nTImw7QYJm8g,30158
|
|
20
|
+
leanwarp-0.2.3.dist-info/METADATA,sha256=pqZNZaYyvNsQV9O-XxG3Dq8_fM_diTrxtehHHVMxARs,11121
|
|
21
|
+
leanwarp-0.2.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
22
|
+
leanwarp-0.2.3.dist-info/entry_points.txt,sha256=H3r3aaETa4w3OaKOnPzJHXlFW0TTh1720MFYpgbLrB8,53
|
|
23
|
+
leanwarp-0.2.3.dist-info/licenses/LICENSE,sha256=gElcqYwHBv8HCj8Ug0QNdTDu-HHmPi5mn8KBUrGOC1s,1069
|
|
24
|
+
leanwarp-0.2.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Isagoge Labs
|
|
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,25 @@
|
|
|
1
|
+
"""Client for hosted LeanWarp; mathematical execution remains server-side."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .client import LeanWarpCloud, LeanWarpCloudError, OperationTimeout
|
|
5
|
+
from .config import load_client
|
|
6
|
+
from .outcome import OperationOutcome
|
|
7
|
+
from .project import UploadPlan, collect_lean_sources, plan_upload
|
|
8
|
+
from .session import ProjectSession, SessionError, UsageError, WaitError, check_project
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"LeanWarpCloud",
|
|
12
|
+
"LeanWarpCloudError",
|
|
13
|
+
"OperationOutcome",
|
|
14
|
+
"OperationTimeout",
|
|
15
|
+
"ProjectSession",
|
|
16
|
+
"SessionError",
|
|
17
|
+
"UploadPlan",
|
|
18
|
+
"UsageError",
|
|
19
|
+
"WaitError",
|
|
20
|
+
"__version__",
|
|
21
|
+
"check_project",
|
|
22
|
+
"collect_lean_sources",
|
|
23
|
+
"load_client",
|
|
24
|
+
"plan_upload",
|
|
25
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""The installed SDK version, shared by the command line and the HTTP client."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("leanwarp")
|
|
7
|
+
except PackageNotFoundError: # Imported from a source tree without installation.
|
|
8
|
+
__version__ = "0.0.0"
|
leanwarp_cloud/agents.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Install the LeanWarp skill and MCP server for the agents on this machine.
|
|
2
|
+
|
|
3
|
+
Installing a Python package cannot run code, so `leanwarp setup` does this step.
|
|
4
|
+
Each agent is set up only when its configuration directory exists, unless named.
|
|
5
|
+
The skill directory is LeanWarp's own and is replaced; an agent's MCP
|
|
6
|
+
configuration only gains a `leanwarp` entry when it has none, and is never
|
|
7
|
+
otherwise changed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import tomllib
|
|
17
|
+
from collections.abc import Callable, Sequence
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from importlib.resources import files
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Literal
|
|
22
|
+
|
|
23
|
+
AgentName = Literal["claude-code", "codex", "cursor", "agents"]
|
|
24
|
+
AGENTS: tuple[AgentName, ...] = ("claude-code", "codex", "cursor", "agents")
|
|
25
|
+
_SKILL_FILES = ("SKILL.md", "references/usage.md")
|
|
26
|
+
|
|
27
|
+
Run = Callable[..., subprocess.CompletedProcess[str]]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _run(command: Sequence[str], cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
|
|
31
|
+
return subprocess.run( # noqa: S603 - fixed arguments, no shell
|
|
32
|
+
list(command), capture_output=True, text=True, check=False, timeout=60, cwd=cwd
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class _Agent:
|
|
38
|
+
name: AgentName
|
|
39
|
+
home: Path
|
|
40
|
+
# Where its skills live, if it reads skills.
|
|
41
|
+
skills: Path | None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _agents(home: Path, environ: dict[str, str]) -> dict[AgentName, _Agent]:
|
|
45
|
+
claude = Path(environ.get("CLAUDE_CONFIG_DIR") or home / ".claude")
|
|
46
|
+
codex = Path(environ.get("CODEX_HOME") or home / ".codex")
|
|
47
|
+
return {
|
|
48
|
+
"claude-code": _Agent("claude-code", claude, claude / "skills"),
|
|
49
|
+
"codex": _Agent("codex", codex, codex / "skills"),
|
|
50
|
+
"cursor": _Agent("cursor", home / ".cursor", None),
|
|
51
|
+
"agents": _Agent("agents", home / ".agents", home / ".agents" / "skills"),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _install_skill(skills: Path) -> str:
|
|
56
|
+
target = skills / "leanwarp"
|
|
57
|
+
for name in _SKILL_FILES:
|
|
58
|
+
path = target / name
|
|
59
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
source = files("leanwarp_cloud").joinpath(f"skills/leanwarp/{name}")
|
|
61
|
+
path.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")
|
|
62
|
+
return str(target)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _command() -> str:
|
|
66
|
+
"""The `leanwarp` this machine runs, so an MCP client finds it without a PATH."""
|
|
67
|
+
return shutil.which("leanwarp") or "leanwarp"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _claude_mcp(home: Path, run: Run) -> str:
|
|
71
|
+
"""Add the server for the user, from the home folder, so an entry in the current
|
|
72
|
+
project's `.mcp.json` is never taken for the user-wide one."""
|
|
73
|
+
try:
|
|
74
|
+
added = run(
|
|
75
|
+
["claude", "mcp", "add", "--scope", "user", "leanwarp", "--", _command(), "mcp"],
|
|
76
|
+
cwd=home,
|
|
77
|
+
)
|
|
78
|
+
except FileNotFoundError:
|
|
79
|
+
return "claude_cli_not_found"
|
|
80
|
+
except (OSError, subprocess.SubprocessError):
|
|
81
|
+
return "failed"
|
|
82
|
+
if added.returncode == 0:
|
|
83
|
+
return "configured"
|
|
84
|
+
# Claude Code reports an existing user entry as "already exists in user config".
|
|
85
|
+
return "already_configured" if "already exists" in added.stdout + added.stderr else "failed"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _codex_servers(text: str) -> dict[str, Any] | None:
|
|
89
|
+
"""The config's MCP servers as TOML reads them, or None if it cannot be read."""
|
|
90
|
+
try:
|
|
91
|
+
servers = tomllib.loads(text).get("mcp_servers", {})
|
|
92
|
+
except tomllib.TOMLDecodeError:
|
|
93
|
+
return None
|
|
94
|
+
return servers if isinstance(servers, dict) else None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _codex_mcp(home: Path) -> str:
|
|
98
|
+
config = home / "config.toml"
|
|
99
|
+
text = config.read_text(encoding="utf-8") if config.exists() else ""
|
|
100
|
+
# Read the TOML, not its spelling: `[mcp_servers."leanwarp"]` is the same table.
|
|
101
|
+
servers = _codex_servers(text)
|
|
102
|
+
if servers is None:
|
|
103
|
+
return "unreadable_config"
|
|
104
|
+
if "leanwarp" in servers:
|
|
105
|
+
return "already_configured"
|
|
106
|
+
entry = f'[mcp_servers.leanwarp]\ncommand = {json.dumps(_command())}\nargs = ["mcp"]\n'
|
|
107
|
+
if text:
|
|
108
|
+
entry = ("\n" if text.endswith("\n") else "\n\n") + entry
|
|
109
|
+
# Write only a config that still reads, with exactly the entry added.
|
|
110
|
+
updated = _codex_servers(text + entry)
|
|
111
|
+
if updated is None or set(updated) != set(servers) | {"leanwarp"}:
|
|
112
|
+
return "unreadable_config"
|
|
113
|
+
config.write_text(text + entry, encoding="utf-8")
|
|
114
|
+
return "configured"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _cursor_mcp(home: Path) -> str:
|
|
118
|
+
config = home / "mcp.json"
|
|
119
|
+
try:
|
|
120
|
+
document = json.loads(config.read_text(encoding="utf-8")) if config.exists() else {}
|
|
121
|
+
except ValueError:
|
|
122
|
+
return "unreadable_config"
|
|
123
|
+
servers = document.setdefault("mcpServers", {}) if isinstance(document, dict) else None
|
|
124
|
+
if not isinstance(servers, dict):
|
|
125
|
+
return "unreadable_config"
|
|
126
|
+
if "leanwarp" in servers:
|
|
127
|
+
return "already_configured"
|
|
128
|
+
servers["leanwarp"] = {
|
|
129
|
+
"command": _command(),
|
|
130
|
+
"args": ["--project", "${workspaceFolder}", "mcp"],
|
|
131
|
+
}
|
|
132
|
+
config.write_text(json.dumps(document, indent=2) + "\n", encoding="utf-8")
|
|
133
|
+
return "configured"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def setup(
|
|
137
|
+
*,
|
|
138
|
+
agents: Sequence[AgentName] = (),
|
|
139
|
+
mcp: bool = True,
|
|
140
|
+
refresh: bool = False,
|
|
141
|
+
home: Path | None = None,
|
|
142
|
+
environ: dict[str, str] | None = None,
|
|
143
|
+
run: Run = _run,
|
|
144
|
+
) -> dict[str, Any]:
|
|
145
|
+
"""Set up each named agent, or every agent found. With `refresh`, only replace
|
|
146
|
+
skills that are already installed and leave MCP configuration alone."""
|
|
147
|
+
home = home or Path.home()
|
|
148
|
+
known = _agents(home, dict(os.environ if environ is None else environ))
|
|
149
|
+
results: list[dict[str, Any]] = []
|
|
150
|
+
for name in AGENTS:
|
|
151
|
+
agent = known[name]
|
|
152
|
+
named = name in agents
|
|
153
|
+
if agents and not named:
|
|
154
|
+
continue
|
|
155
|
+
installed = agent.skills is not None and (agent.skills / "leanwarp").is_dir()
|
|
156
|
+
if refresh and not installed:
|
|
157
|
+
continue
|
|
158
|
+
if not refresh and not named and name != "agents" and not agent.home.is_dir():
|
|
159
|
+
results.append({"agent": name, "status": "not_found"})
|
|
160
|
+
continue
|
|
161
|
+
result: dict[str, Any] = {"agent": name, "status": "ok"}
|
|
162
|
+
if agent.skills is not None:
|
|
163
|
+
result["skill"] = _install_skill(agent.skills)
|
|
164
|
+
if mcp and not refresh and name != "agents":
|
|
165
|
+
agent.home.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
if name == "claude-code":
|
|
167
|
+
result["mcp"] = _claude_mcp(home, run)
|
|
168
|
+
elif name == "codex":
|
|
169
|
+
result["mcp"] = _codex_mcp(agent.home)
|
|
170
|
+
else:
|
|
171
|
+
result["mcp"] = _cursor_mcp(agent.home)
|
|
172
|
+
results.append(result)
|
|
173
|
+
return {"agents": results}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Exact workspace spending limits shared by Python, CLI and MCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate_spending_cap(value: int | None) -> None:
|
|
9
|
+
if value is not None and (type(value) is not int or not 0 <= value <= 10**12):
|
|
10
|
+
raise ValueError("max_spend_microusd must be an integer from 0 to 1000000000000, or None")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def usd_spending_cap(value: str) -> int:
|
|
14
|
+
"""Parse decimal USD without floating-point rounding or silent precision loss."""
|
|
15
|
+
match = re.fullmatch(r"(0|[1-9][0-9]{0,6})(?:\.([0-9]{1,6}))?", value.strip())
|
|
16
|
+
if match is None:
|
|
17
|
+
raise ValueError("enter a USD amount from 0 to 1000000 with up to six decimals")
|
|
18
|
+
result = int(match[1]) * 1_000_000 + int((match[2] or "").ljust(6, "0"))
|
|
19
|
+
validate_spending_cap(result)
|
|
20
|
+
return result
|