from __future__ import annotations import os import subprocess import sys import tempfile import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] def write_skill(root: Path, name: str) -> None: skill = root / "skills" / name skill.mkdir(parents=True) skill.joinpath("SKILL.md").write_text( f"---\nname: {name}\ndescription: >-\n" f" 测试 {name} skill。\n---\n\n# {name}\n", encoding="utf-8", ) class AgentsTargetTests(unittest.TestCase): def setUp(self) -> None: self.temp_dir = tempfile.TemporaryDirectory() self.home = Path(self.temp_dir.name) self.skills_home = self.home / ".skills" self.skills_home.mkdir(parents=True) (self.skills_home / "catalog.yaml").write_text("", encoding="utf-8") write_skill(self.skills_home, "demo-skill") def tearDown(self) -> None: self.temp_dir.cleanup() def run_skiff(self, *args: str) -> subprocess.CompletedProcess[str]: env = os.environ.copy() env["HOME"] = str(self.home) env["PYTHONPATH"] = str(REPO_ROOT) return subprocess.run( [sys.executable, "-m", "skiff", *args], cwd=REPO_ROOT, env=env, text=True, capture_output=True, check=False, ) def test_global_add_creates_symlink_in_agents_skills(self) -> None: result = self.run_skiff("add", "builtin/demo-skill", "-g", "-a", "agents") self.assertEqual(result.returncode, 0, result.stderr) link = self.home / ".agents" / "skills" / "demo-skill" self.assertTrue(link.is_symlink()) self.assertEqual( link.resolve(), (self.skills_home / "skills" / "demo-skill").resolve() ) removed = self.run_skiff("rm", "builtin/demo-skill", "-g", "-a", "agents") self.assertEqual(removed.returncode, 0, removed.stderr) self.assertFalse(link.exists()) def test_project_add_targets_agents_and_codex_share_single_link(self) -> None: project = self.home / "app" project.mkdir() added = self.run_skiff( "add", "builtin/demo-skill", "--project", str(project), "-a", "agents", "-a", "codex", ) self.assertEqual(added.returncode, 0, added.stderr) # agents 与 codex 项目级共用 .agents/skills,软链幂等只写一份 links = list((project / ".agents" / "skills").glob("demo-skill")) self.assertEqual(len(links), 1) self.assertTrue(links[0].is_symlink()) manifest = (project / ".skills.yaml").read_text(encoding="utf-8") self.assertIn("demo-skill", manifest) removed = self.run_skiff( "rm", "builtin/demo-skill", "--project", str(project), "-y" ) self.assertEqual(removed.returncode, 0, removed.stderr) self.assertFalse((project / ".agents" / "skills" / "demo-skill").exists()) def test_status_lists_installed_skill_for_agents_target(self) -> None: installed = self.run_skiff("add", "builtin/demo-skill", "-g", "-a", "agents") status = self.run_skiff("status", "-a", "agents") self.assertEqual(installed.returncode, 0, installed.stderr) self.assertEqual(status.returncode, 0, status.stderr) self.assertIn("demo-skill", status.stdout) def test_opencode_target_is_no_longer_supported(self) -> None: result = self.run_skiff("add", "builtin/demo-skill", "-g", "-a", "opencode") self.assertNotEqual(result.returncode, 0) self.assertIn("未知 agent", result.stderr) if __name__ == "__main__": unittest.main()