Files
2026-10-27 09:12:40 +08:00

54 lines
2.4 KiB
Python

"""Install selected repository skills into Codex or another skill directory."""
import argparse
import os
from pathlib import Path
import re
import shutil
SOURCE = Path(__file__).resolve().parents[1]
def linked(path):
return path.is_symlink() or getattr(path, 'is_junction', lambda: False)()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('names', nargs='*', help='Skill folder names; defaults to handycontrol-wpf')
parser.add_argument('--all', action='store_true', help='Install all component skills')
parser.add_argument('--dest', type=Path, default=Path(os.environ.get('CODEX_HOME', str(Path.home() / '.codex'))) / 'skills')
parser.add_argument('--force', action='store_true', help='Replace files in existing skill folders')
parser.add_argument('--list', action='store_true')
args = parser.parse_args()
available = sorted(p.parent.name for p in SOURCE.glob('*/SKILL.md'))
if args.list:
print('\n'.join(available))
return
if args.all and args.names:
parser.error('Use names or --all, not both')
names = available if args.all else list(dict.fromkeys(args.names or ['handycontrol-wpf']))
destination = args.dest.expanduser().resolve()
# Preflight every selection before copying anything. No deletes, no shell execution.
for name in names:
if not re.fullmatch(r'handycontrol-wpf(?:-[a-z0-9]+)*', name) or name not in available:
parser.error(f'Unknown skill: {name}')
target = destination / name
if target.resolve().is_relative_to(SOURCE.resolve()) or SOURCE.resolve().is_relative_to(target.resolve()):
parser.error(f'Destination overlaps repository skills: {target}')
if linked(target):
parser.error(f'Destination must not be a link: {target}')
if target.exists() and not args.force:
parser.error(f'Already exists: {target}; use --force to update files')
if target.exists() and not target.is_dir():
parser.error(f'Not a directory: {target}')
# Refuse linked descendants too, including when updating an existing skill.
if target.exists() and any(linked(p) for p in target.rglob('*')):
parser.error(f'Destination contains links: {target}')
for name in names:
shutil.copytree(SOURCE / name, destination / name, dirs_exist_ok=args.force)
print(f'Installed {name} -> {destination / name}')
if __name__ == '__main__':
main()