') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - sleepinginsummer/agent-ssh-cli: Provide the SSH CLI tool for the agent to use。提供给agent使用的 ssh cli 工具。 · GitHub
Skip to content

Repository files navigation

agent-ssh-cli

基于 CLI 的 SSH 代理工具,按 ssh-mcp-server 的能力映射为 Agent 可调用的远端操作能力。

远程执行 · 文件上传 · 文件下载 · 连接配置 · 命令白名单 · 命令黑名单 · Agent Skill 集成

CLI agentsshcliLicense MITNode.js >=18npm >=8sys win/mac/linuxrelease v0.4.1PRs welcome

AI 一键安装 · 手动安装 · 配置 · 卸载和清理 · 许可证 · 友情链接

中文 | English

简介

本项目参考 classfang/ssh-mcp-server 的 SSH 操作能力设计,改写为独立 CLI 形式。感谢原项目提供的思路和能力基础。

他能做的事:

  • 解放双手,自动运维服务器
  • 部署代码,更新部署docker
  • 配置nginx,配置证书
  • 所有ssh能做到的事情

他的能力:

  • 列出本地配置中的 SSH 服务器连接
  • 在指定远端服务器上执行命令
  • 上传本地文件到远端服务器,支持临时文件、断点续传和失败重试
  • 从远端服务器下载文件到本地
  • 通过命令黑白名单限制可执行命令

上传稳定性

上传会先写入远端 <remotePath>.part 临时文件,并写入 <remotePath>.part.meta 续传元数据;完成后校验大小,再 rename 为正式目标文件。上传中断后,下次上传同一个本地文件到同一个远端路径会从已有 .part 大小继续。

--no-cache 上传可用 Ctrl+C 停止当前进程;daemon 模式可用 agentsshcli stop-daemon 停止连接池进程,但它会影响同一 daemon 内其它任务,不是精确取消单个上传。

AI 一键安装

安装请阅读 https://github.com/sleepinginsummer/agent-ssh-cli/blob/main/AI_INSTALL.md,按说明安装 CLI 并添加 `SKILL.md`。

手动安装

环境要求

  • Node.js >= 18
  • npm >= 8
  • 系统支持 Windows / macOS / Linux
  • 本机网络可访问目标 SSH 服务器
  • 如使用私钥认证,私钥文件需对当前用户可读
  • 预编译平台包支持 macOS arm64/x64、Linux x64/arm64、Windows x64

常用参数

  • exec --timeout <ms>: 单次命令超时,默认 30000
  • upload / download --timeout <ms>: 传输总超时,默认不限制(大文件允许长时间运行)
  • upload / download --recursive: 递归传输目录,保持相对路径;符号链接不跟随,指向目录的链接跳过、指向文件的链接上传其内容
  • 下载支持断点续传:中断后本地保留 .part 文件,下次自动从断点继续
  • exec / upload / download --json: 输出结构化 JSON(exitCode/stdout/stderr),exitCode 为远端命令真实退出码,便于脚本和 AI 解析
  • agentsshcli init-config: 生成默认配置文件到 ~/.agent-ssh-cli/config.json
  • agentsshcli stop-daemon: 停止当前配置对应的 SSH 缓存进程
  • 远端会话异常终止时提示 [remote] 会话异常终止(无退出状态),不会静默返回成功

安装步骤

  1. 全局安装:
npm install -g agent-ssh-cli
agentsshcli --help
  1. 导入 SKILL.md:

打开 SKILL.md,将其添加到 agent 中。

配置

初始化配置(格式参数和ssh-mcp-server一致):

mkdir -p ~/.agent-ssh-cli

编辑 ~/.agent-ssh-cli/config.json,填写真实连接信息。默认配置文件也可以通过环境变量覆盖:

可以通过以下环境变量修改配置地点

AGENT_SSH_CONFIG=/path/to/config.json

配置文件是数组,每一项是一台服务器:

  • name: 连接名,必须唯一
  • host: SSH 主机地址
  • username: SSH 用户名
  • password / passwordRef / privateKey: 认证方式,密码、密码引用、私钥三类认证只能保留一种
  • port: SSH 端口,默认 22
  • passphrase: 私钥口令,仅配合 privateKey 使用
  • socksProxy: SOCKS5 代理地址,例如 socks5://127.0.0.1:1080;也可省略协议写成 127.0.0.1:1080
  • jumpHost: 跳板机连接名,填写配置文件中另一台机器的 name
  • pty: 是否分配伪终端,默认 false,也可通过 exec --pty 临时开启
  • allowedLocalPaths: 兼容旧配置字段,当前不限制本地路径
  • commandWhitelist: 命令白名单正则数组
  • commandBlacklist: 命令黑名单正则数组

commandWhitelistcommandBlacklist 使用 JavaScript RegExp 语法,不是 POSIX 正则;空白字符请写成 \\s,不要写 [:space:]

完整示例见 example.config.json~/.agent-ssh-cli/config.json 保存真实连接信息。

为防止配置文件中的密码泄露,密码认证会在第一次使用该服务器时被动加密保存:首次写入明文 password 后,执行 execuploaddownload 连接该服务器时,CLI 会把密码加密保存到配置目录下的 secrets.json,生成本地 secret.key,并把配置中的 password 置空、写入 passwordRef。后续运行通过 passwordRef 解密认证;如需修改密码,把空的 password 重新填成新密码,下次连接会自动覆盖旧密文。

参考配置

[
{
"name": "密码服务器",
"host": "192.0.2.10",
"port": 22,
"username": "root",
"password": "",
"passwordRef": "agentsshcli:密码服务器",
"jumpHost": "jump-server",
"commandBlacklist": [
"(^|[;&|()\\s])rm(\\s|$)",
"(^|[;&|()\\s])shutdown(\\s|$)",
"(^|[;&|()\\s])reboot(\\s|$)"
]
},
{
"name": "jump-server",
"host": "198.51.100.20",
"port": 22,
"username": "ubuntu",
"privateKey": "/path/to/jump_key",
"passphrase": "******",
"socksProxy": "socks5://127.0.0.1:1080"
},
{
"name": "密钥服务器",
"host": "198.51.100.10",
"port": 22,
"username": "deploy",
"privateKey": "/path/to/id_rsa",
"passphrase": "******",
"pty": false,
"allowedLocalPaths": [
"./tmp",
"./dist"
],
"commandWhitelist": [
"^pwd$",
"^ls(\\s|$)",
"^cat\\s+/var/log/app\\.log$"
],
"commandBlacklist": [
"(^|[;&|()\\s])rm(\\s|$)",
"(^|[;&|()\\s])shutdown(\\s|$)",
"(^|[;&|()\\s])reboot(\\s|$)"
]
}
]

测试命令

agentsshcli list
agentsshcli exec --no-cache 密码服务器 "pwd"
agentsshcli exec --pty 密码服务器 "tty"
agentsshcli exec 密码服务器 --command-file ./script.sh --timeout 60000

完成安装!

卸载和清理

更新到最新版:

npm install -g agent-ssh-cli@latest

卸载:

npm uninstall -g agent-ssh-cli
npm cache clean --force
#删除配置文件
rm -rf ~/.agent-ssh-cli

许可证

MIT

友情链接

About

Provide the SSH CLI tool for the agent to use。提供给agent使用的 ssh cli 工具。

Resources

Stars

78 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages