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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user