v1.9.40: add Codex integration — skill setup, deploy, GUI buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
IPGO Developer
2026-03-07 07:27:22 +00:00
parent ddd6951610
commit e2bdffb41e
14 changed files with 1067 additions and 130 deletions

View File

@@ -1,12 +1,15 @@
"""
Claude Code integration setup.
Installs ssh.py, encryption.py, /ssh skill, SSH key — everything needed
for Claude Code to manage servers via the shared servers.json.
Local AI agent integration setup.
Installs the shared ssh.py/encryption.py backend, Claude /ssh command,
Codex skill package, platform-specific wrappers, and SSH key material.
"""
import os
import sys
import re
import shutil
import subprocess
import sys
from core.logger import log
SHARED_DIR = os.path.expanduser("~/.server-connections")
@@ -19,13 +22,24 @@ else:
SSH_SCRIPT_SRC = os.path.join(_BASE_DIR, "tools", "ssh.py")
ENCRYPTION_SRC = os.path.join(_BASE_DIR, "core", "encryption.py")
SKILL_SRC = os.path.join(_BASE_DIR, "tools", "skill-ssh.md")
CLAUDE_SKILL_SRC = os.path.join(_BASE_DIR, "tools", "skill-ssh.md")
SKILL_DST_DIR = os.path.expanduser("~/.claude/commands")
SKILL_DST = os.path.join(SKILL_DST_DIR, "ssh.md")
CLAUDE_SKILL_DST_DIR = os.path.expanduser("~/.claude/commands")
CLAUDE_SKILL_DST = os.path.join(CLAUDE_SKILL_DST_DIR, "ssh.md")
SSH_KEY_PATH = os.path.expanduser("~/.ssh/id_ed25519")
GLOBAL_CLAUDE_MD = os.path.expanduser("~/.claude/CLAUDE.md")
CODEX_SKILL_SRC_DIR = os.path.join(_BASE_DIR, ".codex", "skills", "server-manager")
CODEX_SKILL_DST_ROOT = os.path.expanduser("~/.codex/skills")
CODEX_SKILL_DST_DIR = os.path.join(CODEX_SKILL_DST_ROOT, "server-manager")
CODEX_SKILL_ENTRY = os.path.join(CODEX_SKILL_DST_DIR, "SKILL.md")
CODEX_WRAPPER_SRC_SH = os.path.join(CODEX_SKILL_SRC_DIR, "scripts", "codex-ssh-wrapper.sh")
CODEX_WRAPPER_SRC_CMD = os.path.join(CODEX_SKILL_SRC_DIR, "scripts", "codex-ssh-wrapper.cmd")
CODEX_WRAPPER_DST = os.path.join(
SHARED_DIR,
"codex-ssh.cmd" if sys.platform == "win32" else "codex-ssh",
)
_BLOCK_START = "<!-- server-manager:start -->"
_BLOCK_END = "<!-- server-manager:end -->"
@@ -67,6 +81,27 @@ python ~/.server-connections/ssh.py --status # online/offline
"""
def _ensure_executable(path: str):
if sys.platform == "win32" or not os.path.exists(path):
return
mode = os.stat(path).st_mode
os.chmod(path, mode | 0o755)
def _copy_file(src: str, dst: str, executable: bool = False) -> str:
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(src, dst)
if executable:
_ensure_executable(dst)
return dst
def _copy_tree(src: str, dst: str) -> str:
os.makedirs(dst, exist_ok=True)
shutil.copytree(src, dst, dirs_exist_ok=True)
return dst
def check_status() -> dict:
"""Check what's installed and what's missing."""
return {
@@ -74,7 +109,9 @@ def check_status() -> dict:
"servers_json": os.path.exists(os.path.join(SHARED_DIR, "servers.json")),
"ssh_script": os.path.exists(os.path.join(SHARED_DIR, "ssh.py")),
"encryption": os.path.exists(os.path.join(SHARED_DIR, "encryption.py")),
"skill_installed": os.path.exists(SKILL_DST),
"claude_skill_installed": os.path.exists(CLAUDE_SKILL_DST),
"codex_skill_installed": os.path.exists(CODEX_SKILL_ENTRY),
"codex_wrapper_installed": os.path.exists(CODEX_WRAPPER_DST),
"ssh_key_exists": os.path.exists(SSH_KEY_PATH),
"ssh_key_pub": os.path.exists(SSH_KEY_PATH + ".pub"),
}
@@ -85,10 +122,9 @@ def install_ssh_script() -> str:
os.makedirs(SHARED_DIR, exist_ok=True)
results = []
# Copy ssh.py
dst = os.path.join(SHARED_DIR, "ssh.py")
if os.path.exists(SSH_SCRIPT_SRC):
shutil.copy2(SSH_SCRIPT_SRC, dst)
_copy_file(SSH_SCRIPT_SRC, dst, executable=True)
log.info(f"ssh.py installed: {dst}")
results.append(f"ssh.py installed: {dst}")
elif os.path.exists(dst):
@@ -96,10 +132,9 @@ def install_ssh_script() -> str:
else:
results.append("ERROR: ssh.py source not found")
# Copy encryption.py
enc_dst = os.path.join(SHARED_DIR, "encryption.py")
if os.path.exists(ENCRYPTION_SRC):
shutil.copy2(ENCRYPTION_SRC, enc_dst)
_copy_file(ENCRYPTION_SRC, enc_dst)
log.info(f"encryption.py installed: {enc_dst}")
results.append(f"encryption.py installed: {enc_dst}")
elif os.path.exists(enc_dst):
@@ -110,22 +145,59 @@ def install_ssh_script() -> str:
return "\n".join(results)
def install_skill() -> str:
def install_claude_skill() -> str:
"""Install /ssh skill for Claude Code."""
os.makedirs(SKILL_DST_DIR, exist_ok=True)
if os.path.exists(SKILL_SRC):
shutil.copy2(SKILL_SRC, SKILL_DST)
log.info(f"Skill installed: {SKILL_DST}")
return f"Skill installed: {SKILL_DST}"
# Fallback: check existing
if os.path.exists(SKILL_DST):
return f"Skill already exists: {SKILL_DST}"
# Generate minimal skill
os.makedirs(CLAUDE_SKILL_DST_DIR, exist_ok=True)
if os.path.exists(CLAUDE_SKILL_SRC):
_copy_file(CLAUDE_SKILL_SRC, CLAUDE_SKILL_DST)
log.info(f"Claude skill installed: {CLAUDE_SKILL_DST}")
return f"Claude skill installed: {CLAUDE_SKILL_DST}"
if os.path.exists(CLAUDE_SKILL_DST):
return f"Claude skill already exists: {CLAUDE_SKILL_DST}"
skill_content = _generate_skill_content()
with open(SKILL_DST, "w", encoding="utf-8") as f:
with open(CLAUDE_SKILL_DST, "w", encoding="utf-8") as f:
f.write(skill_content)
log.info(f"Skill generated: {SKILL_DST}")
return f"Skill generated: {SKILL_DST}"
log.info(f"Claude skill generated: {CLAUDE_SKILL_DST}")
return f"Claude skill generated: {CLAUDE_SKILL_DST}"
def install_codex_skill() -> str:
"""Install ServerManager skill package for Codex and the local wrapper."""
results = []
if os.path.isdir(CODEX_SKILL_SRC_DIR):
_copy_tree(CODEX_SKILL_SRC_DIR, CODEX_SKILL_DST_DIR)
for rel_path in [
os.path.join("scripts", "server-manager-doctor.sh"),
os.path.join("scripts", "server-manager-doctor.cmd"),
os.path.join("scripts", "codex-ssh-wrapper.sh"),
os.path.join("scripts", "codex-ssh-wrapper.cmd"),
]:
_ensure_executable(os.path.join(CODEX_SKILL_DST_DIR, rel_path))
log.info(f"Codex skill installed: {CODEX_SKILL_DST_DIR}")
results.append(f"Codex skill installed: {CODEX_SKILL_DST_DIR}")
elif os.path.exists(CODEX_SKILL_ENTRY):
results.append(f"Codex skill already exists: {CODEX_SKILL_DST_DIR}")
else:
results.append("ERROR: Codex skill source not found")
wrapper_src = CODEX_WRAPPER_SRC_CMD if sys.platform == "win32" else CODEX_WRAPPER_SRC_SH
if os.path.exists(wrapper_src):
_copy_file(wrapper_src, CODEX_WRAPPER_DST, executable=(sys.platform != "win32"))
log.info(f"Codex wrapper installed: {CODEX_WRAPPER_DST}")
results.append(f"Codex wrapper installed: {CODEX_WRAPPER_DST}")
elif os.path.exists(CODEX_WRAPPER_DST):
results.append(f"Codex wrapper already exists: {CODEX_WRAPPER_DST}")
else:
results.append("ERROR: Codex wrapper source not found")
return "\n".join(results)
def install_skill() -> str:
"""Backward-compatible alias for the Claude /ssh skill installer."""
return install_claude_skill()
def generate_ssh_key() -> str:
@@ -135,7 +207,6 @@ def generate_ssh_key() -> str:
os.makedirs(os.path.dirname(SSH_KEY_PATH), exist_ok=True)
import subprocess
try:
subprocess.run(
["ssh-keygen", "-t", "ed25519", "-f", SSH_KEY_PATH,
@@ -156,11 +227,7 @@ def generate_ssh_key() -> str:
def install_global_claude_md() -> str:
"""Add/update server manager section in global ~/.claude/CLAUDE.md.
Uses start/end markers to safely replace existing block without duplication.
"""
import re
"""Add/update server manager section in global ~/.claude/CLAUDE.md."""
os.makedirs(os.path.dirname(GLOBAL_CLAUDE_MD), exist_ok=True)
existing = ""
@@ -174,29 +241,28 @@ def install_global_claude_md() -> str:
)
if pattern.search(existing):
# Блок уже есть — заменяем на актуальную версию
updated = pattern.sub(GLOBAL_CLAUDE_MD_BLOCK.strip(), existing)
with open(GLOBAL_CLAUDE_MD, "w", encoding="utf-8") as f:
f.write(updated)
log.info(f"Global CLAUDE.md block updated: {GLOBAL_CLAUDE_MD}")
return f"Global CLAUDE.md block updated: {GLOBAL_CLAUDE_MD}"
else:
# Блока нет — добавляем в конец
with open(GLOBAL_CLAUDE_MD, "a", encoding="utf-8") as f:
if existing and not existing.endswith("\n"):
f.write("\n")
f.write("\n" + GLOBAL_CLAUDE_MD_BLOCK)
log.info(f"Global CLAUDE.md block added: {GLOBAL_CLAUDE_MD}")
return f"Global CLAUDE.md block added: {GLOBAL_CLAUDE_MD}"
with open(GLOBAL_CLAUDE_MD, "a", encoding="utf-8") as f:
if existing and not existing.endswith("\n"):
f.write("\n")
f.write("\n" + GLOBAL_CLAUDE_MD_BLOCK)
log.info(f"Global CLAUDE.md block added: {GLOBAL_CLAUDE_MD}")
return f"Global CLAUDE.md block added: {GLOBAL_CLAUDE_MD}"
def install_all() -> list[str]:
"""Full setup — install everything."""
"""Full setup — install everything for Claude Code and Codex."""
results = []
steps = [
("ssh_script", install_ssh_script),
("skill", install_skill),
("claude_skill", install_claude_skill),
("codex_skill", install_codex_skill),
("ssh_key", generate_ssh_key),
("global_claude_md", install_global_claude_md),
]

View File

@@ -46,7 +46,7 @@ _EN = {
"about_desc": (
"Desktop application for managing remote servers.\n"
"SSH terminal, SFTP file transfer, key management,\n"
"encrypted credentials, and Claude Code integration."
"encrypted credentials, and Claude Code / Codex integration."
),
"about_features_title": "⚡ Features",
"about_features": (
@@ -56,13 +56,13 @@ _EN = {
"• TOTP / 2FA (Google Authenticator)\n"
"• Encrypted credentials (Fernet)\n"
"• Automatic backups\n"
"• Claude Code integration"
"• Claude Code and Codex integration"
),
"about_howto_title": "🚀 Quick Start",
"about_howto": (
"1. Click \"+ Add\" to add a server\n"
"2. Select server → Terminal / Files\n"
"3. Setup tab → Claude Code integration"
"3. Setup tab → Claude Code / Codex integration"
),
"version": "Version",
"author": "Author",
@@ -157,6 +157,12 @@ _EN = {
"no_public_key": "[!] No public key to copy",
# Setup
"agent_integration": "AI Agent Integration",
"agent_desc": (
"Setup everything so Claude Code and Codex can manage your servers via shared local skills.\n"
"ServerManager, Claude Code, and Codex share the same servers.json — add a server here,\n"
"both agents see it immediately."
),
"claude_integration": "Claude Code Integration",
"claude_desc": (
"Setup everything so Claude Code can manage your servers via /ssh skill.\n"
@@ -169,11 +175,16 @@ _EN = {
"status_ssh_script": "ssh.py (CLI tool)",
"status_encryption": "Encryption module",
"status_skill": "/ssh skill for Claude Code",
"status_claude_skill": "/ssh skill for Claude Code",
"status_codex_skill": "ServerManager skill for Codex",
"status_codex_wrapper": "Codex wrapper (codex-ssh)",
"status_ssh_key": "SSH key (ed25519)",
"install_everything": "Install Everything",
"installing_all": "Installing...",
"install_ssh_py": "ssh.py",
"install_skill": "/ssh skill",
"install_claude_skill": "Claude skill",
"install_codex_skill": "Codex skill",
"install_ssh_key": "SSH key",
"refresh": "Refresh",
"configuration": "Configuration",
@@ -183,7 +194,7 @@ _EN = {
"select_backup": "Select backup...",
"no_backups": "No backups",
"restore": "Restore",
"install_done": "Done! Claude Code can now use /ssh to manage your servers.",
"install_done": "Done! Claude Code and Codex can now use ServerManager to manage your servers.",
"config_changed": "Config path changed: {path}",
"backup_created": "Backup created: {name}",
"backup_failed": "Backup failed: {e}",
@@ -603,7 +614,7 @@ _RU = {
"about_desc": (
"Настольное приложение для управления удалёнными серверами.\n"
"SSH-терминал, SFTP-передача файлов, управление ключами,\n"
"шифрование паролей и интеграция с Claude Code."
"шифрование паролей и интеграция с Claude Code / Codex."
),
"about_features_title": "⚡ Возможности",
"about_features": (
@@ -613,13 +624,13 @@ _RU = {
"• TOTP / 2FA (Google Authenticator)\n"
"• Шифрование паролей (Fernet)\n"
"• Автоматические бэкапы\n"
"• Интеграция с Claude Code"
"• Интеграция с Claude Code и Codex"
),
"about_howto_title": "🚀 Быстрый старт",
"about_howto": (
"1. Нажмите \"+ Добавить\" для добавления сервера\n"
"2. Выберите сервер → Терминал / Файлы\n"
"3. Вкладка Настройка → интеграция Claude Code"
"3. Вкладка Настройка → интеграция Claude Code / Codex"
),
"version": "Версия",
"author": "Автор",
@@ -714,6 +725,12 @@ _RU = {
"no_public_key": "[!] Нет публичного ключа",
# Setup
"agent_integration": "Интеграция AI-агентов",
"agent_desc": (
"Настройте всё, чтобы Claude Code и Codex могли управлять серверами через локальные skills.\n"
"ServerManager, Claude Code и Codex используют один и тот же servers.json — добавьте сервер здесь,\n"
"и оба агента увидят его сразу."
),
"claude_integration": "Интеграция с Claude Code",
"claude_desc": (
"Настройте всё, чтобы Claude Code мог управлять серверами через скилл /ssh.\n"
@@ -726,11 +743,16 @@ _RU = {
"status_ssh_script": "ssh.py (CLI-утилита)",
"status_encryption": "Модуль шифрования",
"status_skill": "Скилл /ssh для Claude Code",
"status_claude_skill": "Скилл /ssh для Claude Code",
"status_codex_skill": "Скилл ServerManager для Codex",
"status_codex_wrapper": "Обёртка Codex (codex-ssh)",
"status_ssh_key": "SSH-ключ (ed25519)",
"install_everything": "Установить всё",
"installing_all": "Установка...",
"install_ssh_py": "ssh.py",
"install_skill": "Скилл /ssh",
"install_claude_skill": "Скилл Claude",
"install_codex_skill": "Скилл Codex",
"install_ssh_key": "SSH-ключ",
"refresh": "Обновить",
"configuration": "Конфигурация",
@@ -740,7 +762,7 @@ _RU = {
"select_backup": "Выберите бэкап...",
"no_backups": "Нет бэкапов",
"restore": "Восстановить",
"install_done": "Готово! Claude Code теперь может использовать /ssh для управления серверами.",
"install_done": "Готово! Claude Code и Codex теперь могут использовать ServerManager для управления серверами.",
"config_changed": "Путь конфига изменён: {path}",
"backup_created": "Бэкап создан: {name}",
"backup_failed": "Ошибка бэкапа: {e}",
@@ -1160,7 +1182,7 @@ _ZH = {
"about_desc": (
"用于管理远程服务器的桌面应用程序。\n"
"SSH终端、SFTP文件传输、密钥管理、\n"
"凭据加密以及Claude Code集成。"
"凭据加密以及Claude Code / Codex集成。"
),
"about_features_title": "⚡ 功能特点",
"about_features": (
@@ -1170,13 +1192,13 @@ _ZH = {
"• TOTP / 2FAGoogle Authenticator\n"
"• 凭据加密Fernet\n"
"• 自动备份\n"
"• Claude Code集成"
"• Claude Code 和 Codex 集成"
),
"about_howto_title": "🚀 快速开始",
"about_howto": (
"1. 点击\"+ 添加\"来添加服务器\n"
"2. 选择服务器 → 终端 / 文件\n"
"3. 设置标签 → Claude Code集成"
"3. 设置标签 → Claude Code / Codex 集成"
),
"version": "版本",
"author": "作者",
@@ -1271,6 +1293,12 @@ _ZH = {
"no_public_key": "[!] 没有公钥可复制",
# Setup
"agent_integration": "AI代理集成",
"agent_desc": (
"完成设置后Claude Code 和 Codex 都可以通过共享的本地技能来管理您的服务器。\n"
"ServerManager、Claude Code 和 Codex 共用同一个 servers.json — 在此添加服务器后,\n"
"两个代理都会立即看到。"
),
"claude_integration": "Claude Code集成",
"claude_desc": (
"设置一切以便Claude Code通过/ssh技能管理您的服务器。\n"
@@ -1283,11 +1311,16 @@ _ZH = {
"status_ssh_script": "ssh.pyCLI工具",
"status_encryption": "加密模块",
"status_skill": "Claude Code的/ssh技能",
"status_claude_skill": "Claude Code 的 /ssh 技能",
"status_codex_skill": "Codex 的 ServerManager 技能",
"status_codex_wrapper": "Codex 包装器codex-ssh",
"status_ssh_key": "SSH密钥ed25519",
"install_everything": "全部安装",
"installing_all": "安装中...",
"install_ssh_py": "ssh.py",
"install_skill": "/ssh技能",
"install_claude_skill": "Claude 技能",
"install_codex_skill": "Codex 技能",
"install_ssh_key": "SSH密钥",
"refresh": "刷新",
"configuration": "配置",
@@ -1297,7 +1330,7 @@ _ZH = {
"select_backup": "选择备份...",
"no_backups": "无备份",
"restore": "恢复",
"install_done": "完成Claude Code现在可以使用/ssh来管理您的服务器。",
"install_done": "完成Claude Code 和 Codex 现在可以使用 ServerManager 来管理您的服务器。",
"config_changed": "配置路径已更改:{path}",
"backup_created": "备份已创建:{name}",
"backup_failed": "备份失败:{e}",