create-leafmesh 2.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.
- create_leafmesh-2.1.0/PKG-INFO +6 -0
- create_leafmesh-2.1.0/README.md +83 -0
- create_leafmesh-2.1.0/create_leafmesh/__init__.py +3 -0
- create_leafmesh-2.1.0/create_leafmesh/cli.py +252 -0
- create_leafmesh-2.1.0/create_leafmesh/create.py +106 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/Dockerfile +21 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/README.md +309 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/__init__.py +0 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/advisor_agent.py +151 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/external_agents.py +278 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/fallback_researcher_agent.py +80 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/greeter_agent.py +79 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/processor_agent.py +90 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/researcher_agent.py +99 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/scheduler_agent.py +67 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/agency/tools.py +123 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/SKILL.md +2049 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/agent-config-fields.md +1309 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/examples.md +537 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/reference.md +492 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/configs/config.yaml +1028 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/docker-compose.yml +28 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/dockerignore +17 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/env +109 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/gitignore +33 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/hitl_stub_receiver.py +149 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/main.py +105 -0
- create_leafmesh-2.1.0/create_leafmesh/templates/requirements.txt +10 -0
- create_leafmesh-2.1.0/create_leafmesh.egg-info/PKG-INFO +6 -0
- create_leafmesh-2.1.0/create_leafmesh.egg-info/SOURCES.txt +33 -0
- create_leafmesh-2.1.0/create_leafmesh.egg-info/dependency_links.txt +1 -0
- create_leafmesh-2.1.0/create_leafmesh.egg-info/entry_points.txt +2 -0
- create_leafmesh-2.1.0/create_leafmesh.egg-info/top_level.txt +1 -0
- create_leafmesh-2.1.0/pyproject.toml +20 -0
- create_leafmesh-2.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# create-leafmesh
|
|
2
|
+
|
|
3
|
+
Project scaffolding tool for [LeafMesh](https://pypi.org/project/leafmesh/) — the YAML-native multi-agent orchestration platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install create-leafmesh
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Create a Project
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
create-leafmesh create my-project
|
|
15
|
+
cd my-project
|
|
16
|
+
python -m venv .venv && source .venv/bin/activate
|
|
17
|
+
pip install -r requirements.txt
|
|
18
|
+
python main.py
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## CLI Options
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Create in a specific directory
|
|
25
|
+
create-leafmesh create my-project -o /path/to/dir
|
|
26
|
+
|
|
27
|
+
# Skip git initialization
|
|
28
|
+
create-leafmesh create my-project --no-git
|
|
29
|
+
|
|
30
|
+
# Interactive mode (prompts for project name)
|
|
31
|
+
create-leafmesh create
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Generated Project Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
my-project/
|
|
38
|
+
configs/
|
|
39
|
+
config.yaml # Agent definitions, mesh wiring, HITL config
|
|
40
|
+
agency/
|
|
41
|
+
greeter_agent.py # LLM agent with @pre_compose
|
|
42
|
+
processor_agent.py # Programmatic agent with @conditional_chain
|
|
43
|
+
researcher_agent.py # LLM agent with @chain_with_results + smart memory
|
|
44
|
+
fallback_researcher_agent.py # Fast fallback (race pattern)
|
|
45
|
+
advisor_agent.py # LLM fan-in with @chain + @compose
|
|
46
|
+
scheduler_agent.py # Cron-scheduled agent
|
|
47
|
+
tools.py # Custom tools (@global_tool, @tool)
|
|
48
|
+
external_agents.py # Integration reference (CrewAI, LangGraph, etc.)
|
|
49
|
+
main.py # Entry point
|
|
50
|
+
hitl_stub_receiver.py # Webhook stub for testing HITL locally
|
|
51
|
+
requirements.txt
|
|
52
|
+
.env # API keys
|
|
53
|
+
Dockerfile
|
|
54
|
+
docker-compose.yml # Redis + app
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## What's Included
|
|
58
|
+
|
|
59
|
+
- **8 agents** showcasing all 4 agent types (human, llm, programmatic, external)
|
|
60
|
+
- **Human-in-the-Loop (HITL)** with dual webhook mode and `from_agent` routing
|
|
61
|
+
- **Fan-in/fan-out** with OR expressions (`processor AND (researcher OR fallback)`)
|
|
62
|
+
- **Smart memory** with hybrid recency/relevance scoring
|
|
63
|
+
- **Scheduled agents** with cron expressions
|
|
64
|
+
- **Custom tools** with access control and categories
|
|
65
|
+
- **All 5 decorators**: `@pre_compose`, `@chain`, `@chain_with_results`, `@conditional_chain`, `@compose`
|
|
66
|
+
- **HITL test stub** (`hitl_stub_receiver.py`) for local webhook testing
|
|
67
|
+
- **Docker-ready** with Redis included
|
|
68
|
+
- **Full README** with HITL walkthrough (2 scenarios, step-by-step)
|
|
69
|
+
|
|
70
|
+
## Requirements
|
|
71
|
+
|
|
72
|
+
- Python 3.10+
|
|
73
|
+
- Redis server running (default: localhost:6379)
|
|
74
|
+
- At least one LLM API key (e.g. `OPENAI_API_KEY` in `.env`)
|
|
75
|
+
|
|
76
|
+
## Links
|
|
77
|
+
|
|
78
|
+
- [LeafMesh SDK](https://pypi.org/project/leafmesh/)
|
|
79
|
+
- [LeafCraft Studios](https://leafcraft.ai)
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""CLI entry point for create-leafmesh."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from create_leafmesh import __version__
|
|
9
|
+
from create_leafmesh.create import create_project
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# ANSI helpers (graceful fallback on terminals that don't support color)
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
_CYAN = "\033[36m"
|
|
15
|
+
_GREEN = "\033[32m"
|
|
16
|
+
_WHITE = "\033[97m"
|
|
17
|
+
_DIM = "\033[2m"
|
|
18
|
+
_BOLD = "\033[1m"
|
|
19
|
+
_RESET = "\033[0m"
|
|
20
|
+
|
|
21
|
+
def _c(code: str, text: str) -> str:
|
|
22
|
+
"""Wrap text in an ANSI code, reset after."""
|
|
23
|
+
return f"{code}{text}{_RESET}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# ASCII art — block letters for LEAFMESH
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
BANNER = r"""
|
|
30
|
+
██╗ ███████╗ █████╗ ███████╗███╗ ███╗███████╗███████╗██╗ ██╗
|
|
31
|
+
██║ ██╔════╝██╔══██╗██╔════╝████╗ ████║██╔════╝██╔════╝██║ ██║
|
|
32
|
+
██║ █████╗ ███████║█████╗ ██╔████╔██║█████╗ ███████╗███████║
|
|
33
|
+
██║ ██╔══╝ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║
|
|
34
|
+
███████╗███████╗██║ ██║██║ ██║ ╚═╝ ██║███████╗███████║██║ ██║
|
|
35
|
+
╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def print_banner(version: str) -> None:
|
|
39
|
+
print()
|
|
40
|
+
print(_c(_CYAN, BANNER), end="")
|
|
41
|
+
pad = " " * 2
|
|
42
|
+
print(f"{pad} create-leafmesh {_c(_DIM, 'v' + version)} "
|
|
43
|
+
f"{_c(_DIM, 'Agent mesh scaffolding tool')}")
|
|
44
|
+
print(f"{pad} " + "-" * 55)
|
|
45
|
+
print()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Step progress printer
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
def step(n: int, total: int, label: str, done: bool = False) -> None:
|
|
52
|
+
status = _c(_GREEN, "[ OK ]") if done else _c(_DIM, "[ ]")
|
|
53
|
+
print(f" {status} [{n}/{total}] {label}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def step_done(n: int, total: int, label: str) -> None:
|
|
57
|
+
print(f"\033[1A\r {_c(_GREEN, '[ OK ]')} [{n}/{total}] {label}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# File tree renderer
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
FILE_TREE = """\
|
|
64
|
+
{name}/
|
|
65
|
+
|-- agency/
|
|
66
|
+
| |-- greeter_agent.py
|
|
67
|
+
| |-- processor_agent.py
|
|
68
|
+
| |-- researcher_agent.py
|
|
69
|
+
| |-- advisor_agent.py
|
|
70
|
+
| |-- fallback_researcher_agent.py
|
|
71
|
+
| `-- scheduler_agent.py
|
|
72
|
+
|-- configs/
|
|
73
|
+
| `-- config.yaml
|
|
74
|
+
|-- main.py
|
|
75
|
+
|-- hitl_stub_receiver.py
|
|
76
|
+
|-- requirements.txt
|
|
77
|
+
|-- .env
|
|
78
|
+
|-- Dockerfile
|
|
79
|
+
`-- docker-compose.yml"""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Next-steps box
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
def print_success_box(project_name: str, missing_keys: list[str]) -> None:
|
|
86
|
+
w = 58
|
|
87
|
+
|
|
88
|
+
def row(text: str = "") -> str:
|
|
89
|
+
return f" | {text:<{w - 4}}|"
|
|
90
|
+
|
|
91
|
+
def div() -> str:
|
|
92
|
+
return " +" + "-" * (w - 2) + "+"
|
|
93
|
+
|
|
94
|
+
def header(text: str) -> str:
|
|
95
|
+
return " | " + _c(_GREEN, _c(_BOLD, f"{text:<{w - 4}}")) + "|"
|
|
96
|
+
|
|
97
|
+
print()
|
|
98
|
+
print(div())
|
|
99
|
+
print(header(f"Project ready: {project_name}"))
|
|
100
|
+
print(div())
|
|
101
|
+
print(row())
|
|
102
|
+
print(row(f" cd {project_name}"))
|
|
103
|
+
print(row(f" pip install -r requirements.txt"))
|
|
104
|
+
if missing_keys:
|
|
105
|
+
print(row())
|
|
106
|
+
print(row(f" # Add to .env before starting:"))
|
|
107
|
+
for k in missing_keys:
|
|
108
|
+
print(row(f" # {k}=<your-key>"))
|
|
109
|
+
print(row())
|
|
110
|
+
print(row(f" Terminal 1: python hitl_stub_receiver.py"))
|
|
111
|
+
print(row(f" Terminal 2: python main.py"))
|
|
112
|
+
print(row())
|
|
113
|
+
print(row(f" Open: http://127.0.0.1:18820/docs"))
|
|
114
|
+
print(row())
|
|
115
|
+
print(div())
|
|
116
|
+
print(row(f" Docs: https://leafcraft.ai/docs"))
|
|
117
|
+
print(div())
|
|
118
|
+
print()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Validation / prompts
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
def validate_project_name(name: str) -> str:
|
|
125
|
+
if not re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", name):
|
|
126
|
+
raise argparse.ArgumentTypeError(
|
|
127
|
+
f"Invalid project name '{name}'. "
|
|
128
|
+
"Must start with a letter and contain only alphanumeric characters, "
|
|
129
|
+
"hyphens, or underscores."
|
|
130
|
+
)
|
|
131
|
+
return name
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def prompt_project_name() -> str:
|
|
135
|
+
while True:
|
|
136
|
+
name = input(" Project name: ").strip()
|
|
137
|
+
if not name:
|
|
138
|
+
print(" Name cannot be empty.")
|
|
139
|
+
continue
|
|
140
|
+
if not re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", name):
|
|
141
|
+
print(" Must start with a letter, letters/numbers/hyphens/underscores only.")
|
|
142
|
+
continue
|
|
143
|
+
return name
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def prompt_env_vars() -> dict:
|
|
147
|
+
w = 55
|
|
148
|
+
print()
|
|
149
|
+
print(" " + "-" * w)
|
|
150
|
+
print(f" {'Environment setup':^{w}}")
|
|
151
|
+
print(f" {'(press Enter to skip any field)':^{w}}")
|
|
152
|
+
print(" " + "-" * w)
|
|
153
|
+
print()
|
|
154
|
+
|
|
155
|
+
env_vars: dict = {}
|
|
156
|
+
|
|
157
|
+
fields = [
|
|
158
|
+
("LEAFMESH_LICENSE_KEY", "License key (https://leafcraft.ai): "),
|
|
159
|
+
("LEAFMESH_ENV_TOKEN", "Env token (https://leafcraft.ai): "),
|
|
160
|
+
("OPENAI_API_KEY", "OpenAI key (https://platform.openai.com): "),
|
|
161
|
+
("ANTHROPIC_API_KEY", "Anthropic key (optional): "),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
for key, label in fields:
|
|
165
|
+
val = input(f" {label}").strip()
|
|
166
|
+
if val:
|
|
167
|
+
env_vars[key] = val
|
|
168
|
+
|
|
169
|
+
print()
|
|
170
|
+
return env_vars
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Main create flow
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
def _run_create(project_name: str | None, output_dir: str, no_git: bool) -> int:
|
|
177
|
+
print_banner(__version__)
|
|
178
|
+
|
|
179
|
+
if project_name is None:
|
|
180
|
+
project_name = prompt_project_name()
|
|
181
|
+
print()
|
|
182
|
+
else:
|
|
183
|
+
try:
|
|
184
|
+
project_name = validate_project_name(project_name)
|
|
185
|
+
except argparse.ArgumentTypeError as e:
|
|
186
|
+
print(f" Error: {e}")
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
env_vars = prompt_env_vars()
|
|
190
|
+
|
|
191
|
+
# Hand off to create_project, passing our step-printer so it can report progress
|
|
192
|
+
print(f" Creating {_c(_BOLD, project_name + '/')}")
|
|
193
|
+
print()
|
|
194
|
+
|
|
195
|
+
result = create_project(
|
|
196
|
+
project_name,
|
|
197
|
+
output_dir,
|
|
198
|
+
init_git=not no_git,
|
|
199
|
+
env_vars=env_vars,
|
|
200
|
+
step_fn=step,
|
|
201
|
+
step_done_fn=step_done,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if result != 0:
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
# File tree — print line by line with dim so pipe chars aren't swallowed
|
|
208
|
+
print()
|
|
209
|
+
for line in FILE_TREE.format(name=project_name).splitlines():
|
|
210
|
+
print(_c(_DIM, line))
|
|
211
|
+
|
|
212
|
+
# Success box
|
|
213
|
+
missing = [k for k in ("LEAFMESH_LICENSE_KEY", "OPENAI_API_KEY") if k not in env_vars]
|
|
214
|
+
print_success_box(project_name, missing)
|
|
215
|
+
|
|
216
|
+
return 0
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
# CLI entry point
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
def main(argv: list[str] | None = None) -> int:
|
|
223
|
+
parser = argparse.ArgumentParser(
|
|
224
|
+
prog="create-leafmesh",
|
|
225
|
+
description="Project scaffolding tool for LeafMesh",
|
|
226
|
+
epilog="""
|
|
227
|
+
examples:
|
|
228
|
+
create-leafmesh my-project Create a new project
|
|
229
|
+
create-leafmesh my-project -o /tmp Create in a specific directory
|
|
230
|
+
create-leafmesh Interactive mode
|
|
231
|
+
""",
|
|
232
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
233
|
+
)
|
|
234
|
+
parser.add_argument("--version", action="version", version=f"create-leafmesh {__version__}")
|
|
235
|
+
parser.add_argument("project_name", nargs="?", default=None, help="Project name")
|
|
236
|
+
parser.add_argument("-o", "--output-dir", default=".", help="Output directory (default: current)")
|
|
237
|
+
parser.add_argument("--no-git", action="store_true", help="Skip git init")
|
|
238
|
+
|
|
239
|
+
args = parser.parse_args(argv)
|
|
240
|
+
try:
|
|
241
|
+
return _run_create(args.project_name, args.output_dir, args.no_git)
|
|
242
|
+
except KeyboardInterrupt:
|
|
243
|
+
print("\n\n Cancelled.\n")
|
|
244
|
+
return 0
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__ == "__main__":
|
|
248
|
+
try:
|
|
249
|
+
sys.exit(main())
|
|
250
|
+
except KeyboardInterrupt:
|
|
251
|
+
print("\n\n Cancelled.\n")
|
|
252
|
+
sys.exit(0)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Project scaffolding logic."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Callable
|
|
8
|
+
|
|
9
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
|
10
|
+
|
|
11
|
+
RENAME_MAP = {
|
|
12
|
+
"env": ".env",
|
|
13
|
+
"gitignore": ".gitignore",
|
|
14
|
+
"dockerignore": ".dockerignore",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
SUBSTITUTE_EXTENSIONS = {".py", ".yaml", ".yml", ".txt", ".md", ".env", ""}
|
|
18
|
+
|
|
19
|
+
ENV_PLACEHOLDERS = {
|
|
20
|
+
"LEAFMESH_LICENSE_KEY": "your-license-key-here",
|
|
21
|
+
"LEAFMESH_ENV_TOKEN": "your-env-token-here",
|
|
22
|
+
"OPENAI_API_KEY": "your-key-here",
|
|
23
|
+
"ANTHROPIC_API_KEY": "your-anthropic-key",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Step labels — order matches execution
|
|
27
|
+
_STEPS = [
|
|
28
|
+
"Copying template files",
|
|
29
|
+
"Writing .env",
|
|
30
|
+
"Setting up git repository",
|
|
31
|
+
"Installing Claude Code skill",
|
|
32
|
+
]
|
|
33
|
+
_TOTAL = len(_STEPS)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _noop(*args, **kwargs) -> None:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def create_project(
|
|
41
|
+
project_name: str,
|
|
42
|
+
output_dir: str,
|
|
43
|
+
*,
|
|
44
|
+
init_git: bool = True,
|
|
45
|
+
env_vars: dict | None = None,
|
|
46
|
+
step_fn: Callable = _noop,
|
|
47
|
+
step_done_fn: Callable = _noop,
|
|
48
|
+
) -> int:
|
|
49
|
+
target = Path(output_dir).resolve() / project_name
|
|
50
|
+
|
|
51
|
+
if target.exists():
|
|
52
|
+
print(f"\n Error: '{target}' already exists.\n")
|
|
53
|
+
return 1
|
|
54
|
+
|
|
55
|
+
# Step 1 — copy template
|
|
56
|
+
step_fn(1, _TOTAL, _STEPS[0])
|
|
57
|
+
shutil.copytree(TEMPLATES_DIR, target)
|
|
58
|
+
for path in target.rglob("*"):
|
|
59
|
+
if not path.is_file():
|
|
60
|
+
continue
|
|
61
|
+
if path.suffix not in SUBSTITUTE_EXTENSIONS:
|
|
62
|
+
continue
|
|
63
|
+
text = path.read_text(encoding="utf-8")
|
|
64
|
+
if "{{project_name}}" in text:
|
|
65
|
+
path.write_text(text.replace("{{project_name}}", project_name), encoding="utf-8")
|
|
66
|
+
step_done_fn(1, _TOTAL, _STEPS[0])
|
|
67
|
+
|
|
68
|
+
# Step 2 — .env
|
|
69
|
+
step_fn(2, _TOTAL, _STEPS[1])
|
|
70
|
+
if env_vars:
|
|
71
|
+
env_file = target / "env"
|
|
72
|
+
if env_file.exists():
|
|
73
|
+
text = env_file.read_text(encoding="utf-8")
|
|
74
|
+
for key, value in env_vars.items():
|
|
75
|
+
placeholder = ENV_PLACEHOLDERS.get(key)
|
|
76
|
+
if placeholder and placeholder in text:
|
|
77
|
+
text = text.replace(placeholder, value)
|
|
78
|
+
text = text.replace(f"# {key}={value}", f"{key}={value}")
|
|
79
|
+
elif f"# {key}=" in text:
|
|
80
|
+
text = re.sub(rf"# {re.escape(key)}=.*", f"{key}={value}", text)
|
|
81
|
+
env_file.write_text(text, encoding="utf-8")
|
|
82
|
+
for old_name, new_name in RENAME_MAP.items():
|
|
83
|
+
src = target / old_name
|
|
84
|
+
if src.exists():
|
|
85
|
+
src.rename(target / new_name)
|
|
86
|
+
step_done_fn(2, _TOTAL, _STEPS[1])
|
|
87
|
+
|
|
88
|
+
# Step 3 — git
|
|
89
|
+
step_fn(3, _TOTAL, _STEPS[2])
|
|
90
|
+
if init_git:
|
|
91
|
+
try:
|
|
92
|
+
subprocess.run(["git", "init", str(target)], capture_output=True, check=True)
|
|
93
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
94
|
+
pass
|
|
95
|
+
step_done_fn(3, _TOTAL, _STEPS[2])
|
|
96
|
+
|
|
97
|
+
# Step 4 — Claude skill
|
|
98
|
+
step_fn(4, _TOTAL, _STEPS[3])
|
|
99
|
+
claude_skills_src = target / "claude_skills"
|
|
100
|
+
if claude_skills_src.exists():
|
|
101
|
+
claude_dir = target / ".claude" / "skills"
|
|
102
|
+
claude_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
claude_skills_src.rename(claude_dir)
|
|
104
|
+
step_done_fn(4, _TOTAL, _STEPS[3])
|
|
105
|
+
|
|
106
|
+
return 0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
FROM python:3.13-slim AS base
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# System dependencies for compiled packages (grpcio, etc.)
|
|
6
|
+
RUN apt-get update && \
|
|
7
|
+
apt-get install -y --no-install-recommends gcc libffi-dev && \
|
|
8
|
+
rm -rf /var/lib/apt/lists/*
|
|
9
|
+
|
|
10
|
+
# Install Python dependencies
|
|
11
|
+
COPY requirements.txt .
|
|
12
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
13
|
+
|
|
14
|
+
# Copy project files
|
|
15
|
+
COPY configs/ configs/
|
|
16
|
+
COPY agency/ agency/
|
|
17
|
+
COPY main.py .
|
|
18
|
+
|
|
19
|
+
EXPOSE 18820
|
|
20
|
+
|
|
21
|
+
CMD ["python", "main.py"]
|