mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
feat(switch): add --link flag for junction/symlink install
Instead of copying skill folders, --link creates directory junctions (Windows) or symlinks (Linux/Mac) so updates propagate automatically via git pull. Only supported for claude-code platform (other platforms require path rewriting in SKILL.md). Also adds safe_rmtree to prevent shutil.rmtree from following junctions and deleting source files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,14 +8,31 @@
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
Скопируйте каталог `.claude/skills/` в корень вашего проекта. Навыки станут доступны при запуске Claude Code из этого каталога.
|
||||
Скопируйте каталог `.claude/skills/` в корень вашего проекта — или создайте ссылки на папки навыков из склонированного репозитория. Навыки станут доступны при запуске Claude Code из этого каталога.
|
||||
|
||||
Скопируйте каталог `.claude/skills/` из этого репозитория в корень вашего проекта:
|
||||
|
||||
```
|
||||
МойПроект/
|
||||
├── .claude/skills/ ← скопировать из этого репозитория
|
||||
├── .claude/skills/ ← скопировать сюда
|
||||
└── ...
|
||||
```
|
||||
|
||||
Или используйте скрипт установки:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Nikolay-Shirokov/cc-1c-skills.git tools/cc-1c-skills
|
||||
|
||||
# Ссылки (рекомендуется): обновления подхватываются через git pull
|
||||
python tools/cc-1c-skills/scripts/switch.py claude-code --project-dir . --link
|
||||
|
||||
# Копия: независимая копия, обновление — повторный запуск
|
||||
python tools/cc-1c-skills/scripts/switch.py claude-code --project-dir .
|
||||
|
||||
# Интерактивный режим: пошаговый выбор платформы, способа установки и рантайма
|
||||
python tools/cc-1c-skills/scripts/switch.py
|
||||
```
|
||||
|
||||
Не обязательно запоминать команды и параметры — просто опишите задачу своими словами, Claude сам подберёт нужные навыки. Слеш-команды (например `/epf-init МояОбработка`) тоже работают — для точного контроля.
|
||||
|
||||
## Группы навыков
|
||||
@@ -54,11 +71,14 @@
|
||||
python scripts/switch.py # интерактивный режим
|
||||
python scripts/switch.py cursor # скопировать навыки для Cursor
|
||||
python scripts/switch.py cursor --runtime python # Cursor + Python-рантайм
|
||||
python scripts/switch.py claude-code --project-dir /my/proj # установить в другой проект
|
||||
python scripts/switch.py --undo cursor # удалить копию
|
||||
python scripts/switch.py claude-code --project-dir /my/proj # установить копию в проект
|
||||
python scripts/switch.py claude-code --project-dir /my/proj --link # ссылки вместо копий
|
||||
python scripts/switch.py --undo cursor # удалить копию / ссылки
|
||||
```
|
||||
|
||||
Если репозиторий склонирован внутрь проекта (например, в `tools/cc-1c-skills`), используйте `--project-dir` для установки навыков в целевой проект. Обновление — `git pull` и повторный запуск.
|
||||
Если репозиторий склонирован внутрь проекта (например, в `tools/cc-1c-skills`), используйте `--project-dir` для установки навыков в целевой проект.
|
||||
|
||||
**Ссылки vs копии.** Флаг `--link` создаёт directory junction (Windows) или symlink (Linux/Mac) вместо копирования файлов. Обновления в источнике автоматически подхватываются во всех подключённых проектах — достаточно `git pull`. Ссылки доступны только для платформы Claude Code (для остальных платформ требуется перезапись путей в SKILL.md). Удаление ссылок: `--undo` — безопасно удаляет только ссылки, не трогая источник.
|
||||
|
||||
Поддерживаемые платформы:
|
||||
|
||||
|
||||
+135
-6
@@ -1,16 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
# switch.py v1.2 — Переключение навыков 1С между AI-платформами и рантаймами
|
||||
# switch.py v1.3 — Переключение навыков 1С между AI-платформами и рантаймами
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""
|
||||
Копирует навыки из .claude/skills/ на другие AI-платформы (Cursor, Codex, Copilot,
|
||||
Kiro, Gemini CLI, OpenCode, Windsurf, Kilo Code, Cline, Roo Code, Augment и др.)
|
||||
с перезаписью путей, и/или переключает рантайм (PowerShell ↔ Python).
|
||||
Копирует (или создаёт ссылки на) навыки из .claude/skills/ на другие AI-платформы
|
||||
(Cursor, Codex, Copilot, Kiro, Gemini CLI, OpenCode, Windsurf, Kilo Code, Cline,
|
||||
Roo Code, Augment и др.) с перезаписью путей, и/или переключает рантайм (PowerShell ↔ Python).
|
||||
|
||||
Использование:
|
||||
python scripts/switch.py # интерактивный режим
|
||||
python scripts/switch.py cursor # скопировать на Cursor
|
||||
python scripts/switch.py cursor --runtime python # скопировать + Python
|
||||
python scripts/switch.py claude-code --project-dir /my/proj # установить в проект
|
||||
python scripts/switch.py claude-code --project-dir /my/proj --link # ссылки вместо копий
|
||||
python scripts/switch.py --undo cursor # удалить копию
|
||||
python scripts/switch.py --runtime python # сменить runtime in-place
|
||||
"""
|
||||
@@ -59,6 +60,52 @@ RX_PS = re.compile(r'powershell\.exe\s+(?:-NoProfile\s+)?-File\s+(.+?)\.ps1')
|
||||
RX_PY = re.compile(r"python\s+('?[\w./_-]+?)\.py")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Junction / symlink helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
def is_junction(path):
|
||||
"""Check if path is a junction or symlink."""
|
||||
if os.path.islink(path):
|
||||
return True
|
||||
if hasattr(os.path, 'isjunction'):
|
||||
return os.path.isjunction(path)
|
||||
return False
|
||||
|
||||
|
||||
def remove_junction(path):
|
||||
"""Remove junction/symlink without following it."""
|
||||
if sys.platform == 'win32':
|
||||
os.rmdir(path)
|
||||
else:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
def create_junction(src, dst):
|
||||
"""Create directory junction (Windows) or symlink (Unix)."""
|
||||
if sys.platform == 'win32':
|
||||
import _winapi
|
||||
_winapi.CreateJunction(src, dst)
|
||||
else:
|
||||
os.symlink(src, dst, target_is_directory=True)
|
||||
|
||||
|
||||
def safe_rmtree(path):
|
||||
"""Remove directory tree, handling junctions/symlinks safely.
|
||||
|
||||
Unlike shutil.rmtree, this does not follow junctions/symlinks —
|
||||
it removes the link itself without touching the target.
|
||||
"""
|
||||
for entry in os.listdir(path):
|
||||
entry_path = os.path.join(path, entry)
|
||||
if is_junction(entry_path):
|
||||
remove_junction(entry_path)
|
||||
elif os.path.isdir(entry_path):
|
||||
shutil.rmtree(entry_path)
|
||||
else:
|
||||
os.unlink(entry_path)
|
||||
os.rmdir(path)
|
||||
|
||||
|
||||
def repo_root():
|
||||
"""Return the repository root (parent of scripts/)."""
|
||||
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -221,7 +268,7 @@ def cmd_install(platform, runtime, project_dir):
|
||||
existing = scan_skills(target_dir)
|
||||
if existing:
|
||||
print(f"В {target_prefix}/ уже есть {len(existing)} навыков. Обновляю...")
|
||||
shutil.rmtree(target_dir)
|
||||
safe_rmtree(target_dir)
|
||||
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
@@ -291,6 +338,64 @@ def cmd_install(platform, runtime, project_dir):
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_link(platform, project_dir):
|
||||
"""Create junctions/symlinks to skills instead of copying."""
|
||||
if platform != 'claude-code':
|
||||
print(f"Ошибка: ссылки поддерживаются только для claude-code "
|
||||
f"(выбрано: {platform}).", file=sys.stderr)
|
||||
print("Для других платформ требуется перезапись путей в SKILL.md — "
|
||||
"используйте копирование.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
src_dir = source_skills_dir()
|
||||
target_prefix = PLATFORMS[platform]
|
||||
target_dir = os.path.join(project_dir, target_prefix.replace('/', os.sep))
|
||||
|
||||
if not is_different_dir(target_dir, src_dir):
|
||||
print("Ошибка: нельзя создать ссылки на самого себя.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
skills = scan_skills(src_dir)
|
||||
if not skills:
|
||||
print(f"Ошибка: навыки не найдены в {src_dir}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if os.path.isdir(target_dir):
|
||||
existing = scan_skills(target_dir)
|
||||
if existing:
|
||||
print(f"В {target_prefix}/ уже есть {len(existing)} навыков. "
|
||||
f"Обновляю...")
|
||||
safe_rmtree(target_dir)
|
||||
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
# Copy root-level files (.gitignore etc.)
|
||||
for name in os.listdir(src_dir):
|
||||
src_path = os.path.join(src_dir, name)
|
||||
if os.path.isfile(src_path):
|
||||
shutil.copy2(src_path, os.path.join(target_dir, name))
|
||||
|
||||
linked = 0
|
||||
link_type = "junction" if sys.platform == 'win32' else "symlink"
|
||||
print(f"\nСоздание {link_type}-ссылок на {len(skills)} навыков "
|
||||
f"в {target_prefix}/ ...")
|
||||
|
||||
for skill_name in skills:
|
||||
src_skill = os.path.join(src_dir, skill_name)
|
||||
dst_skill = os.path.join(target_dir, skill_name)
|
||||
create_junction(src_skill, dst_skill)
|
||||
print(f" [OK] {skill_name}")
|
||||
linked += 1
|
||||
|
||||
print(f"\nГотово! {linked} навыков подключено через {link_type} "
|
||||
f"в {target_prefix}/")
|
||||
print("Обновления в источнике автоматически подхватятся.")
|
||||
print_gitignore_recommendations(project_dir)
|
||||
print(f"\nДля удаления: python scripts/switch.py --undo claude-code"
|
||||
f" --project-dir \"{project_dir}\"")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_undo(platform, project_dir):
|
||||
"""Remove installed skills for a platform."""
|
||||
target_prefix = PLATFORMS[platform]
|
||||
@@ -301,7 +406,7 @@ def cmd_undo(platform, project_dir):
|
||||
return 0
|
||||
|
||||
skills = scan_skills(target_dir)
|
||||
shutil.rmtree(target_dir)
|
||||
safe_rmtree(target_dir)
|
||||
|
||||
# Clean up empty parent directories
|
||||
parent = os.path.dirname(target_dir)
|
||||
@@ -480,6 +585,17 @@ def interactive_mode():
|
||||
print("Отмена.")
|
||||
return 0
|
||||
|
||||
# Ask install method for claude-code to different project
|
||||
if platform == 'claude-code' and install_mode \
|
||||
and is_different_dir(project_dir, repo_root()):
|
||||
method_options = [
|
||||
("Ссылки (junction)", "обновления подхватываются автоматически"),
|
||||
("Копирование", "независимая копия навыков"),
|
||||
]
|
||||
method = ask_choice("Способ установки:", method_options)
|
||||
if method == 1:
|
||||
return cmd_link('claude-code', project_dir)
|
||||
|
||||
runtime_options = [
|
||||
("PowerShell", "рекомендуется для Windows"),
|
||||
("Python", "рекомендуется для Linux/Mac"),
|
||||
@@ -506,6 +622,7 @@ def main():
|
||||
' python scripts/switch.py cursor\n'
|
||||
' python scripts/switch.py cursor --runtime python\n'
|
||||
' python scripts/switch.py claude-code --project-dir /my/proj\n'
|
||||
' python scripts/switch.py claude-code --project-dir /my/proj --link\n'
|
||||
' python scripts/switch.py --undo cursor\n'
|
||||
' python scripts/switch.py --runtime python\n',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
@@ -518,9 +635,21 @@ def main():
|
||||
help='удалить навыки для указанной платформы')
|
||||
parser.add_argument('--project-dir', default=os.getcwd(),
|
||||
help='путь к целевому проекту (по умолчанию: текущий каталог)')
|
||||
parser.add_argument('--link', action='store_true',
|
||||
help='создать ссылки (junction/symlink) вместо копирования '
|
||||
'(только для claude-code)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# --link: create junctions/symlinks
|
||||
if args.link:
|
||||
if not args.platform:
|
||||
parser.error("--link требует указания платформы")
|
||||
if args.runtime:
|
||||
parser.error("--link несовместим с --runtime "
|
||||
"(ссылки используют рантайм источника)")
|
||||
return cmd_link(args.platform, args.project_dir)
|
||||
|
||||
# --undo requires platform
|
||||
if args.undo:
|
||||
if not args.platform:
|
||||
|
||||
Reference in New Issue
Block a user