feat: add idempotent global CLAUDE.md install with start/end markers

Uses <!-- server-manager:start/end --> markers to safely add or replace
the server-manager section. Re-running install_all() updates the block
without creating duplicates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
delta-cloud-208e
2026-02-27 07:11:39 +00:00
parent b2c6f8fdd3
commit 006ca2bf9f

View File

@@ -24,6 +24,25 @@ 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")
SSH_KEY_PATH = os.path.expanduser("~/.ssh/id_ed25519")
GLOBAL_CLAUDE_MD = os.path.expanduser("~/.claude/CLAUDE.md")
_BLOCK_START = "<!-- server-manager:start -->"
_BLOCK_END = "<!-- server-manager:end -->"
GLOBAL_CLAUDE_MD_BLOCK = f"""{_BLOCK_START}
## Server Manager — управление серверами
**ВСЕГДА** используй server manager для подключения к серверам. Никогда не используй `ssh`, `sshpass` или прямые подключения.
- Скилл: `/ssh ALIAS "command"` — выполнить команду на сервере
- Список серверов: `python3 ~/.server-connections/ssh.py --list`
- Документация: `~/.claude/commands/ssh.md`
- Memory bank: проект `global-infrastructure` → `techContext.md`
- Инфраструктура: https://git.sensey24.ru/aibot777/infrastructure-docs
**Запрещено:** использовать `ssh`, `sshpass`, читать `~/.server-connections/` напрямую, раскрывать IP/пароли/порты.
{_BLOCK_END}
"""
def check_status() -> dict:
@@ -112,6 +131,41 @@ def generate_ssh_key() -> str:
return f"Key generated: {SSH_KEY_PATH}"
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
os.makedirs(os.path.dirname(GLOBAL_CLAUDE_MD), exist_ok=True)
existing = ""
if os.path.exists(GLOBAL_CLAUDE_MD):
with open(GLOBAL_CLAUDE_MD, encoding="utf-8") as f:
existing = f.read()
pattern = re.compile(
re.escape(_BLOCK_START) + r".*?" + re.escape(_BLOCK_END),
re.DOTALL
)
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}"
def install_all() -> list[str]:
"""Full setup — install everything."""
results = []
@@ -120,6 +174,7 @@ def install_all() -> list[str]:
("ssh_script", install_ssh_script),
("skill", install_skill),
("ssh_key", generate_ssh_key),
("global_claude_md", install_global_claude_md),
]
for name, func in steps: