Removes tools from grouping.

This commit is contained in:
2026-02-22 21:31:21 -08:00
parent d43c49d219
commit a5e9835d55
77 changed files with 260 additions and 2497 deletions

View File

@@ -19,7 +19,7 @@ from castle_cli.manifest import (
SystemdSpec,
ToolSpec,
)
from castle_cli.templates.scaffold import scaffold_category_tool, scaffold_project
from castle_cli.templates.scaffold import scaffold_project
def next_available_port(config: object) -> int:
@@ -42,16 +42,11 @@ def run_create(args: argparse.Namespace) -> int:
config = load_config()
name = args.name
proj_type = args.type
category = getattr(args, "category", None)
if name in config.components:
print(f"Error: component '{name}' already exists in castle.yaml")
return 1
# Category tool: add to existing category package
if proj_type == "tool" and category:
return _create_category_tool(config, name, args.description, category)
project_dir = config.root / name
if project_dir.exists():
print(f"Error: directory '{name}' already exists")
@@ -128,59 +123,3 @@ def run_create(args: argparse.Namespace) -> int:
return 0
def _create_category_tool(config: object, name: str, description: str | None, category: str) -> int:
"""Create a tool inside an existing category package."""
category_dir = config.root / "tools" / category
if not category_dir.exists():
print(f"Error: category directory 'tools/{category}/' does not exist")
print(" Create the category package first, then add tools to it.")
return 1
package_name = name.replace("-", "_")
desc = description or "A castle tool"
# Scaffold the .py and .md files into the category package
scaffold_category_tool(
category_dir=category_dir,
tool_name=name,
package_name=package_name,
category=category,
description=desc,
)
# Append entry point to existing pyproject.toml
pyproject_path = category_dir / "pyproject.toml"
if pyproject_path.exists():
content = pyproject_path.read_text()
entry_line = f'{name} = "{category}.{package_name}:main"'
if entry_line not in content:
# Find [project.scripts] section and append
if "[project.scripts]" in content:
content = content.replace(
"[project.scripts]",
f"[project.scripts]\n{entry_line}",
)
pyproject_path.write_text(content)
print(f" Added entry point to tools/{category}/pyproject.toml")
else:
print(" Warning: could not find [project.scripts] in pyproject.toml")
# Register in castle.yaml
manifest = ComponentManifest(
id=name,
description=desc,
tool=ToolSpec(source=f"tools/{category}/"),
install=InstallSpec(path=PathInstallSpec(alias=name)),
)
config.components[name] = manifest
save_config(config)
print(f"Created tool '{name}' in tools/{category}/")
print(f" Source: tools/{category}/src/{category}/{package_name}.py")
print(" Registered in castle.yaml")
print("\nNext steps:")
print(f" Edit tools/{category}/src/{category}/{package_name}.py")
print(f" cd tools/{category} && uv sync")
print(f" castle tool info {name}")
return 0

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
import argparse
from pathlib import Path
from castle_cli.config import load_config
@@ -28,7 +27,7 @@ def run_tool(args: argparse.Namespace) -> int:
def _tool_list() -> int:
"""List tools grouped by category."""
"""List all registered tools."""
config = load_config()
tools = {k: v for k, v in config.components.items() if v.tool}
@@ -36,24 +35,14 @@ def _tool_list() -> int:
print("No tools registered.")
return 0
by_group: dict[str, list[tuple[str, object]]] = {}
for name, manifest in tools.items():
if manifest.tool and manifest.tool.source:
group = Path(manifest.tool.source).name
else:
group = "standalone"
by_group.setdefault(group, []).append((name, manifest))
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 ""
deps = ""
if manifest.tool.system_dependencies:
deps = f" {DIM}[{', '.join(manifest.tool.system_dependencies)}]{RESET}"
print(f" {BOLD}{name:<20}{RESET} {desc}{deps}")
print(f"\n{BOLD}{CYAN}Tools{RESET}")
print(f"{CYAN}{'' * 40}{RESET}")
for name, manifest in sorted(tools.items()):
desc = manifest.description or ""
deps = ""
if manifest.tool and manifest.tool.system_dependencies:
deps = f" {DIM}[{', '.join(manifest.tool.system_dependencies)}]{RESET}"
print(f" {BOLD}{name:<20}{RESET} {desc}{deps}")
print()
return 0
@@ -83,39 +72,5 @@ def _tool_info(name: str) -> int:
if t.system_dependencies:
print(f" {BOLD}requires{RESET}: {', '.join(t.system_dependencies)}")
# Read and display .md documentation if available
if t.source:
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
if content.startswith("---\n"):
end = content.find("\n---\n", 4)
if end != -1:
content = content[end + 5:]
content = content.strip()
if content:
print(f"\n{BOLD}{CYAN}Documentation{RESET}")
print(f"{CYAN}{'' * 40}{RESET}")
print(f"{DIM}{content}{RESET}")
print()
return 0
def _find_md_for_tool(
root: Path, source: str, tool_name: str,
) -> Path | None:
"""Find the .md documentation file for a tool source path."""
source_path = root / source
if source_path.is_file():
md = source_path.with_suffix(".md")
if md.exists():
return md
elif source_path.is_dir():
py_name = tool_name.replace("-", "_")
pkg_name = source_path.name
md = source_path / "src" / pkg_name / f"{py_name}.md"
if md.exists():
return md
return None

View File

@@ -46,11 +46,6 @@ def build_parser() -> argparse.ArgumentParser:
create_parser.add_argument(
"--port", type=int, help="Port number (services only)"
)
create_parser.add_argument(
"--category", default=None,
help="Tool category (e.g. document, search, system)",
)
# castle info
info_parser = subparsers.add_parser("info", help="Show component details")
info_parser.add_argument("project", help="Component name")

View File

@@ -5,99 +5,6 @@ from __future__ import annotations
from pathlib import Path
def scaffold_category_tool(
category_dir: Path,
tool_name: str,
package_name: str,
category: str,
description: str,
) -> None:
"""Scaffold a tool inside an existing category package.
Creates the .py and .md files only — the category package
(pyproject.toml, tests, CLAUDE.md) already exists.
"""
src_dir = category_dir / "src" / category
# tool .py file
_write(
src_dir / f"{package_name}.py",
f'''#!/usr/bin/env python3
"""{tool_name}: {description}
Usage:
{tool_name} [options] [input]
cat input.txt | {tool_name}
Examples:
{tool_name} input.txt
{tool_name} input.txt -o output.txt
cat input.txt | {tool_name} > output.txt
"""
import argparse
import sys
__all__ = ["main"]
def main() -> int:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="{description}",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("input", nargs="?", help="Input file (default: stdin)")
parser.add_argument(
"-o", "--output", default=None, help="Output file (default: stdout)"
)
args = parser.parse_args()
if args.input:
with open(args.input) as f:
data = f.read()
else:
data = sys.stdin.read()
# TODO: implement tool logic
result = data
if args.output:
with open(args.output, "w") as f:
f.write(result)
else:
print(result, end="")
return 0
if __name__ == "__main__":
sys.exit(main())
''',
)
# tool .md documentation
_write(
src_dir / f"{package_name}.md",
f"""---
name: {tool_name}
category: {category}
---
# {tool_name}
{description}
## Usage
```bash
{tool_name} [options] [input]
cat input.txt | {tool_name}
```
""",
)
def scaffold_project(
project_dir: Path,
name: str,