mirror of
https://github.com/paperclipai/agent-plugins.git
synced 2026-11-07 06:15:58 -10:00
75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Link dottastack skills from this checkout; use shared discovery for Codex and Cursor."""
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DIRECTORIES = {"codex": ".agents", "cursor": ".agents", "claude-code": ".claude"}
|
|
|
|
|
|
def matches(path, target):
|
|
return path.is_symlink() and path.resolve() == target.resolve()
|
|
|
|
|
|
def install(agents, home, dry_run=False):
|
|
skills = sorted((ROOT / "dottastack/skills").glob("*/SKILL.md"))
|
|
if not skills:
|
|
raise ValueError("No dottastack skills found")
|
|
planned, obsolete, conflicts = [], [], []
|
|
for directory in dict.fromkeys(DIRECTORIES[agent] for agent in agents):
|
|
for skill in skills:
|
|
target = skill.parent
|
|
dest = home / directory / "skills" / target.name
|
|
if os.path.lexists(dest):
|
|
if not matches(dest, target):
|
|
conflicts.append(str(dest))
|
|
else:
|
|
for parent in dest.parents:
|
|
if os.path.lexists(parent) and not parent.is_dir():
|
|
conflicts.append(str(parent))
|
|
break
|
|
planned.append((dest, target))
|
|
if directory == ".agents":
|
|
for legacy in [".codex", ".cursor"]:
|
|
path = home / legacy / "skills" / target.name
|
|
if os.path.lexists(path):
|
|
if matches(path, target):
|
|
obsolete.append((path, target))
|
|
else:
|
|
conflicts.append(str(path))
|
|
if conflicts:
|
|
raise ValueError("Conflicting entries; nothing changed:\n" + "\n".join(sorted(set(conflicts))))
|
|
for dest, target in planned:
|
|
print(f"{'Would link' if dry_run else 'Link'} {dest} -> {target}")
|
|
if not dry_run:
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
dest.symlink_to(os.path.relpath(target, dest.parent), target_is_directory=True)
|
|
for path, target in obsolete:
|
|
print(f"{'Would remove' if dry_run else 'Remove'} redundant link {path}")
|
|
if not dry_run:
|
|
if not matches(path, target):
|
|
raise ValueError(f"Link changed during installation; preserved {path}")
|
|
path.unlink()
|
|
print(f"{len(planned)} links {'planned' if dry_run else 'created'}; "
|
|
f"{len(obsolete)} redundant links {'would be removed' if dry_run else 'removed'}.")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--agent", choices=DIRECTORIES, action="append", required=True)
|
|
parser.add_argument("--home", type=Path, default=Path.home(), help="Target home; use a temporary path for checks")
|
|
parser.add_argument("--dry-run", action="store_true")
|
|
args = parser.parse_args()
|
|
try:
|
|
install(args.agent, args.home.expanduser().resolve(), args.dry_run)
|
|
except (OSError, ValueError) as error:
|
|
print(error, file=sys.stderr)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|