feat: enhancement workflow preset system with multi-target CLI
- Add YAML-based enhancement workflow presets shipped inside the package (default, minimal, security-focus, architecture-comprehensive, api-documentation) - Add `skill-seekers workflows` subcommand: list, show, copy, add, remove, validate - copy/add/remove all accept multiple names/files in one invocation with partial-failure behaviour - `add --name` override restricted to single-file operations - Add 5 MCP tools: list_workflows, get_workflow, create_workflow, update_workflow, delete_workflow - Fix: create command _add_common_args() now correctly forwards each --enhance-workflow as a separate flag instead of passing the whole list as a single argument - Update README: reposition as "data layer for AI systems" with AI Skills front and centre - Update CHANGELOG, QUICK_REFERENCE, CLAUDE.md with workflow preset details - 1,880+ tests passing Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -376,7 +376,8 @@ class CreateCommand:
|
||||
|
||||
# Enhancement Workflow arguments (NEW - Phase 2)
|
||||
if getattr(self.args, "enhance_workflow", None):
|
||||
argv.extend(["--enhance-workflow", self.args.enhance_workflow])
|
||||
for wf in self.args.enhance_workflow:
|
||||
argv.extend(["--enhance-workflow", wf])
|
||||
if getattr(self.args, "enhance_stage", None):
|
||||
for stage in self.args.enhance_stage:
|
||||
argv.extend(["--enhance-stage", stage])
|
||||
|
||||
@@ -27,6 +27,7 @@ import logging
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from importlib.resources import files as importlib_files
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
@@ -99,25 +100,63 @@ class WorkflowEngine:
|
||||
self.history: list[dict[str, Any]] = []
|
||||
self.enhancer = None # Lazy load UnifiedEnhancer
|
||||
|
||||
def _load_workflow(self, workflow_path: str | Path) -> EnhancementWorkflow:
|
||||
"""Load workflow from YAML file."""
|
||||
workflow_path = Path(workflow_path)
|
||||
def _load_workflow(self, workflow_ref: str | Path) -> EnhancementWorkflow:
|
||||
"""Load workflow from YAML file using 3-level search order.
|
||||
|
||||
# Resolve path (support both absolute and relative)
|
||||
if not workflow_path.is_absolute():
|
||||
# Try relative to CWD first
|
||||
if not workflow_path.exists():
|
||||
# Try in config directory
|
||||
config_dir = Path.home() / ".config" / "skill-seekers" / "workflows"
|
||||
workflow_path = config_dir / workflow_path
|
||||
Search order:
|
||||
1. Raw file path (absolute or relative) — existing behaviour
|
||||
2. ~/.config/skill-seekers/workflows/{name}.yaml — user overrides/custom
|
||||
3. skill_seekers/workflows/{name}.yaml via importlib.resources — bundled defaults
|
||||
"""
|
||||
workflow_ref = Path(workflow_ref)
|
||||
|
||||
if not workflow_path.exists():
|
||||
raise FileNotFoundError(f"Workflow not found: {workflow_path}")
|
||||
# Add .yaml extension for bare names
|
||||
name_str = str(workflow_ref)
|
||||
if not name_str.endswith((".yaml", ".yml")):
|
||||
yaml_ref = Path(name_str + ".yaml")
|
||||
else:
|
||||
yaml_ref = workflow_ref
|
||||
|
||||
logger.info(f"📋 Loading workflow: {workflow_path}")
|
||||
resolved_path: Path | None = None
|
||||
yaml_text: str | None = None
|
||||
|
||||
with open(workflow_path, encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
# Level 1: absolute path or relative-to-CWD
|
||||
if yaml_ref.is_absolute():
|
||||
if yaml_ref.exists():
|
||||
resolved_path = yaml_ref
|
||||
else:
|
||||
cwd_path = Path.cwd() / yaml_ref
|
||||
if cwd_path.exists():
|
||||
resolved_path = cwd_path
|
||||
elif yaml_ref.exists():
|
||||
resolved_path = yaml_ref
|
||||
|
||||
# Level 2: user config directory
|
||||
if resolved_path is None:
|
||||
user_dir = Path.home() / ".config" / "skill-seekers" / "workflows"
|
||||
user_path = user_dir / yaml_ref.name
|
||||
if user_path.exists():
|
||||
resolved_path = user_path
|
||||
|
||||
# Level 3: bundled package workflows via importlib.resources
|
||||
if resolved_path is None:
|
||||
bare_name = yaml_ref.name # e.g. "security-focus.yaml"
|
||||
try:
|
||||
pkg_ref = importlib_files("skill_seekers.workflows").joinpath(bare_name)
|
||||
yaml_text = pkg_ref.read_text(encoding="utf-8")
|
||||
logger.info(f"📋 Loading bundled workflow: {bare_name}")
|
||||
except (FileNotFoundError, TypeError, ModuleNotFoundError):
|
||||
raise FileNotFoundError(
|
||||
f"Workflow '{yaml_ref.stem}' not found. "
|
||||
"Use 'skill-seekers workflows list' to see available workflows."
|
||||
)
|
||||
|
||||
if resolved_path is not None:
|
||||
logger.info(f"📋 Loading workflow: {resolved_path}")
|
||||
with open(resolved_path, encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
else:
|
||||
data = yaml.safe_load(yaml_text)
|
||||
|
||||
# Handle inheritance (extends)
|
||||
if "extends" in data and data["extends"]:
|
||||
@@ -430,103 +469,27 @@ class WorkflowEngine:
|
||||
logger.info(f"💾 Saved workflow history: {output_path}")
|
||||
|
||||
|
||||
def create_default_workflows():
|
||||
"""Create default workflow templates in user config directory."""
|
||||
config_dir = Path.home() / ".config" / "skill-seekers" / "workflows"
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Default workflow
|
||||
default_workflow = {
|
||||
"name": "Default Enhancement",
|
||||
"description": "Standard AI enhancement with all features",
|
||||
"version": "1.0",
|
||||
"applies_to": ["codebase_analysis", "doc_scraping", "github_analysis"],
|
||||
"stages": [
|
||||
{
|
||||
"name": "base_analysis",
|
||||
"type": "builtin",
|
||||
"target": "patterns",
|
||||
"enabled": True,
|
||||
},
|
||||
{
|
||||
"name": "test_examples",
|
||||
"type": "builtin",
|
||||
"target": "examples",
|
||||
"enabled": True,
|
||||
},
|
||||
],
|
||||
"post_process": {
|
||||
"add_metadata": {"enhanced": True, "workflow": "default"}
|
||||
},
|
||||
}
|
||||
|
||||
# Security-focused workflow
|
||||
security_workflow = {
|
||||
"name": "Security-Focused Analysis",
|
||||
"description": "Emphasize security patterns and vulnerabilities",
|
||||
"version": "1.0",
|
||||
"applies_to": ["codebase_analysis"],
|
||||
"variables": {"focus_area": "security"},
|
||||
"stages": [
|
||||
{
|
||||
"name": "base_patterns",
|
||||
"type": "builtin",
|
||||
"target": "patterns",
|
||||
},
|
||||
{
|
||||
"name": "security_analysis",
|
||||
"type": "custom",
|
||||
"target": "security",
|
||||
"uses_history": True,
|
||||
"prompt": """Based on the patterns detected: {previous_results}
|
||||
|
||||
Perform deep security analysis:
|
||||
|
||||
1. **Authentication/Authorization**:
|
||||
- Auth bypass risks?
|
||||
- Token handling secure?
|
||||
- Session management issues?
|
||||
|
||||
2. **Input Validation**:
|
||||
- User input sanitized?
|
||||
- SQL injection risks?
|
||||
- XSS vulnerabilities?
|
||||
|
||||
3. **Data Exposure**:
|
||||
- Sensitive data in logs?
|
||||
- Secrets in config?
|
||||
- PII handling?
|
||||
|
||||
4. **Cryptography**:
|
||||
- Weak algorithms?
|
||||
- Hardcoded keys?
|
||||
- Insecure RNG?
|
||||
|
||||
Output as JSON with 'findings' array.""",
|
||||
},
|
||||
],
|
||||
"post_process": {
|
||||
"add_metadata": {"security_reviewed": True},
|
||||
},
|
||||
}
|
||||
|
||||
# Save workflows
|
||||
workflows = {
|
||||
"default.yaml": default_workflow,
|
||||
"security-focus.yaml": security_workflow,
|
||||
}
|
||||
|
||||
for filename, workflow_data in workflows.items():
|
||||
workflow_file = config_dir / filename
|
||||
if not workflow_file.exists():
|
||||
with open(workflow_file, "w", encoding="utf-8") as f:
|
||||
yaml.dump(workflow_data, f, default_flow_style=False, sort_keys=False)
|
||||
logger.info(f"✅ Created workflow: {workflow_file}")
|
||||
|
||||
return config_dir
|
||||
def list_bundled_workflows() -> list[str]:
|
||||
"""Return names of all bundled default workflows (without .yaml extension)."""
|
||||
try:
|
||||
pkg = importlib_files("skill_seekers.workflows")
|
||||
names = []
|
||||
for item in pkg.iterdir():
|
||||
name = str(item.name)
|
||||
if name.endswith((".yaml", ".yml")):
|
||||
names.append(name.removesuffix(".yaml").removesuffix(".yml"))
|
||||
return sorted(names)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create default workflows
|
||||
create_default_workflows()
|
||||
print("✅ Default workflows created!")
|
||||
def list_user_workflows() -> list[str]:
|
||||
"""Return names of all user-defined workflows (without .yaml extension)."""
|
||||
user_dir = Path.home() / ".config" / "skill-seekers" / "workflows"
|
||||
if not user_dir.exists():
|
||||
return []
|
||||
names = []
|
||||
for p in user_dir.iterdir():
|
||||
if p.suffix in (".yaml", ".yml"):
|
||||
names.append(p.stem)
|
||||
return sorted(names)
|
||||
|
||||
@@ -62,6 +62,7 @@ COMMAND_MODULES = {
|
||||
"update": "skill_seekers.cli.incremental_updater",
|
||||
"multilang": "skill_seekers.cli.multilang_support",
|
||||
"quality": "skill_seekers.cli.quality_metrics",
|
||||
"workflows": "skill_seekers.cli.workflows_command",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ from .stream_parser import StreamParser
|
||||
from .update_parser import UpdateParser
|
||||
from .multilang_parser import MultilangParser
|
||||
from .quality_parser import QualityParser
|
||||
from .workflows_parser import WorkflowsParser
|
||||
|
||||
# Registry of all parsers (in order of usage frequency)
|
||||
PARSERS = [
|
||||
@@ -50,6 +51,7 @@ PARSERS = [
|
||||
UpdateParser(),
|
||||
MultilangParser(),
|
||||
QualityParser(),
|
||||
WorkflowsParser(),
|
||||
]
|
||||
|
||||
|
||||
|
||||
85
src/skill_seekers/cli/parsers/workflows_parser.py
Normal file
85
src/skill_seekers/cli/parsers/workflows_parser.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Workflows subcommand parser."""
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class WorkflowsParser(SubcommandParser):
|
||||
"""Parser for the workflows subcommand."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "workflows"
|
||||
|
||||
@property
|
||||
def help(self) -> str:
|
||||
return "Manage enhancement workflow presets"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return (
|
||||
"List, inspect, copy, add, remove, and validate enhancement workflow "
|
||||
"presets. Bundled presets ship with the package; user presets live in "
|
||||
"~/.config/skill-seekers/workflows/."
|
||||
)
|
||||
|
||||
def add_arguments(self, parser) -> None:
|
||||
subparsers = parser.add_subparsers(dest="workflows_action", metavar="ACTION")
|
||||
|
||||
# list
|
||||
subparsers.add_parser(
|
||||
"list",
|
||||
help="List all available workflows (bundled + user)",
|
||||
)
|
||||
|
||||
# show
|
||||
show_p = subparsers.add_parser(
|
||||
"show",
|
||||
help="Print YAML content of a workflow",
|
||||
)
|
||||
show_p.add_argument("workflow_name", help="Workflow name (e.g. security-focus)")
|
||||
|
||||
# copy
|
||||
copy_p = subparsers.add_parser(
|
||||
"copy",
|
||||
help="Copy bundled workflow(s) to user dir for editing",
|
||||
)
|
||||
copy_p.add_argument(
|
||||
"workflow_names",
|
||||
nargs="+",
|
||||
help="Bundled workflow name(s) to copy",
|
||||
)
|
||||
|
||||
# add
|
||||
add_p = subparsers.add_parser(
|
||||
"add",
|
||||
help="Install a custom YAML file into the user workflow directory",
|
||||
)
|
||||
add_p.add_argument(
|
||||
"files",
|
||||
nargs="+",
|
||||
help="Path(s) to YAML workflow file(s) to install",
|
||||
)
|
||||
add_p.add_argument(
|
||||
"--name",
|
||||
help="Override the workflow filename (stem); only valid when adding a single file",
|
||||
)
|
||||
|
||||
# remove
|
||||
remove_p = subparsers.add_parser(
|
||||
"remove",
|
||||
help="Delete workflow(s) from the user directory (bundled workflows cannot be removed)",
|
||||
)
|
||||
remove_p.add_argument(
|
||||
"workflow_names",
|
||||
nargs="+",
|
||||
help="User workflow name(s) to remove",
|
||||
)
|
||||
|
||||
# validate
|
||||
validate_p = subparsers.add_parser(
|
||||
"validate",
|
||||
help="Parse and validate a workflow by name or file path",
|
||||
)
|
||||
validate_p.add_argument(
|
||||
"workflow_name", help="Workflow name or path to YAML file"
|
||||
)
|
||||
311
src/skill_seekers/cli/workflows_command.py
Normal file
311
src/skill_seekers/cli/workflows_command.py
Normal file
@@ -0,0 +1,311 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user