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.
- strands_zero-0.1.0/.gitignore +21 -0
- strands_zero-0.1.0/LICENSE +15 -0
- strands_zero-0.1.0/PKG-INFO +152 -0
- strands_zero-0.1.0/README.md +123 -0
- strands_zero-0.1.0/examples/agent_demo.py +22 -0
- strands_zero-0.1.0/examples/manual_usage.py +11 -0
- strands_zero-0.1.0/pyproject.toml +42 -0
- strands_zero-0.1.0/src/strands_zero/__init__.py +71 -0
- strands_zero-0.1.0/src/strands_zero/_runner.py +130 -0
- strands_zero-0.1.0/src/strands_zero/installer.py +71 -0
- strands_zero-0.1.0/src/strands_zero/tools.py +375 -0
- strands_zero-0.1.0/tests/test_smoke.py +150 -0
|
@@ -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
|
+
[](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
|
+
[](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"]
|