diff --git a/src/rulebook_ai/core.py b/src/rulebook_ai/core.py index d591e41..241c0f4 100644 --- a/src/rulebook_ai/core.py +++ b/src/rulebook_ai/core.py @@ -6,12 +6,12 @@ import re import shutil import sys +import webbrowser from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path from typing import Dict, List, Optional -import webbrowser import yaml from .assistants import ASSISTANT_MAP, SUPPORTED_ASSISTANTS, AssistantSpec @@ -218,20 +218,32 @@ def _builtin_packs(self) -> List[Dict[str, str]]: if not self.source_packs_dir.is_dir(): return [] packs = [] - for pack_dir in sorted( - [p for p in self.source_packs_dir.iterdir() if p.is_dir() and not p.name.startswith(".")], - key=lambda p: p.name, - ): + pack_dirs = [ + p + for p in self.source_packs_dir.iterdir() + if p.is_dir() and not p.name.startswith(".") + ] + for pack_dir in sorted(pack_dirs, key=lambda p: p.name): manifest_path = pack_dir / "manifest.yaml" + name = pack_dir.name version = "unknown" summary = "" if manifest_path.exists(): manifest = yaml.safe_load(manifest_path.read_text()) or {} + name = manifest.get("name") or name version = manifest.get("version", "unknown") summary = manifest.get("summary", "") - packs.append({"name": pack_dir.name, "version": version, "summary": summary}) + packs.append( + {"name": name, "path": str(pack_dir), "version": version, "summary": summary} + ) return packs + def _find_builtin_pack(self, name: str) -> Optional[Dict[str, str]]: + for pack in self._builtin_packs(): + if name in {pack["name"], Path(pack["path"]).name}: + return pack + return None + def list_packs(self) -> None: builtins = self._builtin_packs() from . import community_packs @@ -262,7 +274,11 @@ def list_packs(self) -> None: if entry["source"] == "built-in": version = entry.get("version") print(f" - {name} (built-in, v{version}) - {summary}") - readme_url = f"https://github.com/botingw/rulebook-ai/blob/main/src/rulebook_ai/packs/{name}/README.md" + pack_dir_name = Path(entry.get("path", name)).name + readme_url = ( + "https://github.com/botingw/rulebook-ai/blob/main/" + f"src/rulebook_ai/packs/{pack_dir_name}/README.md" + ) else: print(f" - {name} (community) - {summary}") path_part = entry.get("path", "").strip("/") @@ -329,9 +345,11 @@ def add_pack(self, name_or_path: str, project_dir: Optional[str] = None) -> int: ) # Handle built-in and index packs by name - name = name_or_path - source = self.source_packs_dir / name - if source.is_dir(): # It's a built-in pack + requested_name = name_or_path + builtin_pack = self._find_builtin_pack(requested_name) + if builtin_pack: # It's a built-in pack + name = builtin_pack["name"] + source = Path(builtin_pack["path"]) dest_dir = project_root / TARGET_INTERNAL_STATE_DIR / "packs" / name if dest_dir.exists(): if (dest_dir / "pack.json").exists(): @@ -362,14 +380,17 @@ def add_pack(self, name_or_path: str, project_dir: Optional[str] = None) -> int: from . import community_packs result = community_packs.add_pack_from_index( - name, + requested_name, project_root, self.source_packs_dir, self._load_selection, self._save_selection, ) if result != 0: - print(f"Pack '{name}' not found as a built-in pack or in the community index.") + print( + f"Pack '{requested_name}' not found as a built-in pack or in " + "the community index." + ) self.list_packs() return result diff --git a/tests/integration/test_packs_add.py b/tests/integration/test_packs_add.py index 0d57243..a202651 100644 --- a/tests/integration/test_packs_add.py +++ b/tests/integration/test_packs_add.py @@ -56,4 +56,32 @@ def test_add_pack_with_source_conflict_fails(tmp_path, run_cli): assert selection["packs"][0].get("version") != "9.9.9" # Make sure it's not the new one assert not ( project_dir / ".rulebook-ai" / "packs" / "light-spec" / "pack.json" - ).exists() \ No newline at end of file + ).exists() + + +def test_packs_add_uses_builtin_manifest_name(tmp_path, run_cli): + project_dir = tmp_path / "proj" + project_dir.mkdir() + + result = run_cli(["packs", "add", "no-memory-interaction-rules"], project_dir) + assert result.returncode == 0, result.stderr + + rulebook_dir = project_dir / ".rulebook-ai" + assert (rulebook_dir / "packs" / "no-memory-interaction-rules").is_dir() + assert not (rulebook_dir / "packs" / "no_memory_interation_rules").exists() + + selection = json.loads((rulebook_dir / "selection.json").read_text()) + assert selection["packs"][0]["name"] == "no-memory-interaction-rules" + + +def test_packs_add_accepts_builtin_directory_alias(tmp_path, run_cli): + project_dir = tmp_path / "proj" + project_dir.mkdir() + + result = run_cli(["packs", "add", "no_memory_interation_rules"], project_dir) + assert result.returncode == 0, result.stderr + + rulebook_dir = project_dir / ".rulebook-ai" + selection = json.loads((rulebook_dir / "selection.json").read_text()) + assert selection["packs"][0]["name"] == "no-memory-interaction-rules" + assert (rulebook_dir / "packs" / "no-memory-interaction-rules").is_dir() diff --git a/tests/integration/test_packs_list.py b/tests/integration/test_packs_list.py index 21c5262..2d59005 100644 --- a/tests/integration/test_packs_list.py +++ b/tests/integration/test_packs_list.py @@ -1,4 +1,3 @@ -import os def test_packs_list_shows_manifest_info(tmp_path, run_cli): project_dir = tmp_path / "proj" project_dir.mkdir() @@ -8,3 +7,7 @@ def test_packs_list_shows_manifest_info(tmp_path, run_cli): assert "light-spec" in result.stdout assert "v0.1.0" in result.stdout assert "simplification" in result.stdout + pack_rows = [line for line in result.stdout.splitlines() if line.startswith(" - ")] + assert any("no-memory-interaction-rules" in line for line in pack_rows) + assert all("no_memory_interation_rules" not in line for line in pack_rows) + assert "src/rulebook_ai/packs/no_memory_interation_rules/README.md" in result.stdout