diff --git a/claude_bottle/backend/docker/backend.py b/claude_bottle/backend/docker/backend.py index ca7719e..145eb9d 100644 --- a/claude_bottle/backend/docker/backend.py +++ b/claude_bottle/backend/docker/backend.py @@ -18,7 +18,6 @@ from pathlib import Path from typing import Iterator from ... import pipelock -from ... import skills as skills_mod from ... import ssh as ssh_mod from ...env_resolve import env_resolve from ...log import die, info @@ -87,7 +86,7 @@ class DockerBottleBackend(BottleBackend): ) if agent.skills: - skills_mod.skills_validate_all(list(agent.skills)) + self.validate_skills(list(agent.skills)) if bottle.ssh: ssh_mod.ssh_validate_entries(bottle.ssh) @@ -289,6 +288,25 @@ class DockerBottleBackend(BottleBackend): agent = plan.spec.manifest.agents[plan.spec.agent_name] return in_container_prompt_path if agent.prompt else None + def validate_skills(self, skills: list[str]) -> None: + """Fail loudly if any named skill is missing from the host's + ~/.claude/skills/. Called from `prepare` before the y/N so the + user doesn't get a launch prompt for a plan that's already + known to break.""" + for name in skills: + path = self._host_skill_dir(name) + if not os.path.isdir(path): + die( + f"skill '{name}' not found on host at {path}. " + f"Create it under ~/.claude/skills/, then re-run." + ) + + def _host_skill_dir(self, name: str) -> str: + home = os.environ.get("HOME") + if not home: + die("HOME not set") + return f"{home}/.claude/skills/{name}" + def provision_skills(self, plan: BottlePlan, target: str) -> None: """Copy each of the agent's named skills from the host's ~/.claude/skills// into the container's equivalent path. @@ -314,7 +332,7 @@ class DockerBottleBackend(BottleBackend): ) for n in agent.skills: - src = skills_mod.host_skill_dir(n) + src = self._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}" diff --git a/claude_bottle/skills.py b/claude_bottle/skills.py deleted file mode 100644 index a879ec8..0000000 --- a/claude_bottle/skills.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Skill discovery and host-side validation. The copy step itself -lives on the backend (e.g. DockerBottleBackend.provision_skills).""" - -from __future__ import annotations - -import os - -from .log import die - - -def host_skill_dir(name: str) -> str: - home = os.environ.get("HOME") - if not home: - die("HOME not set") - return f"{home}/.claude/skills/{name}" - - -def host_skill_exists(name: str) -> bool: - return os.path.isdir(host_skill_dir(name)) - - -def require_host_skill(name: str) -> None: - if not host_skill_exists(name): - die( - f"skill '{name}' not found on host at {host_skill_dir(name)}. " - f"Create it under ~/.claude/skills/, then re-run." - ) - - -def skills_validate_all(names: list[str]) -> None: - """Use BEFORE the y/N so the user does not get asked about a plan - that's already known to fail.""" - for n in names: - require_host_skill(n)