bindai-cli 0.1.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.
Files changed (38) hide show
  1. bindai_cli/__init__.py +1 -0
  2. bindai_cli/__main__.py +9 -0
  3. bindai_cli/app.py +49 -0
  4. bindai_cli/commands/__init__.py +1 -0
  5. bindai_cli/commands/add.py +23 -0
  6. bindai_cli/commands/doctor.py +122 -0
  7. bindai_cli/commands/inspect.py +42 -0
  8. bindai_cli/commands/new.py +84 -0
  9. bindai_cli/commands/run.py +67 -0
  10. bindai_cli/commands/template.py +222 -0
  11. bindai_cli/commands/version.py +13 -0
  12. bindai_cli/commands/workflow.py +112 -0
  13. bindai_cli/generators/__init__.py +9 -0
  14. bindai_cli/generators/agent.py +8 -0
  15. bindai_cli/generators/directories.py +23 -0
  16. bindai_cli/generators/knowledge.py +0 -0
  17. bindai_cli/generators/memory.py +0 -0
  18. bindai_cli/generators/pyproject.py +23 -0
  19. bindai_cli/generators/tool.py +8 -0
  20. bindai_cli/generators/workflow.py +31 -0
  21. bindai_cli/inspectors/project.py +34 -0
  22. bindai_cli/scaffolds/basic/agents/assistant.py +6 -0
  23. bindai_cli/scaffolds/basic/main.py +20 -0
  24. bindai_cli/scaffolds/basic/tools/echo.py +8 -0
  25. bindai_cli/templates/installer.py +58 -0
  26. bindai_cli/templates/models.py +24 -0
  27. bindai_cli/templates/registry.py +51 -0
  28. bindai_cli/templates/validator.py +56 -0
  29. bindai_cli/utils/config.py +14 -0
  30. bindai_cli/utils/providers.py +8 -0
  31. bindai_cli/utils/scaffold.py +62 -0
  32. bindai_cli/workflows/graph.py +34 -0
  33. bindai_cli/workflows/validator.py +32 -0
  34. bindai_cli-0.1.0.dist-info/METADATA +7 -0
  35. bindai_cli-0.1.0.dist-info/RECORD +38 -0
  36. bindai_cli-0.1.0.dist-info/WHEEL +5 -0
  37. bindai_cli-0.1.0.dist-info/entry_points.txt +2 -0
  38. bindai_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from bindai_config import ProjectRuntime
9
+ from rich import print
10
+
11
+ app = typer.Typer()
12
+
13
+
14
+ def resolve_workflow(
15
+ workflow: str | None,
16
+ ) -> Path:
17
+
18
+ runtime = ProjectRuntime(
19
+ Path.cwd(),
20
+ )
21
+
22
+ if workflow is None:
23
+ workflow = runtime.config.entrypoint
24
+
25
+ return Path(workflow)
26
+
27
+
28
+ @app.command("run")
29
+ def run(
30
+ workflow: str | None = typer.Argument(
31
+ None,
32
+ help="Workflow entrypoint.",
33
+ ),
34
+ ):
35
+
36
+ path = resolve_workflow(
37
+ workflow,
38
+ )
39
+
40
+ if not path.exists():
41
+ print(f"[red]{path} not found.[/red]")
42
+
43
+ raise typer.Exit(1)
44
+
45
+ subprocess.run(
46
+ [
47
+ sys.executable,
48
+ str(path),
49
+ ],
50
+ check=True,
51
+ )
52
+
53
+
54
+ @app.command("validate")
55
+ def validate(
56
+ workflow: str | None = typer.Argument(
57
+ None,
58
+ ),
59
+ ):
60
+
61
+ from bindai_cli.workflows.validator import (
62
+ WorkflowValidator,
63
+ )
64
+
65
+ path = resolve_workflow(
66
+ workflow,
67
+ )
68
+
69
+ errors = WorkflowValidator.validate(
70
+ path,
71
+ )
72
+
73
+ if errors:
74
+ print()
75
+
76
+ for error in errors:
77
+ print(f"[red]✗ {error}[/red]")
78
+
79
+ raise typer.Exit(1)
80
+
81
+ print()
82
+
83
+ print("[green]✓ Workflow is valid.[/green]")
84
+
85
+
86
+ @app.command("graph")
87
+ def graph(
88
+ workflow: str | None = typer.Argument(
89
+ None,
90
+ ),
91
+ ):
92
+
93
+ from bindai_cli.workflows.graph import (
94
+ WorkflowGraph,
95
+ )
96
+
97
+ path = resolve_workflow(
98
+ workflow,
99
+ )
100
+
101
+ if not path.exists():
102
+ print(f"[red]{path} not found.[/red]")
103
+
104
+ raise typer.Exit(1)
105
+
106
+ print()
107
+
108
+ print(
109
+ WorkflowGraph.generate(
110
+ path,
111
+ )
112
+ )
@@ -0,0 +1,9 @@
1
+ from .agent import generate_agent
2
+ from .tool import generate_tool
3
+ from .workflow import generate_workflow
4
+
5
+ __all__ = [
6
+ "generate_agent",
7
+ "generate_tool",
8
+ "generate_workflow",
9
+ ]
@@ -0,0 +1,8 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def generate_agent(
5
+ root: Path,
6
+ name: str,
7
+ ) -> None:
8
+ pass
@@ -0,0 +1,23 @@
1
+ from pathlib import Path
2
+
3
+ DIRECTORIES = [
4
+ "agents",
5
+ "knowledge",
6
+ "memory",
7
+ "templates",
8
+ "tools",
9
+ "workflows",
10
+ "tests",
11
+ ]
12
+
13
+
14
+ def generate_directories(root: Path):
15
+ for directory in DIRECTORIES:
16
+ path = root / directory
17
+
18
+ path.mkdir(
19
+ parents=True,
20
+ exist_ok=True,
21
+ )
22
+
23
+ (path / ".gitkeep").touch()
File without changes
File without changes
@@ -0,0 +1,23 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def generate_pyproject(
5
+ root: Path,
6
+ name: str,
7
+ ):
8
+ content = f"""
9
+ [project]
10
+ name = "{name.lower()}"
11
+ version = "0.1.0"
12
+ description = "{name}"
13
+ requires-python = ">=3.11"
14
+
15
+ dependencies = [
16
+ "bindai",
17
+ ]
18
+ """.strip()
19
+
20
+ (root / "pyproject.toml").write_text(
21
+ content,
22
+ encoding="utf-8",
23
+ )
@@ -0,0 +1,8 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def generate_tool(
5
+ root: Path,
6
+ name: str,
7
+ ) -> None:
8
+ pass
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ WORKFLOW_TEMPLATE = """from bindai_workflow import WorkflowBuilder
4
+
5
+
6
+ workflow = (
7
+ WorkflowBuilder()
8
+ .name("{name}")
9
+ .build()
10
+ )
11
+ """
12
+
13
+
14
+ def generate_workflow(
15
+ root: Path,
16
+ name: str,
17
+ ) -> None:
18
+ workflows_dir = root / "workflows"
19
+ workflows_dir.mkdir(parents=True, exist_ok=True)
20
+
21
+ filename = name.replace(" ", "_").replace("-", "_").lower()
22
+
23
+ workflow_file = workflows_dir / f"{filename}.py"
24
+
25
+ if workflow_file.exists():
26
+ raise FileExistsError(f"Workflow '{name}' already exists.")
27
+
28
+ workflow_file.write_text(
29
+ WORKFLOW_TEMPLATE.format(name=name),
30
+ encoding="utf-8",
31
+ )
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class ProjectInspector:
7
+ def __init__(
8
+ self,
9
+ root: Path,
10
+ ):
11
+ self.root = root
12
+
13
+ def inspect(self) -> dict:
14
+
15
+ return {
16
+ "agents": self._files("agents"),
17
+ "tools": self._files("tools"),
18
+ "workflows": self._files("workflows"),
19
+ "knowledge": self._files("knowledge"),
20
+ "memory": self._files("memory"),
21
+ "templates": self._files("templates"),
22
+ }
23
+
24
+ def _files(
25
+ self,
26
+ folder: str,
27
+ ) -> list[str]:
28
+
29
+ path = self.root / folder
30
+
31
+ if not path.exists():
32
+ return []
33
+
34
+ return sorted(file.stem for file in path.glob("*.py") if file.name != "__init__.py")
@@ -0,0 +1,6 @@
1
+ from bindai import Agent
2
+
3
+ agent = Agent(
4
+ name="assistant",
5
+ provider="openai",
6
+ )
@@ -0,0 +1,20 @@
1
+ from agents.assistant import agent
2
+
3
+
4
+ def main():
5
+
6
+ print("BindAI Project")
7
+
8
+ while True:
9
+ message = input("You: ")
10
+
11
+ if message.lower() == "exit":
12
+ break
13
+
14
+ response = agent.run(message)
15
+
16
+ print(f"Assistant: {response}")
17
+
18
+
19
+ if __name__ == "__main__":
20
+ main()
@@ -0,0 +1,8 @@
1
+ from bindai_tool import tool
2
+
3
+
4
+ @tool
5
+ def echo(
6
+ text: str,
7
+ ) -> str:
8
+ return text
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from .registry import TemplateRegistry
7
+ from .validator import TemplateValidator
8
+
9
+
10
+ class TemplateInstaller:
11
+ def __init__(self):
12
+
13
+ self.registry = TemplateRegistry()
14
+
15
+ def install(
16
+ self,
17
+ name: str,
18
+ destination: Path,
19
+ ):
20
+
21
+ template = self.registry.get(name)
22
+
23
+ if template is None:
24
+ raise ValueError(f"Unknown template '{name}'.")
25
+
26
+ source = Path(template.path)
27
+
28
+ errors = TemplateValidator.validate(source)
29
+
30
+ if errors:
31
+ raise ValueError("\n".join(errors))
32
+
33
+ shutil.copytree(
34
+ source,
35
+ destination,
36
+ dirs_exist_ok=True,
37
+ ignore=shutil.ignore_patterns(
38
+ "__pycache__",
39
+ ".pytest_cache",
40
+ "*.pyc",
41
+ ".env",
42
+ ),
43
+ )
44
+
45
+ example = destination / ".env.example"
46
+
47
+ env = destination / ".env"
48
+
49
+ if example.exists() and not env.exists():
50
+ shutil.copy2(
51
+ example,
52
+ env,
53
+ )
54
+
55
+ errors = TemplateValidator.validate(source)
56
+
57
+ if errors:
58
+ raise ValueError("\n".join(errors))
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(slots=True)
7
+ class Template:
8
+ name: str
9
+
10
+ title: str
11
+
12
+ description: str
13
+
14
+ version: str
15
+
16
+ author: str
17
+
18
+ category: str
19
+
20
+ status: str
21
+
22
+ tags: list[str]
23
+
24
+ path: str
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from .models import Template
7
+
8
+
9
+ class TemplateRegistry:
10
+ def __init__(self):
11
+
12
+ current = Path(__file__).resolve()
13
+
14
+ while current.name != "bindai":
15
+ current = current.parent
16
+
17
+ self.root = current / "templates"
18
+
19
+ def list(self) -> list[Template]:
20
+
21
+ templates = []
22
+
23
+ for folder in sorted(self.root.iterdir()):
24
+ metadata = folder / "template.json"
25
+
26
+ if not metadata.exists():
27
+ continue
28
+
29
+ data = json.loads(metadata.read_text(encoding="utf-8"))
30
+
31
+ templates.append(
32
+ Template(
33
+ **data,
34
+ path=str(folder),
35
+ )
36
+ )
37
+
38
+ return templates
39
+
40
+ # <-- ADD THIS METHOD HERE
41
+
42
+ def get(
43
+ self,
44
+ name: str,
45
+ ) -> Template | None:
46
+
47
+ for template in self.list():
48
+ if template.name == name:
49
+ return template
50
+
51
+ return None
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ REQUIRED_FILES = {
7
+ "README.md",
8
+ "main.py",
9
+ "template.json",
10
+ }
11
+
12
+
13
+ class TemplateValidator:
14
+ @staticmethod
15
+ def validate(
16
+ path: Path,
17
+ ) -> list[str]:
18
+
19
+ errors: list[str] = []
20
+
21
+ # Required files
22
+ for filename in REQUIRED_FILES:
23
+ if not (path / filename).exists():
24
+ errors.append(f"Missing required file: {filename}")
25
+
26
+ # Stop early if template.json doesn't exist
27
+ metadata = path / "template.json"
28
+
29
+ if not metadata.exists():
30
+ return errors
31
+
32
+ # Validate metadata structure
33
+ try:
34
+ data = json.loads(
35
+ metadata.read_text(
36
+ encoding="utf-8",
37
+ )
38
+ )
39
+
40
+ except Exception:
41
+ errors.append("Invalid template.json")
42
+
43
+ return errors
44
+
45
+ required_fields = [
46
+ "name",
47
+ "version",
48
+ "category",
49
+ "description",
50
+ ]
51
+
52
+ for field in required_fields:
53
+ if field not in data:
54
+ errors.append(f"template.json missing '{field}'.")
55
+
56
+ return errors
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from bindai_config import ProjectRuntime
6
+
7
+
8
+ def load_config():
9
+
10
+ runtime = ProjectRuntime(
11
+ Path.cwd(),
12
+ )
13
+
14
+ return runtime.config
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from bindai_providers import ProviderRegistry
4
+
5
+
6
+ def installed_providers():
7
+
8
+ return ProviderRegistry.list()
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+
7
+ def copy_scaffold(
8
+ scaffold: Path,
9
+ destination: Path,
10
+ ) -> None:
11
+
12
+ shutil.copytree(
13
+ scaffold,
14
+ destination,
15
+ )
16
+
17
+ replace_placeholders(
18
+ destination,
19
+ destination.name,
20
+ )
21
+
22
+ example = destination / ".env.example"
23
+ env = destination / ".env"
24
+
25
+ if example.exists() and not env.exists():
26
+ shutil.copy2(
27
+ example,
28
+ env,
29
+ )
30
+
31
+
32
+ def replace_placeholders(
33
+ root: Path,
34
+ project_name: str,
35
+ ):
36
+
37
+ for file in root.rglob("*"):
38
+ if not file.is_file():
39
+ continue
40
+
41
+ if file.suffix not in {
42
+ ".py",
43
+ ".toml",
44
+ ".md",
45
+ ".txt",
46
+ ".example",
47
+ }:
48
+ continue
49
+
50
+ text = file.read_text(
51
+ encoding="utf-8",
52
+ )
53
+
54
+ text = text.replace(
55
+ "{{PROJECT_NAME}}",
56
+ project_name,
57
+ )
58
+
59
+ file.write_text(
60
+ text,
61
+ encoding="utf-8",
62
+ )
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+
7
+ class WorkflowGraph:
8
+ @staticmethod
9
+ def generate(
10
+ path: Path,
11
+ ) -> str:
12
+
13
+ source = path.read_text(
14
+ encoding="utf-8",
15
+ )
16
+
17
+ nodes = re.findall(
18
+ r'\.step\(\s*"([^"]+)"',
19
+ source,
20
+ )
21
+
22
+ mermaid = [
23
+ "graph TD",
24
+ ]
25
+
26
+ for i, node in enumerate(nodes):
27
+ mermaid.append(f' N{i}["{node}"]')
28
+
29
+ if i:
30
+ mermaid.append(f" N{i - 1} --> N{i}")
31
+
32
+ return "\n".join(
33
+ mermaid,
34
+ )
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class WorkflowValidator:
7
+ @staticmethod
8
+ def validate(
9
+ path: Path,
10
+ ) -> list[str]:
11
+
12
+ errors: list[str] = []
13
+
14
+ if not path.exists():
15
+ errors.append("Workflow file does not exist.")
16
+
17
+ return errors
18
+
19
+ if path.suffix != ".py":
20
+ errors.append("Workflow must be a Python file.")
21
+
22
+ source = path.read_text(
23
+ encoding="utf-8",
24
+ )
25
+
26
+ if "WorkflowBuilder" not in source:
27
+ errors.append("WorkflowBuilder not found.")
28
+
29
+ if ".build(" not in source:
30
+ errors.append("Workflow is never built.")
31
+
32
+ return errors
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: bindai-cli
3
+ Version: 0.1.0
4
+ Summary: BindAI Command Line Interface
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: typer>=0.16
7
+ Requires-Dist: rich>=14.0
@@ -0,0 +1,38 @@
1
+ bindai_cli/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ bindai_cli/__main__.py,sha256=Gpn78kwiVDeNE0gIwM_6UHrxfbFtK4jWo7YhGyOXEo0,104
3
+ bindai_cli/app.py,sha256=aT5GU0fRaAI8SWgFnxI9c5W9ZdbVznPs-om_NsCD2kI,923
4
+ bindai_cli/commands/__init__.py,sha256=lg-8o1BiEbV_v56pvQFXp65EX44DHFoza_z5mVX4RTQ,33
5
+ bindai_cli/commands/add.py,sha256=ZzSzstAxV54NWiexrsc31W1rfar0rIKelozbFPx8qE4,429
6
+ bindai_cli/commands/doctor.py,sha256=Li4hthN504UMvxv6kLvITLfdxn3QxgWn6UcpR8cYZ6w,2350
7
+ bindai_cli/commands/inspect.py,sha256=okKn0J-GcadjJR9ej3CEeJrAaHA9yBhbCj4xMA_2ctY,805
8
+ bindai_cli/commands/new.py,sha256=o_MXFDBXL3aEVaGK4WbY5yhyXYv2kM0ARWaoix6Zh-E,1643
9
+ bindai_cli/commands/run.py,sha256=ZAtYD9V8uKDpZYRs0-v1xHlV7cJAb7WNFHoDWxE65gs,1182
10
+ bindai_cli/commands/template.py,sha256=4D-jS-JjHtsiLPcSt64KL7FEWNV3Prs8Fe-U0M7fqyQ,4999
11
+ bindai_cli/commands/version.py,sha256=uKmW1CmHFhRrFQW3z5R2PDqIUEfkhxMyqpNcmqqfZVw,200
12
+ bindai_cli/commands/workflow.py,sha256=u_Jc8eww7dkffhh9jkaZn8xoppqsEecCENt0OxnXTVk,1861
13
+ bindai_cli/generators/__init__.py,sha256=Ob4IRmKVbdCZmXDa1mfJ7RcXPlpvxhgSaRl6tSpiEbI,198
14
+ bindai_cli/generators/agent.py,sha256=vI7MkTRU06NrIS2Fkdj1mTZxy-ePNh8A8aiER-mPLtk,106
15
+ bindai_cli/generators/directories.py,sha256=HQ9d8NQK9cXukHNs_j_cn2XCwKY2creM5mSCDM1HfBY,399
16
+ bindai_cli/generators/knowledge.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ bindai_cli/generators/memory.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ bindai_cli/generators/pyproject.py,sha256=VTWXmrDvSfS8RjWlh29RRVCjVLYD1GViVurCywhsaSY,367
19
+ bindai_cli/generators/tool.py,sha256=ArMSIt4iAuUJaYNTmn-wcSkysDJ5BpuWcjLy--bPrUk,105
20
+ bindai_cli/generators/workflow.py,sha256=-LDBylzqHcVMZDrI-_yhIj90C8SLPX1HSSLSjWbj0rU,686
21
+ bindai_cli/inspectors/project.py,sha256=TlADrUvinBDknYRXiP2vhlOdt89m357jrcpdlUxdZQA,805
22
+ bindai_cli/scaffolds/basic/main.py,sha256=lmzY5xQ3kwsgPZGfDBnnHHwj91B_t6ey2UNn3WQNpH4,325
23
+ bindai_cli/scaffolds/basic/agents/assistant.py,sha256=GPclvkb-AxwXDfEeOLdUEBOgsIxJHbknG8Z0QHmlSIM,94
24
+ bindai_cli/scaffolds/basic/tools/echo.py,sha256=0yhBWarqt8ASbv4xNN5El0zZh8tqucxrtBvvjguNwsM,96
25
+ bindai_cli/templates/installer.py,sha256=MNnWBdjGtmIve7Hv6-P2sbGgxqJpzfec5iVUDv34PGw,1313
26
+ bindai_cli/templates/models.py,sha256=ffHS6rljWWm6iU2PEhCyTKvIKpjXuApzm1hLQ2JSTvE,294
27
+ bindai_cli/templates/registry.py,sha256=3OOFtiXDw9IlFewZK7QFTWqafBcLQXnpLqQGPJThND0,1080
28
+ bindai_cli/templates/validator.py,sha256=SOC8gHODWpAf_BIA0C_xnvClP_iNYe4SjdEzXd2jXKM,1266
29
+ bindai_cli/utils/config.py,sha256=W6IwsKKLH57lxdkrIXajkZWHyrrCC99unx8qDLdeLDk,222
30
+ bindai_cli/utils/providers.py,sha256=_DUW4SdOL4Xskfike2pyQPknPrH1bva47yJFUCAGB64,155
31
+ bindai_cli/utils/scaffold.py,sha256=SYOJu6c-Zy7oJGso7DCP3FZr6f-je0KMOmrihtQV4VE,1129
32
+ bindai_cli/workflows/graph.py,sha256=m9MmlXs0Ka45jycbA_r3v0nSVq2G8K7IAjH_jtYJAno,649
33
+ bindai_cli/workflows/validator.py,sha256=C6vhFNOnQboEdrVaBScPg_l56GN-NI2NEBxUA7FU-08,729
34
+ bindai_cli-0.1.0.dist-info/METADATA,sha256=EPFFPQj1FnSWU7c83boOvffvR-uEWiw0Rg6oCLC1aSs,177
35
+ bindai_cli-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
36
+ bindai_cli-0.1.0.dist-info/entry_points.txt,sha256=3YDlkt-YcYlc0pGZi0Kvtn1ZW3ny9wElGwN5n1l8HTQ,52
37
+ bindai_cli-0.1.0.dist-info/top_level.txt,sha256=kN1kzqAvwLSUkhdK7m88HL5ttWVvNRpRnclMeRWN7wM,11
38
+ bindai_cli-0.1.0.dist-info/RECORD,,