bazis-cli 2.4.0__py3-none-any.whl
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.
- bazis/contrib/cli/AGENTS.md +33 -0
- bazis/contrib/cli/__init__.py +19 -0
- bazis/contrib/cli/_version.py +24 -0
- bazis/contrib/cli/agent.py +274 -0
- bazis/contrib/cli/bazis_manifest.toml +32 -0
- bazis/contrib/cli/main.py +180 -0
- bazis/contrib/cli/scaffold.py +273 -0
- bazis_cli-2.4.0.dist-info/METADATA +93 -0
- bazis_cli-2.4.0.dist-info/RECORD +12 -0
- bazis_cli-2.4.0.dist-info/WHEEL +5 -0
- bazis_cli-2.4.0.dist-info/entry_points.txt +2 -0
- bazis_cli-2.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# bazis-cli — guide for AI agents
|
|
2
|
+
|
|
3
|
+
The command `bazis` creates, extends and audits Bazis projects with Claude: it runs Claude
|
|
4
|
+
Code (Claude Agent SDK) in the project with the MCP server bazis-mcp and the rules of
|
|
5
|
+
Bazis. It is a tool for the developer's machine, not a dependency of a project.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
- `bazis new DIR "what the project does"` writes the skeleton of a project (package with
|
|
10
|
+
settings, root router and ASGI app, `project.env`, `.env` with a generated secret key,
|
|
11
|
+
`requirements*.txt`, `tests/`, `.mcp.json`, `AGENTS.md`), creates `.venv` with Bazis and
|
|
12
|
+
bazis-mcp (`--no-venv` skips it), then the agent chooses the Bazis packages and builds the
|
|
13
|
+
apps, migrations and tests.
|
|
14
|
+
- `bazis add PACKAGE ["what for"]` installs a Bazis package and its Bazis dependencies and
|
|
15
|
+
sets them up as their guides say.
|
|
16
|
+
- `bazis audit [--fix]` reviews the project against the guides and checks of its packages;
|
|
17
|
+
without `--fix` the agent can only read.
|
|
18
|
+
|
|
19
|
+
Options: `--project-dir` (default: the current directory), `--model` (default
|
|
20
|
+
`claude-opus-5-5`), `--effort` (default `high`), `--budget USD`, `--yes` (run shell commands
|
|
21
|
+
without asking).
|
|
22
|
+
|
|
23
|
+
## Rules
|
|
24
|
+
|
|
25
|
+
- Edits of the files of the project are accepted; every shell command and every other tool
|
|
26
|
+
call (edits or reads outside the project, web) is asked in the terminal (`a` allows the
|
|
27
|
+
rest of the run) unless `--yes`; without a terminal and `--yes` they are denied.
|
|
28
|
+
- `.env` cannot be read; git cannot commit, push, reset or rebase: the user reviews and
|
|
29
|
+
commits the changes.
|
|
30
|
+
- Only the MCP server bazis-mcp and no Claude Code settings of the project or the user are
|
|
31
|
+
loaded. The checks of bazis-mcp run the project code: use the commands in trusted
|
|
32
|
+
projects.
|
|
33
|
+
- Credentials are those of Claude Code: `claude login` or `ANTHROPIC_API_KEY`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
17
|
+
__version__ = version('bazis-cli')
|
|
18
|
+
except PackageNotFoundError:
|
|
19
|
+
__version__ = 'dev'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '2.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 4, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'g2d504c4db'
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
The agent that does the work of a command: Claude Code (Claude Agent SDK) in the project
|
|
17
|
+
directory, with the MCP server bazis-mcp of the project and the rules of Bazis.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
import warnings
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import TextIO
|
|
27
|
+
|
|
28
|
+
from claude_agent_sdk import (
|
|
29
|
+
AssistantMessage,
|
|
30
|
+
ClaudeAgentOptions,
|
|
31
|
+
HookMatcher,
|
|
32
|
+
PermissionResultAllow,
|
|
33
|
+
PermissionResultDeny,
|
|
34
|
+
ResultError,
|
|
35
|
+
ResultMessage,
|
|
36
|
+
TextBlock,
|
|
37
|
+
ToolUseBlock,
|
|
38
|
+
query,
|
|
39
|
+
)
|
|
40
|
+
from claude_agent_sdk.types import CanUseToolShadowedWarning
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
DEFAULT_MODEL = 'claude-opus-5-5'
|
|
44
|
+
DEFAULT_EFFORT = 'high'
|
|
45
|
+
|
|
46
|
+
#: git commands that change the repository: the user commits, never the agent
|
|
47
|
+
GIT_DENY = ['Bash(git commit:*)', 'Bash(git push:*)', 'Bash(git reset:*)', 'Bash(git rebase:*)']
|
|
48
|
+
#: tools a review cannot use
|
|
49
|
+
WRITE_TOOLS = ['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'Bash', 'WebFetch', 'WebSearch']
|
|
50
|
+
GIT_COMMAND_RE = re.compile(r'(^|[;&|(]\s*)git\s+(commit|push|reset|rebase)\b', re.MULTILINE)
|
|
51
|
+
|
|
52
|
+
RULES = """\
|
|
53
|
+
# Bazis
|
|
54
|
+
|
|
55
|
+
You work on a Bazis project in the current directory. Bazis builds JSON:API services on
|
|
56
|
+
Django, FastAPI and Pydantic; the core is the package `bazis`, every other feature is a
|
|
57
|
+
separate package `bazis-<name>`. A project installs only the packages it needs.
|
|
58
|
+
|
|
59
|
+
- The MCP server `bazis` is the source of truth about Bazis, not your memory of it:
|
|
60
|
+
`list_packages` (what each package solves and requires), `package_guide` (how to use a
|
|
61
|
+
package, also `bazis` for the core), `project_info` (the installed packages, settings,
|
|
62
|
+
models and routes of this project) and `run_doctor` (the system checks). Read the guide
|
|
63
|
+
of every package before you use it and follow it exactly.
|
|
64
|
+
- Read `AGENTS.md` (and `CLAUDE.md` if there is one) of the project first.
|
|
65
|
+
- The Python environment is `.venv` (`.venv/bin/python`, on Windows
|
|
66
|
+
`.venv\\Scripts\\python.exe`). Add a package to `requirements.txt`, install it with
|
|
67
|
+
`.venv/bin/python -m pip install -r requirements.txt` and list its app in
|
|
68
|
+
`BS_INSTALLED_APPS` of `project.env` when its guide says so.
|
|
69
|
+
- After changing models run `.venv/bin/python manage.py makemigrations`. After every change
|
|
70
|
+
run `run_doctor` and fix the errors and the warnings of the packages you used. Run the
|
|
71
|
+
tests with `.venv/bin/python -m pytest` when PostgreSQL and Redis are available (`.env`);
|
|
72
|
+
if they are not, say so instead of skipping the tests silently.
|
|
73
|
+
- Write tests for the behavior you add (`tests/`), with `bazis_test_utils` as the guides show.
|
|
74
|
+
- Never read, print or copy the values of `.env`. Never run git commands that change the
|
|
75
|
+
repository (commit, push, reset): the user reviews and commits the changes.
|
|
76
|
+
- End with a short summary: what you built or changed, the Bazis packages used, how to run
|
|
77
|
+
it, and what is left for the user (for example the database).
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class Task:
|
|
83
|
+
"""
|
|
84
|
+
What the agent is asked to do in a project.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
prompt: str
|
|
88
|
+
project_dir: Path
|
|
89
|
+
#: the agent may change files and run commands
|
|
90
|
+
writes: bool = True
|
|
91
|
+
model: str = DEFAULT_MODEL
|
|
92
|
+
effort: str = DEFAULT_EFFORT
|
|
93
|
+
max_budget_usd: float | None = None
|
|
94
|
+
#: run shell commands without asking
|
|
95
|
+
yes: bool = False
|
|
96
|
+
extra_rules: list[str] = field(default_factory=list)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def mcp_server(project_dir: Path) -> dict:
|
|
100
|
+
"""
|
|
101
|
+
The stdio MCP server bazis-mcp of the project, run with the Python of the CLI (it uses
|
|
102
|
+
`.venv` of the project for the project tools).
|
|
103
|
+
"""
|
|
104
|
+
return {
|
|
105
|
+
'type': 'stdio',
|
|
106
|
+
'command': sys.executable,
|
|
107
|
+
'args': ['-m', 'bazis.contrib.mcp.server', '--project-dir', str(project_dir)],
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _path_rule(tool: str, path: Path) -> str:
|
|
112
|
+
"""
|
|
113
|
+
A permission rule for an absolute path (`//` starts an absolute path in the rules).
|
|
114
|
+
"""
|
|
115
|
+
return f'{tool}(//{path.resolve().as_posix().lstrip("/")})'
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def options(task: Task, asker: 'Asker | None' = None) -> ClaudeAgentOptions:
|
|
119
|
+
"""
|
|
120
|
+
The options of the agent for a task. Only the MCP server bazis-mcp of the CLI is used
|
|
121
|
+
(`strict_mcp_config`) and no settings of the project or the user are loaded, so the
|
|
122
|
+
permissions below are the only ones.
|
|
123
|
+
|
|
124
|
+
A read-only task can only read the project and call the MCP tools. A task that writes
|
|
125
|
+
gets the edits of files in the project accepted (`acceptEdits`); everything else is
|
|
126
|
+
asked: shell commands through a PreToolUse hook (the CLI would otherwise allow commands
|
|
127
|
+
such as `rm` or `sed` in `acceptEdits` mode), the other tools through `can_use_tool`.
|
|
128
|
+
In both, `.env` cannot be read and git cannot change the repository.
|
|
129
|
+
"""
|
|
130
|
+
rules = '\n'.join([RULES, *task.extra_rules])
|
|
131
|
+
secrets = [_path_rule(tool, task.project_dir / '.env') for tool in ('Read', 'Edit')]
|
|
132
|
+
common = dict(
|
|
133
|
+
cwd=str(task.project_dir),
|
|
134
|
+
model=task.model,
|
|
135
|
+
effort=task.effort,
|
|
136
|
+
max_budget_usd=task.max_budget_usd,
|
|
137
|
+
mcp_servers={'bazis': mcp_server(task.project_dir)},
|
|
138
|
+
strict_mcp_config=True,
|
|
139
|
+
setting_sources=[],
|
|
140
|
+
system_prompt={'type': 'preset', 'preset': 'claude_code', 'append': rules},
|
|
141
|
+
allowed_tools=['mcp__bazis'],
|
|
142
|
+
)
|
|
143
|
+
if not task.writes:
|
|
144
|
+
return ClaudeAgentOptions(
|
|
145
|
+
disallowed_tools=[*WRITE_TOOLS, *secrets],
|
|
146
|
+
permission_mode='dontAsk',
|
|
147
|
+
**common,
|
|
148
|
+
)
|
|
149
|
+
asker = asker or Asker(task.yes)
|
|
150
|
+
return ClaudeAgentOptions(
|
|
151
|
+
disallowed_tools=[*GIT_DENY, *secrets],
|
|
152
|
+
permission_mode='acceptEdits',
|
|
153
|
+
can_use_tool=asker.can_use_tool,
|
|
154
|
+
hooks={'PreToolUse': [HookMatcher(matcher='Bash', hooks=[asker.bash_hook])]},
|
|
155
|
+
**common,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Asker:
|
|
160
|
+
"""
|
|
161
|
+
Asks the user in the terminal before the tools that are not allowed in advance: `y`
|
|
162
|
+
allows the call, `a` allows the rest of the run. With `yes` everything is allowed;
|
|
163
|
+
without a terminal everything is denied. Commands that change the git repository are
|
|
164
|
+
always denied.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def __init__(self, yes: bool, stdin: TextIO | None = None, out: TextIO | None = None):
|
|
168
|
+
self.allow_all = yes
|
|
169
|
+
self.stdin = stdin or sys.stdin
|
|
170
|
+
self.out = out or sys.stderr
|
|
171
|
+
|
|
172
|
+
def decide(self, tool_name: str, tool_input: dict) -> str | None:
|
|
173
|
+
"""
|
|
174
|
+
None if allowed, else the reason of the denial.
|
|
175
|
+
"""
|
|
176
|
+
command = tool_input.get('command', '') if tool_name == 'Bash' else ''
|
|
177
|
+
if GIT_COMMAND_RE.search(command):
|
|
178
|
+
return 'The user commits the changes: git commands that change the repository are not allowed.'
|
|
179
|
+
if self.allow_all:
|
|
180
|
+
return None
|
|
181
|
+
if not self.stdin.isatty():
|
|
182
|
+
return 'Not allowed: no terminal to ask the user (run bazis with --yes to allow).'
|
|
183
|
+
details = command or json.dumps(tool_input, ensure_ascii=False, indent=2)
|
|
184
|
+
why = tool_input.get('description')
|
|
185
|
+
self.out.write(f'\n? {tool_name}' + (f' — {why}' if why else '') + '\n')
|
|
186
|
+
self.out.write(''.join(f' {line}\n' for line in details.splitlines()))
|
|
187
|
+
self.out.write(' allow [y/N/a=all]: ')
|
|
188
|
+
self.out.flush()
|
|
189
|
+
answer = self.stdin.readline().strip().lower()
|
|
190
|
+
if answer == 'a':
|
|
191
|
+
self.allow_all = True
|
|
192
|
+
if answer in ('y', 'yes', 'a'):
|
|
193
|
+
return None
|
|
194
|
+
return 'The user did not allow it.'
|
|
195
|
+
|
|
196
|
+
async def can_use_tool(self, tool_name: str, tool_input: dict, context) -> object:
|
|
197
|
+
reason = self.decide(tool_name, tool_input)
|
|
198
|
+
if reason is None:
|
|
199
|
+
return PermissionResultAllow()
|
|
200
|
+
return PermissionResultDeny(message=reason)
|
|
201
|
+
|
|
202
|
+
async def bash_hook(self, hook_input: dict, tool_use_id, context) -> dict:
|
|
203
|
+
reason = self.decide('Bash', hook_input.get('tool_input', {}))
|
|
204
|
+
return {
|
|
205
|
+
'hookSpecificOutput': {
|
|
206
|
+
'hookEventName': 'PreToolUse',
|
|
207
|
+
'permissionDecision': 'allow' if reason is None else 'deny',
|
|
208
|
+
'permissionDecisionReason': reason or 'Allowed by the user.',
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def describe(tool_name: str, tool_input: dict) -> str:
|
|
214
|
+
"""
|
|
215
|
+
One line about a tool call, for the progress output.
|
|
216
|
+
"""
|
|
217
|
+
for key in ('command', 'file_path', 'path', 'pattern', 'url', 'name'):
|
|
218
|
+
if value := tool_input.get(key):
|
|
219
|
+
lines = str(value).splitlines()
|
|
220
|
+
more = ' ...' if len(lines) > 1 else ''
|
|
221
|
+
return lines[0][:200] + more
|
|
222
|
+
return ''
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def render(message, out: TextIO | None = None) -> ResultMessage | None:
|
|
226
|
+
"""
|
|
227
|
+
Prints the text of the agent and one line per tool call; returns the final result.
|
|
228
|
+
"""
|
|
229
|
+
out = out or sys.stdout
|
|
230
|
+
if isinstance(message, AssistantMessage):
|
|
231
|
+
for block in message.content:
|
|
232
|
+
if isinstance(block, TextBlock) and block.text.strip():
|
|
233
|
+
out.write(block.text.rstrip() + '\n')
|
|
234
|
+
elif isinstance(block, ToolUseBlock):
|
|
235
|
+
name = block.name.removeprefix('mcp__bazis__')
|
|
236
|
+
out.write(f' > {name} {describe(block.name, block.input)}'.rstrip() + '\n')
|
|
237
|
+
out.flush()
|
|
238
|
+
elif isinstance(message, ResultMessage):
|
|
239
|
+
return message
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def run(task: Task, out: TextIO | None = None) -> ResultMessage | None:
|
|
244
|
+
"""
|
|
245
|
+
Runs the task and prints the progress; returns the final result of the agent.
|
|
246
|
+
"""
|
|
247
|
+
result = None
|
|
248
|
+
# the read-only MCP tools of bazis-mcp are allowed in advance on purpose
|
|
249
|
+
warnings.filterwarnings('ignore', category=CanUseToolShadowedWarning)
|
|
250
|
+
try:
|
|
251
|
+
async for message in query(prompt=task.prompt, options=options(task)):
|
|
252
|
+
result = render(message, out) or result
|
|
253
|
+
except ResultError:
|
|
254
|
+
# the CLI exits with an error after a failed result (e.g. the budget is spent):
|
|
255
|
+
# the result says why
|
|
256
|
+
if result is None:
|
|
257
|
+
raise
|
|
258
|
+
return result
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def error_text(result: ResultMessage) -> str:
|
|
262
|
+
"""
|
|
263
|
+
Why a run failed.
|
|
264
|
+
"""
|
|
265
|
+
reasons = {
|
|
266
|
+
'error_max_budget_usd': 'the budget (--budget) is spent',
|
|
267
|
+
'error_max_turns': 'the agent reached the maximum number of turns',
|
|
268
|
+
}
|
|
269
|
+
return (
|
|
270
|
+
'; '.join(result.errors or [])
|
|
271
|
+
or reasons.get(result.subtype)
|
|
272
|
+
or result.result
|
|
273
|
+
or result.subtype
|
|
274
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# What the package is for and how to use it, for tools and AI agents (see AGENTS.md).
|
|
2
|
+
# `import` paths and `check` ids are verified by tests/test_cli.py.
|
|
3
|
+
|
|
4
|
+
[package]
|
|
5
|
+
name = "bazis-cli"
|
|
6
|
+
summary = "the bazis command: creates, extends and audits Bazis projects with Claude"
|
|
7
|
+
solves = [
|
|
8
|
+
"create a Bazis project from a description of what it does",
|
|
9
|
+
"add a Bazis package to a project and set it up",
|
|
10
|
+
"review a project against the guides and checks of its Bazis packages",
|
|
11
|
+
]
|
|
12
|
+
requires = ["bazis-mcp"]
|
|
13
|
+
pairs_well = []
|
|
14
|
+
|
|
15
|
+
[[extension_points]]
|
|
16
|
+
name = "bazis"
|
|
17
|
+
import = "bazis.contrib.cli.main.main"
|
|
18
|
+
use_when = "the command line entry point"
|
|
19
|
+
|
|
20
|
+
[[extension_points]]
|
|
21
|
+
name = "Task"
|
|
22
|
+
import = "bazis.contrib.cli.agent.Task"
|
|
23
|
+
use_when = "run the agent of a command from Python (bazis.contrib.cli.agent.run)"
|
|
24
|
+
|
|
25
|
+
[[pitfalls]]
|
|
26
|
+
text = "the agent spends API credits of the account of Claude Code; limit a run with --budget"
|
|
27
|
+
|
|
28
|
+
[[pitfalls]]
|
|
29
|
+
text = "the system checks run the project code with its .venv, also in a read-only audit: use bazis in trusted projects"
|
|
30
|
+
|
|
31
|
+
[[pitfalls]]
|
|
32
|
+
text = "the tests of a new project need PostgreSQL with PostGIS and Redis (.env); without them the agent only checks and migrates"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
The `bazis` command:
|
|
17
|
+
|
|
18
|
+
- `bazis new DIR "what the project does"` creates a project and builds it;
|
|
19
|
+
- `bazis add PACKAGE [what for]` adds a Bazis package to the project;
|
|
20
|
+
- `bazis audit [--fix]` reviews the project against the guides and checks of its packages.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import asyncio
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
from claude_agent_sdk import ClaudeSDKError, CLIConnectionError
|
|
29
|
+
|
|
30
|
+
from . import __version__, agent, scaffold
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def new_task(args) -> agent.Task:
|
|
34
|
+
directory = args.directory.resolve()
|
|
35
|
+
name = args.name or scaffold.package_name(directory)
|
|
36
|
+
scaffold.write_files(directory, name)
|
|
37
|
+
print(f'Created the project {name} in {directory}')
|
|
38
|
+
if not args.no_venv:
|
|
39
|
+
print('Installing the dependencies into .venv ...')
|
|
40
|
+
try:
|
|
41
|
+
scaffold.create_venv(directory)
|
|
42
|
+
except scaffold.ScaffoldError as err:
|
|
43
|
+
raise scaffold.ScaffoldError(
|
|
44
|
+
f'{err}\nThe files of the project are written: create .venv and install '
|
|
45
|
+
f'requirements-dev.txt by hand, or delete {directory} and run bazis new again.'
|
|
46
|
+
) from err
|
|
47
|
+
prompt = f"""\
|
|
48
|
+
Build this Bazis project: {args.description}
|
|
49
|
+
|
|
50
|
+
The skeleton is ready: the project package `{name}` (settings, root router, ASGI app in
|
|
51
|
+
`{name}/main.py`), `project.env`, `.env`, `requirements.txt`, `tests/`, and `.venv` with
|
|
52
|
+
Bazis installed. Read the guide of the core (`package_guide("bazis")`), choose the Bazis
|
|
53
|
+
packages the project needs with `list_packages`, read their guides, then create the apps
|
|
54
|
+
with their models, routes and tests, and register the routers in `{name}/router.py`.
|
|
55
|
+
"""
|
|
56
|
+
return _task(args, prompt, directory)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def add_task(args) -> agent.Task:
|
|
60
|
+
goal = f' The project needs it for: {args.goal}' if args.goal else ''
|
|
61
|
+
prompt = f"""\
|
|
62
|
+
Add the Bazis package {args.package} to this project and use it.{goal}
|
|
63
|
+
|
|
64
|
+
Read its guide with `package_guide("{args.package}")` and the guides of the Bazis packages
|
|
65
|
+
it requires that are not installed (`list_packages`), install them, follow the setup of the
|
|
66
|
+
guides, change the models and routes they concern, create the migrations, add tests, and
|
|
67
|
+
make `run_doctor` pass without the warnings of the new packages.
|
|
68
|
+
"""
|
|
69
|
+
return _task(args, prompt, args.project_dir.resolve())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def audit_task(args) -> agent.Task:
|
|
73
|
+
if args.fix:
|
|
74
|
+
action = 'Then fix the problems you are sure about and run the checks and the tests again.'
|
|
75
|
+
else:
|
|
76
|
+
action = 'Change nothing: this is a review.'
|
|
77
|
+
prompt = f"""\
|
|
78
|
+
Audit this Bazis project.
|
|
79
|
+
|
|
80
|
+
1. Run `run_doctor` with `deploy=true` and `project_info`.
|
|
81
|
+
2. For every installed Bazis package read `package_guide` and compare the project with its
|
|
82
|
+
rules and pitfalls: routes that bypass permissions or the update checks, settings that
|
|
83
|
+
are unsafe in production, packages that are installed but not used, needs that a Bazis
|
|
84
|
+
package solves but the project implements by hand, missing tests and migrations.
|
|
85
|
+
3. Report the problems ordered by severity, each with the file, the reason and the fix.
|
|
86
|
+
|
|
87
|
+
{action}
|
|
88
|
+
"""
|
|
89
|
+
return _task(args, prompt, args.project_dir.resolve(), writes=args.fix)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _task(args, prompt: str, project_dir: Path, writes: bool = True) -> agent.Task:
|
|
93
|
+
return agent.Task(
|
|
94
|
+
prompt=prompt,
|
|
95
|
+
project_dir=project_dir,
|
|
96
|
+
writes=writes,
|
|
97
|
+
model=args.model,
|
|
98
|
+
effort=args.effort,
|
|
99
|
+
max_budget_usd=args.budget,
|
|
100
|
+
yes=args.yes,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def parser() -> argparse.ArgumentParser:
|
|
105
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
106
|
+
common.add_argument('--model', default=agent.DEFAULT_MODEL, help='Default: %(default)s.')
|
|
107
|
+
common.add_argument(
|
|
108
|
+
'--effort', default=agent.DEFAULT_EFFORT,
|
|
109
|
+
choices=['low', 'medium', 'high', 'xhigh', 'max'], help='Default: %(default)s.',
|
|
110
|
+
)
|
|
111
|
+
common.add_argument('--budget', type=float, help='Stop after spending this many USD.')
|
|
112
|
+
common.add_argument(
|
|
113
|
+
'--yes', '-y', action='store_true',
|
|
114
|
+
help='Allow shell commands and the other tools without asking (edits of the files of '
|
|
115
|
+
'the project are always allowed; git cannot commit, push or reset).',
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
project = argparse.ArgumentParser(add_help=False)
|
|
119
|
+
project.add_argument(
|
|
120
|
+
'--project-dir', type=Path, default=Path.cwd(),
|
|
121
|
+
help='The directory of manage.py (default: the current directory).',
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
root = argparse.ArgumentParser(
|
|
125
|
+
prog='bazis', description='Builds and audits Bazis projects with Claude.'
|
|
126
|
+
)
|
|
127
|
+
root.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
|
|
128
|
+
commands = root.add_subparsers(dest='command', required=True)
|
|
129
|
+
|
|
130
|
+
new = commands.add_parser('new', parents=[common], help='Create and build a project.')
|
|
131
|
+
new.add_argument('directory', type=Path, help='A new or empty directory.')
|
|
132
|
+
new.add_argument('description', help='What the project does, in your words.')
|
|
133
|
+
new.add_argument('--name', help='The project package (default: from the directory name).')
|
|
134
|
+
new.add_argument('--no-venv', action='store_true', help='Do not create .venv.')
|
|
135
|
+
new.set_defaults(make_task=new_task)
|
|
136
|
+
|
|
137
|
+
add = commands.add_parser('add', parents=[common, project], help='Add a Bazis package.')
|
|
138
|
+
add.add_argument('package', help='The package, e.g. bazis-permit.')
|
|
139
|
+
add.add_argument('goal', nargs='?', help='What the project needs it for.')
|
|
140
|
+
add.set_defaults(make_task=add_task)
|
|
141
|
+
|
|
142
|
+
audit = commands.add_parser('audit', parents=[common, project], help='Review the project.')
|
|
143
|
+
audit.add_argument('--fix', action='store_true', help='Also fix the problems.')
|
|
144
|
+
audit.set_defaults(make_task=audit_task)
|
|
145
|
+
return root
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def main(argv: list[str] | None = None) -> int:
|
|
149
|
+
args = parser().parse_args(argv)
|
|
150
|
+
try:
|
|
151
|
+
task = args.make_task(args)
|
|
152
|
+
except scaffold.ScaffoldError as err:
|
|
153
|
+
print(f'bazis: {err}', file=sys.stderr)
|
|
154
|
+
return 1
|
|
155
|
+
try:
|
|
156
|
+
result = asyncio.run(agent.run(task))
|
|
157
|
+
except ClaudeSDKError as err:
|
|
158
|
+
print(f'bazis: {err}', file=sys.stderr)
|
|
159
|
+
if isinstance(err, CLIConnectionError) or 'auth' in str(err).lower():
|
|
160
|
+
print(
|
|
161
|
+
'The agent runs Claude Code: sign in with `claude login` or set '
|
|
162
|
+
'ANTHROPIC_API_KEY.',
|
|
163
|
+
file=sys.stderr,
|
|
164
|
+
)
|
|
165
|
+
return 1
|
|
166
|
+
except KeyboardInterrupt:
|
|
167
|
+
return 130
|
|
168
|
+
if result is None:
|
|
169
|
+
print('bazis: the agent ended without a result', file=sys.stderr)
|
|
170
|
+
return 1
|
|
171
|
+
cost = f', ${result.total_cost_usd:.2f}' if result.total_cost_usd is not None else ''
|
|
172
|
+
print(f'\n[{result.num_turns} turns{cost}]', file=sys.stderr)
|
|
173
|
+
if result.is_error:
|
|
174
|
+
print(f'bazis: {agent.error_text(result)}', file=sys.stderr)
|
|
175
|
+
return 1
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == '__main__':
|
|
180
|
+
sys.exit(main())
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
The skeleton of a new Bazis project: the files every project has, written without the
|
|
17
|
+
model, and its virtual environment. The agent then adds the apps the project needs.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import importlib.util
|
|
21
|
+
import json
|
|
22
|
+
import keyword
|
|
23
|
+
import re
|
|
24
|
+
import secrets
|
|
25
|
+
import shutil
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
#: the packages of every project; the agent adds the Bazis packages the project needs
|
|
32
|
+
REQUIREMENTS = ['bazis>=2.4.1']
|
|
33
|
+
REQUIREMENTS_DEV = ['bazis-mcp>=2.4.1', 'bazis-test-utils>=2.4.0']
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
MANAGE_PY = '''#!/usr/bin/env python
|
|
37
|
+
import os
|
|
38
|
+
import sys
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{name}.settings')
|
|
43
|
+
from django.core.management import execute_from_command_line
|
|
44
|
+
|
|
45
|
+
execute_from_command_line(sys.argv)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == '__main__':
|
|
49
|
+
main()
|
|
50
|
+
'''
|
|
51
|
+
|
|
52
|
+
SETTINGS_PY = '''# Settings are environment variables with the BS_ prefix (project.env, .env):
|
|
53
|
+
# bazis.core.configure builds the Django settings from them.
|
|
54
|
+
import bazis.core.configure # noqa: F401
|
|
55
|
+
'''
|
|
56
|
+
|
|
57
|
+
MAIN_PY = '''import os
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{name}.settings')
|
|
61
|
+
|
|
62
|
+
from bazis.core.app import app # noqa: E402, F401
|
|
63
|
+
'''
|
|
64
|
+
|
|
65
|
+
ROUTER_PY = '''from bazis.core.routing import BazisRouter
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
router = BazisRouter(prefix='/api/v1')
|
|
69
|
+
# register the routers of the apps: router.register('<app>.router')
|
|
70
|
+
'''
|
|
71
|
+
|
|
72
|
+
URLS_PY = '''from django.conf import settings
|
|
73
|
+
from django.contrib import admin
|
|
74
|
+
from django.urls import path, re_path
|
|
75
|
+
from django.views.static import serve
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
urlpatterns = [
|
|
79
|
+
path('admin/', admin.site.urls),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
if settings.DEBUG:
|
|
83
|
+
urlpatterns += [
|
|
84
|
+
re_path(r'^media/(?P<path>.*)$', serve, dict(document_root=settings.MEDIA_ROOT)),
|
|
85
|
+
re_path(r'^static/(?P<path>.*)$', serve, dict(document_root=settings.STATIC_ROOT)),
|
|
86
|
+
]
|
|
87
|
+
'''
|
|
88
|
+
|
|
89
|
+
WSGI_PY = '''import os
|
|
90
|
+
|
|
91
|
+
from django.core.wsgi import get_wsgi_application
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{name}.settings')
|
|
95
|
+
|
|
96
|
+
application = get_wsgi_application()
|
|
97
|
+
'''
|
|
98
|
+
|
|
99
|
+
TEMPLATES = [
|
|
100
|
+
{
|
|
101
|
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
102
|
+
'DIRS': ['__BASE_DIR__/templates'],
|
|
103
|
+
'APP_DIRS': True,
|
|
104
|
+
'OPTIONS': {
|
|
105
|
+
'context_processors': [
|
|
106
|
+
'django.template.context_processors.debug',
|
|
107
|
+
'django.template.context_processors.request',
|
|
108
|
+
'django.contrib.auth.context_processors.auth',
|
|
109
|
+
'django.contrib.messages.context_processors.messages',
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
PROJECT_ENV = '''# Settings of the project shared by all environments (secrets go to .env)
|
|
116
|
+
BS_INSTALLED_APPS='[]'
|
|
117
|
+
BS_ROOT_URLCONF={name}.urls
|
|
118
|
+
BS_BAZIS_ROUTER_MODULE={name}.router
|
|
119
|
+
BS_WSGI_APPLICATION={name}.wsgi.application
|
|
120
|
+
BS_DEFAULT_AUTO_FIELD=django.db.models.BigAutoField
|
|
121
|
+
BS_LANGUAGE_CODE=en
|
|
122
|
+
BS_TEMPLATES='{templates}'
|
|
123
|
+
'''
|
|
124
|
+
|
|
125
|
+
DOT_ENV = '''# Local settings and secrets of this environment; not committed
|
|
126
|
+
BS_DEBUG=true
|
|
127
|
+
BS_SECRET_KEY={secret_key}
|
|
128
|
+
BS_DATABASES__DEFAULT__HOST=localhost
|
|
129
|
+
BS_DATABASES__DEFAULT__PORT=5432
|
|
130
|
+
BS_DATABASES__DEFAULT__NAME={name}
|
|
131
|
+
BS_DATABASES__DEFAULT__USER=postgres
|
|
132
|
+
BS_DATABASES__DEFAULT__PASSWORD=postgres
|
|
133
|
+
BS_CACHES__DEFAULT__LOCATION=redis://localhost:6379/0
|
|
134
|
+
'''
|
|
135
|
+
|
|
136
|
+
GITIGNORE = '''.env
|
|
137
|
+
.venv/
|
|
138
|
+
__pycache__/
|
|
139
|
+
*.pyc
|
|
140
|
+
.pytest_cache/
|
|
141
|
+
media/
|
|
142
|
+
static/
|
|
143
|
+
'''
|
|
144
|
+
|
|
145
|
+
PYTEST_INI = '''[pytest]
|
|
146
|
+
DJANGO_SETTINGS_MODULE = {name}.settings
|
|
147
|
+
'''
|
|
148
|
+
|
|
149
|
+
CONFTEST_PY = '''import pytest
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@pytest.fixture
|
|
153
|
+
def app():
|
|
154
|
+
from {name}.main import app
|
|
155
|
+
|
|
156
|
+
return app
|
|
157
|
+
'''
|
|
158
|
+
|
|
159
|
+
WINDOWS = sys.platform == 'win32'
|
|
160
|
+
VENV_BIN = '.venv/Scripts' if WINDOWS else '.venv/bin'
|
|
161
|
+
EXE = '.exe' if WINDOWS else ''
|
|
162
|
+
|
|
163
|
+
AGENTS_MD = '''# {name}
|
|
164
|
+
|
|
165
|
+
A [Bazis](https://github.com/ecofuture-tech/bazis) project: JSON:API services on Django,
|
|
166
|
+
FastAPI and Pydantic.
|
|
167
|
+
|
|
168
|
+
- The Python environment is `.venv`; the dependencies are in `requirements.txt` and
|
|
169
|
+
`requirements-dev.txt` (install them with `.venv/bin/python -m pip install -r ...`).
|
|
170
|
+
- Settings are `BS_*` variables: shared ones in `project.env`, local ones and secrets in
|
|
171
|
+
`.env` (not committed). Apps, including Bazis packages, are listed in `BS_INSTALLED_APPS`.
|
|
172
|
+
- The MCP server `bazis` (`.mcp.json`) gives the catalog and the guides of the Bazis
|
|
173
|
+
packages and the facts and checks of this project. Read the guide of a package before
|
|
174
|
+
using it, and run `python manage.py bazis_doctor` and the tests after every change.
|
|
175
|
+
- PostgreSQL with PostGIS and Redis are required to run the project and the tests
|
|
176
|
+
(`.env`); the checks and `makemigrations` work without them.
|
|
177
|
+
'''
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ScaffoldError(Exception):
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def package_name(directory: Path) -> str:
|
|
185
|
+
"""
|
|
186
|
+
The name of the project package: the directory name as a Python identifier that does
|
|
187
|
+
not hide a module (`django`, `json`) or the tests.
|
|
188
|
+
"""
|
|
189
|
+
name = re.sub(r'\W+', '_', directory.name.lower()).strip('_')
|
|
190
|
+
if not name or name[0].isdigit():
|
|
191
|
+
name = f'project_{name}'.rstrip('_')
|
|
192
|
+
if not valid_package_name(name):
|
|
193
|
+
name = f'{name}_project'
|
|
194
|
+
return name
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def valid_package_name(name: str) -> bool:
|
|
198
|
+
return (
|
|
199
|
+
name.isidentifier()
|
|
200
|
+
and not keyword.iskeyword(name)
|
|
201
|
+
and name not in ('tests', 'test')
|
|
202
|
+
and importlib.util.find_spec(name) is None
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def write_files(directory: Path, name: str) -> list[Path]:
|
|
207
|
+
"""
|
|
208
|
+
Writes the skeleton of the project. Fails if the directory has files already.
|
|
209
|
+
"""
|
|
210
|
+
if directory.exists() and any(directory.iterdir()):
|
|
211
|
+
raise ScaffoldError(f'{directory} is not empty')
|
|
212
|
+
if not valid_package_name(name):
|
|
213
|
+
raise ScaffoldError(
|
|
214
|
+
f'{name!r} cannot be the project package: it must be an identifier that is not '
|
|
215
|
+
'a keyword, "tests" or the name of an installed module'
|
|
216
|
+
)
|
|
217
|
+
files = {
|
|
218
|
+
'manage.py': MANAGE_PY.format(name=name),
|
|
219
|
+
f'{name}/__init__.py': '',
|
|
220
|
+
f'{name}/settings.py': SETTINGS_PY,
|
|
221
|
+
f'{name}/main.py': MAIN_PY.format(name=name),
|
|
222
|
+
f'{name}/router.py': ROUTER_PY,
|
|
223
|
+
f'{name}/urls.py': URLS_PY,
|
|
224
|
+
f'{name}/wsgi.py': WSGI_PY.format(name=name),
|
|
225
|
+
'project.env': PROJECT_ENV.format(name=name, templates=json.dumps(TEMPLATES)),
|
|
226
|
+
'.env': DOT_ENV.format(name=name, secret_key=secrets.token_urlsafe(48)),
|
|
227
|
+
'.gitignore': GITIGNORE,
|
|
228
|
+
'requirements.txt': '\n'.join(REQUIREMENTS) + '\n',
|
|
229
|
+
'requirements-dev.txt': '-r requirements.txt\n' + '\n'.join(REQUIREMENTS_DEV) + '\n',
|
|
230
|
+
'pytest.ini': PYTEST_INI.format(name=name),
|
|
231
|
+
'tests/__init__.py': '',
|
|
232
|
+
'tests/conftest.py': CONFTEST_PY.format(name=name),
|
|
233
|
+
'.mcp.json': json.dumps(
|
|
234
|
+
{'mcpServers': {'bazis': {'command': f'{VENV_BIN}/bazis-mcp{EXE}', 'args': []}}},
|
|
235
|
+
indent=2,
|
|
236
|
+
) + '\n',
|
|
237
|
+
'AGENTS.md': AGENTS_MD.format(name=name),
|
|
238
|
+
}
|
|
239
|
+
written = []
|
|
240
|
+
for relative, content in files.items():
|
|
241
|
+
path = directory / relative
|
|
242
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
243
|
+
path.write_text(content, encoding='utf-8')
|
|
244
|
+
written.append(path)
|
|
245
|
+
return written
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def create_venv(directory: Path, run=subprocess.run) -> Path:
|
|
249
|
+
"""
|
|
250
|
+
Creates `.venv` with the dependencies of the project (with uv if it is installed) and
|
|
251
|
+
returns its Python.
|
|
252
|
+
"""
|
|
253
|
+
venv = directory / '.venv'
|
|
254
|
+
python = venv / ('Scripts/python.exe' if sys.platform == 'win32' else 'bin/python')
|
|
255
|
+
uv = shutil.which('uv')
|
|
256
|
+
if uv:
|
|
257
|
+
commands = [
|
|
258
|
+
# --seed installs pip, which the agent uses to add packages
|
|
259
|
+
[uv, 'venv', '--quiet', '--seed', '--python', sys.executable, str(venv)],
|
|
260
|
+
[uv, 'pip', 'install', '--quiet', '--python', str(python),
|
|
261
|
+
'-r', str(directory / 'requirements-dev.txt')],
|
|
262
|
+
]
|
|
263
|
+
else:
|
|
264
|
+
commands = [
|
|
265
|
+
[sys.executable, '-m', 'venv', str(venv)],
|
|
266
|
+
[str(python), '-m', 'pip', 'install', '--quiet',
|
|
267
|
+
'-r', str(directory / 'requirements-dev.txt')],
|
|
268
|
+
]
|
|
269
|
+
for command in commands:
|
|
270
|
+
done = run(command, cwd=directory, capture_output=True, text=True)
|
|
271
|
+
if done.returncode:
|
|
272
|
+
raise ScaffoldError(f'{" ".join(command)} failed:\n{done.stderr.strip()[-2000:]}')
|
|
273
|
+
return python
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bazis-cli
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: The bazis command: creates, extends and audits Bazis projects with Claude (Claude Agent SDK).
|
|
5
|
+
Author-email: Ilya Kharyn <ilya.tt07@gmail.com>
|
|
6
|
+
Maintainer-email: Ilya Kharyn <ilya.tt07@gmail.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Project-URL: Home, https://github.com/ecofuture-tech/bazis-cli
|
|
9
|
+
Keywords: bazis,django,fastapi,pydantic,framework,jsonapi,cli,ai,agents,claude
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Framework :: Django
|
|
18
|
+
Classifier: Framework :: FastAPI
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: bazis-mcp>=2.4.1
|
|
22
|
+
Requires-Dist: claude-agent-sdk>=0.2.163
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# bazis-cli
|
|
29
|
+
|
|
30
|
+
The `bazis` command creates, extends and audits [Bazis](https://github.com/ecofuture-tech/bazis)
|
|
31
|
+
projects with Claude. It runs Claude Code (the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk))
|
|
32
|
+
in the project with the MCP server [bazis-mcp](https://github.com/ecofuture-tech/bazis-mcp),
|
|
33
|
+
so the agent chooses the Bazis packages from their catalog, follows their guides and checks
|
|
34
|
+
the result with the system checks of the project instead of relying on its memory.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install bazis-cli # or: uv tool install bazis-cli
|
|
40
|
+
claude login # or set ANTHROPIC_API_KEY
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The agent uses the account of Claude Code and spends its credits; limit a run with
|
|
44
|
+
`--budget`.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# a new project from a description
|
|
50
|
+
bazis new shop "An online shop: products with categories, orders of users, \
|
|
51
|
+
users see only their own orders"
|
|
52
|
+
|
|
53
|
+
# a Bazis package for an existing project (run in the directory of manage.py)
|
|
54
|
+
bazis add bazis-statusy "orders go through new -> paid -> shipped"
|
|
55
|
+
|
|
56
|
+
# a review against the guides and checks of the packages; --fix also fixes
|
|
57
|
+
bazis audit
|
|
58
|
+
bazis audit --fix
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `bazis new DIR DESCRIPTION` writes the skeleton of a project: the project package
|
|
62
|
+
(settings, root router, ASGI app), `project.env`, `.env` with a generated secret key,
|
|
63
|
+
`requirements.txt`, `requirements-dev.txt`, `tests/`, `.mcp.json` and `AGENTS.md`. It
|
|
64
|
+
creates `.venv` with Bazis and bazis-mcp (with uv if it is installed; `--no-venv` skips
|
|
65
|
+
it). Then the agent chooses the Bazis packages, creates the apps, models, routes,
|
|
66
|
+
migrations and tests, and runs the checks.
|
|
67
|
+
- `bazis add PACKAGE [GOAL]` installs a Bazis package with the Bazis packages it requires
|
|
68
|
+
and sets them up as their guides say.
|
|
69
|
+
- `bazis audit [--fix]` reports the problems of the project by severity. Without `--fix`
|
|
70
|
+
the agent can only read.
|
|
71
|
+
|
|
72
|
+
Options of every command: `--project-dir` (`add`, `audit`; default: the current
|
|
73
|
+
directory), `--model` (default `claude-opus-5-5`), `--effort` (`low` ... `max`, default
|
|
74
|
+
`high`), `--budget USD`, `--yes`.
|
|
75
|
+
|
|
76
|
+
## Permissions
|
|
77
|
+
|
|
78
|
+
- The agent edits the files of the project directory without asking.
|
|
79
|
+
- Everything else is shown in full and asked in the terminal: every shell command
|
|
80
|
+
(installing packages, migrations, tests), edits and reads outside the project, web access.
|
|
81
|
+
`y` allows it, `a` allows the rest of the run. `--yes` allows all of it; without a
|
|
82
|
+
terminal and without `--yes` it is denied.
|
|
83
|
+
- The agent cannot read `.env` and cannot commit, push or reset with git: review the changes
|
|
84
|
+
and commit them yourself.
|
|
85
|
+
- Only the settings of bazis-cli apply: the Claude Code settings, hooks and MCP servers of
|
|
86
|
+
the project and of the user are not loaded.
|
|
87
|
+
- `bazis audit` without `--fix` can only read the project and call bazis-mcp. bazis-mcp
|
|
88
|
+
runs the system checks of the project, that is the project code with its `.venv`: run
|
|
89
|
+
the commands only in projects you trust.
|
|
90
|
+
|
|
91
|
+
The tests of a project need PostgreSQL with PostGIS and Redis (settings in `.env`); without
|
|
92
|
+
them the agent creates the migrations and runs the system checks, and says that the tests
|
|
93
|
+
did not run.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
bazis/contrib/cli/AGENTS.md,sha256=mgBQ_Sr0GqLMoY3WVqGLYgc7XGl1K1B5_k9Qk5QpsKE,1802
|
|
2
|
+
bazis/contrib/cli/__init__.py,sha256=OYHBOe-gKbJL9WElLNLkYKhNZB6Sa2G19oNVnjBo-T4,777
|
|
3
|
+
bazis/contrib/cli/_version.py,sha256=9x8v3jtROsCpS8s3YKlkPGuPEcXwPyPiZdq4RuGDXMo,528
|
|
4
|
+
bazis/contrib/cli/agent.py,sha256=hUbqyhVOZkF65TJGaVtj9jlRsu5n_S7z1HAIxiydt8M,10851
|
|
5
|
+
bazis/contrib/cli/bazis_manifest.toml,sha256=4iZKcXoy_AQpyafGgHeEERPyeC4gsY9lWxFMgjPTZiw,1184
|
|
6
|
+
bazis/contrib/cli/main.py,sha256=TmT1Lrt2vhdIa2DvAgkDYmK5LAIW5Jiqoz9N2t9SNUs,7391
|
|
7
|
+
bazis/contrib/cli/scaffold.py,sha256=VBGQkH86W5os-67hUB5l_Gn0Xw6DbNCwlT9HTayzuiQ,8708
|
|
8
|
+
bazis_cli-2.4.0.dist-info/METADATA,sha256=gaikRaAC6qdYo3I7q8d1R6-K7BC39igFNJgb1ZajlEc,4259
|
|
9
|
+
bazis_cli-2.4.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
10
|
+
bazis_cli-2.4.0.dist-info/entry_points.txt,sha256=M5dr9d744jnX9cWly8Vsrzvoxb46kDpiaokM8VmACdU,54
|
|
11
|
+
bazis_cli-2.4.0.dist-info/top_level.txt,sha256=WgdrPZTZBMG8i_EqxA3vU5qI4ETQ_RsqKqSqsfIApHY,6
|
|
12
|
+
bazis_cli-2.4.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bazis
|