/
davydov
/
NGPowers
Обзор
Документация
Войти
/
davydov
/
NGPowers
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/install_agent_env.py
282 строки
10 KB
Roman Davydov
feat(ngpowers): implement pre-archival safety gate and task auto-archiving safety
11 авг 2026, 01:33
11 авг 2026, 01:33
add1f7e
Код
Авторство
О чём код?
#!/usr/bin/env python3 """ install_agent_env.py - NGPowers Universal Multi-IDE Lifecycle Manager. Supports automatic discovery, 1-click installation, seamless updates, and clean uninstallation across all AI IDEs: - GigaCode (.gigacode / gigacode-extension.json) - Cursor (.cursor/rules/ngpowers.mdc) - Windsurf (.windsurf/ / .windsurfrules) - Google Antigravity / Gemini (.agents/ / GEMINI.md) - Claude Code (.claude/ / CLAUDE.md) - Qwen (.qwen/ / QWEN.md) - VS Code / Continue / Roo Code (.vscode/ / mcp.json) """ import os import sys import json import argparse import subprocess import shutil # Dynamic project root & root scripts path resolution _script_dir = os.path.dirname(os.path.abspath(__file__)) _curr = _script_dir _project_root = None while _curr != os.path.dirname(_curr): if (os.path.exists(os.path.join(_curr, '.ngpowers')) or os.path.exists(os.path.join(_curr, '.agents')) or os.path.exists(os.path.join(_curr, 'service_spec.md')) or os.path.exists(os.path.join(_curr, '.git'))): _project_root = _curr break _curr = os.path.dirname(_curr) if not _project_root: _project_root = os.path.abspath(os.path.join(_script_dir, '..', '..', '..')) if _script_dir not in sys.path: sys.path.insert(0, _script_dir) _root_scripts = os.path.join(_project_root, 'scripts') if os.path.exists(_root_scripts) and _root_scripts not in sys.path: sys.path.insert(0, _root_scripts) _bootstrap_scripts = os.path.join(_project_root, 'skills', 'ngpowers-bootstrap', 'scripts') if os.path.exists(_bootstrap_scripts) and _bootstrap_scripts not in sys.path: sys.path.insert(0, _bootstrap_scripts) SUPPORTED_ENVS = { "cursor": { "dir": ".cursor", "doc": ".cursor/rules/ngpowers.mdc", "label": "Cursor IDE" }, "gigacode": { "dir": ".gigacode", "doc": "GIGACODE.md", "label": "GigaCode IDE" }, "windsurf": { "dir": ".windsurf", "doc": ".windsurfrules", "label": "Windsurf (Cascade)" }, "antigravity": { "dir": ".agents", "doc": "GEMINI.md", "label": "Google Antigravity / Gemini 2.0" }, "claude": { "dir": ".claude", "doc": "CLAUDE.md", "label": "Claude Code" }, "qwen": { "dir": ".qwen", "doc": "QWEN.md", "label": "Qwen Code" }, "vscode": { "dir": ".vscode", "doc": ".vscode/mcp.json", "label": "VS Code / Continue / Roo Code" } } SUBDIRS_TO_LINK = ["skills", "workflows", "templates", "rules"] MARKER_START = "<!-- NGPowers Rules Start -->" MARKER_END = "<!-- NGPowers Rules End -->" def create_symlink(target_path, link_path, is_dir=False): target_path = os.path.normpath(target_path) link_path = os.path.normpath(link_path) if os.path.lexists(link_path): if is_dir and os.path.isdir(link_path): if os.path.islink(link_path): return for item in os.listdir(target_path): s_item = os.path.join(target_path, item) d_item = os.path.join(link_path, item) create_symlink(s_item, d_item, is_dir=os.path.isdir(s_item)) return else: return os.makedirs(os.path.dirname(link_path), exist_ok=True) if sys.platform == "win32": if is_dir: cmd = f'mklink /J "{link_path}" "{target_path}"' else: cmd = f'mklink /H "{link_path}" "{target_path}"' res = subprocess.run(cmd, shell=True, capture_output=True, text=True) if res.returncode != 0: if is_dir: shutil.copytree(target_path, link_path, dirs_exist_ok=True) else: shutil.copy2(target_path, link_path) else: os.symlink(target_path, link_path, target_is_directory=is_dir) def auto_discover_envs(target_dir): detected = [] for env_key, info in SUPPORTED_ENVS.items(): env_path = os.path.join(target_dir, info["dir"]) if os.path.exists(env_path): detected.append(env_key) # Default fallbacks if none detected: cursor, gigacode, antigravity if not detected: detected = ["cursor", "gigacode", "antigravity", "vscode"] return detected def install_ngpowers(target_dir, selected_envs=None): ngpowers_root = _project_root if not selected_envs or "all" in selected_envs: selected_envs = auto_discover_envs(target_dir) print(f"[NGPowers Installer] Installing NGPowers into: {target_dir}") print(f"[NGPowers Installer] Target IDE Environments: {', '.join(selected_envs)}") # 1. Initialize .ngpowers config & state DB ngpowers_dir = os.path.join(target_dir, ".ngpowers") os.makedirs(ngpowers_dir, exist_ok=True) os.makedirs(os.path.join(ngpowers_dir, "workspaces"), exist_ok=True) config_yml = os.path.join(ngpowers_dir, "config.yml") if not os.path.exists(config_yml): from load_config import load_config import yaml default_cfg = load_config(ngpowers_root) with open(config_yml, "w", encoding="utf-8") as f: yaml.dump(default_cfg, f, default_flow_style=False) print(f"[OK] Created default config at .ngpowers/config.yml") # 2. Link subdirectories & rules into each target environment for env_key in selected_envs: if env_key not in SUPPORTED_ENVS: continue info = SUPPORTED_ENVS[env_key] env_dir = os.path.join(target_dir, info["dir"]) os.makedirs(env_dir, exist_ok=True) for sdir in SUBDIRS_TO_LINK: src_path = os.path.join(ngpowers_root, sdir) dst_path = os.path.join(env_dir, sdir) if os.path.exists(src_path): create_symlink(src_path, dst_path, is_dir=True) print(f"[OK] Environment '{info['label']}' configured at {info['dir']}") # 3. Setup Git pre-commit hooks try: from setup_hooks import install_git_hook install_git_hook(target_dir) print("[OK] Git pre-commit quality gate hook installed.") except Exception as e: print(f"Warning setting up git hooks: {e}") # 4. Generate VS Code / Generic MCP config (mcp.json) mcp_json_path = os.path.join(target_dir, ".vscode", "mcp.json") os.makedirs(os.path.dirname(mcp_json_path), exist_ok=True) mcp_config = { "mcpServers": { "ngpowers-triage": { "command": sys.executable, "args": [os.path.join(ngpowers_root, "scripts", "mcp_server.py")] } } } with open(mcp_json_path, "w", encoding="utf-8") as f: json.dump(mcp_config, f, indent=2) print(f"[OK] Universal MCP config generated at .vscode/mcp.json") return {"status": "success", "installed_envs": selected_envs} def update_ngpowers(target_dir): print(f"[NGPowers Installer] Updating NGPowers framework at: {target_dir}") res = install_ngpowers(target_dir) print("[OK] NGPowers framework updated successfully (User config preserved).") return res def remove_ngpowers_links(link_path, src_path): link_path = os.path.normpath(link_path) src_path = os.path.normpath(src_path) if not os.path.lexists(link_path): return is_link_or_junction = os.path.islink(link_path) if not is_link_or_junction and sys.platform == "win32": try: st = os.lstat(link_path) if hasattr(st, 'st_file_attributes') and (st.st_file_attributes & 1024): is_link_or_junction = True except Exception: pass if is_link_or_junction: try: os.unlink(link_path) except Exception: try: os.rmdir(link_path) except Exception: pass return if os.path.isdir(link_path): if os.path.exists(src_path) and os.path.isdir(src_path): ng_items = set(os.listdir(src_path)) for item in os.listdir(link_path): if item.startswith("ngpowers-") or item in ng_items: item_path = os.path.join(link_path, item) item_src = os.path.join(src_path, item) remove_ngpowers_links(item_path, item_src) def uninstall_ngpowers(target_dir): ngpowers_root = _project_root print(f"[NGPowers Installer] Uninstalling NGPowers from: {target_dir}") # 1. Clean environment symlinks safely without deleting user directories for env_key, info in SUPPORTED_ENVS.items(): env_dir = os.path.join(target_dir, info["dir"]) if os.path.exists(env_dir): for sdir in SUBDIRS_TO_LINK: link_path = os.path.join(env_dir, sdir) src_path = os.path.join(ngpowers_root, sdir) remove_ngpowers_links(link_path, src_path) print(f"[OK] Cleaned NGPowers links from {info['dir']}") # 2. Remove git hooks if present hook_path = os.path.join(target_dir, ".git", "hooks", "pre-commit") if os.path.exists(hook_path): try: os.unlink(hook_path) print("[OK] Removed NGPowers Git pre-commit hook.") except Exception: pass print("[OK] NGPowers completely uninstalled (Source code & user skills untouched).") return {"status": "success"} def main(): parser = argparse.ArgumentParser(description="NGPowers Universal Multi-IDE Lifecycle Manager.") parser.add_argument("action", choices=["install", "update", "uninstall"], default="install", nargs="?") parser.add_argument("--dir", default=".", help="Target project directory") parser.add_argument("--env", nargs="*", choices=list(SUPPORTED_ENVS.keys()) + ["all"], help="Target IDE environments") parser.add_argument("--json", action="store_true", help="Output as JSON.") args = parser.parse_args() target_dir = os.path.abspath(args.dir) if args.action == "install": res = install_ngpowers(target_dir, args.env) elif args.action == "update": res = update_ngpowers(target_dir) elif args.action == "uninstall": res = uninstall_ngpowers(target_dir) if args.json: print(json.dumps(res, indent=2, ensure_ascii=False)) if __name__ == "__main__": main()