pep-agentes 1.2.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 (34) hide show
  1. pep/__init__.py +5 -0
  2. pep/cli.py +69 -0
  3. pep/core/__init__.py +1 -0
  4. pep/core/detection.py +47 -0
  5. pep/core/markers.py +66 -0
  6. pep/core/models.py +41 -0
  7. pep/core/paths.py +27 -0
  8. pep/core/version.py +5 -0
  9. pep/providers/__init__.py +24 -0
  10. pep/providers/base.py +23 -0
  11. pep/providers/claude.py +101 -0
  12. pep/providers/codex.py +131 -0
  13. pep/services/__init__.py +1 -0
  14. pep/services/manager.py +93 -0
  15. pep_agentes-1.2.0.data/data/share/pep-agentes/claude/CLAUDE.md +45 -0
  16. pep_agentes-1.2.0.data/data/share/pep-agentes/claude/commands/pep.md +29 -0
  17. pep_agentes-1.2.0.data/data/share/pep-agentes/codex/AGENTS.md +59 -0
  18. pep_agentes-1.2.0.data/data/share/pep-agentes/codex/prompts/pepcodex.md +15 -0
  19. pep_agentes-1.2.0.data/data/share/pep-agentes/codex/skills/pepcodex/SKILL.md +139 -0
  20. pep_agentes-1.2.0.data/data/share/pep-agentes/codex/skills/pepcodex/agents/openai.yaml +6 -0
  21. pep_agentes-1.2.0.data/data/share/pep-agentes/codex/skills/pepcodex/references/PROTOCOLO.md +141 -0
  22. pep_agentes-1.2.0.data/data/share/pep-agentes/codex/skills/pepcodex/tools/context_eco.py +340 -0
  23. pep_agentes-1.2.0.data/data/share/pep-agentes/docs/COMO_FOI_GERADO.md +34 -0
  24. pep_agentes-1.2.0.data/data/share/pep-agentes/docs/PEP-CODEX.md +67 -0
  25. pep_agentes-1.2.0.data/data/share/pep-agentes/docs/documentacao.pdf +74 -0
  26. pep_agentes-1.2.0.data/data/share/pep-agentes/prompts/pep-agentes-claude.txt +132 -0
  27. pep_agentes-1.2.0.data/data/share/pep-agentes/prompts/pep-agentes-completo.txt +207 -0
  28. pep_agentes-1.2.0.data/data/share/pep-agentes/prompts/pep-agentes-curto.txt +7 -0
  29. pep_agentes-1.2.0.dist-info/METADATA +192 -0
  30. pep_agentes-1.2.0.dist-info/RECORD +34 -0
  31. pep_agentes-1.2.0.dist-info/WHEEL +5 -0
  32. pep_agentes-1.2.0.dist-info/entry_points.txt +2 -0
  33. pep_agentes-1.2.0.dist-info/licenses/LICENSE +201 -0
  34. pep_agentes-1.2.0.dist-info/top_level.txt +1 -0
pep/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """PEP-Agentes Manager core package."""
2
+
3
+ from pep.core.version import __version__
4
+
5
+ __all__ = ["__version__"]
pep/cli.py ADDED
@@ -0,0 +1,69 @@
1
+ """Console entrypoint for the PEP-Agentes Manager."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from pep.core.version import __version__
8
+ from pep.services.manager import (
9
+ collect_doctor,
10
+ collect_status,
11
+ format_results,
12
+ format_status,
13
+ resolve_scopes,
14
+ run_operation,
15
+ )
16
+
17
+ PROVIDERS = ("claude", "codex", "all")
18
+
19
+
20
+ def add_scope_args(parser: argparse.ArgumentParser) -> None:
21
+ parser.add_argument("--here", action="store_true", help="usar o diretorio atual")
22
+ parser.add_argument("--path", action="append", metavar="CAMINHO", help="usar um projeto especifico")
23
+ parser.add_argument("--global", dest="global_scope", action="store_true", help="usar instalacao global")
24
+ parser.add_argument("--legacy-prompt", action="store_true", help="incluir /prompts:pepcodex para Codex")
25
+
26
+
27
+ def build_parser() -> argparse.ArgumentParser:
28
+ parser = argparse.ArgumentParser(description="PEP-Agentes Manager")
29
+ sub = parser.add_subparsers(dest="command", required=True)
30
+ for command in ("install", "update", "uninstall", "repair", "status"):
31
+ p = sub.add_parser(command)
32
+ p.add_argument("provider", nargs="?", default="all", choices=PROVIDERS)
33
+ add_scope_args(p)
34
+ if command in {"install", "update"}:
35
+ p.add_argument("--force", action="store_true", help="sobrescrever arquivos gerenciados")
36
+ p = sub.add_parser("doctor")
37
+ p.add_argument("provider", nargs="?", default="all", choices=PROVIDERS)
38
+ add_scope_args(p)
39
+ sub.add_parser("version")
40
+ return parser
41
+
42
+
43
+ def main(argv: list[str] | None = None) -> int:
44
+ args = build_parser().parse_args(argv)
45
+ if args.command == "version":
46
+ print(__version__)
47
+ return 0
48
+
49
+ scopes = resolve_scopes(args.here, args.path, args.global_scope)
50
+ if not scopes:
51
+ print("Nenhum alvo selecionado. Use --here, --path CAMINHO ou --global.")
52
+ return 2
53
+
54
+ if args.command == "status":
55
+ print(format_status(collect_status(args.provider, scopes, args.legacy_prompt)))
56
+ return 0
57
+ if args.command == "doctor":
58
+ print(collect_doctor(args.provider, scopes, args.legacy_prompt))
59
+ return 0
60
+
61
+ results = run_operation(
62
+ args.command,
63
+ args.provider,
64
+ scopes,
65
+ force=getattr(args, "force", False),
66
+ legacy_prompt=args.legacy_prompt,
67
+ )
68
+ print(format_results(results))
69
+ return 0
pep/core/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Core utilities for PEP-Agentes Manager."""
pep/core/detection.py ADDED
@@ -0,0 +1,47 @@
1
+ """Local-only tool detection used by doctor/status."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import platform
7
+ import shutil
8
+ import sys
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class ToolStatus:
14
+ name: str
15
+ found: bool
16
+ detail: str
17
+
18
+
19
+ def detect_command(name: str, command: str) -> ToolStatus:
20
+ path = shutil.which(command)
21
+ return ToolStatus(name, bool(path), path or "nao encontrado no PATH")
22
+
23
+
24
+ def detect_python() -> ToolStatus:
25
+ return ToolStatus("Python", True, sys.version.split()[0])
26
+
27
+
28
+ def detect_system() -> ToolStatus:
29
+ return ToolStatus("Sistema", True, f"{platform.system()} {platform.release()}".strip())
30
+
31
+
32
+ def detect_genpyexe() -> ToolStatus:
33
+ if importlib.util.find_spec("genpyexeks"):
34
+ return ToolStatus("GenPyEXE", True, "modulo genpyexeks disponivel")
35
+ path = shutil.which("genpyexe")
36
+ return ToolStatus("GenPyEXE", bool(path), path or "instale com: pip install genpyexe")
37
+
38
+
39
+ def doctor_tools() -> list[ToolStatus]:
40
+ return [
41
+ detect_system(),
42
+ detect_python(),
43
+ detect_command("Git", "git"),
44
+ detect_command("Claude Code", "claude"),
45
+ detect_command("Codex", "codex"),
46
+ detect_genpyexe(),
47
+ ]
pep/core/markers.py ADDED
@@ -0,0 +1,66 @@
1
+ """Marker-managed Markdown block operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+
9
+ class MarkerError(RuntimeError):
10
+ """Raised when a managed marker block is malformed."""
11
+
12
+
13
+ def _has_broken_markers(text: str, start: str, end: str, pattern: re.Pattern[str]) -> bool:
14
+ starts = text.count(start)
15
+ ends = text.count(end)
16
+ if starts != ends:
17
+ return True
18
+ return starts > 0 and not pattern.search(text)
19
+
20
+
21
+ def read_managed_block(source: Path, pattern: re.Pattern[str]) -> str:
22
+ text = source.read_text(encoding="utf-8")
23
+ match = pattern.search(text)
24
+ if not match:
25
+ raise MarkerError(f"Marcadores PEP nao encontrados em {source}")
26
+ return match.group(0)
27
+
28
+
29
+ def upsert_block(path: Path, block: str, pattern: re.Pattern[str], start: str, end: str, header: str) -> str:
30
+ path.parent.mkdir(parents=True, exist_ok=True)
31
+ if path.exists():
32
+ current = path.read_text(encoding="utf-8")
33
+ if _has_broken_markers(current, start, end, pattern):
34
+ raise MarkerError(f"Markers corrompidos em {path}")
35
+ if pattern.search(current):
36
+ path.write_text(pattern.sub(lambda _: block, current), encoding="utf-8")
37
+ return "atualizado"
38
+ sep = "" if current.endswith("\n") else "\n"
39
+ path.write_text(f"{current}{sep}\n{block}\n", encoding="utf-8")
40
+ return "bloco adicionado"
41
+ path.write_text(f"{header}{block}\n", encoding="utf-8")
42
+ return "criado"
43
+
44
+
45
+ def remove_block(path: Path, pattern: re.Pattern[str], start: str, end: str) -> str:
46
+ if not path.exists():
47
+ return "inexistente"
48
+ current = path.read_text(encoding="utf-8")
49
+ if _has_broken_markers(current, start, end, pattern):
50
+ raise MarkerError(f"Markers corrompidos em {path}")
51
+ if not pattern.search(current):
52
+ return "sem bloco"
53
+ path.write_text(pattern.sub("", current).rstrip() + "\n", encoding="utf-8")
54
+ return "bloco removido"
55
+
56
+
57
+ def block_state(path: Path, expected: str, pattern: re.Pattern[str], start: str, end: str) -> str:
58
+ if not path.exists():
59
+ return "missing"
60
+ current = path.read_text(encoding="utf-8")
61
+ if _has_broken_markers(current, start, end, pattern):
62
+ return "corrupt"
63
+ match = pattern.search(current)
64
+ if not match:
65
+ return "missing"
66
+ return "current" if match.group(0) == expected else "outdated"
pep/core/models.py ADDED
@@ -0,0 +1,41 @@
1
+ """Small data models used by providers and services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from pathlib import Path
8
+
9
+
10
+ class InstallState(str, Enum):
11
+ NOT_INSTALLED = "not_installed"
12
+ INSTALLED = "installed"
13
+ CURRENT = "current"
14
+ OUTDATED = "outdated"
15
+ PARTIAL = "partial"
16
+ CORRUPT = "corrupt"
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class Scope:
21
+ path: Path
22
+ is_global: bool = False
23
+
24
+ @property
25
+ def label(self) -> str:
26
+ return "GLOBAL" if self.is_global else str(self.path)
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class Status:
31
+ provider: str
32
+ scope: Scope
33
+ state: InstallState
34
+ detail: str
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class OperationResult:
39
+ provider: str
40
+ scope: Scope
41
+ messages: tuple[str, ...]
pep/core/paths.py ADDED
@@ -0,0 +1,27 @@
1
+ """Path helpers shared by CLI, GUI and providers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ def package_root() -> Path:
10
+ """Return the project/resource root, supporting PyInstaller onefile."""
11
+ if getattr(sys, "frozen", False):
12
+ return Path(getattr(sys, "_MEIPASS", Path(sys.executable).resolve().parent))
13
+ source_root = Path(__file__).resolve().parents[2]
14
+ if (source_root / "claude").exists() and (source_root / "codex").exists():
15
+ return source_root
16
+ installed_root = Path(sys.prefix) / "share" / "pep-agentes"
17
+ if installed_root.exists():
18
+ return installed_root
19
+ return source_root
20
+
21
+
22
+ def user_home() -> Path:
23
+ return Path.home()
24
+
25
+
26
+ def prompt_path(relative: str) -> Path:
27
+ return package_root() / relative
pep/core/version.py ADDED
@@ -0,0 +1,5 @@
1
+ """Single version source for PEP-Agentes."""
2
+
3
+ __version__ = "1.2.0"
4
+ APP_NAME = "PEP-Agentes"
5
+ APP_TITLE = f"{APP_NAME} v{__version__}"
@@ -0,0 +1,24 @@
1
+ """Provider registry for PEP-Agentes Manager."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pep.providers.claude import ClaudeProvider
6
+ from pep.providers.codex import CodexProvider
7
+
8
+
9
+ PROVIDERS = {
10
+ "claude": ClaudeProvider(),
11
+ "codex": CodexProvider(),
12
+ }
13
+
14
+
15
+ def get_provider(name: str):
16
+ if name not in PROVIDERS:
17
+ raise ValueError(f"Provider desconhecido: {name}")
18
+ return PROVIDERS[name]
19
+
20
+
21
+ def selected_providers(name: str):
22
+ if name == "all":
23
+ return list(PROVIDERS.values())
24
+ return [get_provider(name)]
pep/providers/base.py ADDED
@@ -0,0 +1,23 @@
1
+ """Provider protocol shared by Claude and Codex adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Protocol
7
+
8
+ from pep.core.models import OperationResult, Scope, Status
9
+
10
+
11
+ class Provider(Protocol):
12
+ name: str
13
+ display_name: str
14
+
15
+ def prompt_path(self) -> Path | None: ...
16
+
17
+ def install(self, scope: Scope, force: bool = False, legacy_prompt: bool = False) -> OperationResult: ...
18
+
19
+ def uninstall(self, scope: Scope, legacy_prompt: bool = False) -> OperationResult: ...
20
+
21
+ def status(self, scope: Scope, legacy_prompt: bool = False) -> Status: ...
22
+
23
+ def repair(self, scope: Scope, legacy_prompt: bool = False) -> OperationResult: ...
@@ -0,0 +1,101 @@
1
+ """Claude Code provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ from pep.core import paths
10
+ from pep.core.markers import block_state, read_managed_block, remove_block, upsert_block
11
+ from pep.core.models import InstallState, OperationResult, Scope, Status
12
+
13
+ START = "<!-- PEP-AGENTES:START"
14
+ END = "<!-- PEP-AGENTES:END -->"
15
+ BLOCK_RE = re.compile(r"<!-- PEP-AGENTES:START.*?<!-- PEP-AGENTES:END -->", re.DOTALL)
16
+ HEADER = (
17
+ "# Instrucoes do projeto (Claude Code)\n\n"
18
+ "Bloco PEP-Agentes gerenciado por scripts/install_claude.py (nao edite entre os marcadores).\n\n"
19
+ )
20
+
21
+
22
+ class ClaudeProvider:
23
+ name = "claude"
24
+ display_name = "Claude Code"
25
+
26
+ @property
27
+ def source_claude_md(self) -> Path:
28
+ return paths.package_root() / "claude" / "CLAUDE.md"
29
+
30
+ @property
31
+ def source_command(self) -> Path:
32
+ return paths.package_root() / "claude" / "commands" / "pep.md"
33
+
34
+ def prompt_path(self) -> Path:
35
+ return paths.package_root() / "prompts" / "pep-agentes-claude.txt"
36
+
37
+ def block(self) -> str:
38
+ return read_managed_block(self.source_claude_md, BLOCK_RE)
39
+
40
+ def command_text(self) -> str:
41
+ return self.source_command.read_text(encoding="utf-8")
42
+
43
+ def targets_for(self, scope: Scope) -> tuple[Path, Path]:
44
+ if scope.is_global:
45
+ base = scope.path
46
+ return base / "CLAUDE.md", base / "commands" / "pep.md"
47
+ return scope.path / "CLAUDE.md", scope.path / ".claude" / "commands" / "pep.md"
48
+
49
+ def install(self, scope: Scope, force: bool = False, legacy_prompt: bool = False) -> OperationResult:
50
+ del legacy_prompt
51
+ claude_md, command_path = self.targets_for(scope)
52
+ block = self.block()
53
+ command_text = self.command_text()
54
+ messages = [f"CLAUDE.md: {upsert_block(claude_md, block, BLOCK_RE, START, END, HEADER)} -> {claude_md}"]
55
+ command_path.parent.mkdir(parents=True, exist_ok=True)
56
+ if command_path.exists() and not force:
57
+ if command_path.read_text(encoding="utf-8") == command_text:
58
+ messages.append(f"/pep: ja atualizado -> {command_path}")
59
+ else:
60
+ messages.append(f"/pep: JA EXISTE (use --force para sobrescrever) -> {command_path}")
61
+ else:
62
+ command_path.write_text(command_text, encoding="utf-8")
63
+ messages.append(f"/pep: instalado -> {command_path}")
64
+ return OperationResult(self.name, scope, tuple(messages))
65
+
66
+ def uninstall(self, scope: Scope, legacy_prompt: bool = False) -> OperationResult:
67
+ del legacy_prompt
68
+ claude_md, command_path = self.targets_for(scope)
69
+ messages = [f"CLAUDE.md: {remove_block(claude_md, BLOCK_RE, START, END)} -> {claude_md}"]
70
+ if command_path.exists():
71
+ command_path.unlink()
72
+ messages.append(f"/pep: removido -> {command_path}")
73
+ else:
74
+ messages.append(f"/pep: inexistente -> {command_path}")
75
+ return OperationResult(self.name, scope, tuple(messages))
76
+
77
+ def status(self, scope: Scope, legacy_prompt: bool = False) -> Status:
78
+ del legacy_prompt
79
+ claude_md, command_path = self.targets_for(scope)
80
+ block = self.block()
81
+ md_state = block_state(claude_md, block, BLOCK_RE, START, END)
82
+ command_ok = command_path.exists() and command_path.read_text(encoding="utf-8") == self.command_text()
83
+ if md_state == "corrupt":
84
+ state = InstallState.CORRUPT
85
+ elif md_state == "missing" and not command_path.exists():
86
+ state = InstallState.NOT_INSTALLED
87
+ elif md_state == "current" and command_ok:
88
+ state = InstallState.CURRENT
89
+ elif md_state == "outdated" or (command_path.exists() and not command_ok):
90
+ state = InstallState.OUTDATED
91
+ else:
92
+ state = InstallState.PARTIAL
93
+ return Status(self.name, scope, state, f"CLAUDE.md={md_state}; command={'ok' if command_ok else 'missing/outdated'}")
94
+
95
+ def repair(self, scope: Scope, legacy_prompt: bool = False) -> OperationResult:
96
+ status = self.status(scope, legacy_prompt)
97
+ if status.state == InstallState.CURRENT:
98
+ return OperationResult(self.name, scope, ("Claude Code ja esta atualizado.",))
99
+ if status.state == InstallState.CORRUPT:
100
+ return OperationResult(self.name, scope, ("Markers corrompidos; reparo automatico cancelado para preservar conteudo manual.",))
101
+ return self.install(scope, force=True)
pep/providers/codex.py ADDED
@@ -0,0 +1,131 @@
1
+ """Codex provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import filecmp
6
+ import re
7
+ import shutil
8
+ from pathlib import Path
9
+
10
+ from pep.core import paths
11
+ from pep.core.markers import block_state, read_managed_block, remove_block, upsert_block
12
+ from pep.core.models import InstallState, OperationResult, Scope, Status
13
+
14
+ START = "<!-- PEP-CODEX:START"
15
+ END = "<!-- PEP-CODEX:END -->"
16
+ BLOCK_RE = re.compile(r"<!-- PEP-CODEX:START.*?<!-- PEP-CODEX:END -->", re.DOTALL)
17
+ HEADER = (
18
+ "# Instrucoes do projeto (Codex)\n\n"
19
+ "Bloco PEP-Codex gerenciado por scripts/install_codex.py.\n\n"
20
+ )
21
+
22
+
23
+ class CodexProvider:
24
+ name = "codex"
25
+ display_name = "Codex"
26
+
27
+ @property
28
+ def source_agents(self) -> Path:
29
+ return paths.package_root() / "codex" / "AGENTS.md"
30
+
31
+ @property
32
+ def source_skill(self) -> Path:
33
+ return paths.package_root() / "codex" / "skills" / "pepcodex"
34
+
35
+ @property
36
+ def source_prompt(self) -> Path:
37
+ return paths.package_root() / "codex" / "prompts" / "pepcodex.md"
38
+
39
+ def prompt_path(self) -> Path:
40
+ return self.source_prompt
41
+
42
+ def block(self) -> str:
43
+ return read_managed_block(self.source_agents, BLOCK_RE)
44
+
45
+ def targets_for(self, scope: Scope) -> tuple[Path, Path]:
46
+ if scope.is_global:
47
+ return scope.path / "AGENTS.md", paths.user_home() / ".agents" / "skills" / "pepcodex"
48
+ return scope.path / "AGENTS.md", scope.path / ".agents" / "skills" / "pepcodex"
49
+
50
+ def legacy_prompt_target(self) -> Path:
51
+ return paths.user_home() / ".codex" / "prompts" / "pepcodex.md"
52
+
53
+ def _copy_skill(self, dest: Path, force: bool) -> str:
54
+ if dest.exists():
55
+ if not force:
56
+ return "ja existe (use --force para atualizar)"
57
+ shutil.rmtree(dest)
58
+ dest.parent.mkdir(parents=True, exist_ok=True)
59
+ shutil.copytree(self.source_skill, dest)
60
+ return "instalada"
61
+
62
+ def _skill_matches(self, dest: Path) -> bool:
63
+ if not dest.exists():
64
+ return False
65
+ src_files = sorted(p.relative_to(self.source_skill) for p in self.source_skill.rglob("*") if p.is_file())
66
+ dst_files = sorted(p.relative_to(dest) for p in dest.rglob("*") if p.is_file())
67
+ if src_files != dst_files:
68
+ return False
69
+ return all(filecmp.cmp(self.source_skill / rel, dest / rel, shallow=False) for rel in src_files)
70
+
71
+ def install(self, scope: Scope, force: bool = False, legacy_prompt: bool = False) -> OperationResult:
72
+ agents, skill = self.targets_for(scope)
73
+ messages = [f"AGENTS.md: {upsert_block(agents, self.block(), BLOCK_RE, START, END, HEADER)} -> {agents}"]
74
+ messages.append(f"$pepcodex: {self._copy_skill(skill, force)} -> {skill}")
75
+ if legacy_prompt:
76
+ dest = self.legacy_prompt_target()
77
+ if dest.exists() and not force:
78
+ messages.append(f"/prompts:pepcodex: ja existe (use --force para atualizar) -> {dest}")
79
+ else:
80
+ dest.parent.mkdir(parents=True, exist_ok=True)
81
+ shutil.copy2(self.source_prompt, dest)
82
+ messages.append(f"/prompts:pepcodex: instalado -> {dest}")
83
+ return OperationResult(self.name, scope, tuple(messages))
84
+
85
+ def uninstall(self, scope: Scope, legacy_prompt: bool = False) -> OperationResult:
86
+ agents, skill = self.targets_for(scope)
87
+ messages = [f"AGENTS.md: {remove_block(agents, BLOCK_RE, START, END)} -> {agents}"]
88
+ if skill.exists():
89
+ shutil.rmtree(skill)
90
+ messages.append(f"$pepcodex: removida -> {skill}")
91
+ else:
92
+ messages.append(f"$pepcodex: inexistente -> {skill}")
93
+ if legacy_prompt:
94
+ dest = self.legacy_prompt_target()
95
+ if dest.exists():
96
+ dest.unlink()
97
+ messages.append(f"/prompts:pepcodex: removido -> {dest}")
98
+ else:
99
+ messages.append(f"/prompts:pepcodex: inexistente -> {dest}")
100
+ return OperationResult(self.name, scope, tuple(messages))
101
+
102
+ def status(self, scope: Scope, legacy_prompt: bool = False) -> Status:
103
+ agents, skill = self.targets_for(scope)
104
+ md_state = block_state(agents, self.block(), BLOCK_RE, START, END)
105
+ skill_ok = self._skill_matches(skill)
106
+ legacy_ok = True
107
+ if legacy_prompt:
108
+ dest = self.legacy_prompt_target()
109
+ legacy_ok = dest.exists() and filecmp.cmp(self.source_prompt, dest, shallow=False)
110
+ if md_state == "corrupt":
111
+ state = InstallState.CORRUPT
112
+ elif md_state == "missing" and not skill.exists():
113
+ state = InstallState.NOT_INSTALLED
114
+ elif md_state == "current" and skill_ok and legacy_ok:
115
+ state = InstallState.CURRENT
116
+ elif md_state == "outdated" or (skill.exists() and not skill_ok) or not legacy_ok:
117
+ state = InstallState.OUTDATED
118
+ else:
119
+ state = InstallState.PARTIAL
120
+ detail = f"AGENTS.md={md_state}; skill={'ok' if skill_ok else 'missing/outdated'}"
121
+ if legacy_prompt:
122
+ detail += f"; legacy={'ok' if legacy_ok else 'missing/outdated'}"
123
+ return Status(self.name, scope, state, detail)
124
+
125
+ def repair(self, scope: Scope, legacy_prompt: bool = False) -> OperationResult:
126
+ status = self.status(scope, legacy_prompt)
127
+ if status.state == InstallState.CURRENT:
128
+ return OperationResult(self.name, scope, ("Codex ja esta atualizado.",))
129
+ if status.state == InstallState.CORRUPT:
130
+ return OperationResult(self.name, scope, ("Markers corrompidos; reparo automatico cancelado para preservar conteudo manual.",))
131
+ return self.install(scope, force=True, legacy_prompt=legacy_prompt)
@@ -0,0 +1 @@
1
+ """Service layer for PEP-Agentes Manager."""
@@ -0,0 +1,93 @@
1
+ """High-level operations shared by CLI and GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from pep.core.detection import doctor_tools
8
+ from pep.core.models import OperationResult, Scope, Status
9
+ from pep.providers import selected_providers
10
+
11
+
12
+ def resolve_scopes(here: bool = False, paths: list[str] | None = None, global_scope: bool = False) -> list[Scope]:
13
+ scopes: list[Scope] = []
14
+ if here:
15
+ scopes.append(Scope(Path.cwd(), False))
16
+ for raw in paths or []:
17
+ scopes.append(Scope(Path(raw).expanduser().resolve(), False))
18
+ if global_scope:
19
+ from pep.core.paths import user_home
20
+
21
+ scopes.append(Scope(user_home() / ".codex", True))
22
+ return scopes
23
+
24
+
25
+ def resolve_provider_scope(provider_name: str, scope: Scope) -> Scope:
26
+ if not scope.is_global:
27
+ return scope
28
+ from pep.core.paths import user_home
29
+
30
+ if provider_name == "claude":
31
+ return Scope(user_home() / ".claude", True)
32
+ if provider_name == "codex":
33
+ return Scope(user_home() / ".codex", True)
34
+ return scope
35
+
36
+
37
+ def run_operation(
38
+ action: str,
39
+ provider_name: str,
40
+ scopes: list[Scope],
41
+ force: bool = False,
42
+ legacy_prompt: bool = False,
43
+ ) -> list[OperationResult]:
44
+ results: list[OperationResult] = []
45
+ for provider in selected_providers(provider_name):
46
+ for scope in scopes:
47
+ provider_scope = resolve_provider_scope(provider.name, scope)
48
+ if action in {"install", "update"}:
49
+ results.append(provider.install(provider_scope, force=force or action == "update", legacy_prompt=legacy_prompt))
50
+ elif action == "uninstall":
51
+ results.append(provider.uninstall(provider_scope, legacy_prompt=legacy_prompt))
52
+ elif action == "repair":
53
+ results.append(provider.repair(provider_scope, legacy_prompt=legacy_prompt))
54
+ else:
55
+ raise ValueError(f"Acao desconhecida: {action}")
56
+ return results
57
+
58
+
59
+ def collect_status(provider_name: str, scopes: list[Scope], legacy_prompt: bool = False) -> list[Status]:
60
+ rows: list[Status] = []
61
+ for provider in selected_providers(provider_name):
62
+ for scope in scopes:
63
+ rows.append(provider.status(resolve_provider_scope(provider.name, scope), legacy_prompt=legacy_prompt))
64
+ return rows
65
+
66
+
67
+ def collect_doctor(provider_name: str = "all", scopes: list[Scope] | None = None, legacy_prompt: bool = False) -> str:
68
+ lines = ["PEP Doctor", ""]
69
+ for tool in doctor_tools():
70
+ status = "OK" if tool.found else "ERRO"
71
+ lines.append(f"{tool.name:.<22} {status} - {tool.detail}")
72
+ if scopes:
73
+ lines.append("")
74
+ for row in collect_status(provider_name, scopes, legacy_prompt=legacy_prompt):
75
+ lines.append(f"{row.provider} {row.scope.label}: {row.state.value} - {row.detail}")
76
+ return "\n".join(lines)
77
+
78
+
79
+ def format_results(results: list[OperationResult]) -> str:
80
+ chunks: list[str] = []
81
+ for result in results:
82
+ chunks.append(f"== {result.provider} | {result.scope.label} ==")
83
+ chunks.extend(result.messages)
84
+ chunks.append("")
85
+ chunks.append("Concluido.")
86
+ return "\n".join(chunks)
87
+
88
+
89
+ def format_status(rows: list[Status]) -> str:
90
+ lines = []
91
+ for row in rows:
92
+ lines.append(f"{row.provider} | {row.scope.label}: {row.state.value} - {row.detail}")
93
+ return "\n".join(lines) if lines else "Nenhum alvo selecionado."