strands-zero 0.1.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.
@@ -0,0 +1,21 @@
1
+ # Cloned upstream — not part of this package
2
+ zero/
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ build/
9
+ dist/
10
+ .eggs/
11
+ .pytest_cache/
12
+ .venv/
13
+ venv/
14
+ .coverage
15
+
16
+ # Zero artifacts
17
+ .zero/
18
+
19
+ # Runtime event dirs (not source)
20
+ telegram_events/
21
+ whatsapp_events/
@@ -0,0 +1,15 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: strands-zero
3
+ Version: 0.1.0
4
+ Summary: Strands Agents tools for the Zero programming language - write, check, build, run Zero code from Python agents
5
+ Project-URL: Homepage, https://github.com/cagataycali/strands-zero
6
+ Project-URL: Repository, https://github.com/cagataycali/strands-zero
7
+ Project-URL: Zero Language, https://zerolang.ai
8
+ Author-email: Cagatay Cali <cagataycali@icloud.com>
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: agents,ai,compiler,llm,strands,tools,zero,zero-lang
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software 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
+ Classifier: Topic :: Software Development :: Code Generators
21
+ Classifier: Topic :: Software Development :: Compilers
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: strands-agents>=0.1.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-cov; extra == 'dev'
26
+ Requires-Dist: pytest>=7.0; extra == 'dev'
27
+ Requires-Dist: ruff; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # strands-zero
31
+
32
+ [![PyPI](https://img.shields.io/pypi/v/strands-zero.svg)](https://pypi.org/project/strands-zero/)
33
+
34
+ **Strands Agents tools for the [Zero programming language](https://zerolang.ai).**
35
+
36
+ Wraps the official `zero` CLI as a set of `@tool`-decorated Python functions
37
+ so AI agents (Claude, Strands, anything compatible) can write, type-check,
38
+ compile, run, ship, and inspect Zero programs with structured JSON feedback.
39
+
40
+ > Zero is the programming language for agents — small native tools, explicit
41
+ > effects, predictable memory, and machine-readable compiler output.
42
+
43
+ ---
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install strands-zero
49
+ ```
50
+
51
+ This installs the Python wrappers. You also need the Zero compiler binary:
52
+
53
+ ```bash
54
+ # one-shot from the package
55
+ strands-zero-install
56
+
57
+ # or manually
58
+ curl -fsSL https://zerolang.ai/install.sh | bash
59
+ export PATH="$HOME/.zero/bin:$PATH"
60
+ ```
61
+
62
+ `strands-zero` finds the binary in this order:
63
+ 1. `$ZERO_BIN` env var
64
+ 2. `zero` on `$PATH`
65
+ 3. `~/.zero/bin/zero`
66
+
67
+ ---
68
+
69
+ ## Quick start
70
+
71
+ ```python
72
+ from strands import Agent
73
+ from strands_zero import (
74
+ zero_write, zero_check, zero_run, zero_build,
75
+ zero_explain, zero_fix, zero_doctor, zero_skills,
76
+ zero_graph, zero_size, zero_test, zero_version,
77
+ )
78
+
79
+ agent = Agent(
80
+ tools=[
81
+ zero_write, zero_check, zero_run, zero_build,
82
+ zero_explain, zero_fix, zero_doctor, zero_skills,
83
+ ],
84
+ system_prompt="You are a Zero language expert. Use zero_skills('get','zero-language',full=True) when unsure of syntax.",
85
+ )
86
+
87
+ agent("Write a Zero program that prints the first 10 fibonacci numbers, type-check it, then run it.")
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Tools
93
+
94
+ | Tool | Purpose |
95
+ | ------------------- | ------------------------------------------------------------------- |
96
+ | `zero_version` | Compiler version + host info |
97
+ | `zero_install` | Install/upgrade the Zero compiler |
98
+ | `zero_doctor` | Diagnose the toolchain |
99
+ | `zero_skills` | Access built-in language/diagnostics/builds/agent skill docs |
100
+ | `zero_targets` | List supported compilation targets |
101
+ | `zero_write` | Write `.0` source to disk (or temp file) |
102
+ | `zero_new` | Scaffold a new `cli` / `lib` / `package` |
103
+ | `zero_check` | Type-check; returns structured diagnostics JSON |
104
+ | `zero_explain` | Explain a diagnostic code |
105
+ | `zero_fix` | Get a structured fix plan |
106
+ | `zero_run` | Compile and run a program (with stdin/args) |
107
+ | `zero_build` | Build to `exe` / `obj` / `wasm` |
108
+ | `zero_ship` | Release-grade build (`release-small` / `tiny` / `audit`) |
109
+ | `zero_test` | Run `test` blocks |
110
+ | `zero_fmt` | Format source |
111
+ | `zero_graph` | Module / dep graph |
112
+ | `zero_size` | Binary size breakdown |
113
+ | `zero_mem` | Static memory layout |
114
+ | `zero_routes` | List declared web routes |
115
+ | `zero_clean` | Clean `.zero/` cache |
116
+
117
+ All tools accept a `cwd` arg; build/run tools accept `timeout`.
118
+ JSON-capable tools default to `json_output=True` and parse stdout into
119
+ `result["json"]`.
120
+
121
+ ---
122
+
123
+ ## Manual usage (no agent)
124
+
125
+ ```python
126
+ from strands_zero import zero_write, zero_run
127
+
128
+ write = zero_write('''
129
+ pub fun main(world: World) -> Void raises {
130
+ check world.out.write("hello\\n")
131
+ }
132
+ ''')
133
+
134
+ run = zero_run(write["path"])
135
+ print(run["stdout"]) # hello
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Why?
141
+
142
+ Zero exposes everything an agent needs through `--json` flags
143
+ (`check --json`, `fix --plan --json`, `graph --json`, `size --json`, `doctor --json`, ...).
144
+ This package gives that surface area to a Strands agent verbatim, so an agent
145
+ can iterate on Zero code with the same explicit, machine-readable feedback the
146
+ language is designed for.
147
+
148
+ ---
149
+
150
+ ## License
151
+
152
+ Apache-2.0
@@ -0,0 +1,123 @@
1
+ # strands-zero
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/strands-zero.svg)](https://pypi.org/project/strands-zero/)
4
+
5
+ **Strands Agents tools for the [Zero programming language](https://zerolang.ai).**
6
+
7
+ Wraps the official `zero` CLI as a set of `@tool`-decorated Python functions
8
+ so AI agents (Claude, Strands, anything compatible) can write, type-check,
9
+ compile, run, ship, and inspect Zero programs with structured JSON feedback.
10
+
11
+ > Zero is the programming language for agents — small native tools, explicit
12
+ > effects, predictable memory, and machine-readable compiler output.
13
+
14
+ ---
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install strands-zero
20
+ ```
21
+
22
+ This installs the Python wrappers. You also need the Zero compiler binary:
23
+
24
+ ```bash
25
+ # one-shot from the package
26
+ strands-zero-install
27
+
28
+ # or manually
29
+ curl -fsSL https://zerolang.ai/install.sh | bash
30
+ export PATH="$HOME/.zero/bin:$PATH"
31
+ ```
32
+
33
+ `strands-zero` finds the binary in this order:
34
+ 1. `$ZERO_BIN` env var
35
+ 2. `zero` on `$PATH`
36
+ 3. `~/.zero/bin/zero`
37
+
38
+ ---
39
+
40
+ ## Quick start
41
+
42
+ ```python
43
+ from strands import Agent
44
+ from strands_zero import (
45
+ zero_write, zero_check, zero_run, zero_build,
46
+ zero_explain, zero_fix, zero_doctor, zero_skills,
47
+ zero_graph, zero_size, zero_test, zero_version,
48
+ )
49
+
50
+ agent = Agent(
51
+ tools=[
52
+ zero_write, zero_check, zero_run, zero_build,
53
+ zero_explain, zero_fix, zero_doctor, zero_skills,
54
+ ],
55
+ system_prompt="You are a Zero language expert. Use zero_skills('get','zero-language',full=True) when unsure of syntax.",
56
+ )
57
+
58
+ agent("Write a Zero program that prints the first 10 fibonacci numbers, type-check it, then run it.")
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Tools
64
+
65
+ | Tool | Purpose |
66
+ | ------------------- | ------------------------------------------------------------------- |
67
+ | `zero_version` | Compiler version + host info |
68
+ | `zero_install` | Install/upgrade the Zero compiler |
69
+ | `zero_doctor` | Diagnose the toolchain |
70
+ | `zero_skills` | Access built-in language/diagnostics/builds/agent skill docs |
71
+ | `zero_targets` | List supported compilation targets |
72
+ | `zero_write` | Write `.0` source to disk (or temp file) |
73
+ | `zero_new` | Scaffold a new `cli` / `lib` / `package` |
74
+ | `zero_check` | Type-check; returns structured diagnostics JSON |
75
+ | `zero_explain` | Explain a diagnostic code |
76
+ | `zero_fix` | Get a structured fix plan |
77
+ | `zero_run` | Compile and run a program (with stdin/args) |
78
+ | `zero_build` | Build to `exe` / `obj` / `wasm` |
79
+ | `zero_ship` | Release-grade build (`release-small` / `tiny` / `audit`) |
80
+ | `zero_test` | Run `test` blocks |
81
+ | `zero_fmt` | Format source |
82
+ | `zero_graph` | Module / dep graph |
83
+ | `zero_size` | Binary size breakdown |
84
+ | `zero_mem` | Static memory layout |
85
+ | `zero_routes` | List declared web routes |
86
+ | `zero_clean` | Clean `.zero/` cache |
87
+
88
+ All tools accept a `cwd` arg; build/run tools accept `timeout`.
89
+ JSON-capable tools default to `json_output=True` and parse stdout into
90
+ `result["json"]`.
91
+
92
+ ---
93
+
94
+ ## Manual usage (no agent)
95
+
96
+ ```python
97
+ from strands_zero import zero_write, zero_run
98
+
99
+ write = zero_write('''
100
+ pub fun main(world: World) -> Void raises {
101
+ check world.out.write("hello\\n")
102
+ }
103
+ ''')
104
+
105
+ run = zero_run(write["path"])
106
+ print(run["stdout"]) # hello
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Why?
112
+
113
+ Zero exposes everything an agent needs through `--json` flags
114
+ (`check --json`, `fix --plan --json`, `graph --json`, `size --json`, `doctor --json`, ...).
115
+ This package gives that surface area to a Strands agent verbatim, so an agent
116
+ can iterate on Zero code with the same explicit, machine-readable feedback the
117
+ language is designed for.
118
+
119
+ ---
120
+
121
+ ## License
122
+
123
+ Apache-2.0
@@ -0,0 +1,22 @@
1
+ """Minimal end-to-end demo: agent writes, checks, and runs Zero code."""
2
+ from strands import Agent
3
+ from strands_zero import (
4
+ zero_write, zero_check, zero_run, zero_explain,
5
+ zero_fix, zero_skills, zero_doctor, zero_version,
6
+ )
7
+
8
+ agent = Agent(
9
+ tools=[
10
+ zero_write, zero_check, zero_run, zero_explain,
11
+ zero_fix, zero_skills, zero_doctor, zero_version,
12
+ ],
13
+ system_prompt=(
14
+ "You are a Zero language expert. "
15
+ "Use zero_skills('get','zero-language',full=True) when unsure of syntax. "
16
+ "Always zero_check before zero_run. "
17
+ "On diagnostics: use zero_explain + zero_fix, then retry."
18
+ ),
19
+ )
20
+
21
+ if __name__ == "__main__":
22
+ agent("Write a Zero program that prints the first 5 squares, type-check it, then run it.")
@@ -0,0 +1,11 @@
1
+ """Use the tools directly without an agent."""
2
+ from strands_zero import zero_write, zero_check, zero_run, zero_version
3
+
4
+ print(zero_version())
5
+ w = zero_write('''
6
+ pub fun main(world: World) -> Void raises {
7
+ check world.out.write("hello\\n")
8
+ }
9
+ ''')
10
+ print(zero_check(w["path"]))
11
+ print(zero_run(w["path"])["stdout"])
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "strands-zero"
7
+ version = "0.1.0"
8
+ description = "Strands Agents tools for the Zero programming language - write, check, build, run Zero code from Python agents"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Cagatay Cali", email = "cagataycali@icloud.com" }]
13
+ keywords = ["strands", "agents", "zero", "zero-lang", "compiler", "ai", "llm", "tools"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: Apache Software 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 :: Software Development :: Compilers",
24
+ "Topic :: Software Development :: Code Generators",
25
+ ]
26
+ dependencies = [
27
+ "strands-agents>=0.1.0",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=7.0", "pytest-cov", "ruff"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/cagataycali/strands-zero"
35
+ Repository = "https://github.com/cagataycali/strands-zero"
36
+ "Zero Language" = "https://zerolang.ai"
37
+
38
+ [project.scripts]
39
+ strands-zero-install = "strands_zero.installer:cli"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/strands_zero"]
@@ -0,0 +1,71 @@
1
+ """
2
+ strands-zero: Strands Agents tools for the Zero programming language.
3
+
4
+ Wraps the `zero` CLI (https://zerolang.ai) so AI agents can write, check,
5
+ compile, and run Zero code with structured JSON feedback.
6
+
7
+ Usage:
8
+ from strands import Agent
9
+ from strands_zero import (
10
+ zero_check, zero_run, zero_build, zero_doctor,
11
+ zero_explain, zero_fix, zero_write, zero_skills,
12
+ zero_graph, zero_size, zero_test, zero_new,
13
+ zero_version, zero_install,
14
+ )
15
+
16
+ agent = Agent(tools=[zero_check, zero_run, zero_build, zero_explain, zero_write])
17
+ agent("Write a Zero program that prints 'hello' and run it")
18
+ """
19
+
20
+ from strands_zero._runner import find_zero_binary, run_zero
21
+ from strands_zero.tools import (
22
+ zero_version,
23
+ zero_check,
24
+ zero_run,
25
+ zero_build,
26
+ zero_ship,
27
+ zero_test,
28
+ zero_fmt,
29
+ zero_doctor,
30
+ zero_explain,
31
+ zero_fix,
32
+ zero_skills,
33
+ zero_graph,
34
+ zero_size,
35
+ zero_mem,
36
+ zero_routes,
37
+ zero_targets,
38
+ zero_clean,
39
+ zero_new,
40
+ zero_write,
41
+ zero_install,
42
+ )
43
+
44
+ __version__ = "0.1.0"
45
+
46
+ __all__ = [
47
+ # Core helpers
48
+ "find_zero_binary",
49
+ "run_zero",
50
+ # Tool functions
51
+ "zero_version",
52
+ "zero_check",
53
+ "zero_run",
54
+ "zero_build",
55
+ "zero_ship",
56
+ "zero_test",
57
+ "zero_fmt",
58
+ "zero_doctor",
59
+ "zero_explain",
60
+ "zero_fix",
61
+ "zero_skills",
62
+ "zero_graph",
63
+ "zero_size",
64
+ "zero_mem",
65
+ "zero_routes",
66
+ "zero_targets",
67
+ "zero_clean",
68
+ "zero_new",
69
+ "zero_write",
70
+ "zero_install",
71
+ ]
@@ -0,0 +1,130 @@
1
+ """Internal subprocess runner for the `zero` CLI."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional, Tuple
10
+
11
+
12
+ def find_zero_binary() -> Optional[str]:
13
+ """Locate the `zero` binary.
14
+
15
+ Search order:
16
+ 1. ZERO_BIN env var (explicit override)
17
+ 2. `zero` on PATH
18
+ 3. ~/.zero/bin/zero (default install dir)
19
+ """
20
+ explicit = os.environ.get("ZERO_BIN")
21
+ if explicit and Path(explicit).exists() and os.access(explicit, os.X_OK):
22
+ return explicit
23
+
24
+ on_path = shutil.which("zero")
25
+ if on_path:
26
+ return on_path
27
+
28
+ home_bin = Path.home() / ".zero" / "bin" / "zero"
29
+ if home_bin.exists() and os.access(str(home_bin), os.X_OK):
30
+ return str(home_bin)
31
+
32
+ return None
33
+
34
+
35
+ def run_zero(
36
+ args: List[str],
37
+ *,
38
+ cwd: Optional[str] = None,
39
+ timeout: int = 120,
40
+ input_text: Optional[str] = None,
41
+ parse_json: bool = False,
42
+ ) -> Dict[str, Any]:
43
+ """Execute the zero CLI and return a structured result.
44
+
45
+ Returns a dict with:
46
+ ok: bool
47
+ exit_code: int
48
+ stdout: str
49
+ stderr: str
50
+ command: list[str]
51
+ json: parsed JSON if parse_json=True and stdout is valid JSON
52
+ error: error message if zero binary missing or timeout
53
+ """
54
+ binary = find_zero_binary()
55
+ if not binary:
56
+ return {
57
+ "ok": False,
58
+ "exit_code": -1,
59
+ "stdout": "",
60
+ "stderr": "",
61
+ "command": ["zero"] + args,
62
+ "error": (
63
+ "zero binary not found. Install with: "
64
+ "curl -fsSL https://zerolang.ai/install.sh | bash "
65
+ "(then add ~/.zero/bin to PATH), or call zero_install()."
66
+ ),
67
+ }
68
+
69
+ cmd = [binary] + args
70
+ try:
71
+ proc = subprocess.run(
72
+ cmd,
73
+ cwd=cwd,
74
+ input=input_text,
75
+ capture_output=True,
76
+ text=True,
77
+ timeout=timeout,
78
+ )
79
+ except subprocess.TimeoutExpired as e:
80
+ return {
81
+ "ok": False,
82
+ "exit_code": -1,
83
+ "stdout": e.stdout or "",
84
+ "stderr": e.stderr or "",
85
+ "command": cmd,
86
+ "error": f"timeout after {timeout}s",
87
+ }
88
+ except Exception as e:
89
+ return {
90
+ "ok": False,
91
+ "exit_code": -1,
92
+ "stdout": "",
93
+ "stderr": "",
94
+ "command": cmd,
95
+ "error": f"{type(e).__name__}: {e}",
96
+ }
97
+
98
+ result: Dict[str, Any] = {
99
+ "ok": proc.returncode == 0,
100
+ "exit_code": proc.returncode,
101
+ "stdout": proc.stdout,
102
+ "stderr": proc.stderr,
103
+ "command": cmd,
104
+ }
105
+
106
+ if parse_json and proc.stdout.strip():
107
+ try:
108
+ result["json"] = json.loads(proc.stdout)
109
+ except json.JSONDecodeError:
110
+ # Some commands print non-JSON warnings before JSON; try last line.
111
+ for line in reversed(proc.stdout.splitlines()):
112
+ line = line.strip()
113
+ if line.startswith("{") or line.startswith("["):
114
+ try:
115
+ result["json"] = json.loads(line)
116
+ break
117
+ except json.JSONDecodeError:
118
+ continue
119
+
120
+ return result
121
+
122
+
123
+ def resolve_target(target: Optional[str]) -> Tuple[str, str]:
124
+ """Resolve a Zero source target. Returns (target_arg, label)."""
125
+ if target is None:
126
+ return ("", "(no target)")
127
+ p = Path(target).expanduser()
128
+ if p.exists():
129
+ return (str(p), str(p))
130
+ return (target, target)
@@ -0,0 +1,71 @@
1
+ """Install/upgrade the Zero compiler binary."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ INSTALL_URL = "https://zerolang.ai/install.sh"
10
+
11
+
12
+ def install_zero(install_dir: str | None = None) -> dict:
13
+ """Run the official Zero install script.
14
+
15
+ Args:
16
+ install_dir: Override install dir (defaults to ~/.zero/bin via the script).
17
+ """
18
+ env = os.environ.copy()
19
+ if install_dir:
20
+ env["ZERO_INSTALL_DIR"] = install_dir
21
+
22
+ try:
23
+ # Pipe curl into sh, but capture both for the agent.
24
+ curl = subprocess.run(
25
+ ["curl", "-fsSL", INSTALL_URL],
26
+ capture_output=True,
27
+ text=True,
28
+ timeout=60,
29
+ )
30
+ if curl.returncode != 0:
31
+ return {"ok": False, "error": f"curl failed: {curl.stderr}"}
32
+
33
+ sh = subprocess.run(
34
+ ["sh", "-s"],
35
+ input=curl.stdout,
36
+ capture_output=True,
37
+ text=True,
38
+ timeout=300,
39
+ env=env,
40
+ )
41
+ return {
42
+ "ok": sh.returncode == 0,
43
+ "exit_code": sh.returncode,
44
+ "stdout": sh.stdout,
45
+ "stderr": sh.stderr,
46
+ "install_dir": env.get("ZERO_INSTALL_DIR")
47
+ or str(Path.home() / ".zero" / "bin"),
48
+ }
49
+ except FileNotFoundError as e:
50
+ return {"ok": False, "error": f"missing required command: {e}"}
51
+ except subprocess.TimeoutExpired:
52
+ return {"ok": False, "error": "install timed out"}
53
+ except Exception as e:
54
+ return {"ok": False, "error": f"{type(e).__name__}: {e}"}
55
+
56
+
57
+ def cli() -> int:
58
+ """Entry point for `strands-zero-install` script."""
59
+ install_dir = sys.argv[1] if len(sys.argv) > 1 else None
60
+ result = install_zero(install_dir)
61
+ if result["ok"]:
62
+ print(result.get("stdout", ""))
63
+ bin_dir = result.get("install_dir", "~/.zero/bin")
64
+ print(f"\n✓ Zero installed. Add to PATH:\n export PATH=\"{bin_dir}:$PATH\"")
65
+ return 0
66
+ print(f"✗ Install failed: {result.get('error') or result.get('stderr')}", file=sys.stderr)
67
+ return 1
68
+
69
+
70
+ if __name__ == "__main__":
71
+ sys.exit(cli())
@@ -0,0 +1,375 @@
1
+ """Strands @tool wrappers for the Zero CLI.
2
+
3
+ Every tool returns a dict with keys: ok, stdout, stderr, exit_code, command,
4
+ and (when --json was used) a parsed `json` field. Tools are deliberately thin
5
+ wrappers — the agent decides what to do with the output.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import tempfile
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from strands import tool
15
+
16
+ from strands_zero._runner import run_zero, find_zero_binary
17
+ from strands_zero.installer import install_zero
18
+
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Meta / environment
22
+ # ---------------------------------------------------------------------------
23
+
24
+ @tool
25
+ def zero_version(json_output: bool = True) -> Dict[str, Any]:
26
+ """Get the installed Zero compiler version and host info.
27
+
28
+ Args:
29
+ json_output: Return structured JSON (default True).
30
+ """
31
+ args = ["--version"] + (["--json"] if json_output else [])
32
+ return run_zero(args, parse_json=json_output)
33
+
34
+
35
+ @tool
36
+ def zero_install(install_dir: Optional[str] = None) -> Dict[str, Any]:
37
+ """Install (or reinstall) the Zero compiler from zerolang.ai.
38
+
39
+ Args:
40
+ install_dir: Optional install directory (defaults to ~/.zero/bin).
41
+ """
42
+ return install_zero(install_dir)
43
+
44
+
45
+ @tool
46
+ def zero_doctor(json_output: bool = True) -> Dict[str, Any]:
47
+ """Run `zero doctor` to check the toolchain and environment health."""
48
+ args = ["doctor"] + (["--json"] if json_output else [])
49
+ return run_zero(args, parse_json=json_output)
50
+
51
+
52
+ @tool
53
+ def zero_targets() -> Dict[str, Any]:
54
+ """List available compilation targets."""
55
+ return run_zero(["targets"])
56
+
57
+
58
+ @tool
59
+ def zero_skills(action: str = "list", name: Optional[str] = None,
60
+ full: bool = False) -> Dict[str, Any]:
61
+ """Access Zero's built-in skill docs (language, diagnostics, builds, etc).
62
+
63
+ Args:
64
+ action: One of "list", "get", "path".
65
+ name: Skill name when action="get" or action="path"
66
+ (e.g. "zero-language", "zero-diagnostics", "zero-agent").
67
+ full: When True with get, request full skill body.
68
+ """
69
+ if action == "list":
70
+ return run_zero(["skills", "list", "--json"], parse_json=True)
71
+ if action in ("get", "path"):
72
+ if not name:
73
+ return {"ok": False, "error": f"name required for action='{action}'"}
74
+ args = ["skills", action, name]
75
+ if action == "get" and full:
76
+ args.append("--full")
77
+ return run_zero(args)
78
+ return {"ok": False, "error": f"unknown action: {action}"}
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Authoring
83
+ # ---------------------------------------------------------------------------
84
+
85
+ @tool
86
+ def zero_write(
87
+ code: str,
88
+ path: Optional[str] = None,
89
+ overwrite: bool = True,
90
+ ) -> Dict[str, Any]:
91
+ """Write Zero source code (`.0`) to disk.
92
+
93
+ Args:
94
+ code: The Zero source to write.
95
+ path: Destination path. If None, writes to a temp `.0` file.
96
+ If a path without `.0` extension is given, `.0` is appended.
97
+ overwrite: Overwrite existing file (default True).
98
+
99
+ Returns: dict with ok, path, bytes_written.
100
+ """
101
+ if path is None:
102
+ fd, tmp = tempfile.mkstemp(suffix=".0", prefix="zero_")
103
+ os.close(fd)
104
+ target = Path(tmp)
105
+ else:
106
+ target = Path(path).expanduser()
107
+ if target.suffix == "":
108
+ target = target.with_suffix(".0")
109
+ target.parent.mkdir(parents=True, exist_ok=True)
110
+ if target.exists() and not overwrite:
111
+ return {
112
+ "ok": False,
113
+ "error": f"file exists and overwrite=False: {target}",
114
+ "path": str(target),
115
+ }
116
+
117
+ target.write_text(code, encoding="utf-8")
118
+ return {
119
+ "ok": True,
120
+ "path": str(target),
121
+ "bytes_written": len(code.encode("utf-8")),
122
+ }
123
+
124
+
125
+ @tool
126
+ def zero_new(kind: str, name: str, cwd: Optional[str] = None) -> Dict[str, Any]:
127
+ """Scaffold a new Zero project: `zero new cli|lib|package <name>`.
128
+
129
+ Args:
130
+ kind: "cli", "lib", or "package".
131
+ name: Project name.
132
+ cwd: Working directory in which to create the project.
133
+ """
134
+ if kind not in ("cli", "lib", "package"):
135
+ return {"ok": False, "error": f"kind must be cli/lib/package, got: {kind}"}
136
+ return run_zero(["new", kind, name], cwd=cwd)
137
+
138
+
139
+ # ---------------------------------------------------------------------------
140
+ # Diagnostics
141
+ # ---------------------------------------------------------------------------
142
+
143
+ @tool
144
+ def zero_check(target: str, json_output: bool = True,
145
+ cwd: Optional[str] = None) -> Dict[str, Any]:
146
+ """Type-check and validate Zero source without running.
147
+
148
+ Args:
149
+ target: Path to a `.0` file, project dir, or `zero.json`.
150
+ json_output: Return structured diagnostics JSON (preferred).
151
+ cwd: Working directory for the check.
152
+ """
153
+ args = ["check"]
154
+ if json_output:
155
+ args.append("--json")
156
+ args.append(target)
157
+ return run_zero(args, cwd=cwd, parse_json=json_output)
158
+
159
+
160
+ @tool
161
+ def zero_explain(code: str, json_output: bool = True) -> Dict[str, Any]:
162
+ """Explain a diagnostic code from `zero check`.
163
+
164
+ Args:
165
+ code: Diagnostic code (e.g. "E0123").
166
+ json_output: Return JSON.
167
+ """
168
+ args = ["explain"]
169
+ if json_output:
170
+ args.append("--json")
171
+ args.append(code)
172
+ return run_zero(args, parse_json=json_output)
173
+
174
+
175
+ @tool
176
+ def zero_fix(target: str, cwd: Optional[str] = None) -> Dict[str, Any]:
177
+ """Generate a structured fix plan for diagnostics in `target`.
178
+
179
+ Runs `zero fix --plan --json <target>`.
180
+ """
181
+ return run_zero(["fix", "--plan", "--json", target], cwd=cwd, parse_json=True)
182
+
183
+
184
+ # ---------------------------------------------------------------------------
185
+ # Build / run / test
186
+ # ---------------------------------------------------------------------------
187
+
188
+ @tool
189
+ def zero_run(
190
+ target: str,
191
+ args: Optional[List[str]] = None,
192
+ profile: Optional[str] = None,
193
+ target_arch: Optional[str] = None,
194
+ out: Optional[str] = None,
195
+ cwd: Optional[str] = None,
196
+ timeout: int = 120,
197
+ ) -> Dict[str, Any]:
198
+ """Compile and run a Zero program.
199
+
200
+ Args:
201
+ target: Path to `.0` file, project dir, or `zero.json`.
202
+ args: Args to pass to the program (after `--`).
203
+ profile: debug | dev | release-fast | release-small | tiny | audit.
204
+ target_arch: e.g. "darwin-arm64", "linux-musl-x64", "wasm32-wasi".
205
+ out: Optional output binary path.
206
+ cwd: Working directory.
207
+ timeout: Seconds before killing the process (default 120).
208
+ """
209
+ cmd = ["run"]
210
+ if target_arch:
211
+ cmd += ["--target", target_arch]
212
+ if profile:
213
+ cmd += ["--profile", profile]
214
+ if out:
215
+ cmd += ["--out", out]
216
+ cmd.append(target)
217
+ if args:
218
+ cmd += ["--", *args]
219
+ return run_zero(cmd, cwd=cwd, timeout=timeout)
220
+
221
+
222
+ @tool
223
+ def zero_build(
224
+ target: str,
225
+ emit: str = "exe",
226
+ profile: Optional[str] = None,
227
+ target_arch: Optional[str] = None,
228
+ out: Optional[str] = None,
229
+ json_output: bool = False,
230
+ cwd: Optional[str] = None,
231
+ timeout: int = 300,
232
+ ) -> Dict[str, Any]:
233
+ """Compile a Zero program to an executable / object / wasm.
234
+
235
+ Args:
236
+ target: Source file, project dir, or `zero.json`.
237
+ emit: "exe" | "obj" | "wasm".
238
+ profile: debug | dev | release-fast | release-small | tiny | audit.
239
+ target_arch: Compilation target triple (e.g. "linux-musl-x64").
240
+ out: Output artifact path.
241
+ json_output: Emit JSON build report.
242
+ cwd: Working directory.
243
+ timeout: Seconds before killing the process.
244
+ """
245
+ if emit not in ("exe", "obj", "wasm"):
246
+ return {"ok": False, "error": f"emit must be exe/obj/wasm, got: {emit}"}
247
+ cmd = ["build"]
248
+ if json_output:
249
+ cmd.append("--json")
250
+ cmd += ["--emit", emit]
251
+ if target_arch:
252
+ cmd += ["--target", target_arch]
253
+ if profile:
254
+ cmd += ["--profile", profile]
255
+ if out:
256
+ cmd += ["--out", out]
257
+ cmd.append(target)
258
+ return run_zero(cmd, cwd=cwd, parse_json=json_output, timeout=timeout)
259
+
260
+
261
+ @tool
262
+ def zero_ship(
263
+ target: str,
264
+ target_arch: Optional[str] = None,
265
+ profile: str = "release-small",
266
+ out: Optional[str] = None,
267
+ json_output: bool = True,
268
+ cwd: Optional[str] = None,
269
+ timeout: int = 600,
270
+ ) -> Dict[str, Any]:
271
+ """Produce a release-grade artifact via `zero ship`.
272
+
273
+ Args:
274
+ target: Source / project / zero.json.
275
+ target_arch: e.g. "linux-musl-x64".
276
+ profile: release-small | tiny | audit (default release-small).
277
+ out: Output path.
278
+ json_output: Emit JSON ship report.
279
+ cwd: Working directory.
280
+ timeout: Seconds budget.
281
+ """
282
+ cmd = ["ship"]
283
+ if json_output:
284
+ cmd.append("--json")
285
+ if target_arch:
286
+ cmd += ["--target", target_arch]
287
+ cmd += ["--profile", profile]
288
+ if out:
289
+ cmd += ["--out", out]
290
+ cmd.append(target)
291
+ return run_zero(cmd, cwd=cwd, parse_json=json_output, timeout=timeout)
292
+
293
+
294
+ @tool
295
+ def zero_test(target: str, json_output: bool = True,
296
+ cwd: Optional[str] = None, timeout: int = 300) -> Dict[str, Any]:
297
+ """Run `test` blocks in Zero source / project."""
298
+ cmd = ["test"]
299
+ if json_output:
300
+ cmd.append("--json")
301
+ cmd.append(target)
302
+ return run_zero(cmd, cwd=cwd, parse_json=json_output, timeout=timeout)
303
+
304
+
305
+ @tool
306
+ def zero_fmt(target: str, cwd: Optional[str] = None) -> Dict[str, Any]:
307
+ """Format Zero source in-place."""
308
+ return run_zero(["fmt", target], cwd=cwd)
309
+
310
+
311
+ # ---------------------------------------------------------------------------
312
+ # Inspection
313
+ # ---------------------------------------------------------------------------
314
+
315
+ @tool
316
+ def zero_graph(target: str, json_output: bool = True,
317
+ cwd: Optional[str] = None) -> Dict[str, Any]:
318
+ """Dump the module/package dependency graph."""
319
+ cmd = ["graph"]
320
+ if json_output:
321
+ cmd.append("--json")
322
+ cmd.append(target)
323
+ return run_zero(cmd, cwd=cwd, parse_json=json_output)
324
+
325
+
326
+ @tool
327
+ def zero_size(target: str, artifact: Optional[str] = None,
328
+ json_output: bool = True, cwd: Optional[str] = None) -> Dict[str, Any]:
329
+ """Report binary size breakdown for a built artifact or source target."""
330
+ cmd = ["size"]
331
+ if json_output:
332
+ cmd.append("--json")
333
+ if artifact:
334
+ cmd += ["--out", artifact]
335
+ cmd.append(target)
336
+ return run_zero(cmd, cwd=cwd, parse_json=json_output)
337
+
338
+
339
+ @tool
340
+ def zero_mem(target: str, target_arch: Optional[str] = None,
341
+ json_output: bool = True, cwd: Optional[str] = None) -> Dict[str, Any]:
342
+ """Report static memory layout / usage for a target."""
343
+ cmd = ["mem"]
344
+ if json_output:
345
+ cmd.append("--json")
346
+ if target_arch:
347
+ cmd += ["--target", target_arch]
348
+ cmd.append(target)
349
+ return run_zero(cmd, cwd=cwd, parse_json=json_output)
350
+
351
+
352
+ @tool
353
+ def zero_routes(target: str, json_output: bool = True,
354
+ cwd: Optional[str] = None) -> Dict[str, Any]:
355
+ """List HTTP/web routes declared in a Zero web project."""
356
+ cmd = ["routes"]
357
+ if json_output:
358
+ cmd.append("--json")
359
+ cmd.append(target)
360
+ return run_zero(cmd, cwd=cwd, parse_json=json_output)
361
+
362
+
363
+ @tool
364
+ def zero_clean(target_dir: Optional[str] = None, all_artifacts: bool = False,
365
+ cwd: Optional[str] = None) -> Dict[str, Any]:
366
+ """Clean compiled artifacts (`.zero/` cache).
367
+
368
+ Args:
369
+ target_dir: Project directory (default cwd).
370
+ all_artifacts: Pass --all to remove the entire `.zero/` cache.
371
+ """
372
+ cmd = ["clean"]
373
+ if all_artifacts:
374
+ cmd.append("--all")
375
+ return run_zero(cmd, cwd=target_dir or cwd)
@@ -0,0 +1,150 @@
1
+ """Smoke tests — exercise every tool against the real `zero` binary.
2
+
3
+ Skips gracefully if Zero isn't installed. Some tests tolerate upstream-binary
4
+ quirks (skills CLI, dyld LC_UUID on macOS) since this is an external tool.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from strands_zero._runner import find_zero_binary, run_zero
13
+ from strands_zero.tools import (
14
+ zero_version, zero_doctor, zero_skills, zero_write, zero_check,
15
+ zero_run, zero_build, zero_explain, zero_targets, zero_graph,
16
+ zero_size, zero_fmt, zero_clean,
17
+ )
18
+
19
+
20
+ HELLO = '''pub fun main(world: World) -> Void raises {
21
+ check world.out.write("hello from zero\\n")
22
+ }
23
+ '''
24
+
25
+
26
+ @pytest.fixture(scope="session")
27
+ def zero_available():
28
+ if not find_zero_binary():
29
+ pytest.skip("zero binary not installed")
30
+
31
+
32
+ def _unwrap(t):
33
+ """Unwrap a Strands @tool to its underlying function for direct call."""
34
+ return getattr(t, "__wrapped__", None) or getattr(t, "func", None) or t
35
+
36
+
37
+ def test_find_binary(zero_available):
38
+ assert find_zero_binary() is not None
39
+
40
+
41
+ def test_version(zero_available):
42
+ r = _unwrap(zero_version)(json_output=True)
43
+ assert r["ok"], r
44
+ assert "0.1" in r["stdout"] or (r.get("json") and "version" in r["json"])
45
+
46
+
47
+ def test_targets(zero_available):
48
+ r = _unwrap(zero_targets)()
49
+ assert r["ok"], r
50
+ assert "darwin" in r["stdout"] or "linux" in r["stdout"]
51
+
52
+
53
+ def test_skills_runs(zero_available):
54
+ """skills CLI may require repo-local bin/zero; just check our wrapper invokes it."""
55
+ r = _unwrap(zero_skills)(action="list")
56
+ assert "command" in r
57
+ assert "skills" in r["command"]
58
+
59
+
60
+ def test_doctor(zero_available):
61
+ r = _unwrap(zero_doctor)(json_output=True)
62
+ assert r["exit_code"] in (0, 1, 2), r
63
+
64
+
65
+ def test_write_and_check(zero_available, tmp_path):
66
+ w = _unwrap(zero_write)(HELLO, path=str(tmp_path / "hello.0"))
67
+ assert w["ok"] and Path(w["path"]).exists()
68
+
69
+ c = _unwrap(zero_check)(w["path"], json_output=True)
70
+ assert c["ok"], c
71
+ # Successful check should produce JSON diagnostics surface
72
+ assert "json" in c or c["stdout"] != ""
73
+
74
+
75
+ def test_run_attempt(zero_available, tmp_path):
76
+ """zero run may fail on macOS 26 due to dyld LC_UUID issue in prebuilt binary.
77
+ We assert the wrapper invocation is correct, not that the program executes.
78
+ """
79
+ src = tmp_path / "hello.0"
80
+ src.write_text(HELLO)
81
+ r = _unwrap(zero_run)(str(src))
82
+ assert "command" in r
83
+ # If it ran, it should print our message; otherwise stderr should mention dyld
84
+ if r["ok"]:
85
+ assert "hello from zero" in r["stdout"]
86
+
87
+
88
+ def test_build_exe(zero_available, tmp_path):
89
+ src = tmp_path / "h.0"
90
+ src.write_text(HELLO)
91
+ out = tmp_path / "h"
92
+ b = _unwrap(zero_build)(str(src), emit="exe", out=str(out))
93
+ assert "command" in b
94
+
95
+
96
+ def test_build_invalid_emit(zero_available):
97
+ r = _unwrap(zero_build)("dummy.0", emit="bogus")
98
+ assert not r["ok"]
99
+ assert "emit" in r["error"]
100
+
101
+
102
+ def test_explain(zero_available):
103
+ r = _unwrap(zero_explain)("E0001", json_output=True)
104
+ assert "command" in r
105
+
106
+
107
+ def test_graph(zero_available, tmp_path):
108
+ src = tmp_path / "g.0"
109
+ src.write_text(HELLO)
110
+ r = _unwrap(zero_graph)(str(src), json_output=True)
111
+ assert "command" in r
112
+
113
+
114
+ def test_fmt(zero_available, tmp_path):
115
+ src = tmp_path / "f.0"
116
+ src.write_text(HELLO)
117
+ r = _unwrap(zero_fmt)(str(src))
118
+ assert "command" in r
119
+
120
+
121
+ def test_clean(zero_available, tmp_path):
122
+ r = _unwrap(zero_clean)(target_dir=str(tmp_path), all_artifacts=True)
123
+ assert "command" in r
124
+
125
+
126
+ def test_skills_get_requires_name():
127
+ """Pure unit test - no zero binary needed."""
128
+ r = _unwrap(zero_skills)(action="get")
129
+ assert not r["ok"]
130
+ assert "name required" in r["error"]
131
+
132
+
133
+ def test_unknown_skills_action():
134
+ r = _unwrap(zero_skills)(action="bogus")
135
+ assert not r["ok"]
136
+
137
+
138
+ def test_zero_new_invalid_kind():
139
+ r = _unwrap(__import__("strands_zero.tools", fromlist=["zero_new"]).zero_new)(
140
+ kind="bogus", name="x"
141
+ )
142
+ assert not r["ok"]
143
+
144
+
145
+ def test_run_zero_returns_error_when_missing(monkeypatch):
146
+ """Wrapper handles missing binary gracefully."""
147
+ monkeypatch.setattr("strands_zero._runner.find_zero_binary", lambda: None)
148
+ r = run_zero(["--version"])
149
+ assert not r["ok"]
150
+ assert "not found" in r["error"]