Tabby 本地终端 Shell 扩展开发指南:基于 ShellProvider 的自定义 Shell 插件实现
Tabby 的 tabby-local 插件负责本地终端(Local terminal)功能,其核心扩展点是通过抽象类 ShellProvider 向应用注册可用的 Shell。本文以 tabby-local/README.md 描述的 API 为主线,结合 tabby-local/src/api.ts、tabby-local/src/profiles.ts 与 tabby-electron/src/index.ts 等源码,完整讲解 Shell 注册接口、Shell 到内置 Profile 的转换机制、会话启动参数与终端配置项,帮助开发者写出可被 Tabby 正确加载的自定义 Shell 插件。
tabby-local 插件的职责与装配
tabby-local 是一个独立的 Angular 插件包,tabby-local/src/index.ts 中的 LocalTerminalModule 集中声明了它对外暴露的全部扩展点:
{ provide: ProfileProvider, useClass: LocalProfilesService, multi: true }—— 注册本地终端的 Profile 提供者;{ provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true }—— 在设置面板中提供 "Shell" 选项卡;{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }—— 工具栏"新建标签页"按钮;{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }—— 标签页状态恢复;{ provide: CLIHandler, ... }—— 注册TerminalCLIHandler、OpenPathCLIHandler、AutoOpenTabCLIHandler三个命令行处理器,支持从命令行打开目录、运行脚本(详见命令行集成);- 模块构造函数还订阅了
new-tab/new-window快捷键,分别调用terminal.openTab()与hostApp.newWindow()。
从源码结构看,tabby-local 本身不实现具体的 Shell 列表,而是依赖注入收集所有 ShellProvider 实现——这就是 README 所描述的扩展方式。
ShellProvider API:README 核心内容
tabby-local/README.md 给出的用法分为两步。第一步是引入 API:
import { ShellProvider } from 'tabby-local'
第二步是在插件模块中导出自己的子类:
@NgModule({
...
providers: [
...
{ provide: ShellProvider, useClass: MyShellPlugin, multi: true },
...
]
})
注意 multi: true 是必需的——ShellProvider 是聚合型令牌,Tabby 会同时收集所有注册的实现。ShellProvider 在 tabby-local/src/api.ts 中是一个仅含单个抽象方法的抽象类:
/**
* Extend to add support for more shells
*/
export abstract class ShellProvider {
abstract provide (): Promise<Shell[]>
}
实现方只需重写 provide(),返回 Shell[]。Shell 列表会被 LocalProfilesService.getShells()(tabby-local/src/profiles.ts)通过 Promise.all 并发收集并合并为一个数组,其中 this.config.enabledServices(...) 保证被用户在设置中禁用的 Shell 提供者会被跳过。
Shell 接口字段详解
Shell 的完整定义见 tabby-local/src/api.ts,各字段含义如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
string |
是 | Shell 唯一标识,最终构成内置 Profile ID local:${id}(见下文) |
name |
string |
是 | 显示名称,用作 Profile 名称 |
command |
string |
是 | 要启动的可执行文件路径 |
args |
string[] |
否 | 命令行参数,缺省时转换为空数组 |
env |
Record<string, string> |
是 | 传给 PTY 进程的环境变量 |
fsBase |
string |
否 | Shell 内部文件系统对应的宿主路径,源码注释注明"Currently used for WSL only" |
cwd |
string |
否 | 初始工作目录,缺省为 null |
icon |
string |
否 | SVG 图标 |
shellType |
'unix' | 'powershell' | 'cmd' |
否 | Shell 类型,影响行为分支(如 COMSPEC 处理) |
hidden |
boolean |
否 | 是否从 Shell 列表中隐藏 |
shellType 的取值联合类型定义在同一文件 tabby-local/src/api.ts:
export type ShellType = 'unix' | 'powershell' | 'cmd'
从 Shell 到内置 Profile 的转换
注册的 Shell 如何变成界面中可见的 Profile?实现位于 tabby-local/src/profiles.ts 的 LocalProfilesService:
getBuiltinProfiles()遍历所有 Shell,生成 ID 为local:${shell.id}、type: 'local'、isBuiltin: true的内置 Profile,名称与图标直接取自 Shell;optionsFromShell()把 Shell 字段映射为SessionOptions:command、args、env、cwd、shellType,其余字段沿用configDefaults.options的默认值;configDefaults(profiles.ts L14-L29)定义了 Local Profile 的完整默认选项,包括restoreFromPTYID: null、pauseAfterExit: false、runAsAdministrator: false等,env上带有__nonStructural: true标记以避免结构校验干扰。
此外,getNewTabParameters() 实现了"继承当前目录"的交互细节:新建本地标签页时,若 Profile 未显式指定 cwd,会尝试从当前活动标签页(或其分屏中聚焦的终端)通过 session.getWorkingDirectory() 取到当前工作目录并写入新标签页选项,使新终端默认在当前目录下启动。
官方内置 Shell 提供者的实现参考
tabby-electron 包提供了 12 个内置 ShellProvider,其注册方式与 README 示例完全一致,见 tabby-electron/src/index.ts:
{ provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true },
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
{ provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true },
{ provide: ShellProvider, useClass: CmderShellProvider, multi: true },
{ provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
{ provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
{ provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
{ provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
{ provide: ShellProvider, useClass: MSYS2ShellProvider, multi: true },
{ provide: ShellProvider, useClass: WSLShellProvider, multi: true },
{ provide: ShellProvider, useClass: VSDevToolsProvider, multi: true },
这些实现位于 tabby-electron/src/shells/ 目录:
- linuxDefault.ts、macDefault.ts、winDefault.ts 分别返回
$SHELL(或默认/bin/sh)、macOS 默认 Shell、Windows 默认 Shell; - wsl.ts 的
WSLShellProvider是fsBase字段的典型使用方——WSL 内部路径与宿主 Windows 路径不同,需要通过fsBase做文件系统相对定位; - posix.ts 的
POSIXShellsProvider、cmder.ts、cygwin32.ts、cygwin64.ts、msys2.ts、gitBash.ts、powershellCore.ts、vs.ts、windowsStock.ts 等则覆盖 Windows 生态的各类 Shell 与开发工具终端。
从源码结构看,编写自定义 Shell 插件时可直接对照这些文件:provide() 通常先做平台判断(如仅 Windows 生效),再从文件系统或注册表探测 Shell 安装位置,最后组装 Shell 对象返回。
SessionOptions 与会话启动流程
当用户打开某个本地 Profile 时,tabby-local/src/components/terminalTab.component.ts 的 TerminalTabComponent 会实例化 Session 并传入 SessionOptions。SessionOptions 定义于 tabby-local/src/api.ts,与 Shell 的映射关系见上一节;LocalProfile 则是在 BaseTerminalProfile 基础上附加 options: SessionOptions 的 Profile 结构。
会话启动逻辑集中在 tabby-local/src/session.ts 的 Session.start(),关键行为包括:
- PTY 恢复:若
options.restoreFromPTYID存在,先通过ptyInterface.restore(id)恢复原 PTY(供标签页恢复/迁移使用),恢复成功后清空该字段; - 环境变量合并:按优先级合并四类来源——
getEnvironment()获取的基础环境(Windows 上可选从注册表重建,见下文)、Tabby 固定注入的COLORTERM=truecolor/TERM=xterm-256color/TERM_PROGRAM=Tabby、Profile 中substituteEnv(options.env)展开后的用户变量、以及全局config.store.terminal.environment配置项;mergeEnv以不区分大小写的方式去重(session.ts L11-L23); - Windows 细节:启用
setComSpec时会把COMSPEC指向 Tabby 自身的可执行文件;macOS 下若未设置LC_ALL,会依据LC_CTYPE(默认en_US.UTF-8)补全LANG、LC_ALL、LC_MESSAGES、LC_NUMERIC、LC_COLLATE等 locale 变量; - CWD 校验:
cwd缺省时回退到HOME,若路径不存在则打印警告并放弃该参数; - PTY 生成参数:
name: 'xterm-256color',cols/rows缺省 80×30;Windows 且配置了useConPTY时传useConpty: 1——源码注释说明传数字1而非布尔true是为了"forces ConPTY even if unstable"(强制启用即使标记为不稳定); - 输出与退出处理:订阅
data事件时先ackData再emitOutput,Windows 下还会对输出做 CWD 猜测;exit/close事件触发销毁,若pauseAfterExit为真则改为输出 "Press any key to close"。
Windows 环境变量的注册表重建
tabby-local/src/environment.ts 实现了 Windows 上的环境刷新:buildWindowsEnvironment() 依次从注册表读取系统级(HKLM\...\Environment)、用户级(HKCU\Environment)与易失性(HKCU\Volatile Environment)变量,其中 Path/PATHEXT 采用追加合并(mergeRegistryEnv 对路径类变量拼接而不是覆盖),保留 Electron/Node 等进程私有变量,并迭代展开 %VAR% 引用(最多 10 轮)。该行为仅在 windowsRefreshEnvironment 配置为真时启用,仿效 Windows Terminal 的环境刷新机制;非 Windows 平台直接使用 process.env(缓存一份并剔除 undefined 值)。
substituteEnv()(environment.ts L137-L149)则负责 Profile 环境变量的插值:Windows 上展开 %VAR%,POSIX 上展开 $VAR,查找不区分大小写(Windows),未命中的引用会被替换为空字符串。
终端配置项与平台默认值
tabby-local/src/config.ts 的 TerminalConfigProvider 定义了 terminal 配置块:
terminal:
autoOpen: true # 启动时自动打开终端标签页(配合 CLI 单实例逻辑)
useConPTY: true # Windows 上使用 ConPTY(实验性 API)
environment: {} # 全局注入的环境变量(最高优先级)
setComSpec: false # 将 COMSPEC 指向 Tabby 可执行文件
windowsRefreshEnvironment: true # Windows 上从注册表重建环境变量
同时按平台给出默认 Profile 与 new-tab 快捷键:
| 平台 | 默认 Profile | new-tab 快捷键 |
|---|---|---|
| macOS | local:default |
⌘-T |
| Windows | local:cmd-clink |
Ctrl-Shift-T |
| Linux | local:default |
Ctrl-Shift-T |
设置界面中的 "Shell" 选项卡(tabby-local/src/components/shellSettingsTab.component.pug)暴露了其中两项交互:Use ConPTY 开关(仅当当前构建支持 ConPTY 时显示,绑定 config.store.terminal.useConPTY),以及两条提示——ConPTY 在未标记稳定的构建上建议 Windows 10 18309 以上系统;WSL 终端仅在使用 ConPTY 时支持 TrueColor。
命令行与自动化:CLIHandler
tabby-local/src/cli.ts 提供三个 CLIHandler,可用于从外部脚本驱动 Tabby:
TerminalCLIHandler(优先级 0,firstMatchOnly):处理tabby open <dir>(打开目录并前置窗口)与tabby run <command...>(弹出确认框后以临时 Local Profile 运行命令,并设置pauseAfterExit使窗口在命令结束后等待按键);OpenPathCLIHandler(优先级 -100):当参数本身是路径时,目录则用默认 Profile 打开;.sh/.command脚本用默认 Shell 执行,.bat找cmdProfile、.ps1找powershellProfile 执行,且都设置pauseAfterExit: true;AutoOpenTabCLIHandler(优先级 -1000):当terminal.autoOpen为真、未启用 Welcome 标签页且非第二实例启动时,应用就绪后若没有任何标签页则自动openTab()。
新建标签页的编程入口:TerminalService
tabby-local/src/services/terminal.service.ts 是运行时打开本地终端的服务入口,可被其他插件注入使用:
getDefaultProfile():优先取terminal.profile配置指定的 Profile,找不到则回退到第一个内置 local Profile;openTab(profile?, cwd?, pause?):未传 Profile 时用默认 Profile;对cwd做存在性检查(不存在则忽略并警告);pause参数会叠加到pauseAfterExit上——即"Shell 退出后等待按键"的行为既可通过 Profile 选项配置,也可在调用时临时指定;最终通过profilesService.openNewTabForProfile()创建标签页。
配合 tabby-local/src/hotkeys.ts 注册的 new-tab 快捷键与 tabby-local/src/buttonProvider.ts 的工具栏按钮,构成了 UI 层的三个打开入口。
编写自定义 Shell 插件的完整步骤
综合 README 与源码证据,一个可被正确加载的自定义 Shell 插件应满足:
- 从
tabby-local导入ShellProvider、Shell类型(README 示例的第一步); - 实现一个类,重写
provide(): Promise<Shell[]>,返回至少包含id、name、command、env的 Shell 对象,按需补充args、cwd、icon、shellType、hidden; - 在插件的
@NgModule中按 README 示例注册{ provide: ShellProvider, useClass: MyShellPlugin, multi: true }; - 验证结果:插件加载后,该 Shell 会以
local:<id>的形式出现在 Profile 列表中(LocalProfilesService.getBuiltinProfiles()的映射规则),并在 Shell 设置、新标签页、CLI 等入口可见。
需要注意的两个适用前提:其一,provide() 的返回结果会经过 config.enabledServices() 过滤,被禁用的提供者的 Shell 不会出现;其二,内置 Profile 的 options 由 optionsFromShell() 生成,width、height、restoreFromPTYID、pauseAfterExit、runAsAdministrator 等字段不来自 Shell 定义,而是取 configDefaults 的默认值,如需修改应通过 Profile 配置覆盖而非修改 Shell 对象。
小结
tabby-local 的 Shell 扩展机制把"有哪些 Shell 可用"与"终端会话如何运行"彻底解耦:插件侧只需实现 ShellProvider.provide() 返回声明式的 Shell 对象(README 给出的两步 API 即为此设计的完整入口),LocalProfilesService 负责将其转化为内置 Profile,Session 统一处理环境合并、CWD 校验、ConPTY 与 PTY 生命周期。基于 tabby-electron/src/shells/ 中 12 个内置提供者的实现范式,开发者可以为任意平台、任意 Shell 或开发工具终端编写符合 Tabby 规范的自定义 Shell 插件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00