#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""``skills`` subcommand (argparse flavor) + the shared execution helpers.
``_skills_list`` / ``_skills_export`` / ``_skills_get`` are shared with
``_skills_click.py`` (the Click flavor) -- both front-ends dispatch into
the SAME execution path so behavior never drifts between the two CLI
frameworks a downstream package might choose.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
import scitex_logging as slogging
log = slogging.getLogger(__name__)
console = slogging.getConsole(f"{__name__}.console")
[docs]
def register_skills_subcommand(
subparsers: argparse._SubParsersAction,
package: str,
) -> argparse.ArgumentParser:
"""Register ``skills`` with ``list`` and ``get`` verb subcommands.
Usage::
scitex-stats skills list # List skill pages
scitex-stats skills get # Show main SKILL.md
scitex-stats skills get test-selection # Show a reference page
"""
prog = package.replace("_", "-")
parser = subparsers.add_parser(
"skills",
help=f"View skills for {package}",
description=f"Browse {package} skills (workflow-oriented guides).",
epilog=(
f"Examples:\n"
f" {prog} skills list\n"
f" {prog} skills get test-selection\n"
f" {prog} skills export --dry-run\n"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--help-recursive",
action="store_true",
help="Show help for all subcommands",
)
skills_sub = parser.add_subparsers(dest="skills_command", title="Commands")
# skills list
list_p = skills_sub.add_parser(
"list",
help="List available skill pages",
epilog=f"Examples:\n {prog} skills list\n {prog} skills list --json\n",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
list_p.add_argument("--json", action="store_true", dest="as_json")
list_p.set_defaults(func=lambda args: _skills_list(args, package))
# skills get [name]
get_p = skills_sub.add_parser(
"get",
help="Show a skill page",
epilog=(
f"Examples:\n"
f" {prog} skills get # list available\n"
f" {prog} skills get test-selection # show specific\n"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
get_p.add_argument(
"name",
nargs="?",
default=None,
help="Skill name (see 'skills list')",
)
get_p.add_argument("--json", action="store_true", dest="as_json")
get_p.add_argument(
"--owner", default=None, help="Owning distribution from skill discovery"
)
get_p.set_defaults(func=lambda args: _skills_get(args, package))
# skills export
export_p = skills_sub.add_parser(
"export",
help="Export skills to ~/.claude/skills/scitex/",
epilog=(
f"Examples:\n"
f" {prog} skills export # default dest\n"
f" {prog} skills export --dest /tmp/skills\n"
f" {prog} skills export --dry-run --json\n"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
export_p.add_argument(
"--dest",
default=None,
help="Exact target directory (default: ~/.claude/skills/scitex/)",
)
export_p.add_argument(
"--source",
choices=["installed", "pypi"],
default="installed",
help="local (installed packages) or pypi (download wheels)",
)
export_p.add_argument(
"--clean",
action="store_true",
help="Delete package subdirs before exporting",
)
export_p.add_argument(
"--dry-run",
action="store_true",
help="Preview what would be exported without writing",
)
export_p.add_argument(
"--yes",
"-y",
action="store_true",
help="Skip confirmation when overwriting destination",
)
export_p.add_argument(
"--json",
action="store_true",
dest="as_json",
help="Output as JSON",
)
export_p.set_defaults(func=lambda args: _skills_export(args, package))
parser.set_defaults(
func=lambda args: parser.print_help() if args.skills_command is None else None
)
return parser
def _skills_list(args: argparse.Namespace, package: str) -> None:
import logging
slogging.getLogger("scitex_dev._core.discovery").setLevel(logging.ERROR)
from ..._ecosystem._skills.skills import list_skills
result = list_skills(package=package)
if args.as_json:
slogging.getPlainConsole(__name__).emit(
json.dumps(result, indent=2), flush=False
)
else:
items = result.get(package, [])
if not items:
console.info(f"No skills found for {package}.")
return
console.info(f"Available skills for {package}:\n")
for s in items:
desc = f" {s['description']}" if s["description"] else ""
console.info(f" {s['name']}")
if desc:
console.info(f" {s['description']}")
prog = package.replace("_", "-")
console.info(f"\nUsage: {prog} skills get <name>")
def _skills_export(args: argparse.Namespace, package: str) -> None:
import logging
slogging.getLogger("scitex_dev._core.discovery").setLevel(logging.ERROR)
from ..._ecosystem._skills.skills import export_skills
from ..._ecosystem._skills.skills import _get_default_export_dest
dest = (
Path(args.dest) if getattr(args, "dest", None) else _get_default_export_dest()
)
source = getattr(args, "source", "installed")
clean = getattr(args, "clean", False)
if getattr(args, "dry_run", False):
from ..._ecosystem._skills.skills import list_skills
result = {
k: [e["name"] + ".md" for e in v]
for k, v in list_skills(package=package).items()
}
total = sum(len(v) for v in result.values())
if getattr(args, "as_json", False):
slogging.getPlainConsole(__name__).emit(
json.dumps(
{"dest": str(dest), "source": source, "packages": result}, indent=2
),
flush=False,
)
else:
console.info(f"Would export {total} files to {dest}/ (source={source})")
for k, v in sorted(result.items()):
console.info(f" {k}/: {len(v)} files")
return
exported = export_skills(dest, package=package, clean=clean, source=source)
if not exported:
log.error(f"No skills found for {package}.")
sys.exit(2)
if getattr(args, "as_json", False):
slogging.getPlainConsole(__name__).emit(
json.dumps({k: [str(f) for f in v] for k, v in exported.items()}, indent=2),
flush=False,
)
else:
total = sum(len(v) for v in exported.values())
console.info(f"Exported {total} files across {len(exported)} packages")
for k, v in exported.items():
console.info(f" {k}: {len(v)} files")
def _skills_get(args: argparse.Namespace, package: str) -> None:
import logging
slogging.getLogger("scitex_dev._core.discovery").setLevel(logging.ERROR)
# No name given → show available names
if args.name is None and getattr(args, "owner", None) is None:
_skills_list(argparse.Namespace(as_json=False), package)
return
from ..._ecosystem._skills.skills import get_skill
content = get_skill(
package=package, name=args.name, owner=getattr(args, "owner", None)
)
if content:
if args.as_json:
slogging.getPlainConsole(__name__).emit(
json.dumps({"package": package, "name": args.name, "content": content}),
flush=False,
)
else:
console.info(content)
else:
log.error(
f"Skill '{args.name}' not found in {package}. Run: {package} skills list"
)
sys.exit(2)
__all__ = [
"register_skills_subcommand",
"_skills_list",
"_skills_export",
"_skills_get",
]