brewtools/skills/ssh/SKILL.md
SSH server management — connect, configure, deploy, administer Linux servers with safety gates.
npx skillsauth add kochetkov-ma/claude-brewcode brewtools:sshInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
<instructions>Manage remote Linux servers — connect, configure, deploy, administer with safety gates and persistent config.
| Rule | Applies to |
|------|-----------|
| Every Bash call MUST end with && echo "OK ..." \|\| echo "FAILED ..." | ALL scripts |
| On FAILED — stop current phase, report error to user, DO NOT retry same command blindly | ALL phases |
| SSH commands MUST use -o ConnectTimeout=10 -o BatchMode=yes | ALL SSH calls |
| Max 2 retries per failed operation. After 2nd failure — report and stop | ALL phases |
| If a script exits non-zero — read its stderr, diagnose, fix root cause, then retry ONCE | Scripts |
| Rule | Limit | |------|-------| | Phase 2 (Connection Setup) — max 3 key attempts, then ask user | 3 keys | | Phase 2 → Phase 5 round-trips — if sent back to Phase 2 more than once, stop and report | 1 re-entry | | Phase 5 (Execute) — max 5 SSH commands per invocation. If task needs more, delegate to ssh-admin agent via Task | 5 commands | | update-agent mode — max 3 servers per run. If more, process first 3 and report remaining | 3 servers | | AskUserQuestion — max 3 questions per phase. If skill needs more info, summarize what's missing in one combined question | 3 per phase |
| Operation | Timeout | Action on timeout |
|-----------|---------|-------------------|
| SSH connection test | 10s (ConnectTimeout=10) | Report "Server unreachable", stop |
| server-discover.sh | 30s (timeout 30 bash ...) | Report partial results, continue |
| Any single SSH command | 60s (timeout 60 ssh ...) | Kill, report "Command timed out", ask user |
| Entire skill invocation | Do not exceed 15 SSH calls total | Stop, report progress, suggest continuing manually |
If a script fails and cannot be fixed:
Manual fallback examples:
| Failed script | Manual alternative |
|---------------|--------------------|
| detect-mode.sh | Parse $ARGUMENTS yourself — keyword matching is simple |
| ssh-env-check.sh | Run ls ~/.ssh/id_* 2>/dev/null, ssh-add -l, cat ~/.ssh/config |
| server-discover.sh | Run individual commands via SSH: uname -a, docker version, df -h |
| claude-local-ops.sh | Read/write CLAUDE.local.md directly with Read/Edit tools |
On ANY failure — before stopping or asking user — output:
SCRIPT_ERROR: <script-name>
EXIT_CODE: <code>
STDERR: <error message>
PHASE: <current phase>
ACTION: <what was attempted>
FALLBACK: <what will be tried next OR "asking user">
This is non-negotiable. Silent failures are bugs.
EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/detect-mode.sh" "$ARGUMENTS"
Output format:
ARGS: [arguments received]
MODE: [detected mode]
Use the MODE value and GOTO that mode section below.
| Keyword in args | MODE | |-----------------|------| | setup, new server, add server | setup | | connect to, ssh to, login | connect | | configure, config, harden | configure | | update agent, refresh agent, refresh | update-agent | | (any other text) | execute | | (empty, no servers configured) | setup | | (empty, servers configured) | execute (prompt user) |
Runs for ALL modes before branching.
EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/ssh-env-check.sh" && echo "OK env-check" || echo "FAILED env-check"
STOP if FAILED -- fix SSH environment before continuing.
Parse output key=value pairs. Note available keys and ssh-agent status.
EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/claude-local-ops.sh" list 2>/dev/null || echo "NO_SERVERS"
Branching logic:
| Condition | Action |
|-----------|--------|
| NO_SERVERS AND mode=setup | GOTO Phase 2: Connection Setup |
| NO_SERVERS AND mode=execute/connect | GOTO Phase 2 (need server first) |
| 1 server AND mode=connect/execute | Use as default, GOTO Phase 5 |
| Multiple servers AND mode=connect/execute | AskUserQuestion: which server? Then GOTO Phase 5 |
| mode=setup (servers exist) | GOTO Phase 2 (adding new server) |
| mode=configure | AskUserQuestion: which server? Then GOTO Phase 5 |
| mode=update-agent | GOTO Mode: update-agent |
Use AskUserQuestion:
header: "SSH Server Setup"
question: "Provide connection details for the new server."
Collect via follow-up questions if not in $ARGUMENTS:
EXECUTE using Bash tool -- try existing keys:
bash "${CLAUDE_SKILL_DIR}/scripts/ssh-env-check.sh" && echo "OK keys" || echo "FAILED keys"
Parse available keys from output. Try connection with each key (ed25519 first, then rsa, then ecdsa):
EXECUTE using Bash tool:
ssh-keyscan -p PORT HOST >> ~/.ssh/known_hosts 2>/dev/null && echo "OK keyscan" || echo "FAILED keyscan"
Replace PORT and HOST with actual values.
EXECUTE using Bash tool -- test key auth:
ssh -o BatchMode=yes -o ConnectTimeout=10 -p PORT USER@HOST echo "OK auth" 2>/dev/null || echo "FAILED auth"
Use AskUserQuestion:
header: "SSH Authentication"
question: "Key authentication failed. Choose auth method:"
options:
- label: "Password login (will set up key auth)"
description: "Connect with password, then install SSH key"
- label: "Specify key path"
description: "Provide path to an existing private key"
- label: "Cancel"
description: "Abort server setup"
If password login:
EXECUTE using Bash tool:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_SERVERNAME -N "" -C "claude@SERVERNAME" && echo "OK keygen" || echo "FAILED keygen"
Replace SERVERNAME with the server name alias.
Interactive command required. Run this in your terminal:
! ssh-copy-id -i ~/.ssh/id_ed25519_SERVERNAME.pub -p PORT USER@HOSTThis requires password entry which Claude Code cannot do non-interactively.
EXECUTE using Bash tool:
ssh -o BatchMode=yes -o ConnectTimeout=10 -i ~/.ssh/id_ed25519_SERVERNAME -p PORT USER@HOST echo "OK key-auth" 2>/dev/null || echo "FAILED key-auth"
STOP if FAILED -- key auth must work before proceeding.
EXECUTE using Bash tool:
grep -q "^Host SERVERNAME$" ~/.ssh/config 2>/dev/null && echo "EXISTS" || echo "NEW"
If NEW, add config entry using Edit/Write to ~/.ssh/config:
Host SERVERNAME
HostName HOST
User USER
Port PORT
IdentityFile ~/.ssh/id_ed25519_SERVERNAME
StrictHostKeyChecking accept-new
EXECUTE using Bash tool:
ssh -o BatchMode=yes -o ConnectTimeout=10 SERVERNAME echo "OK connection" 2>/dev/null || echo "FAILED connection"
STOP if FAILED -- connection must work before discovery.
EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/server-discover.sh" "USER@HOST" PORT && echo "OK discovery" || echo "FAILED discovery"
Replace USER@HOST and PORT with actual values. If using SSH config alias, pass that instead.
Parse output key=value pairs. Key fields:
OS, KERNEL, ARCHDOCKER_VERSION, DOCKER_COMPOSEDISK_INFO (data disks, mount points)RUNNING_CONTAINERSSERVICESCURRENT_USER, USER_GROUPSEXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/claude-local-ops.sh" add "SERVERNAME" "HOST" "USER" "PORT" "KEYPATH" && echo "OK add" || echo "FAILED add"
Replace placeholders with actual values.
EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/claude-local-ops.sh" update "SERVERNAME" "OS_VALUE" "KERNEL_VALUE" "DOCKER_VALUE" "DISK_VALUE" "WORKDIR_VALUE" && echo "OK update" || echo "FAILED update"
Replace placeholders with discovered values from Phase 3.
EXECUTE using Bash tool:
grep -q "CLAUDE.local.md" .gitignore 2>/dev/null && echo "EXISTS" || (echo "CLAUDE.local.md" >> .gitignore && echo "ADDED")
Read the agent template:
EXECUTE using Bash tool:
cat "${CLAUDE_SKILL_DIR}/templates/ssh-admin-agent.md.template"
Replace placeholders in template:
{{SERVER_INVENTORY}} -- server table from CLAUDE.local.md{{SERVER_DETAILS}} -- discovered OS/Docker/disk info per server{{LAST_UPDATED}} -- current ISO timestampWrite result to .claude/agents/ssh-admin.md in the project using Write tool.
If this is the first server, set as default automatically.
If multiple servers exist, use AskUserQuestion:
header: "Default Server"
question: "Set SERVERNAME as the default SSH server?"
options:
- label: "Yes"
- label: "No"
If yes: EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/claude-local-ops.sh" set-default "SERVERNAME" && echo "OK default" || echo "FAILED default"
Read references/safety-rules.md from skill directory for command classification rules.
Analyze the user's request from $ARGUMENTS. Create execution plan:
| Step | Command(s) | Classification | Confirmation | |------|-----------|----------------|--------------| | 1 | ... | READ/CREATE/MODIFY/SERVICE/DELETE/PRIVILEGE | free/confirm |
For MODIFY/SERVICE commands -- use AskUserQuestion:
header: "SSH Action Confirmation"
question: "About to execute on SERVER:\n\n[command list]\n\nProceed?"
options:
- label: "Yes, execute"
- label: "Cancel"
For DELETE/PRIVILEGE commands -- use AskUserQuestion with explicit warning:
header: "DESTRUCTIVE SSH Action"
question: "WARNING: About to execute DESTRUCTIVE commands on SERVER:\n\n[command list]\n\nThis cannot be undone. Proceed?"
options:
- label: "Yes, I understand the risks"
- label: "Cancel"
For READ/CREATE commands -- execute freely, no confirmation needed.
For complex multi-step operations, delegate to ssh-admin agent via Task tool:
| Parameter | Value |
|-----------|-------|
| subagent_type | ssh-admin |
| prompt | [Detailed task description with server info, safety classification, and specific commands to run] |
For simple single-command operations, execute directly:
EXECUTE using Bash tool:
ssh SERVERNAME "COMMAND" && echo "OK" || echo "FAILED"
If task involves Docker registry operations, read references/docker-auth-flow.md for auth patterns.
Use AskUserQuestion for registry credentials -- NEVER hardcode tokens.
| Field | Value | |-------|-------| | Server | SERVERNAME (HOST) | | Mode | [detected mode] | | Actions | [list of actions performed] | | Changes | [list of changes made on server] | | Status | success / partial / failed |
bash "${CLAUDE_SKILL_DIR}/scripts/claude-local-ops.sh" update "SERVERNAME" ...
Re-discover all configured servers and refresh the ssh-admin agent.
EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/claude-local-ops.sh" list
For each server from list, run discovery:
EXECUTE using Bash tool:
bash "${CLAUDE_SKILL_DIR}/scripts/server-discover.sh" "USER@HOST" PORT && echo "OK discovery" || echo "FAILED discovery"
Update CLAUDE.local.md with fresh data for each server.
Regenerate .claude/agents/ssh-admin.md from template with updated inventory.
Set {{LAST_UPDATED}} to current timestamp.
Report what changed since last update.
</instructions># SSH [MODE]
## Detection
| Field | Value |
|-------|-------|
| Arguments | `$ARGUMENTS` |
| Mode | `[detected mode]` |
## Environment
| Component | Status |
|-----------|--------|
| SSH keys | [types found] |
| ssh-agent | [running/stopped] |
| SSH config | [exists/missing] |
| Servers configured | [N] |
## Server: [NAME]
| Property | Value |
|----------|-------|
| Host | [IP/hostname] |
| OS | [distribution] |
| Docker | [version] |
| Status | [connected/failed] |
## Actions Taken
- [action 1]
- [action 2]
## Status
[success / partial / failed]
devops
Publish content to brewpage.app — text, markdown, any file, or multi-file site. Asks namespace and password, returns public URL. Triggers: publish, share link, upload to brewpage, host page, brewpage, publish site, upload site, upload directory, deploy site, сделай публичную ссылку, опубликуй.
tools
Toggles terse-output mode for Claude Code - injects brief directives into main conversation (SessionStart) and sub-agent prompts (PreToolUse:Task) to cut preamble and filler. Profiles: light, medium, aggressive. Scope: global or project (project overrides). Triggers: think-short, be terse, think shorter, reduce tokens mode, level 1/2/3.
devops
Publish content to brewpage.app — text, markdown, JSON, file, or multi-file site. Asks namespace and password, returns public URL. Triggers: publish, share link, upload to brewpage, host page, brewpage, publish site, upload site, upload directory, deploy site, сделай публичную ссылку, опубликуй.
tools
Disables or enables individual plugin skills by writing skillOverrides to ~/.claude/settings.json (survives plugin updates). Modes: off, user-invocable-only, name-only. Triggers: disable skill, enable skill, toggle skill, hide skill, skill-toggle.