create-ailab 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.
- create_ai/__init__.py +13 -0
- create_ai/__main__.py +8 -0
- create_ai/cli/__init__.py +1 -0
- create_ai/cli/app.py +82 -0
- create_ai/cli/commands/__init__.py +1 -0
- create_ai/cli/commands/add.py +28 -0
- create_ai/cli/commands/doctor.py +62 -0
- create_ai/cli/commands/list_types.py +28 -0
- create_ai/cli/commands/new.py +346 -0
- create_ai/cli/console.py +36 -0
- create_ai/cli/prompts.py +184 -0
- create_ai/cli/reporters.py +31 -0
- create_ai/core/__init__.py +6 -0
- create_ai/core/configuration.py +166 -0
- create_ai/core/dependencies.py +128 -0
- create_ai/core/generator.py +195 -0
- create_ai/core/project.py +100 -0
- create_ai/core/registry.py +98 -0
- create_ai/core/templating.py +130 -0
- create_ai/core/validation.py +22 -0
- create_ai/errors.py +46 -0
- create_ai/generators/__init__.py +18 -0
- create_ai/generators/base.py +94 -0
- create_ai/generators/computer_vision.py +34 -0
- create_ai/generators/deep_learning.py +31 -0
- create_ai/generators/llm.py +37 -0
- create_ai/generators/ml.py +33 -0
- create_ai/integrations/__init__.py +20 -0
- create_ai/integrations/base.py +58 -0
- create_ai/integrations/docker.py +18 -0
- create_ai/integrations/git.py +55 -0
- create_ai/integrations/jupyter.py +11 -0
- create_ai/integrations/package_manager.py +88 -0
- create_ai/integrations/python.py +33 -0
- create_ai/integrations/uv.py +44 -0
- create_ai/py.typed +0 -0
- create_ai/templates/common/.dockerignore.j2 +22 -0
- create_ai/templates/common/.env.example.j2 +14 -0
- create_ai/templates/common/.github/workflows/ci.yml.j2 +38 -0
- create_ai/templates/common/.gitignore.j2 +47 -0
- create_ai/templates/common/.pre-commit-config.yaml.j2 +15 -0
- create_ai/templates/common/Dockerfile.j2 +27 -0
- create_ai/templates/common/LICENSE.j2 +21 -0
- create_ai/templates/common/Makefile.j2 +29 -0
- create_ai/templates/common/README.md.j2 +84 -0
- create_ai/templates/common/configs/config.yaml.j2 +9 -0
- create_ai/templates/common/notebooks/01-exploration.ipynb.j2 +29 -0
- create_ai/templates/common/pyproject.toml.j2 +58 -0
- create_ai/templates/common/src/{{package_name}}/__init__.py.j2 +5 -0
- create_ai/templates/common/src/{{package_name}}/config.py.j2 +35 -0
- create_ai/templates/common/src/{{package_name}}/tracking.py.j2 +63 -0
- create_ai/templates/common/tests/conftest.py.j2 +13 -0
- create_ai/templates/computer_vision/pytorch/configs/model.yaml.j2 +5 -0
- create_ai/templates/computer_vision/pytorch/configs/training.yaml.j2 +10 -0
- create_ai/templates/computer_vision/pytorch/evaluate.py.j2 +31 -0
- create_ai/templates/computer_vision/pytorch/inference.py.j2 +32 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/datasets/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/datasets/folder.py.j2 +27 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/datasets/synthetic.py.j2 +58 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/evaluation/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/evaluation/evaluate.py.j2 +22 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/inference/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/inference/predict.py.j2 +27 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/models/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/models/cnn.py.j2 +49 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/training/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/training/trainer.py.j2 +81 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/transforms/__init__.py.j2 +5 -0
- create_ai/templates/computer_vision/pytorch/src/{{package_name}}/transforms/pipelines.py.j2 +31 -0
- create_ai/templates/computer_vision/pytorch/tests/test_model.py.j2 +15 -0
- create_ai/templates/computer_vision/pytorch/tests/test_trainer.py.j2 +23 -0
- create_ai/templates/computer_vision/pytorch/tests/test_transforms.py.j2 +14 -0
- create_ai/templates/computer_vision/pytorch/train.py.j2 +53 -0
- create_ai/templates/computer_vision/tensorflow/configs/model.yaml.j2 +5 -0
- create_ai/templates/computer_vision/tensorflow/configs/training.yaml.j2 +9 -0
- create_ai/templates/computer_vision/tensorflow/evaluate.py.j2 +30 -0
- create_ai/templates/computer_vision/tensorflow/inference.py.j2 +33 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/datasets/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/datasets/synthetic.py.j2 +43 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/evaluation/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/evaluation/evaluate.py.j2 +11 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/inference/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/inference/predict.py.j2 +12 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/models/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/models/cnn.py.j2 +36 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/training/__init__.py.j2 +1 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/training/trainer.py.j2 +28 -0
- create_ai/templates/computer_vision/tensorflow/src/{{package_name}}/transforms/__init__.py.j2 +18 -0
- create_ai/templates/computer_vision/tensorflow/tests/test_model.py.j2 +16 -0
- create_ai/templates/computer_vision/tensorflow/tests/test_trainer.py.j2 +24 -0
- create_ai/templates/computer_vision/tensorflow/train.py.j2 +48 -0
- create_ai/templates/deep_learning/pytorch/configs/model.yaml.j2 +6 -0
- create_ai/templates/deep_learning/pytorch/configs/training.yaml.j2 +10 -0
- create_ai/templates/deep_learning/pytorch/evaluate.py.j2 +31 -0
- create_ai/templates/deep_learning/pytorch/inference.py.j2 +30 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/datasets/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/datasets/synthetic.py.j2 +58 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/evaluation/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/evaluation/evaluate.py.j2 +22 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/inference/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/inference/predict.py.j2 +27 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/losses/__init__.py.j2 +17 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/models/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/models/mlp.py.j2 +39 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/training/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/pytorch/src/{{package_name}}/training/trainer.py.j2 +84 -0
- create_ai/templates/deep_learning/pytorch/tests/test_inference.py.j2 +16 -0
- create_ai/templates/deep_learning/pytorch/tests/test_model.py.j2 +15 -0
- create_ai/templates/deep_learning/pytorch/tests/test_trainer.py.j2 +27 -0
- create_ai/templates/deep_learning/pytorch/train.py.j2 +55 -0
- create_ai/templates/deep_learning/tensorflow/configs/model.yaml.j2 +6 -0
- create_ai/templates/deep_learning/tensorflow/configs/training.yaml.j2 +9 -0
- create_ai/templates/deep_learning/tensorflow/evaluate.py.j2 +30 -0
- create_ai/templates/deep_learning/tensorflow/inference.py.j2 +31 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/datasets/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/datasets/synthetic.py.j2 +41 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/evaluation/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/evaluation/evaluate.py.j2 +11 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/inference/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/inference/predict.py.j2 +12 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/models/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/models/mlp.py.j2 +31 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/training/__init__.py.j2 +1 -0
- create_ai/templates/deep_learning/tensorflow/src/{{package_name}}/training/trainer.py.j2 +28 -0
- create_ai/templates/deep_learning/tensorflow/tests/test_model.py.j2 +16 -0
- create_ai/templates/deep_learning/tensorflow/tests/test_trainer.py.j2 +24 -0
- create_ai/templates/deep_learning/tensorflow/train.py.j2 +48 -0
- create_ai/templates/llm/configs/evaluation.yaml.j2 +5 -0
- create_ai/templates/llm/configs/model.yaml.j2 +4 -0
- create_ai/templates/llm/configs/training.yaml.j2 +8 -0
- create_ai/templates/llm/data/sample.jsonl +8 -0
- create_ai/templates/llm/evaluate.py.j2 +40 -0
- create_ai/templates/llm/inference.py.j2 +34 -0
- create_ai/templates/llm/src/{{package_name}}/datasets/__init__.py.j2 +1 -0
- create_ai/templates/llm/src/{{package_name}}/datasets/jsonl.py.j2 +34 -0
- create_ai/templates/llm/src/{{package_name}}/evaluation/__init__.py.j2 +1 -0
- create_ai/templates/llm/src/{{package_name}}/evaluation/perplexity.py.j2 +26 -0
- create_ai/templates/llm/src/{{package_name}}/inference/__init__.py.j2 +1 -0
- create_ai/templates/llm/src/{{package_name}}/inference/generate.py.j2 +27 -0
- create_ai/templates/llm/src/{{package_name}}/models/__init__.py.j2 +1 -0
- create_ai/templates/llm/src/{{package_name}}/models/loader.py.j2 +22 -0
- create_ai/templates/llm/src/{{package_name}}/prompts/__init__.py.j2 +1 -0
- create_ai/templates/llm/src/{{package_name}}/prompts/templates.py.j2 +29 -0
- create_ai/templates/llm/src/{{package_name}}/training/__init__.py.j2 +1 -0
- create_ai/templates/llm/src/{{package_name}}/training/finetune.py.j2 +42 -0
- create_ai/templates/llm/src/{{package_name}}/utils/__init__.py.j2 +1 -0
- create_ai/templates/llm/src/{{package_name}}/utils/seeding.py.j2 +25 -0
- create_ai/templates/llm/tests/test_config.py.j2 +10 -0
- create_ai/templates/llm/tests/test_datasets.py.j2 +20 -0
- create_ai/templates/llm/tests/test_prompts.py.j2 +17 -0
- create_ai/templates/llm/train.py.j2 +60 -0
- create_ai/templates/ml/configs/config.yaml.j2 +18 -0
- create_ai/templates/ml/evaluate.py.j2 +33 -0
- create_ai/templates/ml/predict.py.j2 +33 -0
- create_ai/templates/ml/src/{{package_name}}/data/__init__.py.j2 +1 -0
- create_ai/templates/ml/src/{{package_name}}/data/dataset.py.j2 +56 -0
- create_ai/templates/ml/src/{{package_name}}/evaluation/__init__.py.j2 +1 -0
- create_ai/templates/ml/src/{{package_name}}/evaluation/metrics.py.j2 +22 -0
- create_ai/templates/ml/src/{{package_name}}/features/__init__.py.j2 +1 -0
- create_ai/templates/ml/src/{{package_name}}/features/preprocessing.py.j2 +12 -0
- create_ai/templates/ml/src/{{package_name}}/inference/__init__.py.j2 +1 -0
- create_ai/templates/ml/src/{{package_name}}/inference/predict.py.j2 +21 -0
- create_ai/templates/ml/src/{{package_name}}/models/__init__.py.j2 +1 -0
- create_ai/templates/ml/src/{{package_name}}/models/model.py.j2 +36 -0
- create_ai/templates/ml/src/{{package_name}}/pipeline.py.j2 +56 -0
- create_ai/templates/ml/tests/test_data.py.j2 +21 -0
- create_ai/templates/ml/tests/test_model.py.j2 +24 -0
- create_ai/templates/ml/tests/test_pipeline.py.j2 +19 -0
- create_ai/templates/ml/train.py.j2 +23 -0
- create_ai/utils/__init__.py +1 -0
- create_ai/utils/filesystem.py +68 -0
- create_ai/utils/shell.py +111 -0
- create_ai/utils/validation.py +95 -0
- create_ailab-0.1.0.dist-info/METADATA +299 -0
- create_ailab-0.1.0.dist-info/RECORD +178 -0
- create_ailab-0.1.0.dist-info/WHEEL +4 -0
- create_ailab-0.1.0.dist-info/entry_points.txt +2 -0
- create_ailab-0.1.0.dist-info/licenses/LICENSE +21 -0
create_ai/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""create-ai: the Vite-style project scaffolder for AI/ML engineering.
|
|
2
|
+
|
|
3
|
+
The public surface is intentionally small. Most consumers only need
|
|
4
|
+
:data:`__version__` and the CLI entrypoint in :mod:`create_ai.cli.app`.
|
|
5
|
+
Programmatic users can build a :class:`~create_ai.core.configuration.ProjectConfig`
|
|
6
|
+
and hand it to :class:`~create_ai.core.generator.GenerationEngine`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
__all__ = ["__version__"]
|
create_ai/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command-line interface - a thin adapter over :mod:`create_ai.core`."""
|
create_ai/cli/app.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Typer application wiring.
|
|
2
|
+
|
|
3
|
+
``create-ai`` behaves like ``npm create vite``:
|
|
4
|
+
|
|
5
|
+
* ``create-ai`` -> interactive project creation
|
|
6
|
+
* ``create-ai my-project`` -> ``create-ai new my-project``
|
|
7
|
+
* ``create-ai new my-project`` -> same, explicit
|
|
8
|
+
* ``create-ai doctor`` / ``list`` / ``add``
|
|
9
|
+
|
|
10
|
+
The "bare name is really ``new``" behaviour is a tiny ``TyperGroup`` subclass
|
|
11
|
+
that rewrites the argument list *before* command resolution, so there is exactly
|
|
12
|
+
one implementation of project creation and no dependency on click internals.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
from typer.core import TyperGroup
|
|
21
|
+
|
|
22
|
+
from create_ai import __version__
|
|
23
|
+
from create_ai.cli.commands.add import add
|
|
24
|
+
from create_ai.cli.commands.doctor import doctor
|
|
25
|
+
from create_ai.cli.commands.list_types import list_types
|
|
26
|
+
from create_ai.cli.commands.new import new, run_default
|
|
27
|
+
|
|
28
|
+
_DEFAULT_COMMAND = "new"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DefaultCommandGroup(TyperGroup):
|
|
32
|
+
"""Route an unknown first token to ``new`` (so ``create-ai <name>`` works)."""
|
|
33
|
+
|
|
34
|
+
def resolve_command(self, ctx: Any, args: list[str]) -> Any:
|
|
35
|
+
if args and not args[0].startswith("-") and args[0] not in self.commands:
|
|
36
|
+
args = [_DEFAULT_COMMAND, *args]
|
|
37
|
+
return super().resolve_command(ctx, args)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
app = typer.Typer(
|
|
41
|
+
cls=DefaultCommandGroup,
|
|
42
|
+
add_completion=False,
|
|
43
|
+
no_args_is_help=False,
|
|
44
|
+
rich_markup_mode="rich",
|
|
45
|
+
help="Scaffold production-ready AI/ML projects - the Vite experience for AI.",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
app.command("new")(new)
|
|
49
|
+
app.command("doctor")(doctor)
|
|
50
|
+
app.command("list")(list_types)
|
|
51
|
+
app.command("add")(add)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _version_callback(value: bool) -> None:
|
|
55
|
+
if value:
|
|
56
|
+
typer.echo(f"create-ai {__version__}")
|
|
57
|
+
raise typer.Exit
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.callback(invoke_without_command=True)
|
|
61
|
+
def _root(
|
|
62
|
+
ctx: typer.Context,
|
|
63
|
+
version: bool = typer.Option(
|
|
64
|
+
False,
|
|
65
|
+
"--version",
|
|
66
|
+
"-V",
|
|
67
|
+
help="Show the create-ai version and exit.",
|
|
68
|
+
callback=_version_callback,
|
|
69
|
+
is_eager=True,
|
|
70
|
+
),
|
|
71
|
+
) -> None:
|
|
72
|
+
if ctx.invoked_subcommand is None:
|
|
73
|
+
# Bare `create-ai` -> interactive `new` (with all-default arguments).
|
|
74
|
+
run_default()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main() -> None: # pragma: no cover - console-script shim
|
|
78
|
+
app()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__": # pragma: no cover
|
|
82
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Sub-command implementations for the ``create-ai`` CLI."""
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""``create-ai add`` - placeholder for the v0.2 "extend an existing project" flow.
|
|
2
|
+
|
|
3
|
+
The command exists now so the CLI surface is stable and discoverable; the
|
|
4
|
+
implementation lands with the plugin architecture in v0.2.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from create_ai.cli.console import console
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def add(
|
|
15
|
+
component: str | None = typer.Argument(
|
|
16
|
+
None, help="Component to add (e.g. mlflow, docker, robotics)."
|
|
17
|
+
),
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Add a component to an existing project (coming in v0.2)."""
|
|
20
|
+
|
|
21
|
+
console.print(
|
|
22
|
+
"[yellow]`create-ai add` is planned for v0.2.[/yellow]\n"
|
|
23
|
+
"It will layer integrations (MLflow, Docker, W&B) and new domains "
|
|
24
|
+
"(Robotics, ROS 2, RL) onto an existing project."
|
|
25
|
+
)
|
|
26
|
+
if component:
|
|
27
|
+
console.print(f"[dim]Requested component: {component}[/dim]")
|
|
28
|
+
raise typer.Exit(code=0)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""``create-ai doctor`` - report on the external tools create-ai can use."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from create_ai.cli.console import console
|
|
9
|
+
from create_ai.integrations import docker as docker_integration
|
|
10
|
+
from create_ai.integrations import git as git_integration
|
|
11
|
+
from create_ai.integrations import python as python_integration
|
|
12
|
+
from create_ai.utils.shell import tool_version
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class Check:
|
|
17
|
+
"""One environment probe. ``probe`` returns a version string or ``None``."""
|
|
18
|
+
|
|
19
|
+
label: str
|
|
20
|
+
probe: Callable[[], str | None]
|
|
21
|
+
required: bool = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Ordered; add new probes here - the command body never changes.
|
|
25
|
+
CHECKS: tuple[Check, ...] = (
|
|
26
|
+
Check("Python", lambda: python_integration.current_version(), required=True),
|
|
27
|
+
Check("Git", git_integration.version),
|
|
28
|
+
Check("uv", lambda: tool_version("uv")),
|
|
29
|
+
Check("Docker", docker_integration.version),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _clean(value: str, label: str) -> str:
|
|
34
|
+
"""Trim a redundant leading ``"<tool> version"`` from a version string."""
|
|
35
|
+
|
|
36
|
+
text = value.strip()
|
|
37
|
+
for prefix in (f"{label.lower()} version", label.lower(), "version"):
|
|
38
|
+
if text.lower().startswith(prefix):
|
|
39
|
+
text = text[len(prefix) :].strip()
|
|
40
|
+
return text or "(version unknown)"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def doctor() -> None:
|
|
44
|
+
"""Check for Python, Git, uv and Docker and print a short report."""
|
|
45
|
+
|
|
46
|
+
console.print("[bold]create-ai doctor[/bold]\n")
|
|
47
|
+
missing_required = False
|
|
48
|
+
for check in CHECKS:
|
|
49
|
+
version = check.probe()
|
|
50
|
+
if version:
|
|
51
|
+
console.print(f"[green]✔[/green] {check.label} {_clean(version, check.label)}")
|
|
52
|
+
elif check.required:
|
|
53
|
+
missing_required = True
|
|
54
|
+
console.print(f"[red]✗[/red] {check.label} not found (required)")
|
|
55
|
+
else:
|
|
56
|
+
console.print(f"[dim]○ {check.label} not installed[/dim]")
|
|
57
|
+
|
|
58
|
+
console.print()
|
|
59
|
+
if missing_required:
|
|
60
|
+
console.print("[red]Some required tools are missing.[/red]")
|
|
61
|
+
raise SystemExit(1)
|
|
62
|
+
console.print("[green]All required tools are available.[/green]")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""``create-ai list`` - show the registered project types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from create_ai.cli.console import console
|
|
8
|
+
from create_ai.generators import generator_registry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def list_types() -> None:
|
|
12
|
+
"""List every project type create-ai can scaffold."""
|
|
13
|
+
|
|
14
|
+
table = Table(title="Available project types", title_justify="left")
|
|
15
|
+
table.add_column("key", style="bold cyan")
|
|
16
|
+
table.add_column("name")
|
|
17
|
+
table.add_column("frameworks", style="dim")
|
|
18
|
+
table.add_column("description")
|
|
19
|
+
|
|
20
|
+
for key, generator_cls in generator_registry.items():
|
|
21
|
+
frameworks = ", ".join(sorted(f.value for f in generator_cls.supported_frameworks))
|
|
22
|
+
table.add_row(
|
|
23
|
+
key,
|
|
24
|
+
generator_cls.project_type.label,
|
|
25
|
+
frameworks,
|
|
26
|
+
generator_cls.summary,
|
|
27
|
+
)
|
|
28
|
+
console.print(table)
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""``create-ai new`` - create a project (also the default, bare ``create-ai``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TypeVar
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
from create_ai.cli.console import console, err_console, print_error, summary_table
|
|
13
|
+
from create_ai.cli.prompts import run_questionnaire, stdin_is_interactive
|
|
14
|
+
from create_ai.cli.reporters import RichReporter
|
|
15
|
+
from create_ai.core.configuration import (
|
|
16
|
+
DEFAULT_PYTHON_VERSION,
|
|
17
|
+
EnvironmentManager,
|
|
18
|
+
ExperimentTracking,
|
|
19
|
+
Framework,
|
|
20
|
+
Linting,
|
|
21
|
+
ProjectConfig,
|
|
22
|
+
ProjectType,
|
|
23
|
+
Testing,
|
|
24
|
+
)
|
|
25
|
+
from create_ai.core.generator import GenerationEngine, GenerationResult
|
|
26
|
+
from create_ai.core.project import Project
|
|
27
|
+
from create_ai.core.validation import validate_config
|
|
28
|
+
from create_ai.errors import CreateAIError
|
|
29
|
+
from create_ai.generators import generator_registry
|
|
30
|
+
from create_ai.integrations import resolve_environment_manager
|
|
31
|
+
from create_ai.utils.filesystem import is_empty_dir
|
|
32
|
+
from create_ai.utils.validation import resolve_destination
|
|
33
|
+
|
|
34
|
+
_E = TypeVar("_E", bound=Enum)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _coerce(enum_cls: type[_E], value: str, flag: str) -> _E:
|
|
38
|
+
try:
|
|
39
|
+
return enum_cls(value)
|
|
40
|
+
except ValueError:
|
|
41
|
+
allowed = ", ".join(m.value for m in enum_cls)
|
|
42
|
+
raise CreateAIError(
|
|
43
|
+
f"Invalid value {value!r} for {flag}.", hint=f"Choose one of: {allowed}."
|
|
44
|
+
) from None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _config_from_flags(
|
|
48
|
+
*,
|
|
49
|
+
name: str,
|
|
50
|
+
project_type: str | None,
|
|
51
|
+
python_version: str | None,
|
|
52
|
+
framework: str | None,
|
|
53
|
+
manager: str | None,
|
|
54
|
+
testing: str | None,
|
|
55
|
+
linting: str | None,
|
|
56
|
+
tracking: str | None,
|
|
57
|
+
docker: bool | None,
|
|
58
|
+
jupyter: bool | None,
|
|
59
|
+
git: bool | None,
|
|
60
|
+
) -> ProjectConfig:
|
|
61
|
+
ptype = _coerce(ProjectType, project_type or ProjectType.MACHINE_LEARNING.value, "--type")
|
|
62
|
+
if project_type is None:
|
|
63
|
+
console.print("[dim]No --type given; defaulting to Machine Learning.[/dim]")
|
|
64
|
+
fw_value = framework or generator_registry.get(ptype.value).default_framework.value
|
|
65
|
+
return ProjectConfig(
|
|
66
|
+
name=name,
|
|
67
|
+
project_type=ptype,
|
|
68
|
+
python_version=python_version or DEFAULT_PYTHON_VERSION,
|
|
69
|
+
framework=_coerce(Framework, fw_value, "--framework"),
|
|
70
|
+
environment_manager=_coerce(EnvironmentManager, manager or "uv", "--manager"),
|
|
71
|
+
testing=_coerce(Testing, testing or "pytest", "--testing"),
|
|
72
|
+
linting=_coerce(Linting, linting or "ruff", "--linting"),
|
|
73
|
+
experiment_tracking=_coerce(ExperimentTracking, tracking or "none", "--tracking"),
|
|
74
|
+
docker=False if docker is None else docker,
|
|
75
|
+
jupyter=False if jupyter is None else jupyter,
|
|
76
|
+
git=True if git is None else git,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _resolve_existing_directory(config: ProjectConfig, parent: Path) -> tuple[Path, ProjectConfig]:
|
|
81
|
+
"""Interactively deal with a non-empty target directory (never deletes)."""
|
|
82
|
+
|
|
83
|
+
import questionary
|
|
84
|
+
|
|
85
|
+
while True:
|
|
86
|
+
destination, _ = resolve_destination(config.name, parent=parent)
|
|
87
|
+
if is_empty_dir(destination):
|
|
88
|
+
return destination, config
|
|
89
|
+
choice = questionary.select(
|
|
90
|
+
f'Directory "{destination}" already exists and is not empty.',
|
|
91
|
+
choices=["Cancel", "Choose another name"],
|
|
92
|
+
qmark="?",
|
|
93
|
+
).ask()
|
|
94
|
+
if choice != "Choose another name":
|
|
95
|
+
raise CreateAIError("Cancelled - existing directory left untouched.")
|
|
96
|
+
new_name = questionary.text("New project name:", qmark="?").ask()
|
|
97
|
+
if not new_name:
|
|
98
|
+
raise CreateAIError("Cancelled.")
|
|
99
|
+
config = config.model_copy(update={"name": new_name.strip()})
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _print_next_steps(result: GenerationResult, *, installed: bool, dry_run: bool) -> None:
|
|
103
|
+
project = result.project
|
|
104
|
+
lines: list[str] = []
|
|
105
|
+
rel = project.destination.name
|
|
106
|
+
lines.append(f" cd {rel}")
|
|
107
|
+
if installed:
|
|
108
|
+
manager = resolve_environment_manager(project.destination, project.config)
|
|
109
|
+
lines.append(f" {manager.activation_hint()}")
|
|
110
|
+
else:
|
|
111
|
+
setup = (
|
|
112
|
+
"uv sync"
|
|
113
|
+
if project.config.environment_manager is EnvironmentManager.UV
|
|
114
|
+
else ("python -m venv .venv && source .venv/bin/activate && pip install -e .")
|
|
115
|
+
)
|
|
116
|
+
lines.append(f" {setup}")
|
|
117
|
+
entry = project.generator.entrypoints[0]
|
|
118
|
+
lines.append(f" {project.config.run_prefix}python {entry}")
|
|
119
|
+
|
|
120
|
+
if result.warnings:
|
|
121
|
+
for warning in result.warnings:
|
|
122
|
+
console.print(f"[yellow]![/yellow] {warning}")
|
|
123
|
+
|
|
124
|
+
header = (
|
|
125
|
+
"[bold]Dry run complete[/bold] - no files were written."
|
|
126
|
+
if dry_run
|
|
127
|
+
else "[bold green]🎉 Project created successfully![/bold green]"
|
|
128
|
+
)
|
|
129
|
+
body = "\n".join([header, "", "Next steps:", *lines])
|
|
130
|
+
console.print()
|
|
131
|
+
console.print(Panel(body, border_style="green", expand=False))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _run(
|
|
135
|
+
name: str | None,
|
|
136
|
+
*,
|
|
137
|
+
project_type: str | None,
|
|
138
|
+
python_version: str | None,
|
|
139
|
+
framework: str | None,
|
|
140
|
+
manager: str | None,
|
|
141
|
+
testing: str | None,
|
|
142
|
+
linting: str | None,
|
|
143
|
+
tracking: str | None,
|
|
144
|
+
docker: bool | None,
|
|
145
|
+
jupyter: bool | None,
|
|
146
|
+
git: bool | None,
|
|
147
|
+
yes: bool,
|
|
148
|
+
install: bool,
|
|
149
|
+
force: bool,
|
|
150
|
+
dry_run: bool,
|
|
151
|
+
output_dir: Path | None,
|
|
152
|
+
) -> None:
|
|
153
|
+
parent = (output_dir or Path.cwd()).resolve()
|
|
154
|
+
interactive = not yes and stdin_is_interactive()
|
|
155
|
+
|
|
156
|
+
if interactive and _needs_prompt(name, project_type):
|
|
157
|
+
config = run_questionnaire(
|
|
158
|
+
name=name,
|
|
159
|
+
project_type=project_type,
|
|
160
|
+
python_version=python_version,
|
|
161
|
+
framework=framework,
|
|
162
|
+
environment_manager=manager,
|
|
163
|
+
testing=testing,
|
|
164
|
+
linting=linting,
|
|
165
|
+
experiment_tracking=tracking,
|
|
166
|
+
docker=docker,
|
|
167
|
+
jupyter=jupyter,
|
|
168
|
+
git=git,
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
if name is None:
|
|
172
|
+
raise CreateAIError(
|
|
173
|
+
"A project name is required.",
|
|
174
|
+
hint="Run `create-ai <name> --type <type>` or use interactive mode.",
|
|
175
|
+
)
|
|
176
|
+
config = _config_from_flags(
|
|
177
|
+
name=name,
|
|
178
|
+
project_type=project_type,
|
|
179
|
+
python_version=python_version,
|
|
180
|
+
framework=framework,
|
|
181
|
+
manager=manager,
|
|
182
|
+
testing=testing,
|
|
183
|
+
linting=linting,
|
|
184
|
+
tracking=tracking,
|
|
185
|
+
docker=docker,
|
|
186
|
+
jupyter=jupyter,
|
|
187
|
+
git=git,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
validate_config(config)
|
|
191
|
+
|
|
192
|
+
if interactive:
|
|
193
|
+
destination, config = _resolve_existing_directory(config, parent)
|
|
194
|
+
else:
|
|
195
|
+
destination, _ = resolve_destination(config.name, parent=parent)
|
|
196
|
+
|
|
197
|
+
project = Project.build(config, destination=destination)
|
|
198
|
+
|
|
199
|
+
if interactive and not yes:
|
|
200
|
+
console.print()
|
|
201
|
+
plan = Panel(summary_table(config.summary_rows()), title="Project plan", expand=False)
|
|
202
|
+
console.print(plan)
|
|
203
|
+
import questionary
|
|
204
|
+
|
|
205
|
+
if not questionary.confirm("Create this project?", default=True, qmark="?").ask():
|
|
206
|
+
raise CreateAIError("Cancelled.")
|
|
207
|
+
console.print()
|
|
208
|
+
|
|
209
|
+
engine = GenerationEngine(
|
|
210
|
+
project,
|
|
211
|
+
reporter=RichReporter(console),
|
|
212
|
+
install=install and not dry_run,
|
|
213
|
+
init_git=config.git,
|
|
214
|
+
force=force,
|
|
215
|
+
dry_run=dry_run,
|
|
216
|
+
)
|
|
217
|
+
result = engine.run()
|
|
218
|
+
_print_next_steps(
|
|
219
|
+
result,
|
|
220
|
+
installed=install and not dry_run and _env_created(result),
|
|
221
|
+
dry_run=dry_run,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _env_created(result: GenerationResult) -> bool:
|
|
226
|
+
return any(
|
|
227
|
+
title == "Creating environment" and outcome.ok and not outcome.skipped
|
|
228
|
+
for title, outcome in result.steps
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _needs_prompt(name: str | None, project_type: str | None) -> bool:
|
|
233
|
+
return name is None or project_type is None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _guarded(
|
|
237
|
+
name: str | None,
|
|
238
|
+
*,
|
|
239
|
+
project_type: str | None = None,
|
|
240
|
+
python_version: str | None = None,
|
|
241
|
+
framework: str | None = None,
|
|
242
|
+
manager: str | None = None,
|
|
243
|
+
testing: str | None = None,
|
|
244
|
+
linting: str | None = None,
|
|
245
|
+
tracking: str | None = None,
|
|
246
|
+
docker: bool | None = None,
|
|
247
|
+
jupyter: bool | None = None,
|
|
248
|
+
git: bool | None = None,
|
|
249
|
+
yes: bool = False,
|
|
250
|
+
install: bool = True,
|
|
251
|
+
force: bool = False,
|
|
252
|
+
dry_run: bool = False,
|
|
253
|
+
output_dir: Path | None = None,
|
|
254
|
+
debug: bool = False,
|
|
255
|
+
) -> None:
|
|
256
|
+
"""Run the create flow, converting expected errors into a clean exit."""
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
_run(
|
|
260
|
+
name,
|
|
261
|
+
project_type=project_type,
|
|
262
|
+
python_version=python_version,
|
|
263
|
+
framework=framework,
|
|
264
|
+
manager=manager,
|
|
265
|
+
testing=testing,
|
|
266
|
+
linting=linting,
|
|
267
|
+
tracking=tracking,
|
|
268
|
+
docker=docker,
|
|
269
|
+
jupyter=jupyter,
|
|
270
|
+
git=git,
|
|
271
|
+
yes=yes,
|
|
272
|
+
install=install,
|
|
273
|
+
force=force,
|
|
274
|
+
dry_run=dry_run,
|
|
275
|
+
output_dir=output_dir,
|
|
276
|
+
)
|
|
277
|
+
except CreateAIError as exc:
|
|
278
|
+
print_error(exc)
|
|
279
|
+
raise typer.Exit(code=1) from None
|
|
280
|
+
except typer.Exit:
|
|
281
|
+
raise
|
|
282
|
+
except Exception as exc: # pragma: no cover - safety net
|
|
283
|
+
if debug:
|
|
284
|
+
raise
|
|
285
|
+
err_console.print(
|
|
286
|
+
f"[red]✗ unexpected error:[/red] {exc}\n[dim]Re-run with --debug for a traceback.[/dim]"
|
|
287
|
+
)
|
|
288
|
+
raise typer.Exit(code=1) from None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def run_default() -> None:
|
|
292
|
+
"""Entry point for bare ``create-ai`` (no subcommand): interactive creation."""
|
|
293
|
+
|
|
294
|
+
_guarded(None)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def new(
|
|
298
|
+
name: str | None = typer.Argument(None, help="Project name / directory to create."),
|
|
299
|
+
project_type: str | None = typer.Option(
|
|
300
|
+
None, "--type", "-t", help="ml | deep-learning | llm | computer-vision"
|
|
301
|
+
),
|
|
302
|
+
python_version: str | None = typer.Option(None, "--python", help="CPython version, e.g. 3.12"),
|
|
303
|
+
framework: str | None = typer.Option(
|
|
304
|
+
None, "--framework", "-f", help="pytorch | tensorflow | sklearn | none"
|
|
305
|
+
),
|
|
306
|
+
manager: str | None = typer.Option(None, "--manager", "-m", help="uv | venv"),
|
|
307
|
+
testing: str | None = typer.Option(None, "--testing", help="pytest | none"),
|
|
308
|
+
linting: str | None = typer.Option(None, "--linting", help="ruff | ruff+black | none"),
|
|
309
|
+
tracking: str | None = typer.Option(None, "--tracking", help="mlflow | wandb | none"),
|
|
310
|
+
docker: bool | None = typer.Option(None, "--docker/--no-docker", help="Generate Docker files."),
|
|
311
|
+
jupyter: bool | None = typer.Option(
|
|
312
|
+
None, "--jupyter/--no-jupyter", help="Add notebooks/ and Jupyter deps."
|
|
313
|
+
),
|
|
314
|
+
git: bool | None = typer.Option(None, "--git/--no-git", help="Run git init."),
|
|
315
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip prompts; accept defaults."),
|
|
316
|
+
install: bool = typer.Option(
|
|
317
|
+
True, "--install/--no-install", help="Create the environment and install deps."
|
|
318
|
+
),
|
|
319
|
+
force: bool = typer.Option(False, "--force", help="Allow writing into a non-empty directory."),
|
|
320
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Report actions without writing files."),
|
|
321
|
+
output_dir: Path | None = typer.Option(
|
|
322
|
+
None, "--output-dir", "-o", help="Parent directory for the new project."
|
|
323
|
+
),
|
|
324
|
+
debug: bool = typer.Option(False, "--debug", help="Show full tracebacks on error."),
|
|
325
|
+
) -> None:
|
|
326
|
+
"""Scaffold a new AI/ML project."""
|
|
327
|
+
|
|
328
|
+
_guarded(
|
|
329
|
+
name,
|
|
330
|
+
project_type=project_type,
|
|
331
|
+
python_version=python_version,
|
|
332
|
+
framework=framework,
|
|
333
|
+
manager=manager,
|
|
334
|
+
testing=testing,
|
|
335
|
+
linting=linting,
|
|
336
|
+
tracking=tracking,
|
|
337
|
+
docker=docker,
|
|
338
|
+
jupyter=jupyter,
|
|
339
|
+
git=git,
|
|
340
|
+
yes=yes,
|
|
341
|
+
install=install,
|
|
342
|
+
force=force,
|
|
343
|
+
dry_run=dry_run,
|
|
344
|
+
output_dir=output_dir,
|
|
345
|
+
debug=debug,
|
|
346
|
+
)
|
create_ai/cli/console.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Shared Rich consoles and small rendering helpers.
|
|
2
|
+
|
|
3
|
+
Kept deliberately spare: a couple of consoles, an error panel, a key/value
|
|
4
|
+
table. No animations, no banners bigger than a single line.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
from create_ai.errors import CreateAIError
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
err_console = Console(stderr=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def print_error(exc: CreateAIError) -> None:
|
|
21
|
+
"""Render a typed error as a compact red panel with an optional hint."""
|
|
22
|
+
|
|
23
|
+
body = Text(exc.message)
|
|
24
|
+
if exc.hint:
|
|
25
|
+
body.append("\n\n")
|
|
26
|
+
body.append(exc.hint, style="dim")
|
|
27
|
+
err_console.print(Panel(body, title="[bold red]✗ error", border_style="red", expand=False))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def summary_table(rows: list[tuple[str, str]], *, title: str | None = None) -> Table:
|
|
31
|
+
table = Table(show_header=False, box=None, pad_edge=False, title=title)
|
|
32
|
+
table.add_column(style="dim")
|
|
33
|
+
table.add_column(style="bold")
|
|
34
|
+
for label, value in rows:
|
|
35
|
+
table.add_row(label, value)
|
|
36
|
+
return table
|