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.
Files changed (50) hide show
  1. clified/__init__.py +3 -0
  2. clified/__main__.py +7 -0
  3. clified/bundled/__init__.py +0 -0
  4. clified/bundled/config/clified.yml.example +17 -0
  5. clified/bundled/config/gamedev-constraints.txt.example +5 -0
  6. clified/bundled/config/install-all-constraints.txt +2 -0
  7. clified/bundled/examples/tools.denv.yaml.example +17 -0
  8. clified/bundled/examples/tools.gamedev.yaml.example +63 -0
  9. clified/bundled/examples/tools.pc.yaml.example +17 -0
  10. clified/bundled/tools.yaml.example +8 -0
  11. clified/cli/__init__.py +19 -0
  12. clified/cli/app.py +56 -0
  13. clified/cli/decorators.py +61 -0
  14. clified/cli/output.py +103 -0
  15. clified/cli/progress.py +106 -0
  16. clified/core/__init__.py +56 -0
  17. clified/core/circuit_breaker.py +195 -0
  18. clified/core/config.py +113 -0
  19. clified/core/exceptions.py +56 -0
  20. clified/core/paths.py +72 -0
  21. clified/core/retry.py +179 -0
  22. clified/core/state_store.py +117 -0
  23. clified/hooks/__init__.py +18 -0
  24. clified/hooks/mcp.py +36 -0
  25. clified/hooks/pytorch.py +72 -0
  26. clified/hooks/skills.py +68 -0
  27. clified/installer/__init__.py +45 -0
  28. clified/installer/__main__.py +7 -0
  29. clified/installer/base.py +442 -0
  30. clified/installer/bootstrap.py +57 -0
  31. clified/installer/bun_installer.py +232 -0
  32. clified/installer/python_installer.py +380 -0
  33. clified/installer/registry.py +290 -0
  34. clified/installer/rust_installer.py +177 -0
  35. clified/installer/unified.py +574 -0
  36. clified/integrations/__init__.py +9 -0
  37. clified/integrations/mcp.py +91 -0
  38. clified/logging.py +114 -0
  39. clified/paths.py +103 -0
  40. clified/patterns/__init__.py +20 -0
  41. clified/patterns/base.json +287 -0
  42. clified/patterns/loader.py +251 -0
  43. clified/patterns/reporter.py +50 -0
  44. clified/patterns/services/build.json +56 -0
  45. clified/patterns/services/python.json +31 -0
  46. clified-0.4.0.dist-info/METADATA +181 -0
  47. clified-0.4.0.dist-info/RECORD +50 -0
  48. clified-0.4.0.dist-info/WHEEL +4 -0
  49. clified-0.4.0.dist-info/entry_points.txt +3 -0
  50. clified-0.4.0.dist-info/licenses/LICENSE +21 -0
clified/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Clified — instalador universal e biblioteca CLI."""
2
+
3
+ __version__ = "0.4.0"
clified/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Permite ``python -m clified``."""
2
+
3
+ import sys
4
+
5
+ from clified.installer.unified import main
6
+
7
+ sys.exit(main())
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,5 @@
1
+ # Constraints para ``clified-install all`` em workspaces ML (GameDev)
2
+ # Copie para config/install-all-constraints.txt quando usar várias tools PyTorch
3
+
4
+ transformers==4.51.3
5
+ numpy>=2.4.5,<3
@@ -0,0 +1,2 @@
1
+ # Constraints opcionais para ``clified-install all`` (pip -c).
2
+ # Deixe vazio ou comente linhas que não usar.
@@ -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
@@ -0,0 +1,8 @@
1
+ # Template — copiado para ~/.config/clified/tools.yaml na primeira execução
2
+ # Cada projecto deve usar o seu tools.yaml via CLIFIED_TOOLS (recomendado).
3
+
4
+ workspace:
5
+ root: .
6
+ name: "Workspace"
7
+
8
+ tools: {}
@@ -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()
@@ -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
+ }
@@ -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
+ ]