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.
Files changed (35) hide show
  1. create_leafmesh-2.1.0/PKG-INFO +6 -0
  2. create_leafmesh-2.1.0/README.md +83 -0
  3. create_leafmesh-2.1.0/create_leafmesh/__init__.py +3 -0
  4. create_leafmesh-2.1.0/create_leafmesh/cli.py +252 -0
  5. create_leafmesh-2.1.0/create_leafmesh/create.py +106 -0
  6. create_leafmesh-2.1.0/create_leafmesh/templates/Dockerfile +21 -0
  7. create_leafmesh-2.1.0/create_leafmesh/templates/README.md +309 -0
  8. create_leafmesh-2.1.0/create_leafmesh/templates/agency/__init__.py +0 -0
  9. create_leafmesh-2.1.0/create_leafmesh/templates/agency/advisor_agent.py +151 -0
  10. create_leafmesh-2.1.0/create_leafmesh/templates/agency/external_agents.py +278 -0
  11. create_leafmesh-2.1.0/create_leafmesh/templates/agency/fallback_researcher_agent.py +80 -0
  12. create_leafmesh-2.1.0/create_leafmesh/templates/agency/greeter_agent.py +79 -0
  13. create_leafmesh-2.1.0/create_leafmesh/templates/agency/processor_agent.py +90 -0
  14. create_leafmesh-2.1.0/create_leafmesh/templates/agency/researcher_agent.py +99 -0
  15. create_leafmesh-2.1.0/create_leafmesh/templates/agency/scheduler_agent.py +67 -0
  16. create_leafmesh-2.1.0/create_leafmesh/templates/agency/tools.py +123 -0
  17. create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/SKILL.md +2049 -0
  18. create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/agent-config-fields.md +1309 -0
  19. create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/examples.md +537 -0
  20. create_leafmesh-2.1.0/create_leafmesh/templates/claude_skills/leafmesh/reference.md +492 -0
  21. create_leafmesh-2.1.0/create_leafmesh/templates/configs/config.yaml +1028 -0
  22. create_leafmesh-2.1.0/create_leafmesh/templates/docker-compose.yml +28 -0
  23. create_leafmesh-2.1.0/create_leafmesh/templates/dockerignore +17 -0
  24. create_leafmesh-2.1.0/create_leafmesh/templates/env +109 -0
  25. create_leafmesh-2.1.0/create_leafmesh/templates/gitignore +33 -0
  26. create_leafmesh-2.1.0/create_leafmesh/templates/hitl_stub_receiver.py +149 -0
  27. create_leafmesh-2.1.0/create_leafmesh/templates/main.py +105 -0
  28. create_leafmesh-2.1.0/create_leafmesh/templates/requirements.txt +10 -0
  29. create_leafmesh-2.1.0/create_leafmesh.egg-info/PKG-INFO +6 -0
  30. create_leafmesh-2.1.0/create_leafmesh.egg-info/SOURCES.txt +33 -0
  31. create_leafmesh-2.1.0/create_leafmesh.egg-info/dependency_links.txt +1 -0
  32. create_leafmesh-2.1.0/create_leafmesh.egg-info/entry_points.txt +2 -0
  33. create_leafmesh-2.1.0/create_leafmesh.egg-info/top_level.txt +1 -0
  34. create_leafmesh-2.1.0/pyproject.toml +20 -0
  35. create_leafmesh-2.1.0/setup.cfg +4 -0
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: create-leafmesh
3
+ Version: 2.1.0
4
+ Summary: Project scaffolding tool for LeafMesh
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
@@ -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,3 @@
1
+ """create-leafmesh — Project scaffolding tool for LeafMesh."""
2
+
3
+ __version__ = "2.1.0"
@@ -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"]