feat: Refactor tool management and update documentation for clarity
This commit is contained in:
@@ -18,7 +18,6 @@ from castle_cli.manifest import (
|
||||
RunPythonUvTool,
|
||||
SystemdSpec,
|
||||
ToolSpec,
|
||||
ToolType,
|
||||
)
|
||||
from castle_cli.templates.scaffold import scaffold_category_tool, scaffold_project
|
||||
|
||||
@@ -102,10 +101,7 @@ def run_create(args: argparse.Namespace) -> int:
|
||||
manifest = ComponentManifest(
|
||||
id=name,
|
||||
description=args.description or f"A castle {proj_type}",
|
||||
tool=ToolSpec(
|
||||
tool_type=ToolType.PYTHON_STANDALONE,
|
||||
source=f"{name}/",
|
||||
),
|
||||
tool=ToolSpec(source=f"{name}/"),
|
||||
install=InstallSpec(path=PathInstallSpec(alias=name)),
|
||||
)
|
||||
else:
|
||||
@@ -173,11 +169,7 @@ def _create_category_tool(config: object, name: str, description: str | None, ca
|
||||
manifest = ComponentManifest(
|
||||
id=name,
|
||||
description=desc,
|
||||
tool=ToolSpec(
|
||||
tool_type=ToolType.PYTHON_STANDALONE,
|
||||
category=category,
|
||||
source=f"tools/{category}/",
|
||||
),
|
||||
tool=ToolSpec(source=f"tools/{category}/"),
|
||||
install=InstallSpec(path=PathInstallSpec(alias=name)),
|
||||
)
|
||||
config.components[name] = manifest
|
||||
|
||||
@@ -85,7 +85,6 @@ def run_info(args: argparse.Namespace) -> int:
|
||||
# Tool
|
||||
if manifest.tool:
|
||||
t = manifest.tool
|
||||
print(f" {BOLD}category{RESET}: {t.category or 'uncategorized'}")
|
||||
if t.source:
|
||||
print(f" {BOLD}source{RESET}: {t.source}")
|
||||
if t.system_dependencies:
|
||||
|
||||
@@ -8,7 +8,6 @@ import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from castle_cli.config import ensure_dirs, load_config
|
||||
from castle_cli.manifest import ToolType
|
||||
|
||||
|
||||
def run_sync(args: argparse.Namespace) -> int:
|
||||
@@ -46,7 +45,7 @@ def run_sync(args: argparse.Namespace) -> int:
|
||||
print(" OK")
|
||||
synced_dirs.add(project_dir)
|
||||
|
||||
# Install tools by type
|
||||
# Install tools — infer method from project structure
|
||||
uv_path = shutil.which("uv") or "uv"
|
||||
installed_dirs: set[Path] = set()
|
||||
|
||||
@@ -54,18 +53,19 @@ def run_sync(args: argparse.Namespace) -> int:
|
||||
if not manifest.tool:
|
||||
continue
|
||||
|
||||
if manifest.tool.tool_type == ToolType.PYTHON_STANDALONE:
|
||||
source = manifest.tool.source
|
||||
if not source:
|
||||
continue
|
||||
project_dir = config.root / source
|
||||
if not (project_dir / "pyproject.toml").exists():
|
||||
continue
|
||||
if project_dir in installed_dirs:
|
||||
source = manifest.tool.source
|
||||
if not source:
|
||||
continue
|
||||
|
||||
source_dir = config.root / source
|
||||
|
||||
if (source_dir / "pyproject.toml").exists():
|
||||
# Python package — uv tool install
|
||||
if source_dir in installed_dirs:
|
||||
continue
|
||||
print(f"\nInstalling {name}...")
|
||||
result = subprocess.run(
|
||||
[uv_path, "tool", "install", "--editable", str(project_dir),
|
||||
[uv_path, "tool", "install", "--editable", str(source_dir),
|
||||
"--force"],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
@@ -77,23 +77,19 @@ def run_sync(args: argparse.Namespace) -> int:
|
||||
all_ok = False
|
||||
else:
|
||||
print(f" {name}: OK")
|
||||
installed_dirs.add(project_dir)
|
||||
installed_dirs.add(source_dir)
|
||||
|
||||
elif manifest.tool.tool_type == ToolType.SCRIPT:
|
||||
# Verify script tools are accessible
|
||||
elif source_dir.is_file():
|
||||
# Script file — symlink to ~/.local/bin/
|
||||
alias = name
|
||||
if manifest.install and manifest.install.path and manifest.install.path.alias:
|
||||
alias = manifest.install.path.alias
|
||||
if not shutil.which(alias):
|
||||
source = manifest.tool.source
|
||||
if source:
|
||||
script_path = config.root / source
|
||||
if script_path.exists():
|
||||
link = Path.home() / ".local" / "bin" / alias
|
||||
link.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not link.exists():
|
||||
link.symlink_to(script_path)
|
||||
print(f"\n Linked {alias} → {script_path}")
|
||||
link = Path.home() / ".local" / "bin" / alias
|
||||
link.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not link.exists():
|
||||
link.symlink_to(source_dir)
|
||||
print(f"\n Linked {alias} → {source_dir}")
|
||||
|
||||
if all_ok:
|
||||
print("\nAll projects synced.")
|
||||
|
||||
@@ -36,14 +36,17 @@ def _tool_list() -> int:
|
||||
print("No tools registered.")
|
||||
return 0
|
||||
|
||||
by_category: dict[str, list[tuple[str, object]]] = {}
|
||||
by_group: dict[str, list[tuple[str, object]]] = {}
|
||||
for name, manifest in tools.items():
|
||||
cat = manifest.tool.category or "uncategorized"
|
||||
by_category.setdefault(cat, []).append((name, manifest))
|
||||
if manifest.tool and manifest.tool.source:
|
||||
group = Path(manifest.tool.source).name
|
||||
else:
|
||||
group = "standalone"
|
||||
by_group.setdefault(group, []).append((name, manifest))
|
||||
|
||||
for category in sorted(by_category):
|
||||
items = by_category[category]
|
||||
print(f"\n{BOLD}{CYAN}{category}{RESET}")
|
||||
for group in sorted(by_group):
|
||||
items = by_group[group]
|
||||
print(f"\n{BOLD}{CYAN}{group}{RESET}")
|
||||
print(f"{CYAN}{'─' * 40}{RESET}")
|
||||
for name, manifest in sorted(items):
|
||||
desc = manifest.description or ""
|
||||
@@ -74,7 +77,6 @@ def _tool_info(name: str) -> int:
|
||||
print(f"{'─' * 40}")
|
||||
if manifest.description:
|
||||
print(f" {manifest.description}")
|
||||
print(f" {BOLD}category{RESET}: {t.category or 'uncategorized'}")
|
||||
print(f" {BOLD}version{RESET}: {t.version}")
|
||||
if t.source:
|
||||
print(f" {BOLD}source{RESET}: {t.source}")
|
||||
@@ -83,7 +85,7 @@ def _tool_info(name: str) -> int:
|
||||
|
||||
# Read and display .md documentation if available
|
||||
if t.source:
|
||||
md_path = _find_md_for_tool(config.root, t.source, name, t.category)
|
||||
md_path = _find_md_for_tool(config.root, t.source, name)
|
||||
if md_path and md_path.exists():
|
||||
content = md_path.read_text()
|
||||
# Strip YAML frontmatter
|
||||
@@ -102,7 +104,7 @@ def _tool_info(name: str) -> int:
|
||||
|
||||
|
||||
def _find_md_for_tool(
|
||||
root: Path, source: str, tool_name: str, category: str | None = None,
|
||||
root: Path, source: str, tool_name: str,
|
||||
) -> Path | None:
|
||||
"""Find the .md documentation file for a tool source path."""
|
||||
source_path = root / source
|
||||
@@ -111,10 +113,9 @@ def _find_md_for_tool(
|
||||
if md.exists():
|
||||
return md
|
||||
elif source_path.is_dir():
|
||||
# Directory source — look for src/<category>/<tool_name>.md
|
||||
py_name = tool_name.replace("-", "_")
|
||||
if category:
|
||||
md = source_path / "src" / category / f"{py_name}.md"
|
||||
if md.exists():
|
||||
return md
|
||||
pkg_name = source_path.name
|
||||
md = source_path / "src" / pkg_name / f"{py_name}.md"
|
||||
if md.exists():
|
||||
return md
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user