feat(adapters): package OMP and confine autonomous runners
This commit is contained in:
1 parent
2662a80bd8
commit
49752daa7c
5 files changed
+1191
-12
No files matched your search
+66
-12
@@ -1,14 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install dev-workflows into Claude Code, opencode or Hermes Agent.
|
||||
"""Install dev-workflows into Claude Code, OpenCode, OMP or Hermes Agent.
|
||||
|
||||
python3 scripts/install.py claude # plugin via local marketplace (skills, agents, hooks)
|
||||
python3 scripts/install.py opencode # skills + agents + plugin under ~/.config/opencode
|
||||
python3 scripts/install.py omp # native skills + agents under ~/.omp/plugins/dev-workflows
|
||||
python3 scripts/install.py hermes # skills under ~/.hermes/skills (no agents/hooks there)
|
||||
|
||||
opencode and Hermes do not substitute ${CLAUDE_PLUGIN_ROOT}, so the copies point at this checkout:
|
||||
OpenCode and Hermes do not substitute ${CLAUDE_PLUGIN_ROOT}, so their copies point at this checkout:
|
||||
keep it where it is, or re-run the install after moving it.
|
||||
"""
|
||||
import argparse, os, re, shutil, subprocess, sys
|
||||
import argparse, importlib.util, os, re, shutil, subprocess, sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
MARKER = ".dev-workflows-install"
|
||||
@@ -40,23 +41,74 @@ def install_skills(target_dir, force):
|
||||
|
||||
|
||||
def opencode_agent(path):
|
||||
"""Claude Code agent frontmatter -> opencode subagent frontmatter."""
|
||||
text = open(path, encoding="utf-8").read()
|
||||
"""Claude Code agent frontmatter -> OpenCode subagent with explicit permissions."""
|
||||
with open(path, encoding="utf-8") as source:
|
||||
text = source.read()
|
||||
m = re.match(r"---\n(.*?)\n---\n(.*)", text, re.S)
|
||||
if not m:
|
||||
raise ValueError(f"frontmatter inválido em {path}")
|
||||
meta = dict(re.findall(r"^(\w+):\s*(.*)$", m.group(1), re.M))
|
||||
tools = {t.strip() for t in meta.get("tools", "").split(",")}
|
||||
skills = re.findall(r"[\w-]+", meta.get("skills", ""))
|
||||
perm = {"edit": "allow" if tools & {"Edit", "Write"} else "deny",
|
||||
"bash": "allow" if "Bash" in tools else "deny",
|
||||
"webfetch": "allow" if "WebFetch" in tools else "deny"}
|
||||
read_only = not bool(tools & {"Edit", "Write"})
|
||||
allowed = {
|
||||
"read": "Read" in tools,
|
||||
"edit": not read_only and bool(tools & {"Edit", "Write"}),
|
||||
"bash": not read_only and "Bash" in tools,
|
||||
"glob": "Glob" in tools,
|
||||
"grep": "Grep" in tools,
|
||||
"list": False,
|
||||
"task": False,
|
||||
"external_directory": False,
|
||||
"todowrite": False,
|
||||
"webfetch": "WebFetch" in tools,
|
||||
"websearch": "WebSearch" in tools,
|
||||
"lsp": False,
|
||||
"skill": "Skill" in tools,
|
||||
"question": False,
|
||||
"doom_loop": False,
|
||||
}
|
||||
body = m.group(2).replace("${CLAUDE_PLUGIN_ROOT}", ROOT)
|
||||
if skills:
|
||||
body = f"Antes de começar, carregue a(s) skill(s): {', '.join(skills)}.\n\n" + body
|
||||
head = ["# dev-workflows", f"description: {meta['description']}", "mode: subagent", "permission:"]
|
||||
head += [f" {k}: {v}" for k, v in perm.items()]
|
||||
head = ["# dev-workflows", f"description: {meta['description']}", "mode: subagent", "permission:", ' "*": deny']
|
||||
head += [f" {key}: {'allow' if value else 'deny'}" for key, value in allowed.items()]
|
||||
return "---\n" + "\n".join(head) + "\n---\n\n" + body
|
||||
|
||||
|
||||
def _omp_builder(path=None):
|
||||
path = path or os.path.join(ROOT, "adapters", "omp", "build.py")
|
||||
spec = importlib.util.spec_from_file_location("dev_workflows_omp_build", path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"não foi possível carregar o adapter OMP: {path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module.build_package
|
||||
|
||||
|
||||
def install_omp(base=None, runner=None, build_package=None):
|
||||
"""Build and link the OMP package without replacing a foreign installation."""
|
||||
omp_root = os.path.realpath(base or os.path.expanduser("~/.omp"))
|
||||
dest = os.path.join(omp_root, "plugins", "dev-workflows")
|
||||
if os.path.lexists(dest) and (os.path.islink(dest) or not os.path.isdir(dest)):
|
||||
sys.exit(f"{dest} existe e não é um pacote OMP deste checkout.")
|
||||
build = build_package or _omp_builder()
|
||||
try:
|
||||
build(ROOT, dest)
|
||||
except FileExistsError as exc:
|
||||
sys.exit(str(exc))
|
||||
run = runner or subprocess.run
|
||||
command = ["omp", "plugin", "link", dest]
|
||||
try:
|
||||
result = run(command, check=False)
|
||||
except OSError:
|
||||
sys.exit("OMP não encontrado; pacote gerado, mas `omp plugin link` não foi executado.")
|
||||
if result.returncode != 0:
|
||||
sys.exit(f"`omp plugin link {dest}` falhou com código {result.returncode}.")
|
||||
print(f"plugin OMP instalado em {dest}")
|
||||
|
||||
|
||||
def install_opencode(force):
|
||||
base = os.path.expanduser("~/.config/opencode")
|
||||
install_skills(os.path.join(base, "skills"), force)
|
||||
@@ -83,13 +135,15 @@ def install_claude():
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument("target", choices=["claude", "opencode", "hermes"])
|
||||
ap.add_argument("--force", action="store_true", help="sobrescreve pastas com o mesmo nome que não vieram deste script")
|
||||
ap.add_argument("target", choices=["claude", "opencode", "hermes", "omp"])
|
||||
ap.add_argument("--force", action="store_true", help="sobrescreve skills/agentes externos; OMP nunca sobrescreve pacote não pertencente")
|
||||
a = ap.parse_args()
|
||||
if a.target == "claude":
|
||||
install_claude()
|
||||
elif a.target == "opencode":
|
||||
install_opencode(a.force)
|
||||
elif a.target == "omp":
|
||||
install_omp()
|
||||
else:
|
||||
install_skills(os.path.expanduser("~/.hermes/skills"), a.force)
|
||||
print("Hermes: agentes e hooks não se aplicam; rode o gate manualmente.")
|
||||
|
||||
Reference in new issue
Block a user