clified 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clified/__init__.py +3 -0
- clified/__main__.py +7 -0
- clified/bundled/__init__.py +0 -0
- clified/bundled/config/clified.yml.example +17 -0
- clified/bundled/config/gamedev-constraints.txt.example +5 -0
- clified/bundled/config/install-all-constraints.txt +2 -0
- clified/bundled/examples/tools.denv.yaml.example +17 -0
- clified/bundled/examples/tools.gamedev.yaml.example +63 -0
- clified/bundled/examples/tools.pc.yaml.example +17 -0
- clified/bundled/tools.yaml.example +8 -0
- clified/cli/__init__.py +19 -0
- clified/cli/app.py +56 -0
- clified/cli/decorators.py +61 -0
- clified/cli/output.py +103 -0
- clified/cli/progress.py +106 -0
- clified/core/__init__.py +56 -0
- clified/core/circuit_breaker.py +195 -0
- clified/core/config.py +113 -0
- clified/core/exceptions.py +56 -0
- clified/core/paths.py +72 -0
- clified/core/retry.py +179 -0
- clified/core/state_store.py +117 -0
- clified/hooks/__init__.py +18 -0
- clified/hooks/mcp.py +36 -0
- clified/hooks/pytorch.py +72 -0
- clified/hooks/skills.py +68 -0
- clified/installer/__init__.py +45 -0
- clified/installer/__main__.py +7 -0
- clified/installer/base.py +442 -0
- clified/installer/bootstrap.py +57 -0
- clified/installer/bun_installer.py +232 -0
- clified/installer/python_installer.py +380 -0
- clified/installer/registry.py +290 -0
- clified/installer/rust_installer.py +177 -0
- clified/installer/unified.py +574 -0
- clified/integrations/__init__.py +9 -0
- clified/integrations/mcp.py +91 -0
- clified/logging.py +114 -0
- clified/paths.py +103 -0
- clified/patterns/__init__.py +20 -0
- clified/patterns/base.json +287 -0
- clified/patterns/loader.py +251 -0
- clified/patterns/reporter.py +50 -0
- clified/patterns/services/build.json +56 -0
- clified/patterns/services/python.json +31 -0
- clified-0.4.0.dist-info/METADATA +181 -0
- clified-0.4.0.dist-info/RECORD +50 -0
- clified-0.4.0.dist-info/WHEEL +4 -0
- clified-0.4.0.dist-info/entry_points.txt +3 -0
- clified-0.4.0.dist-info/licenses/LICENSE +21 -0
clified/__init__.py
ADDED
clified/__main__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Configuração global opcional do Clified.
|
|
2
|
+
# Copie para config/clified.yml para personalizar.
|
|
3
|
+
|
|
4
|
+
base_dir: ~/.clified
|
|
5
|
+
|
|
6
|
+
logging:
|
|
7
|
+
level: INFO
|
|
8
|
+
|
|
9
|
+
retry:
|
|
10
|
+
max_attempts: 3
|
|
11
|
+
base_delay: 2.0
|
|
12
|
+
max_delay: 60.0
|
|
13
|
+
|
|
14
|
+
install:
|
|
15
|
+
prefix: ~/.local
|
|
16
|
+
use_uv: true
|
|
17
|
+
default_python: python3
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Exemplo: registrar denv (LocatelliDockerManager) via Clified
|
|
2
|
+
|
|
3
|
+
workspace:
|
|
4
|
+
root: "/home/maikeu/GitClones"
|
|
5
|
+
name: "GitClones"
|
|
6
|
+
|
|
7
|
+
tools:
|
|
8
|
+
denv:
|
|
9
|
+
name: "DENV"
|
|
10
|
+
kind: python
|
|
11
|
+
folder: LocatelliDockerManager
|
|
12
|
+
cli_name: denv
|
|
13
|
+
python_module: denv
|
|
14
|
+
description: "Docker Environment Manager — Swarm, stacks, diagnóstico"
|
|
15
|
+
min_python: [3, 12]
|
|
16
|
+
post_install: clified.hooks:register_mcp_serve
|
|
17
|
+
# Instalação: poetry/pip install -e no projecto; Clified gere venv + wrapper
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Exemplo: migrar monorepo GameDev para Clified
|
|
2
|
+
# Copie secções relevantes para o seu tools.yaml
|
|
3
|
+
# Hooks específicos ficam no repo GameDev (gamedev_shared.installer.clified_hooks)
|
|
4
|
+
|
|
5
|
+
workspace:
|
|
6
|
+
root: "/caminho/para/GameDev"
|
|
7
|
+
name: "GameDev"
|
|
8
|
+
shared_python:
|
|
9
|
+
path: Shared
|
|
10
|
+
import_name: gamedev_shared
|
|
11
|
+
src_subpath: src
|
|
12
|
+
|
|
13
|
+
tools:
|
|
14
|
+
text2d:
|
|
15
|
+
kind: python
|
|
16
|
+
folder: Text2D
|
|
17
|
+
cli_name: text2d
|
|
18
|
+
python_module: text2d
|
|
19
|
+
description: "CLI text-to-image"
|
|
20
|
+
min_python: [3, 13]
|
|
21
|
+
needs_pytorch: true
|
|
22
|
+
install_order: -2
|
|
23
|
+
post_install: clified.hooks:pip_check
|
|
24
|
+
|
|
25
|
+
text3d:
|
|
26
|
+
kind: python
|
|
27
|
+
folder: Text3D
|
|
28
|
+
cli_name: text3d
|
|
29
|
+
python_module: text3d
|
|
30
|
+
description: "Pipeline text-to-3D"
|
|
31
|
+
min_python: [3, 13]
|
|
32
|
+
needs_pytorch: true
|
|
33
|
+
install_order: -1
|
|
34
|
+
install_before: [text2d]
|
|
35
|
+
cross_deps: [text2d]
|
|
36
|
+
post_install: gamedev_shared.installer.clified_hooks:text3d_post_install
|
|
37
|
+
|
|
38
|
+
materialize:
|
|
39
|
+
kind: rust
|
|
40
|
+
folder: Materialize
|
|
41
|
+
cli_name: materialize
|
|
42
|
+
cargo_bin_name: materialize-cli
|
|
43
|
+
description: "PBR maps via GPU"
|
|
44
|
+
|
|
45
|
+
text2sound:
|
|
46
|
+
kind: python
|
|
47
|
+
folder: Text2Sound
|
|
48
|
+
cli_name: text2sound
|
|
49
|
+
python_module: text2sound
|
|
50
|
+
description: "CLI text-to-audio"
|
|
51
|
+
min_python: [3, 13]
|
|
52
|
+
needs_pytorch: true
|
|
53
|
+
custom_install: gamedev_shared.installer.clified_hooks:text2sound_custom_install
|
|
54
|
+
|
|
55
|
+
paint3d:
|
|
56
|
+
kind: python
|
|
57
|
+
folder: Paint3D
|
|
58
|
+
cli_name: paint3d
|
|
59
|
+
python_module: paint3d
|
|
60
|
+
description: "Texturização 3D PBR"
|
|
61
|
+
min_python: [3, 13]
|
|
62
|
+
needs_pytorch: true
|
|
63
|
+
post_install: gamedev_shared.installer.clified_hooks:paint3d_post_install
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Exemplo: registrar pc CLI (ProjetoCursor) via Clified
|
|
2
|
+
|
|
3
|
+
workspace:
|
|
4
|
+
root: "/home/maikeu/GitClones/ProjetoCursor"
|
|
5
|
+
name: "ProjetoCursor"
|
|
6
|
+
|
|
7
|
+
tools:
|
|
8
|
+
pc:
|
|
9
|
+
name: "PC CLI"
|
|
10
|
+
kind: python
|
|
11
|
+
folder: tools
|
|
12
|
+
cli_name: pc
|
|
13
|
+
python_module: pc
|
|
14
|
+
description: "Flutter + Supabase + deploy — Projeto Cursor"
|
|
15
|
+
min_python: [3, 10]
|
|
16
|
+
custom_install: clified_install:custom_install
|
|
17
|
+
post_install: clified_install:post_install
|
clified/cli/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Utilitários CLI reutilizáveis."""
|
|
2
|
+
|
|
3
|
+
from .decorators import (
|
|
4
|
+
EXIT_CONFIG_ERROR,
|
|
5
|
+
EXIT_GENERAL_ERROR,
|
|
6
|
+
EXIT_INFRA_ERROR,
|
|
7
|
+
EXIT_SUCCESS,
|
|
8
|
+
handle_cli_errors,
|
|
9
|
+
)
|
|
10
|
+
from .output import OutputFormatter
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"EXIT_CONFIG_ERROR",
|
|
14
|
+
"EXIT_GENERAL_ERROR",
|
|
15
|
+
"EXIT_INFRA_ERROR",
|
|
16
|
+
"EXIT_SUCCESS",
|
|
17
|
+
"OutputFormatter",
|
|
18
|
+
"handle_cli_errors",
|
|
19
|
+
]
|
clified/cli/app.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Scaffold Click reutilizável (--json, --quiet, --verbose)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import click
|
|
9
|
+
except ImportError: # pragma: no cover - optional dep
|
|
10
|
+
click = None # type: ignore[assignment]
|
|
11
|
+
|
|
12
|
+
from .output import OutputFormatter, get_output_formatter
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def require_click() -> None:
|
|
19
|
+
if click is None:
|
|
20
|
+
msg = "Click não instalado. Use: pip install clified[cli]"
|
|
21
|
+
raise ImportError(
|
|
22
|
+
msg,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_cli_group(name: str = "cli", help_text: str = "CLI tool") -> Any:
|
|
27
|
+
"""Cria grupo Click com opções globais de output."""
|
|
28
|
+
require_click()
|
|
29
|
+
|
|
30
|
+
@click.group(name=name, help=help_text)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--json", "json_output", is_flag=True, help="Saída JSON (machine-readable)"
|
|
33
|
+
)
|
|
34
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suprime output decorativo")
|
|
35
|
+
@click.option("-v", "--verbose", is_flag=True, help="Modo verboso")
|
|
36
|
+
@click.pass_context
|
|
37
|
+
def group(
|
|
38
|
+
ctx: click.Context, json_output: bool, quiet: bool, verbose: bool
|
|
39
|
+
) -> None:
|
|
40
|
+
ctx.ensure_object(dict)
|
|
41
|
+
ctx.obj["output"] = OutputFormatter(json_mode=json_output, quiet=quiet)
|
|
42
|
+
ctx.obj["verbose"] = verbose
|
|
43
|
+
|
|
44
|
+
return group
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def pass_output(func: Callable) -> Callable:
|
|
48
|
+
"""Decorator que injecta ``OutputFormatter`` como kwarg ``output``."""
|
|
49
|
+
require_click()
|
|
50
|
+
|
|
51
|
+
@click.pass_context
|
|
52
|
+
def wrapper(ctx: click.Context, *args: Any, **kwargs: Any) -> Any:
|
|
53
|
+
kwargs["output"] = get_output_formatter(ctx)
|
|
54
|
+
return func(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
return wrapper
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Decorators para comandos CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import sys
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from clified.core.exceptions import (
|
|
10
|
+
ClifiedError,
|
|
11
|
+
ConfigError,
|
|
12
|
+
DeploymentError,
|
|
13
|
+
InfrastructureError,
|
|
14
|
+
InstallError,
|
|
15
|
+
NetworkError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
)
|
|
18
|
+
from clified.logging import Logger
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
|
|
23
|
+
logger = Logger()
|
|
24
|
+
|
|
25
|
+
EXIT_SUCCESS = 0
|
|
26
|
+
EXIT_GENERAL_ERROR = 1
|
|
27
|
+
EXIT_CONFIG_ERROR = 2
|
|
28
|
+
EXIT_INFRA_ERROR = 3
|
|
29
|
+
|
|
30
|
+
_CONFIG_EXCEPTIONS: tuple[type[Exception], ...] = (ConfigError, ValidationError)
|
|
31
|
+
_INFRA_EXCEPTIONS: tuple[type[Exception], ...] = (
|
|
32
|
+
InfrastructureError,
|
|
33
|
+
NetworkError,
|
|
34
|
+
DeploymentError,
|
|
35
|
+
InstallError,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def handle_cli_errors(func: Callable) -> Callable:
|
|
40
|
+
"""Padroniza exit codes: 0 ok, 1 geral, 2 config, 3 infra."""
|
|
41
|
+
|
|
42
|
+
@functools.wraps(func)
|
|
43
|
+
def wrapper(*args, **kwargs):
|
|
44
|
+
try:
|
|
45
|
+
return func(*args, **kwargs)
|
|
46
|
+
except (SystemExit, KeyboardInterrupt):
|
|
47
|
+
raise
|
|
48
|
+
except _CONFIG_EXCEPTIONS as exc:
|
|
49
|
+
logger.error(str(exc))
|
|
50
|
+
sys.exit(EXIT_CONFIG_ERROR)
|
|
51
|
+
except _INFRA_EXCEPTIONS as exc:
|
|
52
|
+
logger.error(str(exc))
|
|
53
|
+
sys.exit(EXIT_INFRA_ERROR)
|
|
54
|
+
except ClifiedError as exc:
|
|
55
|
+
logger.error(str(exc))
|
|
56
|
+
sys.exit(EXIT_GENERAL_ERROR)
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
logger.error(f"Erro inesperado: {exc}")
|
|
59
|
+
sys.exit(EXIT_GENERAL_ERROR)
|
|
60
|
+
|
|
61
|
+
return wrapper
|
clified/cli/output.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Saída CLI: Rich, JSON e quiet."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from rich import box
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OutputFormatter:
|
|
16
|
+
"""Formata saída para humanos ou máquinas (JSON/quiet)."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, json_mode: bool = False, quiet: bool = False) -> None:
|
|
19
|
+
self.json_mode = json_mode
|
|
20
|
+
self.quiet = quiet
|
|
21
|
+
self._console = Console(stderr=True) if json_mode else Console()
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def is_json(self) -> bool:
|
|
25
|
+
return self.json_mode
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def is_quiet(self) -> bool:
|
|
29
|
+
return self.quiet
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def console(self) -> Console:
|
|
33
|
+
return self._console
|
|
34
|
+
|
|
35
|
+
def success(self, data: dict[str, Any], message: str = "") -> None:
|
|
36
|
+
if self.json_mode:
|
|
37
|
+
self._print_json({"status": "success", **data})
|
|
38
|
+
elif not self.quiet and message:
|
|
39
|
+
self._console.print(f"[green]{message}[/green]")
|
|
40
|
+
|
|
41
|
+
def error(self, message: str, details: dict[str, Any] | None = None) -> None:
|
|
42
|
+
if self.json_mode:
|
|
43
|
+
payload: dict[str, Any] = {"status": "error", "error": message}
|
|
44
|
+
if details:
|
|
45
|
+
payload["details"] = details
|
|
46
|
+
self._print_json(payload)
|
|
47
|
+
else:
|
|
48
|
+
self._console.print(f"[red]{message}[/red]", highlight=False)
|
|
49
|
+
|
|
50
|
+
def info(self, message: str) -> None:
|
|
51
|
+
if self.json_mode or self.quiet:
|
|
52
|
+
return
|
|
53
|
+
self._console.print(message)
|
|
54
|
+
|
|
55
|
+
def warning(self, message: str) -> None:
|
|
56
|
+
if self.json_mode or self.quiet:
|
|
57
|
+
return
|
|
58
|
+
self._console.print(f"[yellow]{message}[/yellow]")
|
|
59
|
+
|
|
60
|
+
def warn(self, message: str) -> None:
|
|
61
|
+
"""Alias de ``warning``."""
|
|
62
|
+
self.warning(message)
|
|
63
|
+
|
|
64
|
+
def data(self, payload: dict[str, Any], title: str = "") -> None:
|
|
65
|
+
if self.json_mode:
|
|
66
|
+
self._print_json({"status": "success", **payload})
|
|
67
|
+
elif not self.quiet:
|
|
68
|
+
if title:
|
|
69
|
+
text = json.dumps(payload, indent=2, default=str, ensure_ascii=False)
|
|
70
|
+
self._console.print(Panel(text, title=title, border_style="blue"))
|
|
71
|
+
else:
|
|
72
|
+
self._console.print_json(data=payload)
|
|
73
|
+
|
|
74
|
+
def table(
|
|
75
|
+
self,
|
|
76
|
+
rows: list[dict[str, Any]],
|
|
77
|
+
columns: list[str],
|
|
78
|
+
title: str = "",
|
|
79
|
+
json_key: str = "items",
|
|
80
|
+
) -> None:
|
|
81
|
+
if self.json_mode:
|
|
82
|
+
self._print_json({"status": "success", json_key: rows, "count": len(rows)})
|
|
83
|
+
return
|
|
84
|
+
if self.quiet:
|
|
85
|
+
return
|
|
86
|
+
t = Table(title=title, box=box.ROUNDED, show_header=True, header_style="bold")
|
|
87
|
+
for col in columns:
|
|
88
|
+
t.add_column(col)
|
|
89
|
+
for row in rows:
|
|
90
|
+
t.add_row(*[str(row.get(col, "")) for col in columns])
|
|
91
|
+
self._console.print(t)
|
|
92
|
+
|
|
93
|
+
def _print_json(self, obj: Any) -> None:
|
|
94
|
+
sys.stdout.write(json.dumps(obj, indent=2, default=str, ensure_ascii=False))
|
|
95
|
+
sys.stdout.write("\n")
|
|
96
|
+
sys.stdout.flush()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_output_formatter(ctx: Any = None) -> OutputFormatter:
|
|
100
|
+
"""Obtém formatter do contexto Click ou cria um por defeito."""
|
|
101
|
+
if ctx is not None and hasattr(ctx, "obj") and ctx.obj and "output" in ctx.obj:
|
|
102
|
+
return ctx.obj["output"]
|
|
103
|
+
return OutputFormatter()
|
clified/cli/progress.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""UI de etapas para operações longas (inspirado no DeployUI do pc CLI)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from clified.logging import Logger
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Callable, Iterator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Step:
|
|
18
|
+
name: str
|
|
19
|
+
description: str = ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class StepResult:
|
|
24
|
+
name: str
|
|
25
|
+
success: bool
|
|
26
|
+
duration: float
|
|
27
|
+
message: str = ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class StepRunner:
|
|
31
|
+
"""Executa etapas nomeadas com logging Rich/ANSI."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
steps: list[Step],
|
|
36
|
+
*,
|
|
37
|
+
title: str = "Instalação",
|
|
38
|
+
logger: Logger | None = None,
|
|
39
|
+
quiet: bool = False,
|
|
40
|
+
) -> None:
|
|
41
|
+
self.steps = steps
|
|
42
|
+
self.title = title
|
|
43
|
+
self.logger = logger or Logger()
|
|
44
|
+
self.quiet = quiet
|
|
45
|
+
self.results: list[StepResult] = []
|
|
46
|
+
self._start = time.time()
|
|
47
|
+
|
|
48
|
+
@contextmanager
|
|
49
|
+
def step(self, index: int) -> Iterator[None]:
|
|
50
|
+
if index < 0 or index >= len(self.steps):
|
|
51
|
+
msg = f"Step index {index} fora do intervalo"
|
|
52
|
+
raise IndexError(msg)
|
|
53
|
+
spec = self.steps[index]
|
|
54
|
+
if not self.quiet:
|
|
55
|
+
self.logger.step(f"[{index + 1}/{len(self.steps)}] {spec.name}")
|
|
56
|
+
if spec.description:
|
|
57
|
+
self.logger.info(spec.description)
|
|
58
|
+
t0 = time.time()
|
|
59
|
+
success = True
|
|
60
|
+
message = ""
|
|
61
|
+
try:
|
|
62
|
+
yield
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
success = False
|
|
65
|
+
message = str(exc)
|
|
66
|
+
raise
|
|
67
|
+
finally:
|
|
68
|
+
duration = time.time() - t0
|
|
69
|
+
self.results.append(
|
|
70
|
+
StepResult(
|
|
71
|
+
name=spec.name, success=success, duration=duration, message=message
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
if success and not self.quiet:
|
|
75
|
+
self.logger.success(f"{spec.name} ({duration:.1f}s)")
|
|
76
|
+
|
|
77
|
+
def run(self, actions: list[Callable[[], Any]]) -> bool:
|
|
78
|
+
if len(actions) != len(self.steps):
|
|
79
|
+
msg = "Número de acções deve igualar número de etapas"
|
|
80
|
+
raise ValueError(msg)
|
|
81
|
+
if not self.quiet:
|
|
82
|
+
self.logger.header(self.title)
|
|
83
|
+
ok = True
|
|
84
|
+
for i, action in enumerate(actions):
|
|
85
|
+
try:
|
|
86
|
+
with self.step(i):
|
|
87
|
+
action()
|
|
88
|
+
except Exception:
|
|
89
|
+
ok = False
|
|
90
|
+
break
|
|
91
|
+
return ok
|
|
92
|
+
|
|
93
|
+
def summary(self) -> dict[str, Any]:
|
|
94
|
+
return {
|
|
95
|
+
"title": self.title,
|
|
96
|
+
"total_seconds": time.time() - self._start,
|
|
97
|
+
"steps": [
|
|
98
|
+
{
|
|
99
|
+
"name": r.name,
|
|
100
|
+
"success": r.success,
|
|
101
|
+
"duration": round(r.duration, 2),
|
|
102
|
+
"message": r.message,
|
|
103
|
+
}
|
|
104
|
+
for r in self.results
|
|
105
|
+
],
|
|
106
|
+
}
|
clified/core/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Core Clified — config, retry, circuit breaker, state."""
|
|
2
|
+
|
|
3
|
+
from .circuit_breaker import CircuitBreaker, CircuitBreakerConfig, get_circuit_breaker
|
|
4
|
+
from .config import (
|
|
5
|
+
ClifiedConfig,
|
|
6
|
+
ConfigManager,
|
|
7
|
+
InstallConfig,
|
|
8
|
+
LoggingConfig,
|
|
9
|
+
RetryConfig,
|
|
10
|
+
)
|
|
11
|
+
from .exceptions import (
|
|
12
|
+
ClifiedError,
|
|
13
|
+
ConfigError,
|
|
14
|
+
ConnectionError,
|
|
15
|
+
DeploymentError,
|
|
16
|
+
InfrastructureError,
|
|
17
|
+
InstallError,
|
|
18
|
+
NetworkError,
|
|
19
|
+
RetryableError,
|
|
20
|
+
TimeoutError,
|
|
21
|
+
ToolError,
|
|
22
|
+
ValidationError,
|
|
23
|
+
)
|
|
24
|
+
from .paths import find_project_root, resolve_project_file
|
|
25
|
+
from .retry import RetryEngine, RetryPolicy, RetryResult, with_retry
|
|
26
|
+
from .state_store import StateStore, get_state_store
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"CircuitBreaker",
|
|
30
|
+
"CircuitBreakerConfig",
|
|
31
|
+
"ClifiedConfig",
|
|
32
|
+
"ClifiedError",
|
|
33
|
+
"ConfigError",
|
|
34
|
+
"ConfigManager",
|
|
35
|
+
"ConnectionError",
|
|
36
|
+
"DeploymentError",
|
|
37
|
+
"InfrastructureError",
|
|
38
|
+
"InstallConfig",
|
|
39
|
+
"InstallError",
|
|
40
|
+
"LoggingConfig",
|
|
41
|
+
"NetworkError",
|
|
42
|
+
"RetryConfig",
|
|
43
|
+
"RetryEngine",
|
|
44
|
+
"RetryPolicy",
|
|
45
|
+
"RetryResult",
|
|
46
|
+
"RetryableError",
|
|
47
|
+
"StateStore",
|
|
48
|
+
"TimeoutError",
|
|
49
|
+
"ToolError",
|
|
50
|
+
"ValidationError",
|
|
51
|
+
"find_project_root",
|
|
52
|
+
"get_circuit_breaker",
|
|
53
|
+
"get_state_store",
|
|
54
|
+
"resolve_project_file",
|
|
55
|
+
"with_retry",
|
|
56
|
+
]
|