Files
watermarks-remover/install_skill.py
T
8d79155ad2 feat: add a lightweight Cursor text skill (#35)
* feat: add lightweight Cursor and Codex text skill

Package the text-only workflow with safe cross-platform installation, optional persistent instructions, and focused tests so users can adopt it without the media tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: keep the lightweight skill independently reviewable

Avoid coupling the packaging PR to the separate Unicode safety change so either pull request can merge on its own.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: force UTF-8 for subprocess stdin in Windows CI

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Guillaume Meyer (The Opinionated Man) <1385518+guillaumemeyer@users.noreply.github.com>
2026-08-15 14:37:19 -07:00

103 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Install the lightweight text skill for Cursor."""
from __future__ import annotations
import argparse
import os
import shutil
import sys
import tempfile
import uuid
from pathlib import Path
SKILL_NAME = "clean-user-facing-text"
ROOT = Path(__file__).resolve().parent
SOURCE = ROOT / "skills" / SKILL_NAME
def _present(path: Path) -> bool:
return path.exists() or path.is_symlink()
def _remove_path(path: Path) -> None:
if path.is_symlink() or path.is_file():
path.unlink()
elif path.exists():
shutil.rmtree(path)
def _stage(skills_dir: Path) -> tuple[Path, Path]:
skills_dir.mkdir(parents=True, exist_ok=True)
staging_root = Path(
tempfile.mkdtemp(prefix=f".{SKILL_NAME}.staging.", dir=skills_dir)
)
staged_skill = staging_root / SKILL_NAME
try:
shutil.copytree(SOURCE, staged_skill)
if not (staged_skill / "SKILL.md").is_file():
raise RuntimeError("staged skill is missing SKILL.md")
except BaseException:
shutil.rmtree(staging_root, ignore_errors=True)
raise
return staging_root, staged_skill
def _install(destination: Path, force: bool) -> tuple[bool, Path | None]:
if _present(destination) and not force:
print(f"already exists: {destination}", file=sys.stderr)
print(
"No changes made. Re-run with --force to back up and replace.",
file=sys.stderr,
)
return False, None
staging_root, staged_skill = _stage(destination.parent)
backup: Path | None = None
try:
if _present(destination):
backup = destination.with_name(
f"{destination.name}.backup.{uuid.uuid4().hex[:12]}"
)
os.replace(destination, backup)
try:
os.replace(staged_skill, destination)
except BaseException:
if backup is not None and not _present(destination):
os.replace(backup, destination)
raise
finally:
shutil.rmtree(staging_root, ignore_errors=True)
return True, backup
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--force",
action="store_true",
help="Back up and replace an existing Cursor installation",
)
parser.add_argument("--cursor-home", help="Override Cursor home (default: ~/.cursor)")
args = parser.parse_args()
cursor_home = Path(
args.cursor_home
or os.environ.get("CURSOR_HOME", Path.home() / ".cursor")
).expanduser()
destination = cursor_home / "skills" / SKILL_NAME
installed, backup = _install(destination, args.force)
if not installed:
return 1
if backup is not None:
print(f"Cursor: backed up existing skill to {backup}")
print(f"Cursor: installed {destination}")
print("Start a new Cursor session if the skill does not appear automatically.")
return 0
if __name__ == "__main__":
raise SystemExit(main())