alephprover 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ dry_run:
10
+ description: "Dry run (build only, do not upload)"
11
+ type: boolean
12
+ default: false
13
+
14
+ jobs:
15
+ build:
16
+ runs-on: ubuntu-24.04
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+
24
+ - name: Install build tools
25
+ run: pip install build
26
+
27
+ - name: Build package
28
+ run: python -m build
29
+
30
+ - uses: actions/upload-artifact@v4
31
+ with:
32
+ name: dist
33
+ path: dist/
34
+
35
+ publish:
36
+ needs: build
37
+ runs-on: ubuntu-24.04
38
+ if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && !inputs.dry_run)
39
+ environment: pypi
40
+ permissions:
41
+ id-token: write
42
+ steps:
43
+ - uses: actions/download-artifact@v4
44
+ with:
45
+ name: dist
46
+ path: dist/
47
+
48
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .idea/
8
+ .env
9
+ *.zip
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Logical Intelligence
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,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: alephprover
3
+ Version: 0.0.1
4
+ Summary: CLI tool for submitting Lean proof requests to the Aleph Prover API
5
+ Project-URL: Homepage, https://alephprover.logicalintelligence.com
6
+ Project-URL: Repository, https://github.com/logiq-ai/alephprover
7
+ Author: Logical Intelligence
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: formal-verification,lean,lean4,proof-assistant,theorem-proving
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: click>=8.0
22
+ Requires-Dist: httpx>=0.27
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Aleph Prover CLI
26
+
27
+ Prove Lean4 theorems with the [Aleph Prover](https://alephprover.logicalintelligence.com) API from your terminal or from [Claude Code](https://claude.ai/code).
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ # Run directly with uvx (no install needed)
33
+ uvx alephprover prove <file_path> <theorem_name>
34
+
35
+ # Or install globally
36
+ uv tool install alephprover
37
+
38
+ # Or with pip
39
+ pip install alephprover
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```bash
45
+ # Set your API key (get one at https://alephprover.logicalintelligence.com/account)
46
+ export PROVER_API_KEY="sk-aleph-..."
47
+
48
+ # Submit a proof request
49
+ alephprover prove Mathlib/Algebra/Group/Basic.lean mul_left_cancel
50
+
51
+ # With hints and budgets
52
+ alephprover prove MyProject/Basic.lean my_theorem \
53
+ --hints "try induction on n" \
54
+ --time-budget 30 \
55
+ --cost-budget 10
56
+ ```
57
+
58
+ The CLI will:
59
+ 1. Find the Lean project root (walks up to `lakefile.lean` / `lakefile.toml`)
60
+ 2. Zip the project (excluding build artifacts)
61
+ 3. Upload to the API and poll for completion
62
+ 4. Download and apply the proof diff via `git apply`
63
+
64
+ ## Claude Code Skill
65
+
66
+ Install the `/prove` skill for [Claude Code](https://claude.ai/code):
67
+
68
+ ```bash
69
+ # Install in current project
70
+ uvx alephprover install-skill
71
+
72
+ # Or install globally (available in all projects)
73
+ uvx alephprover install-skill --global
74
+ ```
75
+
76
+ Then in Claude Code:
77
+
78
+ ```
79
+ /prove mul_left_cancel in Mathlib/Algebra/Group/Basic.lean
80
+ /prove my_theorem in MyProject/Basic.lean with hint: try induction on n
81
+ ```
82
+
83
+ ## Configuration
84
+
85
+ | Variable | Required | Default | Description |
86
+ |---|---|---|---|
87
+ | `PROVER_API_KEY` | Yes | — | API key (`sk-aleph-...`) from [Account Settings](https://alephprover.logicalintelligence.com/account) |
88
+ | `PROVER_API_URL` | No | `https://alephprover.logicalintelligence.com` | API base URL |
89
+
90
+ ## Requirements
91
+
92
+ - Python >= 3.10
93
+ - [`uv`](https://docs.astral.sh/uv/) (for `uvx` usage) or `pip install alephprover`
94
+ - `git` (for applying patches)
95
+ - An Aleph Prover account with an API key
@@ -0,0 +1,71 @@
1
+ # Aleph Prover CLI
2
+
3
+ Prove Lean4 theorems with the [Aleph Prover](https://alephprover.logicalintelligence.com) API from your terminal or from [Claude Code](https://claude.ai/code).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # Run directly with uvx (no install needed)
9
+ uvx alephprover prove <file_path> <theorem_name>
10
+
11
+ # Or install globally
12
+ uv tool install alephprover
13
+
14
+ # Or with pip
15
+ pip install alephprover
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```bash
21
+ # Set your API key (get one at https://alephprover.logicalintelligence.com/account)
22
+ export PROVER_API_KEY="sk-aleph-..."
23
+
24
+ # Submit a proof request
25
+ alephprover prove Mathlib/Algebra/Group/Basic.lean mul_left_cancel
26
+
27
+ # With hints and budgets
28
+ alephprover prove MyProject/Basic.lean my_theorem \
29
+ --hints "try induction on n" \
30
+ --time-budget 30 \
31
+ --cost-budget 10
32
+ ```
33
+
34
+ The CLI will:
35
+ 1. Find the Lean project root (walks up to `lakefile.lean` / `lakefile.toml`)
36
+ 2. Zip the project (excluding build artifacts)
37
+ 3. Upload to the API and poll for completion
38
+ 4. Download and apply the proof diff via `git apply`
39
+
40
+ ## Claude Code Skill
41
+
42
+ Install the `/prove` skill for [Claude Code](https://claude.ai/code):
43
+
44
+ ```bash
45
+ # Install in current project
46
+ uvx alephprover install-skill
47
+
48
+ # Or install globally (available in all projects)
49
+ uvx alephprover install-skill --global
50
+ ```
51
+
52
+ Then in Claude Code:
53
+
54
+ ```
55
+ /prove mul_left_cancel in Mathlib/Algebra/Group/Basic.lean
56
+ /prove my_theorem in MyProject/Basic.lean with hint: try induction on n
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ | Variable | Required | Default | Description |
62
+ |---|---|---|---|
63
+ | `PROVER_API_KEY` | Yes | — | API key (`sk-aleph-...`) from [Account Settings](https://alephprover.logicalintelligence.com/account) |
64
+ | `PROVER_API_URL` | No | `https://alephprover.logicalintelligence.com` | API base URL |
65
+
66
+ ## Requirements
67
+
68
+ - Python >= 3.10
69
+ - [`uv`](https://docs.astral.sh/uv/) (for `uvx` usage) or `pip install alephprover`
70
+ - `git` (for applying patches)
71
+ - An Aleph Prover account with an API key
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "alephprover"
7
+ version = "0.0.1"
8
+ description = "CLI tool for submitting Lean proof requests to the Aleph Prover API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{name = "Logical Intelligence"}]
13
+ keywords = ["lean", "lean4", "theorem-proving", "formal-verification", "proof-assistant"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Scientific/Engineering :: Mathematics",
24
+ ]
25
+ dependencies = [
26
+ "httpx>=0.27",
27
+ "click>=8.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://alephprover.logicalintelligence.com"
32
+ Repository = "https://github.com/logiq-ai/alephprover"
33
+
34
+ [project.scripts]
35
+ alephprover = "alephprover.cli:main"
@@ -0,0 +1,3 @@
1
+ """Aleph Prover CLI — submit Lean proof requests from your terminal."""
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,392 @@
1
+ """Aleph Prover CLI — submit Lean proof requests and apply results."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.resources
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ import time
11
+ import zipfile
12
+ from pathlib import Path
13
+
14
+ import click
15
+ import httpx
16
+
17
+ DEFAULT_API_URL = "https://alephprover.logicalintelligence.com"
18
+ POLL_INTERVAL = 15 # seconds
19
+ POLL_TIMEOUT = 60 * 60 # 60 minutes
20
+
21
+ # Root config files to always include in the archive
22
+ ROOT_FILES = ("lakefile.lean", "lakefile.toml", "lean-toolchain", "lake-manifest.json")
23
+
24
+
25
+ def find_project_root(start: Path) -> Path:
26
+ """Walk up from start to find directory containing lakefile.lean or lakefile.toml."""
27
+ current = start.resolve()
28
+ if current.is_file():
29
+ current = current.parent
30
+ while current != current.parent:
31
+ if (current / "lakefile.lean").exists() or (current / "lakefile.toml").exists():
32
+ return current
33
+ current = current.parent
34
+ click.echo(
35
+ "Error: Could not find a Lean project root (lakefile.lean or lakefile.toml).\n"
36
+ "Make sure you are inside a Lean project directory.",
37
+ err=True,
38
+ )
39
+ sys.exit(1)
40
+
41
+
42
+ def validate_api_key() -> str:
43
+ """Check PROVER_API_KEY env var."""
44
+ key = os.environ.get("PROVER_API_KEY", "")
45
+ if not key:
46
+ click.echo(
47
+ "Error: PROVER_API_KEY is not set.\n"
48
+ "\n"
49
+ "To set up:\n"
50
+ " 1. Get an API key from https://alephprover.logicalintelligence.com/account\n"
51
+ ' 2. export PROVER_API_KEY="sk-aleph-..."',
52
+ err=True,
53
+ )
54
+ sys.exit(1)
55
+ if not key.startswith("sk-aleph-"):
56
+ click.echo(
57
+ 'Error: PROVER_API_KEY must start with "sk-aleph-".\n'
58
+ "Check your API key at https://alephprover.logicalintelligence.com/account",
59
+ err=True,
60
+ )
61
+ sys.exit(1)
62
+ return key
63
+
64
+
65
+ def get_api_url() -> str:
66
+ return os.environ.get("PROVER_API_URL", DEFAULT_API_URL).rstrip("/")
67
+
68
+
69
+ def get_lean_src_paths(project_root: Path) -> list[str] | None:
70
+ """Query Lake for source directories. Returns None if Lake is unavailable."""
71
+ try:
72
+ result = subprocess.run(
73
+ ["lake", "env", "printenv", "LEAN_SRC_PATH"],
74
+ capture_output=True,
75
+ text=True,
76
+ cwd=project_root,
77
+ timeout=30,
78
+ )
79
+ if result.returncode == 0 and result.stdout.strip():
80
+ return [p for p in result.stdout.strip().split(":") if p]
81
+ except (FileNotFoundError, subprocess.TimeoutExpired):
82
+ pass
83
+ return None
84
+
85
+
86
+ def zip_project(project_root: Path, verbose: bool = False, all_files: bool = False) -> Path:
87
+ """Create a ZIP archive with only the files needed for building.
88
+
89
+ Uses Lake to discover source directories when available. Falls back to
90
+ including all .lean files outside .lake/.
91
+ """
92
+ # Collect files to archive
93
+ files: list[Path] = []
94
+
95
+ if all_files:
96
+ click.echo("Warning: --all-files includes ALL project files. Archive may be large and may contain sensitive data.", err=True)
97
+ for p in sorted(project_root.rglob("*")):
98
+ if p.is_file():
99
+ files.append(p)
100
+ else:
101
+ src_paths = get_lean_src_paths(project_root)
102
+
103
+ # Always include root config files
104
+ for name in ROOT_FILES:
105
+ p = project_root / name
106
+ if p.exists():
107
+ files.append(p)
108
+
109
+ if src_paths is not None:
110
+ click.echo(f"Lake source paths: {', '.join(src_paths) or '.'}")
111
+ if src_paths:
112
+ for src in src_paths:
113
+ src_dir = project_root / src
114
+ if src_dir.is_dir():
115
+ files.extend(sorted(src_dir.rglob("*.lean")))
116
+ else:
117
+ # Empty LEAN_SRC_PATH means project root is the source dir
118
+ for p in sorted(project_root.rglob("*.lean")):
119
+ rel = p.relative_to(project_root).as_posix()
120
+ if not rel.startswith(".lake/") and not rel.startswith("lake-packages/"):
121
+ files.append(p)
122
+ else:
123
+ click.echo("Lake not available, including all .lean files")
124
+ for p in sorted(project_root.rglob("*.lean")):
125
+ rel = p.relative_to(project_root).as_posix()
126
+ if not rel.startswith(".lake/") and not rel.startswith("lake-packages/"):
127
+ files.append(p)
128
+
129
+ # Deduplicate while preserving order
130
+ seen: set[Path] = set()
131
+ unique_files: list[Path] = []
132
+ for f in files:
133
+ if f not in seen:
134
+ seen.add(f)
135
+ unique_files.append(f)
136
+
137
+ # Create archive
138
+ tmp = tempfile.NamedTemporaryFile(suffix=".zip", prefix="alephprover-", delete=False)
139
+ tmp.close()
140
+ zip_path = Path(tmp.name)
141
+
142
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
143
+ for path in unique_files:
144
+ rel = path.relative_to(project_root).as_posix()
145
+ if verbose:
146
+ click.echo(f" {rel}")
147
+ zf.write(path, rel)
148
+
149
+ size_mb = zip_path.stat().st_size / (1024 * 1024)
150
+ click.echo(f"Archived {len(unique_files)} files ({size_mb:.1f} MB)")
151
+ return zip_path
152
+
153
+
154
+ def submit(
155
+ api_url: str,
156
+ api_key: str,
157
+ zip_path: Path,
158
+ file_path: str,
159
+ theorem_name: str,
160
+ hints: str | None,
161
+ time_budget: int | None,
162
+ cost_budget: float | None,
163
+ ) -> str:
164
+ """Submit the archive and return the request ID."""
165
+ data: dict[str, str] = {
166
+ "file_path": file_path,
167
+ "theorem_name": theorem_name,
168
+ }
169
+ if hints:
170
+ data["hints"] = hints
171
+ if time_budget is not None:
172
+ data["time_budget_minutes"] = str(time_budget)
173
+ if cost_budget is not None:
174
+ data["cost_budget_usd"] = str(cost_budget)
175
+
176
+ with open(zip_path, "rb") as f:
177
+ files = {"archive": ("project.zip", f, "application/zip")}
178
+ response = httpx.post(
179
+ f"{api_url}/api/v1/requests/upload",
180
+ headers={"Authorization": f"Bearer {api_key}"},
181
+ data=data,
182
+ files=files,
183
+ timeout=120.0,
184
+ )
185
+
186
+ if response.status_code != 201:
187
+ click.echo(f"Error: Upload failed (HTTP {response.status_code})", err=True)
188
+ try:
189
+ click.echo(response.json(), err=True)
190
+ except Exception:
191
+ click.echo(response.text, err=True)
192
+ sys.exit(1)
193
+
194
+ request_id = response.json()["id"]
195
+ return request_id
196
+
197
+
198
+ def poll_status(api_url: str, api_key: str, request_id: str) -> str:
199
+ """Poll until terminal status. Returns final status."""
200
+ headers = {"Authorization": f"Bearer {api_key}"}
201
+ start = time.time()
202
+ last_stage = None
203
+
204
+ while time.time() - start < POLL_TIMEOUT:
205
+ try:
206
+ response = httpx.get(
207
+ f"{api_url}/api/v1/requests/{request_id}",
208
+ headers=headers,
209
+ timeout=30.0,
210
+ )
211
+ response.raise_for_status()
212
+ except httpx.HTTPError as e:
213
+ click.echo(f"Warning: Poll request failed ({e}), retrying...", err=True)
214
+ time.sleep(POLL_INTERVAL)
215
+ continue
216
+
217
+ data = response.json()
218
+ status = data["status"]
219
+ stage = data.get("stage")
220
+
221
+ if stage and stage != last_stage:
222
+ click.echo(f" Stage: {stage}")
223
+ last_stage = stage
224
+
225
+ if status in ("completed", "failed", "cancelled"):
226
+ return status
227
+
228
+ time.sleep(POLL_INTERVAL)
229
+
230
+ click.echo(
231
+ f"Timeout: polling exceeded {POLL_TIMEOUT // 60} minutes.\n"
232
+ f"Check status at: {api_url}/requests/{request_id}",
233
+ err=True,
234
+ )
235
+ sys.exit(1)
236
+
237
+
238
+ def download_diff(api_url: str, api_key: str, request_id: str) -> Path | None:
239
+ """Download the diff patch. Returns path or None on failure."""
240
+ response = httpx.get(
241
+ f"{api_url}/api/v1/requests/{request_id}/diff",
242
+ headers={"Authorization": f"Bearer {api_key}"},
243
+ timeout=60.0,
244
+ )
245
+ if response.status_code != 200:
246
+ return None
247
+
248
+ patch_path = Path(tempfile.mktemp(suffix=".patch", prefix=f"proof-{request_id[:8]}-"))
249
+ patch_path.write_bytes(response.content)
250
+ return patch_path
251
+
252
+
253
+ def apply_patch(patch_path: Path, project_root: Path) -> bool:
254
+ """Apply patch with git apply. Returns True on success."""
255
+ result = subprocess.run(
256
+ ["git", "apply", str(patch_path)],
257
+ capture_output=True,
258
+ text=True,
259
+ cwd=project_root,
260
+ )
261
+ if result.returncode == 0:
262
+ return True
263
+
264
+ # Try --3way fallback
265
+ click.echo("Standard apply failed, trying --3way...")
266
+ result = subprocess.run(
267
+ ["git", "apply", "--3way", str(patch_path)],
268
+ capture_output=True,
269
+ text=True,
270
+ cwd=project_root,
271
+ )
272
+ return result.returncode == 0
273
+
274
+
275
+ @click.group()
276
+ @click.version_option(package_name="alephprover")
277
+ def main():
278
+ """Aleph Prover CLI — submit Lean proof requests."""
279
+
280
+
281
+ @main.command()
282
+ @click.argument("file_path")
283
+ @click.argument("theorem_name")
284
+ @click.option("--hints", default=None, help="Hints for the prover")
285
+ @click.option("--time-budget", default=None, type=int, help="Time budget in minutes (default: server-side 900)")
286
+ @click.option("--cost-budget", default=None, type=float, help="Cost budget in USD (default: server-side 50)")
287
+ @click.option("-v", "--verbose", is_flag=True, help="List files included in the archive")
288
+ @click.option("--all-files", is_flag=True, help="Archive ALL project files (may include sensitive data)")
289
+ def prove(
290
+ file_path: str,
291
+ theorem_name: str,
292
+ hints: str | None,
293
+ time_budget: int | None,
294
+ cost_budget: float | None,
295
+ verbose: bool,
296
+ all_files: bool,
297
+ ):
298
+ """Submit a Lean theorem for proving.
299
+
300
+ FILE_PATH is the path to the Lean file (relative to project root).
301
+ THEOREM_NAME is the name of the theorem to prove.
302
+ """
303
+ api_key = validate_api_key()
304
+ api_url = get_api_url()
305
+
306
+ # Find project root
307
+ if Path(file_path).is_absolute():
308
+ start = Path(file_path)
309
+ else:
310
+ start = Path.cwd()
311
+ project_root = find_project_root(start)
312
+ click.echo(f"Project root: {project_root}")
313
+
314
+ # Resolve file_path relative to project root
315
+ abs_file = (project_root / file_path) if not Path(file_path).is_absolute() else Path(file_path)
316
+ if not abs_file.exists():
317
+ click.echo(f"Error: File not found: {abs_file}", err=True)
318
+ sys.exit(1)
319
+ rel_file = abs_file.relative_to(project_root).as_posix()
320
+
321
+ # Zip
322
+ click.echo("Zipping project...")
323
+ zip_path = zip_project(project_root, verbose=verbose, all_files=all_files)
324
+
325
+ try:
326
+ # Submit
327
+ click.echo("Submitting proof request...")
328
+ request_id = submit(api_url, api_key, zip_path, rel_file, theorem_name, hints, time_budget, cost_budget)
329
+ click.echo(f"Request ID: {request_id}")
330
+ click.echo(f"View at: {api_url}/requests/{request_id}")
331
+
332
+ # Poll
333
+ click.echo("Waiting for proof...")
334
+ status = poll_status(api_url, api_key, request_id)
335
+
336
+ if status != "completed":
337
+ click.echo(f"Proof request {status}.", err=True)
338
+ click.echo(f"Details: {api_url}/requests/{request_id}", err=True)
339
+ sys.exit(1)
340
+
341
+ click.echo("Proof completed!")
342
+
343
+ # Download diff
344
+ click.echo("Downloading diff...")
345
+ patch_path = download_diff(api_url, api_key, request_id)
346
+
347
+ if patch_path is None:
348
+ click.echo(
349
+ "Could not download diff. Check results at:\n"
350
+ f" {api_url}/requests/{request_id}",
351
+ err=True,
352
+ )
353
+ sys.exit(1)
354
+
355
+ # Apply
356
+ click.echo("Applying patch...")
357
+ if apply_patch(patch_path, project_root):
358
+ click.echo("Proof applied successfully!")
359
+ # Show what changed
360
+ result = subprocess.run(["git", "diff", "--stat"], capture_output=True, text=True, cwd=project_root)
361
+ if result.stdout:
362
+ click.echo(result.stdout)
363
+ # Cleanup patch
364
+ patch_path.unlink(missing_ok=True)
365
+ else:
366
+ click.echo(
367
+ f"Could not apply patch automatically.\n"
368
+ f"Patch saved to: {patch_path}\n"
369
+ f"Apply manually with: git apply {patch_path}",
370
+ err=True,
371
+ )
372
+ sys.exit(1)
373
+ finally:
374
+ # Cleanup zip
375
+ Path(zip_path).unlink(missing_ok=True)
376
+
377
+
378
+ @main.command("install-skill")
379
+ @click.option("--global", "global_", is_flag=True, help="Install to ~/.claude/commands/ instead of project")
380
+ def install_skill(global_: bool):
381
+ """Install the /prove skill for Claude Code."""
382
+ if global_:
383
+ target = Path.home() / ".claude" / "commands" / "prove.md"
384
+ else:
385
+ target = Path.cwd() / ".claude" / "commands" / "prove.md"
386
+
387
+ source = importlib.resources.files("alephprover") / "prove.md"
388
+ content = source.read_text(encoding="utf-8")
389
+
390
+ target.parent.mkdir(parents=True, exist_ok=True)
391
+ target.write_text(content, encoding="utf-8")
392
+ click.echo(f"Installed /prove skill to {target}")
@@ -0,0 +1,41 @@
1
+ ---
2
+ description: Submit a Lean theorem to Aleph Prover and apply the proof
3
+ allowed-tools: Bash(uvx:*)
4
+ ---
5
+
6
+ # Aleph Prover Skill
7
+
8
+ Submit a Lean theorem to the Aleph Prover API using the `alephprover` CLI.
9
+
10
+ ## Arguments
11
+
12
+ The user provides the theorem name and file path. Examples:
13
+ - `/prove mul_left_cancel in Mathlib/Algebra/Group/Basic.lean`
14
+ - `/prove my_theorem in MyProject/Basic.lean with hint: try induction on n`
15
+
16
+ Parse the user's message to extract:
17
+ - `theorem_name` (required): the theorem to prove
18
+ - `file_path` (required): path to the Lean file containing the theorem
19
+ - `hints` (optional): any hints or guidance after "with hint:" or "hint:"
20
+
21
+ If the theorem name or file path is unclear, ask the user to clarify before proceeding.
22
+
23
+ ## Configuration
24
+
25
+ **Required:** `PROVER_API_KEY` environment variable (starts with `sk-aleph-`)
26
+
27
+ **Optional:** `PROVER_API_URL` environment variable (defaults to `https://alephprover.logicalintelligence.com`)
28
+
29
+ ## Run
30
+
31
+ ```bash
32
+ uvx alephprover prove <file_path> <theorem_name> [--hints "..."] [--time-budget <minutes>] [--cost-budget <usd>] [-v]
33
+ ```
34
+
35
+ Use `timeout=3700000` (just over 60 min) for the command since polling can take up to 60 minutes.
36
+
37
+ ## After completion
38
+
39
+ 1. Show whether the proof was applied successfully
40
+ 2. Show which files were modified (`git diff --stat`)
41
+ 3. Read the modified theorem from the file and show the proof to the user