windguru 0.2.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.
- windguru-0.2.0/.github/workflows/ci.yml +26 -0
- windguru-0.2.0/.gitignore +30 -0
- windguru-0.2.0/AGENTS.md +82 -0
- windguru-0.2.0/LICENSE +21 -0
- windguru-0.2.0/PKG-INFO +176 -0
- windguru-0.2.0/README.md +138 -0
- windguru-0.2.0/docs/WIRE.md +103 -0
- windguru-0.2.0/docs/code_quality.md +149 -0
- windguru-0.2.0/docs/data_engineering_standards.md +247 -0
- windguru-0.2.0/docs/mcp.md +71 -0
- windguru-0.2.0/examples/agent_session.sh +8 -0
- windguru-0.2.0/fixtures/forecast_201_gfs.json +3580 -0
- windguru-0.2.0/fixtures/forecast_spot_201.json +1366 -0
- windguru-0.2.0/fixtures/forecast_spot_48309.json +1696 -0
- windguru-0.2.0/fixtures/model_info_full.json +2656 -0
- windguru-0.2.0/fixtures/search_castelldefels.json +144 -0
- windguru-0.2.0/fixtures/spots_simplemap_nl.json +1728 -0
- windguru-0.2.0/guru/__init__.py +28 -0
- windguru-0.2.0/guru/cli/__init__.py +3 -0
- windguru-0.2.0/guru/cli/console.py +3 -0
- windguru-0.2.0/guru/cli/errors.py +34 -0
- windguru-0.2.0/guru/cli/main.py +209 -0
- windguru-0.2.0/guru/cli/render.py +88 -0
- windguru-0.2.0/guru/core/__init__.py +22 -0
- windguru-0.2.0/guru/core/envelope.py +45 -0
- windguru-0.2.0/guru/core/errors.py +60 -0
- windguru-0.2.0/guru/core/instruct.py +66 -0
- windguru-0.2.0/guru/mcp/__init__.py +1 -0
- windguru-0.2.0/guru/mcp/_entry.py +31 -0
- windguru-0.2.0/guru/mcp/server.py +111 -0
- windguru-0.2.0/guru/models/__init__.py +4 -0
- windguru-0.2.0/guru/models/aliases.py +54 -0
- windguru-0.2.0/guru/models/blend.py +28 -0
- windguru-0.2.0/guru/models/forecast.py +62 -0
- windguru-0.2.0/guru/models/models.py +5 -0
- windguru-0.2.0/guru/search/__init__.py +13 -0
- windguru-0.2.0/guru/search/blend.py +101 -0
- windguru-0.2.0/guru/search/blend_math.py +128 -0
- windguru-0.2.0/guru/search/client.py +62 -0
- windguru-0.2.0/guru/search/exceptions.py +34 -0
- windguru-0.2.0/guru/search/forecast.py +106 -0
- windguru-0.2.0/guru/search/near.py +94 -0
- windguru-0.2.0/guru/search/spots.py +102 -0
- windguru-0.2.0/pyproject.toml +70 -0
- windguru-0.2.0/scripts/capture_fixtures.py +65 -0
- windguru-0.2.0/skills/guru/SKILL.md +49 -0
- windguru-0.2.0/tests/conftest.py +14 -0
- windguru-0.2.0/tests/test_blend.py +36 -0
- windguru-0.2.0/tests/test_cli_json.py +62 -0
- windguru-0.2.0/tests/test_forecast_decode.py +45 -0
- windguru-0.2.0/tests/test_live.py +37 -0
- windguru-0.2.0/tests/test_mcp.py +33 -0
- windguru-0.2.0/tests/test_models.py +13 -0
- windguru-0.2.0/tests/test_near.py +16 -0
- windguru-0.2.0/tests/test_resolve.py +76 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: |
|
|
21
|
+
python -m pip install -U pip
|
|
22
|
+
pip install -e ".[dev,mcp]"
|
|
23
|
+
- name: Lint
|
|
24
|
+
run: ruff check .
|
|
25
|
+
- name: Test
|
|
26
|
+
run: pytest -q -m "not live"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.eggs/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
|
|
15
|
+
# Env / secrets
|
|
16
|
+
.env
|
|
17
|
+
.env.*
|
|
18
|
+
!.env.example
|
|
19
|
+
*.pem
|
|
20
|
+
|
|
21
|
+
# Editors / OS
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
.DS_Store
|
|
27
|
+
|
|
28
|
+
# Local capture dumps (keep fixtures/ tracked intentionally)
|
|
29
|
+
/tmp/
|
|
30
|
+
*.har
|
windguru-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# AGENTS.md — how to extend `guru`
|
|
2
|
+
|
|
3
|
+
You are working on **guru**: a Windguru CLI/library/MCP reverse-engineered the same way [`fli`](https://github.com/punitarani/fli) reverse-engineers Google Flights.
|
|
4
|
+
|
|
5
|
+
## Calling guru from an agent
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pipx install 'windguru[mcp]'
|
|
9
|
+
guru instruct --json
|
|
10
|
+
guru spots "<place>" --json # or: guru near --lat --lon --json
|
|
11
|
+
guru best <id_spot> --json # WINDGURU_DEFAULT → top 3 forecasts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- Prefer **`best` / `best_forecast`**, not raw GFS.
|
|
15
|
+
- Preset is always **WINDGURU_DEFAULT** (only adaptive Tune preset we support).
|
|
16
|
+
- On `error_type: ambiguous`, pick from `candidates` — never silent first-match.
|
|
17
|
+
- Envelope: `ok`, `api_version`, `error_type`, `retryable` (shared CLI + MCP).
|
|
18
|
+
- Do **not** scrape HTML, Tune jBox, or MapLibre. Do **not** require PRO.
|
|
19
|
+
|
|
20
|
+
## Hard rule: clone methods from fli
|
|
21
|
+
|
|
22
|
+
**Strongly prefer copying patterns from fli over inventing new ones.**
|
|
23
|
+
|
|
24
|
+
Keep a local checkout of fli (`git clone https://github.com/punitarani/fli /tmp/fli-ref`) and **read their search client before changing ours**.
|
|
25
|
+
|
|
26
|
+
| fli | guru | Copy this idea |
|
|
27
|
+
|-----|------|----------------|
|
|
28
|
+
| `fli/search/client.py` | `guru/search/client.py` | `curl_cffi` impersonation, retries, env timeouts |
|
|
29
|
+
| `fli/core/errors.py` | `guru/core/errors.py` | Shared CLI/MCP `error_type` vocabulary |
|
|
30
|
+
| capture scripts + fixtures | `scripts/capture_fixtures.py` + `fixtures/` | Browser Network → fixture → offline parser tests |
|
|
31
|
+
| `fli/cli/` | `guru/cli/` | Typer + Rich + `--json` |
|
|
32
|
+
| `fli/mcp/` | `guru/mcp/` | FastMCP STDIO + HTTP |
|
|
33
|
+
| `fli/models/` | `guru/models/` | Pydantic only — no I/O |
|
|
34
|
+
| `_tfs` migration when signed RPC broke | `docs/WIRE.md` SPA `rundef` form | When simple `forecast` dies, use page-derived params |
|
|
35
|
+
|
|
36
|
+
## Reverse-engineering workflow (required)
|
|
37
|
+
|
|
38
|
+
1. Open the spot in **Cursor browser** (`https://www.windguru.cz/201`).
|
|
39
|
+
2. CDP / Network: list requests matching `iapi.php`.
|
|
40
|
+
3. Note host (`.cz` vs `.net`), `q=`, and params (`id_spot`, `id_model`, `rundef`, `opt=simplemap`, …).
|
|
41
|
+
4. Reproduce with `curl_cffi` + **Referer**.
|
|
42
|
+
5. Dump fixture → parser test → update `docs/WIRE.md`.
|
|
43
|
+
|
|
44
|
+
**Never HTML-scrape the forecast table or Tune UI** as the primary path.
|
|
45
|
+
|
|
46
|
+
## Product shape
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
pipx install windguru
|
|
50
|
+
guru spots <query>
|
|
51
|
+
guru near --lat Y --lon X
|
|
52
|
+
guru best <id|name> [--top 3] [--hours N] [--json]
|
|
53
|
+
guru-mcp
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Do / don’t
|
|
57
|
+
|
|
58
|
+
- **Do** extend `MODELS` from live Network captures.
|
|
59
|
+
- **Do** keep free vs PRO failures explicit (PRO is out of core).
|
|
60
|
+
- **Do** stay polite on rate limits.
|
|
61
|
+
- **Don’t** commit cookies / PRO passwords / private nicknames as defaults.
|
|
62
|
+
- **Don’t** ship a worldwide spot dump in the wheel (live `near` / `spots` only).
|
|
63
|
+
- **Don’t** depend on `life-research` — this repo is public and clean-slate.
|
|
64
|
+
|
|
65
|
+
## Engineering standards
|
|
66
|
+
|
|
67
|
+
Keep and follow (do not delete):
|
|
68
|
+
|
|
69
|
+
- [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar
|
|
70
|
+
- [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar
|
|
71
|
+
|
|
72
|
+
## Dev commands
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pipx install -e ".[dev,mcp]"
|
|
76
|
+
# or: source .venv/bin/activate && pip install -e ".[dev,mcp]"
|
|
77
|
+
guru spots "foster city"
|
|
78
|
+
guru best 201 -H 12 --json
|
|
79
|
+
pytest -q
|
|
80
|
+
pytest -m live
|
|
81
|
+
ruff check .
|
|
82
|
+
```
|
windguru-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Berend Gort
|
|
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.
|
windguru-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: windguru
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Windguru CLI, MCP server, and Python library — reverse-engineered forecast access (fli-style)
|
|
5
|
+
Project-URL: Homepage, https://github.com/berendgort/guru
|
|
6
|
+
Project-URL: Repository, https://github.com/berendgort/guru
|
|
7
|
+
Project-URL: Issues, https://github.com/berendgort/guru/issues
|
|
8
|
+
Author-email: Berend Gort <berend.gort@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,forecast,kitesurf,mcp,weather,wind,windguru
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: curl-cffi>=0.7.0
|
|
22
|
+
Requires-Dist: pydantic>=2.10.0
|
|
23
|
+
Requires-Dist: rich>=13.8.0
|
|
24
|
+
Requires-Dist: tenacity>=9.0.0
|
|
25
|
+
Requires-Dist: typer>=0.15.1
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: fastmcp>=3.2; extra == 'all'
|
|
28
|
+
Requires-Dist: mcp>=1.2.0; extra == 'all'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.3.4; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.8.4; extra == 'dev'
|
|
33
|
+
Requires-Dist: twine>=6.0.0; extra == 'dev'
|
|
34
|
+
Provides-Extra: mcp
|
|
35
|
+
Requires-Dist: fastmcp>=3.2; extra == 'mcp'
|
|
36
|
+
Requires-Dist: mcp>=1.2.0; extra == 'mcp'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# guru
|
|
40
|
+
|
|
41
|
+
Windguru **CLI + MCP + Python library**. Same idea as [`fli`](https://github.com/punitarani/fli): reverse-engineered JSON, zero HTML scraping, agent-first.
|
|
42
|
+
|
|
43
|
+
> Free only — named spots + WINDGURU DEFAULT Tune top-3. No PRO lat/lon click-forecast.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# CLI (recommended)
|
|
49
|
+
pipx install windguru
|
|
50
|
+
|
|
51
|
+
# CLI + MCP server
|
|
52
|
+
pipx install 'windguru[mcp]'
|
|
53
|
+
|
|
54
|
+
guru --help
|
|
55
|
+
guru instruct --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Until the PyPI release is live, install from GitHub:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pipx install 'windguru @ git+https://github.com/berendgort/guru.git'
|
|
62
|
+
pipx install 'windguru[mcp] @ git+https://github.com/berendgort/guru.git'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
PyPI name **`windguru`**, command **`guru`**, MCP **`guru-mcp`**.
|
|
66
|
+
|
|
67
|
+
Or with pip:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install windguru
|
|
71
|
+
pip install 'windguru[mcp]'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## MCP Server
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pipx install 'windguru[mcp]'
|
|
78
|
+
|
|
79
|
+
# STDIO (Cursor / Claude Desktop)
|
|
80
|
+
guru-mcp
|
|
81
|
+
|
|
82
|
+
# HTTP (streamable)
|
|
83
|
+
guru-mcp-http # http://127.0.0.1:8000/mcp/
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Connecting to Claude Desktop / Cursor
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"guru": {
|
|
92
|
+
"command": "guru-mcp"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> Tip: if the binary is not on PATH, use the full path from `which guru-mcp`
|
|
99
|
+
> (often `~/.local/bin/guru-mcp`).
|
|
100
|
+
|
|
101
|
+
### MCP tools
|
|
102
|
+
|
|
103
|
+
| Tool | Description |
|
|
104
|
+
|------|-------------|
|
|
105
|
+
| `instruct` | Agent recipe (WINDGURU_DEFAULT → top 3) |
|
|
106
|
+
| `search_spots` | Named spot search |
|
|
107
|
+
| `near_spots` | Free map markers near lat/lon |
|
|
108
|
+
| `resolve_spot` | Name/id → spot (`ambiguous` + candidates) |
|
|
109
|
+
| `best_forecast` | Tune weights → top models → forecasts |
|
|
110
|
+
| `get_forecast` | Single-model escape hatch |
|
|
111
|
+
| `list_models` | Known aliases |
|
|
112
|
+
|
|
113
|
+
## Agent recipe
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
guru instruct --json
|
|
117
|
+
guru spots "castelldefels" --json
|
|
118
|
+
guru best 201 --json
|
|
119
|
+
guru near --lat 51.9 --lon 4.1 --json
|
|
120
|
+
guru best 48309 -H 24 --json # De Slufter
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`guru best` always uses **WINDGURU_DEFAULT** Tune weights and returns the **top 3** models + forecasts. Prefer that over raw GFS.
|
|
124
|
+
|
|
125
|
+
JSON envelope: `{"ok": true|false, "api_version": 1, "data"|error fields}`. Shared `error_type` / `retryable` with MCP.
|
|
126
|
+
|
|
127
|
+
## CLI
|
|
128
|
+
|
|
129
|
+
| Command | Role |
|
|
130
|
+
|---------|------|
|
|
131
|
+
| `guru instruct` | Teach agents the workflow |
|
|
132
|
+
| `guru spots <q>` | Name search |
|
|
133
|
+
| `guru near --lat --lon` | Free map markers near a point |
|
|
134
|
+
| `guru best <spot>` | WINDGURU_DEFAULT → top 3 → forecasts |
|
|
135
|
+
| `guru forecast <spot> -m gfs` | Single model |
|
|
136
|
+
| `guru models` / `schema` / `doctor` | Discoverability |
|
|
137
|
+
|
|
138
|
+
Ambiguous names fail with `error_type: ambiguous` + `candidates` (pass numeric id or `--pick`).
|
|
139
|
+
|
|
140
|
+
## Library
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from guru import search_spots, get_best_forecast, spots_near
|
|
144
|
+
|
|
145
|
+
spots = search_spots("castelldefels")
|
|
146
|
+
best = get_best_forecast(201, top=3, hours=24)
|
|
147
|
+
print(best.models[0].name, best.models[0].weight_pct)
|
|
148
|
+
print(best.forecasts[0].hours[0].wind_kn)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Architecture
|
|
152
|
+
|
|
153
|
+
| Layer | Path | Role |
|
|
154
|
+
|-------|------|------|
|
|
155
|
+
| Core | `guru/core/` | Shared envelope, errors, instruct recipe |
|
|
156
|
+
| CLI | `guru/cli/` | Typer + Rich + `--json` (thin over core) |
|
|
157
|
+
| MCP | `guru/mcp/` | FastMCP over core (no CLI imports) |
|
|
158
|
+
| Search | `guru/search/` | HTTP (`curl_cffi`), blend_math, near, forecast |
|
|
159
|
+
| Models | `guru/models/` | Pydantic + aliases only |
|
|
160
|
+
| Wire | [`docs/WIRE.md`](docs/WIRE.md) | Captured `iapi.php` |
|
|
161
|
+
| MCP | [`docs/mcp.md`](docs/mcp.md) | `guru-mcp` setup + tools |
|
|
162
|
+
|
|
163
|
+
Read [`AGENTS.md`](AGENTS.md) before extending. Capture Network → fixtures → tests.
|
|
164
|
+
|
|
165
|
+
Engineering standards (kept in-repo):
|
|
166
|
+
|
|
167
|
+
- [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar (layers, size caps, pure core)
|
|
168
|
+
- [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar (one writer, clocks, contracts)
|
|
169
|
+
|
|
170
|
+
## Disclaimer
|
|
171
|
+
|
|
172
|
+
Unofficial. Not affiliated with Windguru. Personal / research use; respect ToS and rate limits.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
windguru-0.2.0/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# guru
|
|
2
|
+
|
|
3
|
+
Windguru **CLI + MCP + Python library**. Same idea as [`fli`](https://github.com/punitarani/fli): reverse-engineered JSON, zero HTML scraping, agent-first.
|
|
4
|
+
|
|
5
|
+
> Free only — named spots + WINDGURU DEFAULT Tune top-3. No PRO lat/lon click-forecast.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# CLI (recommended)
|
|
11
|
+
pipx install windguru
|
|
12
|
+
|
|
13
|
+
# CLI + MCP server
|
|
14
|
+
pipx install 'windguru[mcp]'
|
|
15
|
+
|
|
16
|
+
guru --help
|
|
17
|
+
guru instruct --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Until the PyPI release is live, install from GitHub:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pipx install 'windguru @ git+https://github.com/berendgort/guru.git'
|
|
24
|
+
pipx install 'windguru[mcp] @ git+https://github.com/berendgort/guru.git'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
PyPI name **`windguru`**, command **`guru`**, MCP **`guru-mcp`**.
|
|
28
|
+
|
|
29
|
+
Or with pip:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install windguru
|
|
33
|
+
pip install 'windguru[mcp]'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## MCP Server
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pipx install 'windguru[mcp]'
|
|
40
|
+
|
|
41
|
+
# STDIO (Cursor / Claude Desktop)
|
|
42
|
+
guru-mcp
|
|
43
|
+
|
|
44
|
+
# HTTP (streamable)
|
|
45
|
+
guru-mcp-http # http://127.0.0.1:8000/mcp/
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Connecting to Claude Desktop / Cursor
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"guru": {
|
|
54
|
+
"command": "guru-mcp"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> Tip: if the binary is not on PATH, use the full path from `which guru-mcp`
|
|
61
|
+
> (often `~/.local/bin/guru-mcp`).
|
|
62
|
+
|
|
63
|
+
### MCP tools
|
|
64
|
+
|
|
65
|
+
| Tool | Description |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `instruct` | Agent recipe (WINDGURU_DEFAULT → top 3) |
|
|
68
|
+
| `search_spots` | Named spot search |
|
|
69
|
+
| `near_spots` | Free map markers near lat/lon |
|
|
70
|
+
| `resolve_spot` | Name/id → spot (`ambiguous` + candidates) |
|
|
71
|
+
| `best_forecast` | Tune weights → top models → forecasts |
|
|
72
|
+
| `get_forecast` | Single-model escape hatch |
|
|
73
|
+
| `list_models` | Known aliases |
|
|
74
|
+
|
|
75
|
+
## Agent recipe
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
guru instruct --json
|
|
79
|
+
guru spots "castelldefels" --json
|
|
80
|
+
guru best 201 --json
|
|
81
|
+
guru near --lat 51.9 --lon 4.1 --json
|
|
82
|
+
guru best 48309 -H 24 --json # De Slufter
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`guru best` always uses **WINDGURU_DEFAULT** Tune weights and returns the **top 3** models + forecasts. Prefer that over raw GFS.
|
|
86
|
+
|
|
87
|
+
JSON envelope: `{"ok": true|false, "api_version": 1, "data"|error fields}`. Shared `error_type` / `retryable` with MCP.
|
|
88
|
+
|
|
89
|
+
## CLI
|
|
90
|
+
|
|
91
|
+
| Command | Role |
|
|
92
|
+
|---------|------|
|
|
93
|
+
| `guru instruct` | Teach agents the workflow |
|
|
94
|
+
| `guru spots <q>` | Name search |
|
|
95
|
+
| `guru near --lat --lon` | Free map markers near a point |
|
|
96
|
+
| `guru best <spot>` | WINDGURU_DEFAULT → top 3 → forecasts |
|
|
97
|
+
| `guru forecast <spot> -m gfs` | Single model |
|
|
98
|
+
| `guru models` / `schema` / `doctor` | Discoverability |
|
|
99
|
+
|
|
100
|
+
Ambiguous names fail with `error_type: ambiguous` + `candidates` (pass numeric id or `--pick`).
|
|
101
|
+
|
|
102
|
+
## Library
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from guru import search_spots, get_best_forecast, spots_near
|
|
106
|
+
|
|
107
|
+
spots = search_spots("castelldefels")
|
|
108
|
+
best = get_best_forecast(201, top=3, hours=24)
|
|
109
|
+
print(best.models[0].name, best.models[0].weight_pct)
|
|
110
|
+
print(best.forecasts[0].hours[0].wind_kn)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Architecture
|
|
114
|
+
|
|
115
|
+
| Layer | Path | Role |
|
|
116
|
+
|-------|------|------|
|
|
117
|
+
| Core | `guru/core/` | Shared envelope, errors, instruct recipe |
|
|
118
|
+
| CLI | `guru/cli/` | Typer + Rich + `--json` (thin over core) |
|
|
119
|
+
| MCP | `guru/mcp/` | FastMCP over core (no CLI imports) |
|
|
120
|
+
| Search | `guru/search/` | HTTP (`curl_cffi`), blend_math, near, forecast |
|
|
121
|
+
| Models | `guru/models/` | Pydantic + aliases only |
|
|
122
|
+
| Wire | [`docs/WIRE.md`](docs/WIRE.md) | Captured `iapi.php` |
|
|
123
|
+
| MCP | [`docs/mcp.md`](docs/mcp.md) | `guru-mcp` setup + tools |
|
|
124
|
+
|
|
125
|
+
Read [`AGENTS.md`](AGENTS.md) before extending. Capture Network → fixtures → tests.
|
|
126
|
+
|
|
127
|
+
Engineering standards (kept in-repo):
|
|
128
|
+
|
|
129
|
+
- [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar (layers, size caps, pure core)
|
|
130
|
+
- [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar (one writer, clocks, contracts)
|
|
131
|
+
|
|
132
|
+
## Disclaimer
|
|
133
|
+
|
|
134
|
+
Unofficial. Not affiliated with Windguru. Personal / research use; respect ToS and rate limits.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Wire — Windguru `iapi.php`
|
|
2
|
+
|
|
3
|
+
Public JSON the SPA uses. Drive these endpoints with `curl_cffi` + **Referer**. Never scrape HTML, Tune jBox, or MapLibre.
|
|
4
|
+
|
|
5
|
+
Captured 2026-09-22 (Castelldefels `201`, De Slufter `48309`). Fixtures under `fixtures/`.
|
|
6
|
+
|
|
7
|
+
## Hosts
|
|
8
|
+
|
|
9
|
+
| Base | Use |
|
|
10
|
+
|------|-----|
|
|
11
|
+
| `https://www.windguru.cz/int/iapi.php` | Search, simplemap, `forecast_spot`, `forecast`, `model_info_full` |
|
|
12
|
+
| `https://www.windguru.net/int/iapi.php` | SPA `forecast` with `rundef` + `cache_index` (fallback) |
|
|
13
|
+
|
|
14
|
+
| Call | Referer |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| search / model_info | `https://www.windguru.cz/` |
|
|
17
|
+
| spot / forecast | `https://www.windguru.cz/{id_spot}` |
|
|
18
|
+
| simplemap | `https://www.windguru.cz/map/spot/` |
|
|
19
|
+
|
|
20
|
+
## Queries (free)
|
|
21
|
+
|
|
22
|
+
### `q=search_spots&search=…`
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{ "count": N, "spots": [{ "id_spot", "spotname", "country", "nickname?" }] }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Name search only — no lat/lon. CLI: `guru spots`.
|
|
29
|
+
|
|
30
|
+
### `q=spots&opt=simplemap&WGCACHEABLE=1800`
|
|
31
|
+
|
|
32
|
+
Same markers as the [spot map](https://www.windguru.cz/map/spot/):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "count": N, "spots": [[id_spot, spotname, lat, lon], ...] }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
~10k rows worldwide. CLI: `guru near --lat --lon`. No PRO.
|
|
39
|
+
|
|
40
|
+
### `q=forecast_spot&id_spot=…`
|
|
41
|
+
|
|
42
|
+
| Path | Content |
|
|
43
|
+
|------|---------|
|
|
44
|
+
| `spots["{id}"]` | name, lat/lon/alt, **`models`** (id_model list for that spot) |
|
|
45
|
+
| `tabs[0].blend` | **WINDGURU DEFAULT** (`id_blend_settings: 1`): `res_sensitivity`, `init_sensitivity`, `model_koef` |
|
|
46
|
+
| `tabs[0].id_model_arr` | per-model `rundef`, `cache_index`, `initstr` |
|
|
47
|
+
|
|
48
|
+
### `q=model_info_full`
|
|
49
|
+
|
|
50
|
+
Keyed by id string. Fields used by blend: `model_name`, `resolution`, `initstamp`, `priority`, `wave`, `virtual`.
|
|
51
|
+
|
|
52
|
+
### `q=forecast&id_spot=…&id_model=…`
|
|
53
|
+
|
|
54
|
+
Top-level + `fcst` parallel arrays (**knots**): `hours`, `WINDSPD`, `GUST`, `WINDDIR`, `TMP`/`TMPE`, `APCP`/`APCP1`, `TCDC`, `RH`, `initstamp`.
|
|
55
|
+
|
|
56
|
+
Also returns `wgmodel` (resolution_real, initstamp) when needed for debugging.
|
|
57
|
+
|
|
58
|
+
### SPA fallback (if simple `forecast` dies)
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
GET …/windguru.net/int/iapi.php?q=forecast
|
|
62
|
+
&id_model=3&rundef=…&id_spot=201&WGCACHEABLE=21600&cache_index=…
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Same lesson as fli `_tfs`: params from `forecast_spot` tab rows.
|
|
66
|
+
|
|
67
|
+
## WINDGURU DEFAULT weights
|
|
68
|
+
|
|
69
|
+
Port of SPA `di.calcSortByWeights` → `guru/search/blend.py`.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
res_w[id] = normalize( resolution ** (-2 * res_sensitivity) )
|
|
73
|
+
init_w[id] = normalize( ai(init_sensitivity, age_h) )
|
|
74
|
+
wgt_raw = res_w * init_w * model_koef[id]
|
|
75
|
+
weight = wgt_raw / Σ wgt_raw
|
|
76
|
+
|
|
77
|
+
ai(s, age) = max(0, 100*(age+4)^(-0.5*s) - 3*s*age)
|
|
78
|
+
age_h = (max_initstamp - initstamp) / 3600
|
|
79
|
+
normalize = scale so mean≈1, cap individual ≤1.5
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Skip `wave`, `virtual`, and id `100` (WG Mix table). Sort weight desc, then `priority` asc. Product: **top 3**.
|
|
83
|
+
|
|
84
|
+
## Agent path
|
|
85
|
+
|
|
86
|
+
`spots` | `near` → `best` (preset locked) → ignore lower models.
|
|
87
|
+
|
|
88
|
+
## Fixtures
|
|
89
|
+
|
|
90
|
+
| File | Source |
|
|
91
|
+
|------|--------|
|
|
92
|
+
| `search_castelldefels.json` | `search_spots` |
|
|
93
|
+
| `forecast_spot_201.json` | Castelldefels |
|
|
94
|
+
| `forecast_spot_48309.json` | De Slufter |
|
|
95
|
+
| `model_info_full.json` | live catalog |
|
|
96
|
+
| `forecast_201_gfs.json` | GFS sample |
|
|
97
|
+
| `spots_simplemap_nl.json` | NL bbox subset of simplemap |
|
|
98
|
+
|
|
99
|
+
Refresh: `python scripts/capture_fixtures.py`
|
|
100
|
+
|
|
101
|
+
## Rate limits
|
|
102
|
+
|
|
103
|
+
Unknown. Retries via tenacity; prefer `WGCACHEABLE` on simplemap. Be polite.
|