zigpeek 0.3.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.
- zigpeek-0.3.0/.github/workflows/release.yml +72 -0
- zigpeek-0.3.0/.github/workflows/test.yml +34 -0
- zigpeek-0.3.0/.gitignore +5 -0
- zigpeek-0.3.0/.python-version +1 -0
- zigpeek-0.3.0/LICENSE +28 -0
- zigpeek-0.3.0/PKG-INFO +10 -0
- zigpeek-0.3.0/README.md +97 -0
- zigpeek-0.3.0/pyproject.toml +32 -0
- zigpeek-0.3.0/skills/zigpeek/SKILL.md +161 -0
- zigpeek-0.3.0/src/zigpeek/__init__.py +0 -0
- zigpeek-0.3.0/src/zigpeek/_vendor/main.wasm +0 -0
- zigpeek-0.3.0/src/zigpeek/builtins.py +148 -0
- zigpeek-0.3.0/src/zigpeek/cli.py +320 -0
- zigpeek-0.3.0/src/zigpeek/fetch.py +103 -0
- zigpeek-0.3.0/src/zigpeek/stdlib.py +324 -0
- zigpeek-0.3.0/src/zigpeek/version.py +15 -0
- zigpeek-0.3.0/src/zigpeek/wasm.py +210 -0
- zigpeek-0.3.0/tests/__init__.py +0 -0
- zigpeek-0.3.0/tests/conftest.py +22 -0
- zigpeek-0.3.0/tests/fixtures/tiny-langref.html +23 -0
- zigpeek-0.3.0/tests/test_builtins.py +79 -0
- zigpeek-0.3.0/tests/test_cli.py +214 -0
- zigpeek-0.3.0/tests/test_fetch.py +175 -0
- zigpeek-0.3.0/tests/test_stdlib_smoke.py +52 -0
- zigpeek-0.3.0/tests/test_version.py +28 -0
- zigpeek-0.3.0/tests/test_wasm_abi.py +39 -0
- zigpeek-0.3.0/tests/test_wasm_driver.py +36 -0
- zigpeek-0.3.0/uv.lock +289 -0
- zigpeek-0.3.0/vendor/PROVENANCE.md +54 -0
- zigpeek-0.3.0/vendor/patches/walk-drop-asm-legacy.patch +13 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: build distributions
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
|
|
19
|
+
- name: Build sdist + wheel
|
|
20
|
+
run: uv build
|
|
21
|
+
|
|
22
|
+
- name: Verify wheel ships main.wasm
|
|
23
|
+
run: |
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
wheel=$(ls dist/zigpeek-*.whl)
|
|
26
|
+
python -m zipfile -l "$wheel" | grep -q "_vendor/main.wasm"
|
|
27
|
+
|
|
28
|
+
- uses: actions/upload-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: dist
|
|
31
|
+
path: dist/
|
|
32
|
+
if-no-files-found: error
|
|
33
|
+
|
|
34
|
+
publish-pypi:
|
|
35
|
+
name: publish to PyPI
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment:
|
|
39
|
+
name: pypi
|
|
40
|
+
url: https://pypi.org/p/zigpeek
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/download-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
50
|
+
|
|
51
|
+
github-release:
|
|
52
|
+
name: attach artifacts to GitHub Release
|
|
53
|
+
needs: publish-pypi
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
permissions:
|
|
56
|
+
contents: write
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/download-artifact@v4
|
|
59
|
+
with:
|
|
60
|
+
name: dist
|
|
61
|
+
path: dist/
|
|
62
|
+
|
|
63
|
+
- name: Create GitHub Release
|
|
64
|
+
env:
|
|
65
|
+
GH_TOKEN: ${{ github.token }}
|
|
66
|
+
GH_REPO: ${{ github.repository }}
|
|
67
|
+
TAG: ${{ github.ref_name }}
|
|
68
|
+
run: |
|
|
69
|
+
gh release create "$TAG" \
|
|
70
|
+
--title "$TAG" \
|
|
71
|
+
--generate-notes \
|
|
72
|
+
dist/*
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: test-${{ github.workflow }}-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
pytest:
|
|
14
|
+
name: pytest (${{ matrix.os }} / py${{ matrix.python }})
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
os: [ubuntu-latest, macos-latest]
|
|
19
|
+
python: ["3.12", "3.13"]
|
|
20
|
+
runs-on: ${{ matrix.os }}
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python }}
|
|
28
|
+
enable-cache: true
|
|
29
|
+
|
|
30
|
+
- name: Sync dependencies
|
|
31
|
+
run: uv sync --dev
|
|
32
|
+
|
|
33
|
+
- name: Run unit tests
|
|
34
|
+
run: uv run pytest -q
|
zigpeek-0.3.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
zigpeek-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tanuj Vasudeva
|
|
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.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This project bundles `src/zigpeek/_vendor/main.wasm`, built from the
|
|
26
|
+
`zig-mcp` project (https://github.com/loonghao/zig-mcp), which is also
|
|
27
|
+
distributed under the MIT License. See `vendor/PROVENANCE.md` for build
|
|
28
|
+
details.
|
zigpeek-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zigpeek
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Fast CLI for Zig 0.16 stdlib + Skill for coding agents
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
8
|
+
Requires-Dist: httpx>=0.27.0
|
|
9
|
+
Requires-Dist: lxml>=5.0.0
|
|
10
|
+
Requires-Dist: wasmtime>=25.0.0
|
zigpeek-0.3.0/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# zigpeek
|
|
2
|
+
|
|
3
|
+
Fast CLI for Zig 0.16 stdlib + builtin docs lookups. Replaces the
|
|
4
|
+
[`zig-docs` MCP server](https://github.com/loonghao/zig-mcp) in
|
|
5
|
+
environments without MCP support — typically cloud agents.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pipx install git+https://github.com/TanGentleman/zigpeek
|
|
11
|
+
# or
|
|
12
|
+
uv tool install git+https://github.com/TanGentleman/zigpeek
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
zigpeek prefetch # warm cache (once per session)
|
|
19
|
+
zigpeek search ArrayList --limit 10 # fuzzy stdlib search
|
|
20
|
+
zigpeek get std.ArrayList # full docs for an FQN
|
|
21
|
+
zigpeek get std.ArrayList --source-file # source file containing it
|
|
22
|
+
zigpeek builtins list # all @-builtins
|
|
23
|
+
zigpeek builtins get atomic # specific builtin
|
|
24
|
+
zigpeek batch <<EOF # amortize startup
|
|
25
|
+
search ArrayList
|
|
26
|
+
get std.ArrayList
|
|
27
|
+
EOF
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Full agent-facing docs live in [`skills/zigpeek/SKILL.md`](skills/zigpeek/SKILL.md).
|
|
31
|
+
|
|
32
|
+
## Use as a Claude Code skill
|
|
33
|
+
|
|
34
|
+
Drop the `skills/zigpeek/` directory into your skills folder so Claude
|
|
35
|
+
Code can discover it:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
cp -r skills/zigpeek ~/.claude/skills/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The skill assumes `zigpeek` is on `$PATH` (see install above).
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
| File | Role |
|
|
46
|
+
| --------------------------------- | --------------------------------------------------- |
|
|
47
|
+
| `src/zigpeek/cli.py` | argparse entrypoint, exit-code contract |
|
|
48
|
+
| `src/zigpeek/stdlib.py` | markdown rendering (port of `zig-mcp/mcp/std.ts`) |
|
|
49
|
+
| `src/zigpeek/wasm.py` | wasmtime driver + typed wrapper around WASM exports |
|
|
50
|
+
| `src/zigpeek/builtins.py` | langref HTML parser + ranking |
|
|
51
|
+
| `src/zigpeek/fetch.py` | sources.tar / langref download + `/tmp` cache |
|
|
52
|
+
| `src/zigpeek/version.py` | default Zig version + override resolution |
|
|
53
|
+
| `src/zigpeek/_vendor/main.wasm` | autodoc WASM, shipped inside the package |
|
|
54
|
+
| `vendor/PROVENANCE.md` | build instructions + SHA256 + upstream commit |
|
|
55
|
+
| `vendor/patches/` | local patches applied before rebuilding the WASM |
|
|
56
|
+
| `skills/zigpeek/SKILL.md` | skill metadata for Claude Code |
|
|
57
|
+
|
|
58
|
+
## Updating the vendored WASM
|
|
59
|
+
|
|
60
|
+
Bumping the Zig version may need a fresh `main.wasm`. Build steps live in
|
|
61
|
+
[`vendor/PROVENANCE.md`](vendor/PROVENANCE.md). Summary:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
cd ~/Documents/GitHub/zig-mcp
|
|
65
|
+
git pull
|
|
66
|
+
zig build
|
|
67
|
+
cp zig-out/main.wasm <repo>/src/zigpeek/_vendor/main.wasm
|
|
68
|
+
shasum -a 256 <repo>/src/zigpeek/_vendor/main.wasm
|
|
69
|
+
# update SHA256 + commit + date in vendor/PROVENANCE.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Run smoke tests after updating:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
ZIGPEEK_SMOKE=1 uv run pytest -v
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Testing
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
uv sync # install deps
|
|
82
|
+
uv run pytest -q # unit tests (no network)
|
|
83
|
+
ZIGPEEK_SMOKE=1 uv run pytest # adds smoke tests (needs network + WASM)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Why a port and not a wrapper?
|
|
87
|
+
|
|
88
|
+
The autodoc renderer lives inside the WASM as HTML-emitting exports.
|
|
89
|
+
Wrapping the upstream MCP would mean shipping Node.js to every cloud
|
|
90
|
+
agent. Porting the ~700 lines of TS that drive the WASM gets us the same
|
|
91
|
+
output with only Python + a vendored binary.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT — see [`LICENSE`](LICENSE). Bundled `main.wasm` is also MIT (from
|
|
96
|
+
[`zig-mcp`](https://github.com/loonghao/zig-mcp)); see
|
|
97
|
+
[`vendor/PROVENANCE.md`](vendor/PROVENANCE.md).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zigpeek"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Fast CLI for Zig 0.16 stdlib + Skill for coding agents"
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"wasmtime>=25.0.0",
|
|
8
|
+
"httpx>=0.27.0",
|
|
9
|
+
"beautifulsoup4>=4.12.0",
|
|
10
|
+
"lxml>=5.0.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
zigpeek = "zigpeek.cli:main"
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest>=8.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/zigpeek"]
|
|
27
|
+
|
|
28
|
+
# Be explicit: the vendored WASM is part of the package, not just incidental
|
|
29
|
+
# files swept in by hatchling defaults. Keeps editable installs (`uv sync`),
|
|
30
|
+
# wheel builds (`uv build`), and future tool installs in lockstep.
|
|
31
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
32
|
+
"src/zigpeek/_vendor/main.wasm" = "zigpeek/_vendor/main.wasm"
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zigpeek
|
|
3
|
+
description: Look up Zig 0.16 standard library APIs and builtin functions via a local CLI (replaces the zig-docs MCP server in environments without MCP support, e.g. cloud agents). Use before writing or reviewing Zig code that touches stdlib — critical for std.Io filesystem APIs (std.Io.Dir, std.Io.File), Reader/Writer interfaces, and std.process.Init. Triggers when answering "how do I X in Zig" or writing Zig that touches files, dirs, env, or process state. If the zig-docs MCP server is already connected, prefer it over this CLI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# zigpeek
|
|
7
|
+
|
|
8
|
+
A Python+wasmtime port of the four `zig-docs` MCP tools. Loads the same
|
|
9
|
+
autodoc WASM module the official Zig docs use, against the same
|
|
10
|
+
`sources.tar` from `ziglang.org`. Output is markdown, byte-equivalent
|
|
11
|
+
(modulo whitespace) to what the MCP returns.
|
|
12
|
+
|
|
13
|
+
## Setup (run once per agent session/sandbox)
|
|
14
|
+
|
|
15
|
+
Install the `zigpeek` CLI globally with either tool:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pipx install git+https://github.com/TanGentleman/zigpeek
|
|
19
|
+
# or
|
|
20
|
+
uv tool install git+https://github.com/TanGentleman/zigpeek
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then warm the cache so subsequent lookups are offline:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
zigpeek prefetch
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires outbound network access to `ziglang.org` on first use. Caches
|
|
30
|
+
downloads under `/tmp/zigpeek-cache/<version>/`.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
# Search the stdlib
|
|
36
|
+
zigpeek search ArrayList --limit 10
|
|
37
|
+
|
|
38
|
+
# Get full docs for a stdlib item
|
|
39
|
+
zigpeek get std.ArrayList
|
|
40
|
+
|
|
41
|
+
# Get the source file containing an item
|
|
42
|
+
zigpeek get std.ArrayList --source-file
|
|
43
|
+
|
|
44
|
+
# List all builtin functions
|
|
45
|
+
zigpeek builtins list
|
|
46
|
+
|
|
47
|
+
# Look up a builtin (by name or keyword)
|
|
48
|
+
zigpeek builtins get atomic
|
|
49
|
+
|
|
50
|
+
# Pre-populate the cache (so later commands don't need network)
|
|
51
|
+
zigpeek prefetch
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## When to use which command
|
|
55
|
+
|
|
56
|
+
| Need | Command |
|
|
57
|
+
| ------------------------------------------------- | --------------------------------- |
|
|
58
|
+
| Discover stdlib symbols matching a keyword | `zigpeek search <q>` |
|
|
59
|
+
| Read full docs + signature for a known FQN | `zigpeek get <fqn>` |
|
|
60
|
+
| Read the full source file (terse docstring; want invariants, internals, or per-field implementation) | `zigpeek get <fqn> --source-file` |
|
|
61
|
+
| Browse all `@`-builtins | `zigpeek builtins list` |
|
|
62
|
+
| Look up a specific `@builtin` (accepts `atomic` or `@atomic`) | `zigpeek builtins get <q>` |
|
|
63
|
+
| Warm cache before going offline | `zigpeek prefetch` |
|
|
64
|
+
| Run several lookups in one process (cheap) | `zigpeek batch` |
|
|
65
|
+
|
|
66
|
+
## Batching multiple lookups
|
|
67
|
+
|
|
68
|
+
Each `zigpeek` invocation pays ~1 s of Python+wasmtime startup. If you
|
|
69
|
+
plan more than two lookups, pipe them through `zigpeek batch` to share
|
|
70
|
+
that cost across the whole sequence — the WASM instance and parsed
|
|
71
|
+
sources are reused between commands.
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
zigpeek batch <<'EOF'
|
|
75
|
+
search ArrayList --limit 5
|
|
76
|
+
get std.ArrayList
|
|
77
|
+
get std.ArrayList --source-file
|
|
78
|
+
builtins get atomic
|
|
79
|
+
EOF
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Each command's output is framed with a `===> <command>` separator on its
|
|
83
|
+
own line. Per-line failures (not-found, bad input) are reported inline
|
|
84
|
+
and **do not abort the batch**; the process exit code is the worst code
|
|
85
|
+
seen across all lines (`0` if every command succeeded). `prefetch` and
|
|
86
|
+
nested `batch` are rejected — run them outside.
|
|
87
|
+
|
|
88
|
+
Use `zigpeek batch -f commands.txt` to read from a file instead of
|
|
89
|
+
stdin. Blank lines and lines starting with `#` are ignored.
|
|
90
|
+
|
|
91
|
+
**Reach for `--source-file` early** when:
|
|
92
|
+
|
|
93
|
+
- The docstring is one line or missing.
|
|
94
|
+
- The page lists a method signature but elides the body (e.g.
|
|
95
|
+
`MultiArrayList.items` shows the prototype but the per-field pointer
|
|
96
|
+
math from `ptrs[@intFromEnum(field)]` only lives in the source).
|
|
97
|
+
- You need invariants, error sets, or how a private field is computed.
|
|
98
|
+
|
|
99
|
+
## Finding nested types
|
|
100
|
+
|
|
101
|
+
Inner types live under the **defining module's path**, not the
|
|
102
|
+
re-export. `std.MultiArrayList` is a re-export from
|
|
103
|
+
`std.multi_array_list`; its inner `Slice` type only resolves at the
|
|
104
|
+
defining path:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
zigpeek get std.multi_array_list.MultiArrayList.Slice # works
|
|
108
|
+
zigpeek get std.MultiArrayList.Slice # not found
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
If `search` only surfaces a re-export and `get` 404s on the inner type,
|
|
112
|
+
re-run `get` with the module path.
|
|
113
|
+
|
|
114
|
+
## Version override
|
|
115
|
+
|
|
116
|
+
Defaults to Zig `0.16.0`. Override with:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
zigpeek search ArrayList --version 0.15.1
|
|
120
|
+
ZIGPEEK_VERSION=master zigpeek search ArrayList
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Offline mode
|
|
124
|
+
|
|
125
|
+
If the agent will run without internet, prefetch first while you still
|
|
126
|
+
have network:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
# Default cache (/tmp/zigpeek-cache/<version>/)
|
|
130
|
+
zigpeek prefetch
|
|
131
|
+
|
|
132
|
+
# Custom cache directory (persists outside /tmp)
|
|
133
|
+
zigpeek prefetch --cache-dir ~/.cache/zigpeek
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
After prefetch, every `search` / `get` / `builtins` call reads from disk
|
|
137
|
+
and never touches the network. Pass the same `--cache-dir` (or set
|
|
138
|
+
`ZIGPEEK_CACHE_DIR`) on subsequent commands if you used a non-default
|
|
139
|
+
location.
|
|
140
|
+
|
|
141
|
+
If a bundled snapshot ships inside the package (`src/zigpeek/_data/<version>/`),
|
|
142
|
+
the read path uses it automatically and prefetch becomes a no-op.
|
|
143
|
+
|
|
144
|
+
## Exit codes
|
|
145
|
+
|
|
146
|
+
- `0` — success (markdown on stdout)
|
|
147
|
+
- `1` — bad input or "not found" (message on stderr)
|
|
148
|
+
- `2` — network/cache failure (message on stderr)
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
- **`zigpeek: command not found`** — install via `pipx install git+https://github.com/TanGentleman/zigpeek`
|
|
153
|
+
(or `uv tool install ...`). If neither tool is available, install uv:
|
|
154
|
+
`curl -LsSf https://astral.sh/uv/install.sh | sh`.
|
|
155
|
+
- **`network/cache error`** — `ziglang.org` is blocked or unreachable.
|
|
156
|
+
Run `zigpeek prefetch` from a network-enabled host first, or check
|
|
157
|
+
your sandbox network policy.
|
|
158
|
+
- **`Declaration "..." not found`** — the FQN is wrong. Two things to
|
|
159
|
+
try: (1) `zigpeek search` to discover the canonical name; (2) if you
|
|
160
|
+
searched a re-export (e.g. `std.MultiArrayList`), retry `get` against
|
|
161
|
+
the defining module path (e.g. `std.multi_array_list.MultiArrayList`).
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Port of mcp/extract-builtin-functions.ts and the ranking from mcp/tools.ts.
|
|
2
|
+
|
|
3
|
+
Parses Zig's langref HTML into a list of BuiltinFunction records, then ranks
|
|
4
|
+
those records by relevance to a query string.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from bs4 import BeautifulSoup, NavigableString, Tag
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class BuiltinFunction:
|
|
15
|
+
func: str
|
|
16
|
+
signature: str
|
|
17
|
+
docs: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_WS_RE = re.compile(r"\s+")
|
|
21
|
+
_BLANK_LINES_RE = re.compile(r"\n{2,}")
|
|
22
|
+
_TRAILING_NEWLINES_RE = re.compile(r"\n+$")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _rewrite_link(text: str, href: str, link_base_url: str | None) -> str:
|
|
26
|
+
if href.startswith("#") and link_base_url:
|
|
27
|
+
return f"[{text}]({link_base_url}{href})"
|
|
28
|
+
return f"[{text}]({href})"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _inline_text(tag: Tag, link_base_url: str | None) -> str:
|
|
32
|
+
cloned = BeautifulSoup(str(tag), "lxml").find()
|
|
33
|
+
if cloned is None:
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
for a in list(cloned.find_all("a")):
|
|
37
|
+
href = a.get("href", "")
|
|
38
|
+
text = a.get_text()
|
|
39
|
+
a.replace_with(NavigableString(_rewrite_link(text, href, link_base_url)))
|
|
40
|
+
|
|
41
|
+
for code in list(cloned.find_all("code")):
|
|
42
|
+
code.replace_with(NavigableString(f"`{code.get_text()}`"))
|
|
43
|
+
|
|
44
|
+
return _WS_RE.sub(" ", cloned.get_text()).strip()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _next_sibling_tag(tag: Tag) -> Tag | None:
|
|
48
|
+
sib = tag.next_sibling
|
|
49
|
+
while sib is not None and not isinstance(sib, Tag):
|
|
50
|
+
sib = sib.next_sibling
|
|
51
|
+
return sib
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def parse_builtin_functions_html(
|
|
55
|
+
html: str,
|
|
56
|
+
link_base_url: str | None,
|
|
57
|
+
) -> list[BuiltinFunction]:
|
|
58
|
+
soup = BeautifulSoup(html, "lxml")
|
|
59
|
+
section = soup.find("h2", id="Builtin-Functions")
|
|
60
|
+
if section is None:
|
|
61
|
+
raise ValueError("Could not find Builtin Functions section in HTML")
|
|
62
|
+
|
|
63
|
+
builtins: list[BuiltinFunction] = []
|
|
64
|
+
current = _next_sibling_tag(section)
|
|
65
|
+
|
|
66
|
+
while current is not None and current.name != "h2":
|
|
67
|
+
if current.name == "h3" and current.has_attr("id"):
|
|
68
|
+
first_a = current.find("a")
|
|
69
|
+
func = first_a.get_text() if first_a is not None else ""
|
|
70
|
+
if func.startswith("@"):
|
|
71
|
+
pre = _next_sibling_tag(current)
|
|
72
|
+
signature = ""
|
|
73
|
+
desc_start = pre
|
|
74
|
+
if pre is not None and pre.name == "pre":
|
|
75
|
+
signature = pre.get_text().strip()
|
|
76
|
+
desc_start = _next_sibling_tag(pre)
|
|
77
|
+
|
|
78
|
+
description_parts: list[str] = []
|
|
79
|
+
desc_current = desc_start
|
|
80
|
+
while desc_current is not None and desc_current.name not in ("h2", "h3"):
|
|
81
|
+
if desc_current.name == "p":
|
|
82
|
+
description_parts.append(
|
|
83
|
+
_inline_text(desc_current, link_base_url)
|
|
84
|
+
)
|
|
85
|
+
elif desc_current.name == "ul":
|
|
86
|
+
for li in desc_current.find_all("li", recursive=False):
|
|
87
|
+
li_text = _inline_text(li, link_base_url)
|
|
88
|
+
if li_text:
|
|
89
|
+
description_parts.append(f"* {li_text}")
|
|
90
|
+
elif desc_current.name == "figure":
|
|
91
|
+
figcaption = ""
|
|
92
|
+
cap_el = desc_current.find("figcaption")
|
|
93
|
+
if cap_el is not None:
|
|
94
|
+
figcaption = cap_el.get_text().strip()
|
|
95
|
+
pre_el = desc_current.find("pre")
|
|
96
|
+
code = pre_el.get_text() if pre_el is not None else ""
|
|
97
|
+
lang = ""
|
|
98
|
+
label = ""
|
|
99
|
+
if figcaption:
|
|
100
|
+
label = f"**{figcaption}**\n"
|
|
101
|
+
if figcaption.endswith(".zig"):
|
|
102
|
+
lang = "zig"
|
|
103
|
+
elif "shell" in figcaption.lower():
|
|
104
|
+
lang = "sh"
|
|
105
|
+
if code:
|
|
106
|
+
block = f"{label}\n```{lang}\n{code.strip()}\n```"
|
|
107
|
+
description_parts.append(block.strip())
|
|
108
|
+
desc_current = _next_sibling_tag(desc_current)
|
|
109
|
+
|
|
110
|
+
docs = "\n".join(description_parts)
|
|
111
|
+
docs = _BLANK_LINES_RE.sub("\n", docs)
|
|
112
|
+
docs = _TRAILING_NEWLINES_RE.sub("", docs)
|
|
113
|
+
if docs.lower().endswith("see also:"):
|
|
114
|
+
docs = docs[: -len("see also:")].strip()
|
|
115
|
+
|
|
116
|
+
builtins.append(
|
|
117
|
+
BuiltinFunction(func=func, signature=signature, docs=docs)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
current = _next_sibling_tag(current)
|
|
121
|
+
|
|
122
|
+
return builtins
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def rank_builtin_functions(
|
|
126
|
+
functions: list[BuiltinFunction],
|
|
127
|
+
query: str,
|
|
128
|
+
) -> list[BuiltinFunction]:
|
|
129
|
+
q = query.lower().strip()
|
|
130
|
+
if not q:
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
scored: list[tuple[int, BuiltinFunction]] = []
|
|
134
|
+
for fn in functions:
|
|
135
|
+
f_lower = fn.func.lower()
|
|
136
|
+
score = 0
|
|
137
|
+
if f_lower == q:
|
|
138
|
+
score += 1000
|
|
139
|
+
elif f_lower.startswith(q):
|
|
140
|
+
score += 500
|
|
141
|
+
elif q in f_lower:
|
|
142
|
+
score += 300
|
|
143
|
+
if score > 0:
|
|
144
|
+
score += max(0, 50 - len(fn.func))
|
|
145
|
+
scored.append((score, fn))
|
|
146
|
+
|
|
147
|
+
scored.sort(key=lambda pair: pair[0], reverse=True)
|
|
148
|
+
return [fn for _, fn in scored]
|