- 断线后对远程侧的操作(导航、刷新、删改、属性、所有传输)不再没有 反应:弹出「与 会话名 的 SFTP 连接已断开」,说明原因,可直接「重新 连接」;队列里有停止的批次或正在自动重连时只说明该怎么办。远程工具栏 和菜单在未连接时保持可点,只有连接中才禁用 - 列表还没读到目录时显示「正在连接 SFTP…」「正在读取目录…」,不再 先显示「空目录」 - 工具栏固定一行高,放不下的按钮从右往左整个隐藏,不换行,两侧列表 始终对齐 - 新开的 SFTP 标签本地、远程对半分;分隔线位置(含队列)由标签自己 持有,切换标签后不再被重置(不持久化) - 去掉当前面板路径标签的蓝色下划线 - 双击任一中间区标签的标题,显示 / 隐藏左侧会话栏(同 ⌘B) Co-Authored-By: Claude <noreply@anthropic.com>
23 KiB
ShellRS
基于 GPUI Kit 的中文 SSH 会话管理工具,提供持久化会话与分组、本地终端、SSH 多终端和对标 WinSCP 的 SFTP 双栏浏览器。
会话列表
鼠标停在主机上,行的右侧会显示 用户@主机:端口。右键主机可连接、打开 SFTP、编辑、复制、复制 ID 或删除。「复制 ID」复制的是会话的 16 位随机标识(形如 Jwg5rHvXCxw89paM),供其他工具标识这台机器:它在会话创建时生成,改名、改地址都不变,复制出来的会话得到新的 ID,删掉的 ID 不会再分配。没有打开任何标签时,中间区显示「最近连接」列表,双击或回车连接;右键其中的主机弹出同一个菜单,并选中该主机。
拖动分组或主机到同类项目的上侧 / 下侧可调整同级顺序;主机拖到分组会移入该分组,分组拖到另一个分组的中部会成为子分组。拖到列表空白处可移回根层级。分组始终排在同级主机之前,顺序与分组的展开状态会保存到本地数据库。搜索时暂停拖动排序。
设置
会话列表最下方的「设置」按钮(或 ⌘,,其他平台 Ctrl+,)在中间区打开「设置」标签页;已经打开时切换到该标签,不会重复打开。设置页分两栏:左边是分类(外观、终端、外部 CLI),上方可搜索,栏宽可拖动;右边是所选分类的设置。设置改动立即生效并保存在数据目录的 settings.json 里。
- 外观 → 常规 → 界面语言:跟随系统、简体中文、English,默认简体中文。英文界面尚在翻译中,目前只有 GPUI Kit 组件自带的文字(如搜索框提示)会切换。
- 外观 → 常规 → 应用外观:深色、浅色、跟随系统,默认跟随系统;选「跟随系统」时随系统的深浅色切换。标题栏的主题按钮也会改写这一项。
- 终端 → 字体配置:字体(只列出本机安装的等宽字体,默认是系统的等宽字体,macOS 上为 Menlo)、字号(8–32 像素,默认 13)、行高(字号的 1.0–2.0 倍,默认 1.54,即原来的 20 像素行高)。改动立即作用于所有已打开的终端。下方的预览按终端的排法显示字母、数字、易混字符、符号、中文、制表符和一行命令。设置文件里的字体在本机没有安装时,使用默认字体。
- 外部 CLI:见下一节。
外部 CLI
shellrs 命令让 Codex、Claude Code、OpenCode 等 AI Agent 用 ShellRS 里已保存的会话执行远程命令、传输文件。命令本身不接触密码和私钥,也不读数据库:它把请求交给正在运行的 ShellRS,由 ShellRS 用保存的配置和钥匙串去连接。请求走本机的进程间通信:macOS、Linux 上是数据目录下的套接字 cli.sock,Windows 上是命名管道 \\.\pipe\shellrs-cli-<数据目录的散列>。
在 设置 → 外部 CLI 中:
- 启用外部 CLI:默认关闭。关闭时 ShellRS 仍在监听,但拒绝所有请求,命令会提示去这里打开。开启后,本机当前用户下的任何程序都能这样使用已保存的会话。套接字只对当前用户可读写,连接时还会核对对方的用户身份;Windows 的管道同样只允许当前用户、只接受本机连接,命令在发请求之前还会确认管道属于当前用户(或管理员),防止别人抢注同名管道。
- CLI 二进制:把
shellrs放进 PATH。- macOS:链接到
/usr/local/bin/shellrs,目录不可写时弹系统授权框。 - Linux:链接到
~/.local/bin/shellrs。 - macOS、Linux 上「移除」只删除指向 shellrs 的链接,同名的其他文件不会被改动。
- Windows:图形程序没有控制台,命令是单独的控制台程序
shellrs-cli.exe(与shellrs.exe放在一起)。「安装」把它复制成%LOCALAPPDATA%\ShellRS\bin\shellrs.exe,并把这个文件夹加入当前用户的 PATH(不需要管理员),重新打开的终端和 Agent 才能找到它。ShellRS 升级后启动时会自动更新这份副本;更新不了时显示「更新」。副本正被使用时也能更新。「移除」删掉副本并把文件夹移出 PATH。
- macOS:链接到
- Agent Skills:把教 Agent 使用
shellrs的 skill 写到通用目录(~/.agents/skills)或 Codex、Claude Code、OpenCode 各自的 skills 目录;内容和当前版本不同时显示「更新」。「复制 skills」把 skill 全文复制到剪贴板。
命令(shellrs --help 查看完整说明):
shellrs list [--query <关键词>] [--json] # 列出会话;输出不是终端时自动用 JSON
shellrs exec <ID> "<命令>" # 或 --stdin 从标准输入读命令
shellrs upload <ID> <本地路径> <远程路径>
shellrs download <ID> <远程路径> <本地路径>
<ID>是 16 位的会话 ID,即shellrs list的id,也就是会话右键「复制 ID」复制的内容。exec每次临时建立连接,执行完即断开。远程命令没有标准输入,退出码就是远程命令的退出码;被信号终止时是 128 加信号编号。- 上传和下载沿用 SFTP 标签的传输引擎(递归、
.filepart、断线重连),目标路径按 scp 的规则:目标是已存在的目录就放进去并保留原名,否则目标就是副本自己的路径,其父目录必须存在。已存在的文件直接覆盖;新建的文件保留源文件的可执行权限。~表示登录目录。 - 失败时在标准错误输出
shellrs: [错误码] 说明,退出码 255。错误码有not_running(ShellRS 未运行)、not_enabled(未启用外部 CLI)、session_not_found、host_key_unknown(还没在 ShellRS 里连过这台主机)、host_key_changed、missing_credential(没有保存密码或口令)、connect_failed、transfer_failed、bad_request、version_mismatch。CLI 不弹任何询问:陌生主机、缺少密码都直接失败,请先在 ShellRS 里连接一次。传输完成但有项目失败时退出码为 1。 - 支持 macOS、Linux 和 Windows。Windows 的 PowerShell 里,多行或带引号的命令用 here-string 经
--stdin传入(@'…'@ | shellrs exec <ID> --stdin);输出有中文时先执行$OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::new()。命令文本会去掉开头的 BOM、把 CRLF 换成 LF,远程 shell 不会看到多余的回车符。
标签页
远程终端标签和 SFTP 标签都以主机系统徽章为图标(与会话列表相同)。右键标签可重命名(仅本次运行有效,留空恢复为会话名称,用于区分同一台主机的多个连接)、在新标签页中连接、打开 SFTP、复制 IP 地址 / 主机名、重新连接、编辑会话,以及关闭当前、左侧、右侧、其他或全部标签。SFTP 标签的菜单是「重命名标签…」(留空恢复为「会话名称 · SFTP」)、打开 SFTP、复制 IP 地址 / 主机名、重新连接加关闭命令,标签栏右侧和远程终端一样有「SFTP」和「重新连接」按钮;本地终端标签是「重新启动」加关闭命令。标签栏右侧「…」按钮的菜单与当前标签的右键菜单相同。双击任一标签的标题可隐藏或显示左侧的会话列表(同 ⌘B 和标题栏的按钮)。远程终端运行时,标签栏右侧显示这条 SSH 连接的往返延迟,每 5 秒更新一次:低于 100 ms 为绿色,100–200 ms 为黄色,高于 200 ms 或超时为红色。批量关闭时,正在上传的 SFTP 标签仍会先确认。
终端
右键终端内容区可复制、粘贴、查找、清屏;远程终端另有打开 SFTP、重新连接和断开连接。断开连接只断开这一个标签,标签保留,可随时重新连接,同一会话的其他标签和 SFTP 不受影响。
- 查找(⌘F,其他平台 Ctrl+Shift+F):在终端右上角打开查找条,按字面量搜索屏幕和回滚历史。查询全为小写时不区分大小写,含大写字母时区分。所有匹配高亮显示,当前项颜色更深,查找条显示「当前项/总数」。回车或 ⌘G(F3)跳到下一项(更新的输出),Shift+回车或 ⌘⇧G(Shift+F3)跳到上一项(更早的输出),到头后循环。Esc 关闭。打开时如有选中文字,会作为查询词。
- 清屏(⌘K,其他平台 Ctrl+Shift+K;焦点不在终端时 ⌘K 仍是聚焦会话搜索):清掉屏幕和回滚历史,只留下光标所在的提示符行,包括已输入但未回车的内容。清屏只在本地进行,不向服务器发送任何内容。vim、top 等全屏程序运行时不可用。
SFTP
打开会话的「SFTP」标签即可浏览本地与远程目录;和终端一样,每次「打开 SFTP」都新开一个标签,同一会话可以开多个,各自独立连接、各看各的目录。操作方式对标 WinSCP Commander。SFTP 使用独立 SSH 连接,仅请求 SFTP 子系统,不要求服务器提供 Shell;关闭终端不会中止传输。
工具栏
每一侧面板都有两行工具栏,下方是路径标签。面板太窄放不下时,按钮从右往左整个隐藏,不会换行(对应的命令在右键菜单和快捷键里仍然可用)。新开的 SFTP 标签本地、远程两侧各占一半;两侧和队列之间的分隔线拖到哪里,在这个标签关闭之前都会记住,切到别的标签再回来也不变:
- 第一行(导航):
- 「目录列表」:列出从根到当前目录的每一级,本地另有主目录、桌面、文稿、下载,选中即前往。
- 书签:点按钮打开「打开目录/书签」(见下);旁边的下拉箭头列出本会话这一侧的书签,点击即前往,也可添加或移除当前路径。书签保存在本地数据库,删除会话时一并删除。
- 上级目录、根目录、主目录、刷新、后退、前进。
- 第二行(文件):
- 远程是「下载…」,本地是「上传…」,下拉里还有「选择文件上传…」。
- 删除、重命名…、属性…、新建(文件夹… / 文件…)。
路径标签
与 WinSCP 的路径标签相同:
- 显示当前目录的完整路径,例如
/home/tester/。 - 每一级都可以点:悬停时从根到该级高亮,单击即前往。
- 当前使用的一侧(最近点过的一侧)底部有一条深蓝色线。
- 路径放不下时保留根目录和末尾几级,中间折成
…,点…前往被折叠的最后一级。 - 单击当前目录、双击路径右侧空白处打开「打开目录/书签」。
- 右键:前往、刷新、添加路径到书签、复制路径、打开目录/书签…。
打开目录/书签
与 WinSCP 的「打开目录」对话框相同,可以从路径标签、书签按钮、⌘O(其他平台 Ctrl+O;macOS 另有 ⌘⇧G)或路径标签的右键菜单打开:
- 「目录」预填当前目录并已选中,直接输入即可替换。
~表示主目录,相对路径从当前目录算起。本地侧另有「浏览…」。 - 下方是本会话这一侧的书签。与目录相同的书签处于选中状态;单击书签把它填入目录,双击直接打开。
- 「添加」把目录加入书签,「删除」(或在列表中按 Delete)删除选中的书签并选中相邻的一个,「上移」「下移」调整顺序。
- 书签的改动立即保存,按「取消」也不会撤销,与 WinSCP 相同。
- 回车或「打开」前往目录;Esc 或「取消」关闭。
列表下方的状态栏平时显示项目数和已选数量;连接中和读取目录超过 0.3 秒时显示「正在连接 SFTP…」或「正在读取目录…」。读不了目录或连接断开时,原因用红字显示在窗口最左下角(平时显示「已连接 会话名」的位置);空闲时断网也会自己发现,不用等到刷新,时机和同一台主机的终端一样;重新连接用标签栏的「重新连接」按钮或标签菜单;断开时在远程列表里做任何操作(刷新、打开目录、删除、上传下载等)会弹出「SFTP 连接已断开」对话框,说明原因,并可直接「重新连接」。这些都不会让列表移动。
列与 WinSCP 相同:本地为名称、大小、类型、修改时间,远程为名称、大小、修改时间、权限、所有者。
- 大小默认以 KB 显示(
4,008,960 KB,向上取整,只有空文件是 0 KB),与 WinSCP 相同。右键「大小」列标题可切换为「字节」(4,105,175,040 B)、「千字节(KB)」或「简短格式」(按大小用 B、KB、MB、GB,如3.8 GB),对所有 SFTP 标签生效并保存在设置里。按大小排序始终按实际字节数。 - 时间形如
2026/4/22 12:44:53。 - 权限形如
rwxr-xr-x。 - 所有者来自服务器的
ls -l行。 - 链接到目录的符号链接显示为带箭头的文件夹,可以双击进入。
选择与右键菜单
- 与 WinSCP 关闭「整行选择」时相同,「名称」格才是项目:鼠标移上去时只有名称格变灰,单击、双击、右键都作用在名称格上,选中时整个名称格高亮;行里的其他列和列表下方都算空白处。
- 鼠标:单击名称格选中(其他选择取消),⌘ 单击(其他平台 Ctrl 单击)增减,Shift 单击选取范围,双击打开。单击空白处取消全部选择(按住 ⌘ 或 Shift 时不取消)。
- 框选:在名称右边的空白、大小、类型、时间等列或列表下方按下拖动,画出选框,框住的行被选中;按住 ⌘ 或 Shift 开始拖动则加到已有选择上。拖到列表上下边缘时自动滚动。拖动文件名才是把文件拖到另一侧传输:按下未选中的文件名时只选中它(其他选择取消),拖走的就是它一个;按下已选中的文件名则拖走全部已选。拖动时只有到了另一侧列表上才显示「上传 / 下载 N 个项目」,在自己的列表或其他不能放下的地方,指针显示为禁止。
- 键盘:↑↓、Home、End、PageUp、PageDown 移动,加 Shift 扩展;空格或 Insert 切换当前行;⌘A 全选;Tab 切到另一侧。
- 右键未选中的名称格会先选中它;右键空白处取消选择并弹出空白处菜单。
- 文件名上的菜单:打开、上传… / 下载…、删除、重命名…、属性…。
- 空白处的菜单:前往(上级 / 根 / 主目录 / 后退 / 前进)、刷新、添加路径到书签、新建。
快捷键
| 命令 | 快捷键 |
|---|---|
| 上传 / 下载(按所在面板) | F5 |
| 重命名 | F2 |
| 新建文件夹 | F7 |
| 删除 | F8、Delete;macOS 另有 ⌘⌫ |
| 属性 | F9;macOS 另有 ⌘I |
| 刷新 | ⌘R / Ctrl+R |
| 上级目录 | Backspace;macOS 另有 ⌘↑ |
| 根目录 | ⌘\ / Ctrl+\ |
| 主目录 | macOS ⌘⇧H,其他平台 Ctrl+H |
| 后退 / 前进 | ⌥← / ⌥→;macOS 另有 ⌘[ / ⌘] |
| 添加路径到书签 | ⌘D / Ctrl+D |
| 打开目录 | Enter;macOS 另有 ⌘↓ |
| 打开目录/书签 | ⌘O / Ctrl+O;macOS 另有 ⌘⇧G |
文件操作
- 删除前会确认,并写明对象和后果:
- 本地项目移到废纸篓。
- 远程项目永久删除,目录连同其中全部内容。
- 删除符号链接只删除链接本身。
- 重命名和新建:
- 按当前列表检查名称;不能为空,不能是
.或..,不能与已有项目同名。 - 重命名不会覆盖已有项目。
- 按当前列表检查名称;不能为空,不能是
- 属性:
- 显示位置、大小、修改时间,远程另有所有者和组。
- 权限用 3×3 复选框和八进制数字编辑,两者同步。
- 多选时,项目之间不一致的权限位保持原样,除非你改动它。
- 含目录时可以递归应用;递归时默认「为目录添加执行权限(X)」,避免目录无法进入。
- 符号链接不参与修改权限。
上传与下载
- 入口:
- 上传:「上传…」、本地列表中的 F5、「选择文件上传…」,或按住本地项目的文件名拖到远程列表,或把 Finder 中的文件拖到远程列表。
- 下载:「下载…」、远程列表中的 F5,或按住远程项目的文件名拖到本地列表。
- 拖到目录行时放进该目录,拖到其余区域时放进当前目录。
- 所有入口都显示确认框,可以修改目标目录;下载可以用「浏览…」选择本机目录。确认框写明数量和种类(如「上传 2 个文件到 …」),来源的所在目录只写一次,每个项目占一行,悬停可看完整路径。
- 确认后来源、会话端点和目标即固定。每个 SFTP 标签同时运行一个批次(上传和下载共用),批内依次处理;传输进行中还可以继续上传或下载,新的批次排进传输队列,前一批结束后自动开始。其他 SFTP 标签、会话和终端可以继续操作。
- 递归传输包含空目录。符号链接原样创建,不跟随。默认二进制传输,保留普通文件的修改时间。
- 新建项使用目标一侧的默认权限,覆盖时保留目标原有的基本权限。不复制所有者、ACL 或扩展属性。
- 同名目录合并。文件或链接冲突时询问覆盖、跳过或取消,可以应用于本批剩余冲突;目录与非目录冲突不会自动删除。
- 出错时可以重试、跳过或取消,完成后的汇总分别列出成功、跳过和失败。
传输队列
与 WinSCP 的队列面板相同,有传输时出现在双栏下方,高度可拖动,清除全部已结束的批次后收起。
- 每一批一行:操作(上传 / 下载)、来源(一个项目时是它的路径,多个项目时是所在目录)、目标、已传输(单位与「大小」列相同)、时间(剩余时间)、速度(最近几秒)和进度。
- 进行中的批次显示总进度条,下面一行是正在传的文件和它自己的进度条,对应 WinSCP 进度对话框里的两个进度条。
- 进度列:等待中、正在扫描、百分比、等待回答、重连说明、已停止、已完成、部分失败(N 项)、已移出。
- 点行首的箭头展开这一批的逐项结果(完成、已跳过、失败及原因),最新的在最上面。
- 工具栏:继续(停止的批次)、停止(进行中的批次)、移出队列(选中的排队中或已结束的批次,或停止的批次)、丢弃续传进度、清除已完成。
- 停止的批次留在队首,等你继续、丢弃续传进度或移出队列之后,后面的批次才会开始;移出队列不删除续传记录,下次传同样的文件仍会询问是否续传。
- 队列只在本次运行中保留,关闭 SFTP 标签时一起清除;有未完成的批次时关闭标签会先确认。
断点续传和替换
文件先写入 <目标文件名>.filepart。写入请求采用有界流水线,收到每个写入确认并关闭成功后才发布正式文件;服务器支持时先调用 fsync@openssh.com。支持 posix-rename@openssh.com 的服务器使用原子替换;其他服务器先将旧文件移到 .shellrs-<UUID>.backup,再发布新文件,此兼容路径不保证原子性。发布响应丢失时根据受管理临时路径、阶段、类型和大小恢复,无法安全判断则保留恢复文件并报告。
续传记录保存在应用数据目录的 upload-resume/,采用版本化 JSON 和原子写入,不写入密码、口令或私钥,不改变会话数据库结构。记录用于绑定端点、主机指纹、来源、目标和安全替换阶段。续传方式与 WinSCP 相同:读取远端 .filepart 的当前大小,在本地文件中跳过相同长度并继续传输;使用者需确保来源仍是同一版本。上传过程不会为了校验而回读远端文件内容。
下载同样先写入本机的 <目标文件名>.filepart,完成后设置远程的修改时间、fsync,再在同一文件系统内重命名为正式文件(原子替换)。并发的读取请求可能乱序、短读返回,但临时文件严格按顺序写入,因为续传只信它的长度。下载续传记录保存在 download-resume/,绑定端点、主机指纹、远程来源及其大小与修改时间、本地目标;远程文件变化后不能续传,只能重新下载或跳过。
网络故障每批最多自动重连三次,间隔 1、3、10 秒。耗尽后使用「继续上传」或「继续下载」,手动恢复会重置次数。认证失败、主机密钥变化和主动取消需要用户处理。重连沿用原连接配置,会话编辑不会改变正在传输的目标。
取消、关闭标签、断开会话或退出时保留续传记录;关闭正在传输的标签会先确认。重启后不会自动继续,再次选择相同来源和目标时会询问续传。停止状态下可使用「丢弃续传进度」清理该批受管理的临时文件;存在无法判断的恢复备份时会保留文件并说明原因。成功发布的项目自动清理记录和备份。
文件名不能准确表示时会明确报错。当前 russh-sftp 3.0.0 的远端字符串解码会替换无效 UTF-8,浏览器因此拒绝包含替换字符 U+FFFD 的远端名称,避免访问错误路径。符号链接沿用 OpenSSH 的 SFTP v3 参数顺序。
尚未实现:目录同步、前台传输进度对话框、队列排序、过滤、查找文件、目录树和 Dock 布局持久化。
运行和验证
工具链固定 Rust 1.98.1,russh-sftp 固定 3.0.0;保持仓库锁定的 GPUI 与 russh 版本。
cargo run
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --check
数据目录默认是系统应用数据目录中的 shellrs,可通过 SHELLRS_DATA_DIR 覆盖。会话保存在 shellrs.db,主机信任记录保存在该目录的 known_hosts,凭据使用系统钥匙串(服务名 shellrs)。
src/sftp/tests.rs 使用临时目录与故障注入覆盖上传和下载的文件内容、空目录、链接、覆盖、权限、取消、源文件变化、乱序与短读、续传及替换各阶段恢复,以及删除、重命名、新建和递归改权限不跟随链接。Unix 协议测试使用本机 OpenSSH sftp-server(macOS /usr/libexec/sftp-server,Linux /usr/lib/openssh/sftp-server);工作线程测试仅监听 127.0.0.1 的随机端口,使用测试凭据。限制本地套接字的沙箱会跳过该部分,需要在允许回环套接字的环境运行。
tests/ui.rs 驱动真实 Workspace,通过 Workspace::new_with_services 注入 SFTP、本地目录和终端服务,不连接用户服务器或真实钥匙串。Finder 的系统原生拖放仍需在真实 macOS 窗口中补充人工验收;无头测试覆盖原生文件拖入事件和面板内部拖放。
上传交互参考 WinSCP 上传流程,协议适配参考 russh-sftp 请求接口 与 OpenSSH 扩展规范。