Files
skill-seekers-reference/src/skill_seekers/cli/workflows_command.py
yusyus 4b89e0a015 style: apply ruff format to all source and test files
Fixes ruff format --check CI failure. 22 files reformatted to satisfy
the ruff formatter's style requirements. No logic changes, only
whitespace/formatting adjustments.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 22:50:05 +03:00

312 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Workflows CLI Command
Manage enhancement workflow presets:
list List all workflows (bundled + user)
show Print YAML content of a workflow
copy Copy a bundled workflow to user dir for editing
add Install a custom YAML into user dir
remove Delete a user workflow (bundled ones cannot be removed)
validate Parse and validate a workflow YAML
Usage:
skill-seekers workflows list
skill-seekers workflows show security-focus
skill-seekers workflows copy security-focus
skill-seekers workflows add ./my-workflow.yaml
skill-seekers workflows remove my-workflow
skill-seekers workflows validate security-focus
"""
import shutil
import sys
from pathlib import Path
import yaml
from skill_seekers.cli.enhancement_workflow import (
WorkflowEngine,
list_bundled_workflows,
)
USER_WORKFLOWS_DIR = Path.home() / ".config" / "skill-seekers" / "workflows"
def _ensure_user_dir() -> Path:
USER_WORKFLOWS_DIR.mkdir(parents=True, exist_ok=True)
return USER_WORKFLOWS_DIR
def _bundled_yaml_text(name: str) -> str | None:
"""Return raw YAML text of a bundled workflow, or None if not found."""
from importlib.resources import files as importlib_files
for suffix in (".yaml", ".yml"):
try:
pkg_ref = importlib_files("skill_seekers.workflows").joinpath(name + suffix)
return pkg_ref.read_text(encoding="utf-8")
except (FileNotFoundError, TypeError, ModuleNotFoundError):
continue
return None
def _workflow_yaml_text(name_or_path: str) -> str | None:
"""Resolve a workflow by name or path and return its raw YAML text."""
# Try as a file path first
p = Path(name_or_path)
if p.suffix in (".yaml", ".yml") and p.exists():
return p.read_text(encoding="utf-8")
# Try as a name with .yaml extension
for suffix in (".yaml", ".yml"):
candidate = Path(name_or_path + suffix)
if candidate.exists():
return candidate.read_text(encoding="utf-8")
# User dir
user_file = USER_WORKFLOWS_DIR / (name_or_path + ".yaml")
if user_file.exists():
return user_file.read_text(encoding="utf-8")
user_file_yml = USER_WORKFLOWS_DIR / (name_or_path + ".yml")
if user_file_yml.exists():
return user_file_yml.read_text(encoding="utf-8")
# Bundled
return _bundled_yaml_text(name_or_path)
def _list_user_workflow_names() -> list[str]:
"""Return names of user workflows (without extension) from USER_WORKFLOWS_DIR."""
if not USER_WORKFLOWS_DIR.exists():
return []
return sorted(p.stem for p in USER_WORKFLOWS_DIR.iterdir() if p.suffix in (".yaml", ".yml"))
def cmd_list() -> int:
"""List all available workflows."""
bundled = list_bundled_workflows()
user = _list_user_workflow_names()
if not bundled and not user:
print("No workflows found.")
return 0
if bundled:
print("Bundled workflows (read-only):")
for name in bundled:
# Load description from YAML
text = _bundled_yaml_text(name)
desc = ""
if text:
try:
data = yaml.safe_load(text)
desc = data.get("description", "")
except Exception:
pass
print(f" {name:<32} {desc}")
if user:
print("\nUser workflows (~/.config/skill-seekers/workflows/):")
for name in user:
user_file = USER_WORKFLOWS_DIR / (name + ".yaml")
if not user_file.exists():
user_file = USER_WORKFLOWS_DIR / (name + ".yml")
desc = ""
try:
data = yaml.safe_load(user_file.read_text(encoding="utf-8"))
desc = data.get("description", "")
except Exception:
pass
print(f" {name:<32} {desc}")
return 0
def cmd_show(name: str) -> int:
"""Print YAML content of a workflow."""
text = _workflow_yaml_text(name)
if text is None:
print(f"Error: Workflow '{name}' not found.", file=sys.stderr)
print("Use 'skill-seekers workflows list' to see available workflows.", file=sys.stderr)
return 1
print(text, end="")
return 0
def cmd_copy(names: list[str]) -> int:
"""Copy one or more bundled workflows to user dir."""
rc = 0
for name in names:
text = _bundled_yaml_text(name)
if text is None:
print(f"Error: Bundled workflow '{name}' not found.", file=sys.stderr)
bundled = list_bundled_workflows()
if bundled:
print(f"Available bundled workflows: {', '.join(bundled)}", file=sys.stderr)
rc = 1
continue
dest = _ensure_user_dir() / (name + ".yaml")
if dest.exists():
print(f"Warning: '{dest}' already exists. Overwriting.")
dest.write_text(text, encoding="utf-8")
print(f"Copied '{name}' to: {dest}")
print(
f"Edit it with your favourite editor, then reference it as '--enhance-workflow {name}'"
)
return rc
def cmd_add(file_paths: list[str], override_name: str | None = None) -> int:
"""Install one or more custom YAML workflows into user dir."""
if override_name and len(file_paths) > 1:
print("Error: --name cannot be used when adding multiple files.", file=sys.stderr)
return 1
rc = 0
for file_path in file_paths:
src = Path(file_path)
if not src.exists():
print(f"Error: File '{file_path}' does not exist.", file=sys.stderr)
rc = 1
continue
if src.suffix not in (".yaml", ".yml"):
print(f"Error: '{file_path}' must have a .yaml or .yml extension.", file=sys.stderr)
rc = 1
continue
# Validate before installing
try:
text = src.read_text(encoding="utf-8")
data = yaml.safe_load(text)
if not isinstance(data, dict):
raise ValueError("YAML root must be a mapping")
if "stages" not in data:
raise ValueError("Workflow must contain a 'stages' key")
except Exception as exc:
print(f"Error: Invalid workflow YAML '{file_path}' {exc}", file=sys.stderr)
rc = 1
continue
dest_name = override_name if override_name else src.stem
dest = _ensure_user_dir() / (dest_name + ".yaml")
if dest.exists():
print(f"Warning: '{dest}' already exists. Overwriting.")
shutil.copy2(src, dest)
print(f"Installed workflow '{dest_name}' to: {dest}")
return rc
def cmd_remove(names: list[str]) -> int:
"""Delete one or more user workflows."""
rc = 0
bundled = list_bundled_workflows()
for name in names:
if name in bundled:
print(
f"Error: '{name}' is a bundled workflow and cannot be removed.",
file=sys.stderr,
)
print("Use 'skill-seekers workflows copy' to create an editable copy.", file=sys.stderr)
rc = 1
continue
removed = False
for suffix in (".yaml", ".yml"):
candidate = USER_WORKFLOWS_DIR / (name + suffix)
if candidate.exists():
candidate.unlink()
print(f"Removed workflow: {candidate}")
removed = True
break
if not removed:
print(f"Error: User workflow '{name}' not found.", file=sys.stderr)
rc = 1
return rc
def cmd_validate(name_or_path: str) -> int:
"""Parse and validate a workflow."""
try:
engine = WorkflowEngine(name_or_path)
wf = engine.workflow
print(f"✅ Workflow '{wf.name}' is valid.")
print(f" Description : {wf.description}")
print(f" Version : {wf.version}")
print(f" Stages : {len(wf.stages)}")
for stage in wf.stages:
status = "enabled" if stage.enabled else "disabled"
print(f" - {stage.name} ({stage.type}, {status})")
return 0
except FileNotFoundError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
except Exception as exc:
print(f"Error: Invalid workflow {exc}", file=sys.stderr)
return 1
def main(argv=None) -> None:
"""Entry point for skill-seekers-workflows."""
import argparse
parser = argparse.ArgumentParser(
prog="skill-seekers-workflows",
description="Manage enhancement workflow presets",
)
subparsers = parser.add_subparsers(dest="action", metavar="ACTION")
subparsers.add_parser("list", help="List all workflows (bundled + user)")
show_p = subparsers.add_parser("show", help="Print YAML content of a workflow")
show_p.add_argument("workflow_name")
copy_p = subparsers.add_parser("copy", help="Copy bundled workflow(s) to user dir")
copy_p.add_argument("workflow_names", nargs="+")
add_p = subparsers.add_parser("add", help="Install custom YAML file(s) into user dir")
add_p.add_argument("files", nargs="+")
add_p.add_argument("--name")
remove_p = subparsers.add_parser("remove", help="Delete user workflow(s)")
remove_p.add_argument("workflow_names", nargs="+")
validate_p = subparsers.add_parser("validate", help="Validate a workflow by name or file")
validate_p.add_argument("workflow_name")
args = parser.parse_args(argv)
if args.action is None:
parser.print_help()
sys.exit(0)
rc = 0
if args.action == "list":
rc = cmd_list()
elif args.action == "show":
rc = cmd_show(args.workflow_name)
elif args.action == "copy":
rc = cmd_copy(args.workflow_names)
elif args.action == "add":
rc = cmd_add(args.files, getattr(args, "name", None))
elif args.action == "remove":
rc = cmd_remove(args.workflow_names)
elif args.action == "validate":
rc = cmd_validate(args.workflow_name)
else:
parser.print_help()
sys.exit(rc)
if __name__ == "__main__":
main()