refactor(docker): inline skills_copy_into into provision_skills
test / run tests/run_tests.py (pull_request) Successful in 14s
test / run tests/run_tests.py (pull_request) Successful in 14s
The copy logic was Docker-specific (docker exec mkdir / rm -rf, docker cp); it had no reason to live in a top-level skills module. Pull the body into DockerBottleBackend.provision_skills. skills.py keeps the host-side discovery + validation (host_skill_dir, host_skill_exists, require_host_skill, skills_validate_all). The orphaned CONTAINER_HOME / CONTAINER_SKILLS_DIR constants and the now-unused subprocess + info imports are removed.
This commit is contained in:
@@ -292,12 +292,48 @@ class DockerBottleBackend(BottleBackend):
|
|||||||
def provision_skills(self, plan: BottlePlan, target: str) -> None:
|
def provision_skills(self, plan: BottlePlan, target: str) -> None:
|
||||||
"""Copy each of the agent's named skills from the host's
|
"""Copy each of the agent's named skills from the host's
|
||||||
~/.claude/skills/<name>/ into the container's equivalent path.
|
~/.claude/skills/<name>/ into the container's equivalent path.
|
||||||
No-op when the agent has no skills."""
|
For each skill: ensure parent dir, wipe any prior copy, then
|
||||||
|
`docker cp <host>/. <container>:<dst>/` so the contents are
|
||||||
|
copied into a freshly-created destination dir. No-op when the
|
||||||
|
agent has no skills."""
|
||||||
assert isinstance(plan, DockerBottlePlan)
|
assert isinstance(plan, DockerBottlePlan)
|
||||||
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
||||||
if not agent.skills:
|
if not agent.skills:
|
||||||
return
|
return
|
||||||
skills_mod.skills_copy_into(target, list(agent.skills))
|
|
||||||
|
container = target
|
||||||
|
container_home = os.environ.get("CLAUDE_BOTTLE_CONTAINER_HOME", "/home/node")
|
||||||
|
skills_dir = os.environ.get(
|
||||||
|
"CLAUDE_BOTTLE_CONTAINER_SKILLS_DIR", f"{container_home}/.claude/skills"
|
||||||
|
)
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "exec", container, "mkdir", "-p", skills_dir],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for n in agent.skills:
|
||||||
|
src = skills_mod.host_skill_dir(n)
|
||||||
|
if not os.path.isdir(src):
|
||||||
|
die(f"skill '{n}' disappeared from host between validation and copy at {src}.")
|
||||||
|
dst = f"{skills_dir}/{n}"
|
||||||
|
info(f"copying skill {n} into {container}:{dst}")
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "exec", container, "rm", "-rf", dst],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "exec", container, "mkdir", "-p", dst],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "cp", f"{src}/.", f"{container}:{dst}/"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
def provision_ssh(self, plan: BottlePlan, target: str) -> None:
|
def provision_ssh(self, plan: BottlePlan, target: str) -> None:
|
||||||
"""If the bottle has SSH entries, set up the in-container
|
"""If the bottle has SSH entries, set up the in-container
|
||||||
|
|||||||
+3
-45
@@ -1,17 +1,11 @@
|
|||||||
"""Skill copier: host's ~/.claude/skills/<name>/ -> container's
|
"""Skill discovery and host-side validation. The copy step itself
|
||||||
~/.claude/skills/<name>/, preserving directory structure."""
|
lives on the backend (e.g. DockerBottleBackend.provision_skills)."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from .log import die, info
|
from .log import die
|
||||||
|
|
||||||
CONTAINER_HOME = os.environ.get("CLAUDE_BOTTLE_CONTAINER_HOME", "/home/node")
|
|
||||||
CONTAINER_SKILLS_DIR = os.environ.get(
|
|
||||||
"CLAUDE_BOTTLE_CONTAINER_SKILLS_DIR", f"{CONTAINER_HOME}/.claude/skills"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def host_skill_dir(name: str) -> str:
|
def host_skill_dir(name: str) -> str:
|
||||||
@@ -38,39 +32,3 @@ def skills_validate_all(names: list[str]) -> None:
|
|||||||
that's already known to fail."""
|
that's already known to fail."""
|
||||||
for n in names:
|
for n in names:
|
||||||
require_host_skill(n)
|
require_host_skill(n)
|
||||||
|
|
||||||
|
|
||||||
def skills_copy_into(container: str, names: list[str]) -> None:
|
|
||||||
"""For each named skill, ensure the parent dir exists, wipe any
|
|
||||||
prior copy, then `docker cp <host>/. <container>:<dst>/` so the
|
|
||||||
contents are copied into a freshly-created destination dir."""
|
|
||||||
if not names:
|
|
||||||
return
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
["docker", "exec", container, "mkdir", "-p", CONTAINER_SKILLS_DIR],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
for n in names:
|
|
||||||
src = host_skill_dir(n)
|
|
||||||
if not os.path.isdir(src):
|
|
||||||
die(f"skill '{n}' disappeared from host between validation and copy at {src}.")
|
|
||||||
dst = f"{CONTAINER_SKILLS_DIR}/{n}"
|
|
||||||
info(f"copying skill {n} into {container}:{dst}")
|
|
||||||
subprocess.run(
|
|
||||||
["docker", "exec", container, "rm", "-rf", dst],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
|
||||||
["docker", "exec", container, "mkdir", "-p", dst],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
|
||||||
["docker", "cp", f"{src}/.", f"{container}:{dst}/"],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user