mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-23 21:21:04 +03:00
feat: add unified switch.py for cross-platform skill porting
Single script to copy skills between AI platforms (Cursor, Codex, Copilot, Gemini CLI, OpenCode) with path rewriting and optional runtime switching. Includes interactive mode for newcomers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
da0cedf256
commit
7b69228d23
@@ -28,3 +28,10 @@ __pycache__/
|
|||||||
|
|
||||||
# Скриншоты (артефакты тестирования web-test)
|
# Скриншоты (артефакты тестирования web-test)
|
||||||
*.png
|
*.png
|
||||||
|
|
||||||
|
# Навыки, скопированные для других AI-платформ (генерируются scripts/switch.py)
|
||||||
|
.codex/
|
||||||
|
.cursor/skills/
|
||||||
|
.github/skills/
|
||||||
|
.gemini/
|
||||||
|
.opencode/
|
||||||
|
|||||||
@@ -44,13 +44,35 @@
|
|||||||
- **1С:Предприятие 8.3** — для сборки/разборки EPF/ERF (навыки генерации XML работают без платформы)
|
- **1С:Предприятие 8.3** — для сборки/разборки EPF/ERF (навыки генерации XML работают без платформы)
|
||||||
- **Node.js 18+** — для `/web-test` (тестирование через браузер)
|
- **Node.js 18+** — для `/web-test` (тестирование через браузер)
|
||||||
|
|
||||||
### Кроссплатформенный режим (Python)
|
### Другие AI-платформы
|
||||||
|
|
||||||
На Windows рекомендуется использовать PS1-рантайм (по умолчанию) как более стабильный и протестированный. Python-порты — для **Linux/Mac** или если PowerShell недоступен. PS1-скрипты — мастер-версия; Python-порты производные (см. [Python Porting Guide](docs/python-porting-guide.md)).
|
Навыки совместимы с Cursor, GitHub Copilot, OpenAI Codex, Gemini CLI и OpenCode. Скрипт `switch.py` копирует навыки в нужный формат с перезаписью путей:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts/switch-to-python.py # переключить на Python
|
python scripts/switch.py # интерактивный режим (пошаговый диалог)
|
||||||
python scripts/switch-to-powershell.py # вернуть на PowerShell
|
python scripts/switch.py cursor # скопировать навыки для Cursor
|
||||||
|
python scripts/switch.py cursor --runtime python # Cursor + Python-рантайм
|
||||||
|
python scripts/switch.py --undo cursor # удалить копию
|
||||||
|
```
|
||||||
|
|
||||||
|
Поддерживаемые платформы:
|
||||||
|
|
||||||
|
| Платформа | Целевой каталог | Слеш-команды |
|
||||||
|
|-----------|----------------|--------------|
|
||||||
|
| Claude Code | `.claude/skills/` | `/epf-init`, `/mxl-compile`, ... |
|
||||||
|
| Cursor | `.cursor/skills/` | через меню команд |
|
||||||
|
| GitHub Copilot | `.github/skills/` | `/skills` в чате |
|
||||||
|
| OpenAI Codex | `.codex/skills/` | `$skill-name` |
|
||||||
|
| Gemini CLI | `.gemini/skills/` | автоактивация |
|
||||||
|
| OpenCode | `.opencode/skills/` | через skill tool |
|
||||||
|
|
||||||
|
### Переключение рантайма (PowerShell ↔ Python)
|
||||||
|
|
||||||
|
На Windows рекомендуется PS1-рантайм (по умолчанию). Python-порты — для **Linux/Mac** или если PowerShell недоступен. PS1-скрипты — мастер-версия; Python-порты производные (см. [Python Porting Guide](docs/python-porting-guide.md)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/switch.py --runtime python # переключить на Python
|
||||||
|
python scripts/switch.py --runtime powershell # вернуть на PowerShell
|
||||||
```
|
```
|
||||||
|
|
||||||
Дополнительные зависимости Python-рантайма:
|
Дополнительные зависимости Python-рантайма:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import os, re, glob, sys
|
import os, re, glob, sys
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
print("Совет: используйте 'python scripts/switch.py --runtime powershell' (новый интерфейс)\n")
|
||||||
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
skills_dir = os.path.join(repo_root, '.claude', 'skills')
|
skills_dir = os.path.join(repo_root, '.claude', 'skills')
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import os, re, glob, sys
|
import os, re, glob, sys
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
print("Совет: используйте 'python scripts/switch.py --runtime python' (новый интерфейс)\n")
|
||||||
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
skills_dir = os.path.join(repo_root, '.claude', 'skills')
|
skills_dir = os.path.join(repo_root, '.claude', 'skills')
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# switch.py v1.0 — Переключение навыков 1С между AI-платформами и рантаймами
|
||||||
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
"""
|
||||||
|
Копирует навыки из .claude/skills/ на другие AI-платформы (Cursor, Codex, Copilot,
|
||||||
|
Gemini CLI, OpenCode) с перезаписью путей, и/или переключает рантайм (PowerShell ↔ Python).
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
python scripts/switch.py # интерактивный режим
|
||||||
|
python scripts/switch.py cursor # скопировать на Cursor
|
||||||
|
python scripts/switch.py cursor --runtime python # скопировать + Python
|
||||||
|
python scripts/switch.py --undo cursor # удалить копию
|
||||||
|
python scripts/switch.py --runtime python # сменить runtime in-place
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Platform registry
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
PLATFORMS = {
|
||||||
|
'claude-code': '.claude/skills',
|
||||||
|
'codex': '.codex/skills',
|
||||||
|
'cursor': '.cursor/skills',
|
||||||
|
'copilot': '.github/skills',
|
||||||
|
'gemini': '.gemini/skills',
|
||||||
|
'opencode': '.opencode/skills',
|
||||||
|
}
|
||||||
|
|
||||||
|
SOURCE_PREFIX = '.claude/skills'
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Runtime regex patterns (from switch-to-python.py / switch-to-powershell.py)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
RX_PS = re.compile(r'powershell\.exe\s+(?:-NoProfile\s+)?-File\s+(.+?)\.ps1')
|
||||||
|
RX_PY = re.compile(r"python\s+('?[\w./_-]+?)\.py")
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root():
|
||||||
|
"""Return the repository root (parent of scripts/)."""
|
||||||
|
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def source_skills_dir():
|
||||||
|
return os.path.join(repo_root(), '.claude', 'skills')
|
||||||
|
|
||||||
|
|
||||||
|
def scan_skills(skills_dir):
|
||||||
|
"""Return sorted list of skill directory names that contain SKILL.md."""
|
||||||
|
result = []
|
||||||
|
for entry in sorted(os.listdir(skills_dir)):
|
||||||
|
skill_path = os.path.join(skills_dir, entry)
|
||||||
|
if os.path.isdir(skill_path) and os.path.isfile(os.path.join(skill_path, 'SKILL.md')):
|
||||||
|
result.append(entry)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def collect_md_files(skill_dir):
|
||||||
|
"""Return list of .md files in a skill directory."""
|
||||||
|
return sorted(glob.glob(os.path.join(skill_dir, '*.md')))
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Transformations
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def rewrite_paths(content, source_prefix, target_prefix):
|
||||||
|
"""Replace .claude/skills/ path prefix with target platform prefix."""
|
||||||
|
return content.replace(source_prefix + '/', target_prefix + '/')
|
||||||
|
|
||||||
|
|
||||||
|
def switch_runtime_content(content, target_runtime):
|
||||||
|
"""Switch runtime invocations in .md content. Returns (new_content, switched)."""
|
||||||
|
if target_runtime == 'python':
|
||||||
|
new = RX_PS.sub(r'python \1.py', content)
|
||||||
|
elif target_runtime == 'powershell':
|
||||||
|
new = RX_PY.sub(r'powershell.exe -NoProfile -File \1.ps1', content)
|
||||||
|
else:
|
||||||
|
return content, False
|
||||||
|
return new, new != content
|
||||||
|
|
||||||
|
|
||||||
|
def check_runtime_files(skills_dir, target_runtime, root):
|
||||||
|
"""Check that target runtime script files exist. Returns list of warnings."""
|
||||||
|
warnings = []
|
||||||
|
for skill_name in scan_skills(skills_dir):
|
||||||
|
for md_path in collect_md_files(os.path.join(skills_dir, skill_name)):
|
||||||
|
with open(md_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
if target_runtime == 'python':
|
||||||
|
matches = RX_PS.findall(content)
|
||||||
|
for m in matches:
|
||||||
|
py_path = m.lstrip("'") + '.py'
|
||||||
|
if not os.path.isfile(os.path.join(root, py_path)):
|
||||||
|
warnings.append(f" {py_path} не найден")
|
||||||
|
elif target_runtime == 'powershell':
|
||||||
|
matches = RX_PY.findall(content)
|
||||||
|
for m in matches:
|
||||||
|
ps1_path = m.lstrip("'") + '.ps1'
|
||||||
|
if not os.path.isfile(os.path.join(root, ps1_path)):
|
||||||
|
warnings.append(f" {ps1_path} не найден")
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Commands
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def cmd_install(platform, runtime, project_dir):
|
||||||
|
"""Copy skills to target platform directory with path rewriting."""
|
||||||
|
src_dir = source_skills_dir()
|
||||||
|
target_prefix = PLATFORMS[platform]
|
||||||
|
target_dir = os.path.join(project_dir, target_prefix.replace('/', os.sep))
|
||||||
|
|
||||||
|
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)} навыков. Обновляю...")
|
||||||
|
shutil.rmtree(target_dir)
|
||||||
|
|
||||||
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
|
installed = 0
|
||||||
|
warnings = []
|
||||||
|
|
||||||
|
print(f"\nКопирование {len(skills)} навыков в {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)
|
||||||
|
|
||||||
|
# Copy entire skill directory
|
||||||
|
shutil.copytree(src_skill, dst_skill)
|
||||||
|
|
||||||
|
# Rewrite paths in all .md files
|
||||||
|
for md_path in collect_md_files(dst_skill):
|
||||||
|
with open(md_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
new_content = rewrite_paths(content, SOURCE_PREFIX, target_prefix)
|
||||||
|
|
||||||
|
# Apply runtime switch if requested
|
||||||
|
if runtime == 'python':
|
||||||
|
new_content, _ = switch_runtime_content(new_content, 'python')
|
||||||
|
|
||||||
|
# Check .py files exist in source repo
|
||||||
|
for m in RX_PS.findall(content):
|
||||||
|
clean = m.lstrip("'").replace(SOURCE_PREFIX, target_prefix)
|
||||||
|
original_py = m.lstrip("'") + '.py'
|
||||||
|
if not os.path.isfile(os.path.join(repo_root(), original_py)):
|
||||||
|
warnings.append(f" {original_py} не найден ({skill_name})")
|
||||||
|
|
||||||
|
if new_content != content:
|
||||||
|
with open(md_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
print(f" [OK] {skill_name}")
|
||||||
|
installed += 1
|
||||||
|
|
||||||
|
print(f"\nГотово! {installed} навыков установлено в {target_prefix}/")
|
||||||
|
if warnings:
|
||||||
|
print("\nПредупреждения (отсутствующие .py файлы):")
|
||||||
|
for w in warnings:
|
||||||
|
print(w)
|
||||||
|
print(f"\nДля удаления: python scripts/switch.py --undo {platform}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_undo(platform, project_dir):
|
||||||
|
"""Remove installed skills for a platform."""
|
||||||
|
target_prefix = PLATFORMS[platform]
|
||||||
|
target_dir = os.path.join(project_dir, target_prefix.replace('/', os.sep))
|
||||||
|
|
||||||
|
if not os.path.isdir(target_dir):
|
||||||
|
print(f"Директория {target_prefix}/ не найдена — нечего удалять.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
skills = scan_skills(target_dir)
|
||||||
|
shutil.rmtree(target_dir)
|
||||||
|
|
||||||
|
# Clean up empty parent directories
|
||||||
|
parent = os.path.dirname(target_dir)
|
||||||
|
if os.path.isdir(parent) and not os.listdir(parent):
|
||||||
|
os.rmdir(parent)
|
||||||
|
|
||||||
|
print(f"Удалено: {target_prefix}/ ({len(skills)} навыков)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_switch_runtime(runtime, project_dir):
|
||||||
|
"""Switch runtime in-place for skills in the current project."""
|
||||||
|
# Find skills directory: try all known platform dirs
|
||||||
|
skills_dir = None
|
||||||
|
platform_name = None
|
||||||
|
for name, prefix in PLATFORMS.items():
|
||||||
|
candidate = os.path.join(project_dir, prefix.replace('/', os.sep))
|
||||||
|
if os.path.isdir(candidate) and scan_skills(candidate):
|
||||||
|
skills_dir = candidate
|
||||||
|
platform_name = name
|
||||||
|
break
|
||||||
|
|
||||||
|
if not skills_dir:
|
||||||
|
print("Ошибка: не найдена директория навыков в текущем каталоге.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
skills = scan_skills(skills_dir)
|
||||||
|
switched = 0
|
||||||
|
warnings = []
|
||||||
|
|
||||||
|
print(f"\nПереключение на {runtime} в {PLATFORMS[platform_name]}/ ...")
|
||||||
|
|
||||||
|
for skill_name in skills:
|
||||||
|
skill_path = os.path.join(skills_dir, skill_name)
|
||||||
|
for md_path in collect_md_files(skill_path):
|
||||||
|
with open(md_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
new_content, changed = switch_runtime_content(content, runtime)
|
||||||
|
|
||||||
|
# Check target files exist
|
||||||
|
if runtime == 'python':
|
||||||
|
for m in RX_PS.findall(content):
|
||||||
|
py_path = m.lstrip("'") + '.py'
|
||||||
|
full = os.path.join(repo_root(), py_path)
|
||||||
|
if not os.path.isfile(full):
|
||||||
|
md_name = os.path.basename(md_path)
|
||||||
|
warnings.append(f" {py_path} не найден ({skill_name}/{md_name})")
|
||||||
|
elif runtime == 'powershell':
|
||||||
|
for m in RX_PY.findall(content):
|
||||||
|
ps1_path = m.lstrip("'") + '.ps1'
|
||||||
|
full = os.path.join(repo_root(), ps1_path)
|
||||||
|
if not os.path.isfile(full):
|
||||||
|
md_name = os.path.basename(md_path)
|
||||||
|
warnings.append(f" {ps1_path} не найден ({skill_name}/{md_name})")
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
with open(md_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
md_name = os.path.basename(md_path)
|
||||||
|
print(f" [OK] {skill_name}/{md_name}")
|
||||||
|
switched += 1
|
||||||
|
|
||||||
|
print(f"\nПереключено {switched} файлов на {runtime}.")
|
||||||
|
if warnings:
|
||||||
|
print(f"\nПредупреждения (отсутствующие файлы):")
|
||||||
|
for w in warnings:
|
||||||
|
print(w)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Interactive mode
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def ask_choice(prompt, options, default=1):
|
||||||
|
"""Ask user to choose from numbered options. Returns 1-based index."""
|
||||||
|
print(f"\n{prompt}")
|
||||||
|
for i, (label, hint) in enumerate(options, 1):
|
||||||
|
marker = "*" if i == default else " "
|
||||||
|
print(f" {marker}{i}. {label:<16} ({hint})")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
raw = input(f"\nВыбор [{default}]: ").strip()
|
||||||
|
if not raw:
|
||||||
|
return default
|
||||||
|
val = int(raw)
|
||||||
|
if 1 <= val <= len(options):
|
||||||
|
return val
|
||||||
|
print(f" Введите число от 1 до {len(options)}")
|
||||||
|
except ValueError:
|
||||||
|
print(f" Введите число от 1 до {len(options)}")
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
print("\nОтмена.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_mode():
|
||||||
|
"""Run interactive setup wizard."""
|
||||||
|
print("Навыки 1С — настройка платформы")
|
||||||
|
print("=" * 31)
|
||||||
|
|
||||||
|
platform_options = [
|
||||||
|
("Claude Code", ".claude/skills/"),
|
||||||
|
("Cursor", ".cursor/skills/"),
|
||||||
|
("GitHub Copilot", ".github/skills/"),
|
||||||
|
("OpenAI Codex", ".codex/skills/"),
|
||||||
|
("Gemini CLI", ".gemini/skills/"),
|
||||||
|
("OpenCode", ".opencode/skills/"),
|
||||||
|
]
|
||||||
|
platform_keys = ['claude-code', 'cursor', 'copilot', 'codex', 'gemini', 'opencode']
|
||||||
|
|
||||||
|
choice = ask_choice("Для какой платформы настроить навыки?", platform_options)
|
||||||
|
platform = platform_keys[choice - 1]
|
||||||
|
|
||||||
|
# Check if already installed — offer update or remove
|
||||||
|
project_dir = os.getcwd()
|
||||||
|
target_prefix = PLATFORMS[platform]
|
||||||
|
target_dir = os.path.join(project_dir, target_prefix.replace('/', os.sep))
|
||||||
|
|
||||||
|
if platform != 'claude-code' and os.path.isdir(target_dir):
|
||||||
|
existing = scan_skills(target_dir)
|
||||||
|
if existing:
|
||||||
|
action_options = [
|
||||||
|
("Обновить", f"перезаписать {len(existing)} навыков"),
|
||||||
|
("Удалить", f"удалить {target_prefix}/"),
|
||||||
|
("Отмена", "ничего не делать"),
|
||||||
|
]
|
||||||
|
action = ask_choice(
|
||||||
|
f"В {target_prefix}/ уже есть {len(existing)} навыков.",
|
||||||
|
action_options
|
||||||
|
)
|
||||||
|
if action == 2:
|
||||||
|
return cmd_undo(platform, project_dir)
|
||||||
|
if action == 3:
|
||||||
|
print("Отмена.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
runtime_options = [
|
||||||
|
("PowerShell", "рекомендуется для Windows"),
|
||||||
|
("Python", "рекомендуется для Linux/Mac"),
|
||||||
|
]
|
||||||
|
rt_choice = ask_choice("Какой рантайм скриптов?", runtime_options)
|
||||||
|
runtime = 'powershell' if rt_choice == 1 else 'python'
|
||||||
|
|
||||||
|
if platform == 'claude-code':
|
||||||
|
return cmd_switch_runtime(runtime, project_dir)
|
||||||
|
else:
|
||||||
|
return cmd_install(platform, runtime, project_dir)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# CLI argument parsing
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
return interactive_mode()
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Переключение навыков 1С между AI-платформами и рантаймами',
|
||||||
|
epilog='Примеры:\n'
|
||||||
|
' python scripts/switch.py cursor\n'
|
||||||
|
' python scripts/switch.py cursor --runtime python\n'
|
||||||
|
' python scripts/switch.py --undo cursor\n'
|
||||||
|
' python scripts/switch.py --runtime python\n',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
|
)
|
||||||
|
parser.add_argument('platform', nargs='?', choices=list(PLATFORMS.keys()),
|
||||||
|
help='целевая платформа')
|
||||||
|
parser.add_argument('--runtime', choices=['python', 'powershell'],
|
||||||
|
help='рантайм скриптов (python или powershell)')
|
||||||
|
parser.add_argument('--undo', action='store_true',
|
||||||
|
help='удалить навыки для указанной платформы')
|
||||||
|
parser.add_argument('--project-dir', default=os.getcwd(),
|
||||||
|
help='путь к целевому проекту (по умолчанию: текущий каталог)')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# --undo requires platform
|
||||||
|
if args.undo:
|
||||||
|
if not args.platform:
|
||||||
|
parser.error("--undo требует указания платформы")
|
||||||
|
if args.platform == 'claude-code':
|
||||||
|
parser.error("--undo не применим к claude-code (это исходная платформа)")
|
||||||
|
return cmd_undo(args.platform, args.project_dir)
|
||||||
|
|
||||||
|
# --runtime without platform = in-place switch
|
||||||
|
if args.runtime and not args.platform:
|
||||||
|
return cmd_switch_runtime(args.runtime, args.project_dir)
|
||||||
|
|
||||||
|
# platform specified
|
||||||
|
if args.platform:
|
||||||
|
if args.platform == 'claude-code':
|
||||||
|
if args.runtime:
|
||||||
|
return cmd_switch_runtime(args.runtime, args.project_dir)
|
||||||
|
else:
|
||||||
|
parser.error("для claude-code укажите --runtime python или --runtime powershell")
|
||||||
|
runtime = args.runtime or 'powershell'
|
||||||
|
return cmd_install(args.platform, runtime, args.project_dir)
|
||||||
|
|
||||||
|
# No args at all — shouldn't reach here due to len(sys.argv)==1 check
|
||||||
|
return interactive_mode()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main() or 0)
|
||||||
Reference in New Issue
Block a user