npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ai-zen/socket-pty

v0.2.1

Published

Cross-platform PTY transport exposed over a socket via a standard JSON-RPC 2.0 protocol — any client that speaks JSON-RPC can read/write a managed terminal process. Renders the real terminal screen via xterm-headless. MCP stdio management included as a se

Downloads

519

Readme

@ai-zen/socket-pty

version protocol license platform

通过 socket(JSON-RPC 2.0)暴露一个可托管的终端进程。启动一个真实终端进程(bash、vim、node REPL 等)后,它会成为网络端点;任何能建立 socket 连接并遵循 JSON-RPC 2.0 的客户端,均可对该终端进行读写。

目录

  1. 安装
  2. 依赖
  3. 组成与入口
  4. 端点与地址
  5. 启动服务
  6. 连接与协议
  7. 数据包格式
  8. 真屏幕与 read 截取
  9. write 支持的键名
  10. 程序化 API
  11. MCP 适配层
  12. 会话生命周期
  13. 已知限制
  14. 许可证

安装

npm install @ai-zen/socket-pty

在仓库内本地使用时,先构建并经由 dist/ 引入:

npm install
npm run build
node dist/cli.js serve --cmd bash --socket /tmp/x.sock

依赖

  • node-pty(必需):pty 后端。Linux 无预编译二进制,安装时从源码编译,需要 g++ 支持 C++20(gcc-10 及以上)。Windows / macOS 提供预编译二进制,开箱即用。
  • @xterm/headless(必需):屏幕渲染后端,纯 JS 无编译负担。

node-pty 是不可替代的硬依赖,不存在降级后端。


组成与入口

包提供两个入口:

| 入口 | 内容 | 用途 | |------|------|------| | @ai-zen/socket-pty(主入口 .) | PtyServer / PtySession / createSocketPty / 协议与类型 | 以程序化方式启动/持有终端服务 | | @ai-zen/socket-pty/mcp./mcp) | MCPManager / ConnectionPool / MCP 适配层 | 作为 MCP server 暴露终端操作工具 |

主入口只暴露能力本体,不含入口适配。MCP 适配层是独立子路径。


端点与地址

端点(endpoint)是终端服务监听的网络位置,有两种形态:

| 形态 | 描述 | |------|------| | Unix domain socket | { type: "unix", path: "/path/to.sock" },地址形式 unix:/path/to.sock | | TCP loopback | { type: "tcp", port: 5174, host: "127.0.0.1" },地址形式 tcp:127.0.0.1:5174 |

端点地址由启动方显式指定--socket <path>--port <port> 二选一)。连接一个终端的前提是已知其地址;服务端不自动分配地址——它启动时在调用方指定的地址上监听。


启动服务

CLI

# Unix domain socket
socket-pty serve --cmd bash --socket /tmp/v.sock

# TCP loopback
socket-pty serve --cmd bash --port 5174

# Windows:使用 PowerShell / CMD
socket-pty serve --cmd powershell.exe --port 5174
socket-pty serve --cmd cmd.exe --port 5174

serve 选项:

| 选项 | 说明 | |------|------| | --cmd <命令> | 要托管的命令(必填)。Windows 上需带 .exe 后缀,如 powershell.exe / cmd.exe(见命令与平台) | | --socket <路径> | Unix domain socket 监听路径(与 --port 二选一,必给其一) | | --port <端口> | TCP 监听端口,1-65535 的具体端口(与 --socket 二选一,必给其一) | | --cols <n> | 终端列数(默认 100) | | --rows <n> | 终端行数(默认 30) | | --cwd <路径> | 工作目录 | | -h / --help | 帮助 |

--socket--port 二选一,不能同时给出;两者都不给、或 --port 不是 1-65535 的具体端口时,serve 报错退出。

启动成功后打印:

[socket-pty] 会话已启动,命令: bash
[socket-pty] 连接地址: 127.0.0.1:5174

程序化

import { createSocketPty } from "@ai-zen/socket-pty";

const server = createSocketPty({
  endpoint: { type: "unix", path: "/tmp/v.sock" }, // 或 { type: "tcp", port: 5174, host: "127.0.0.1" }
  command: "bash",
  cols: 100,
  rows: 30,
});
const address = await server.listen();
// address: unix:/tmp/v.sock

命令与平台

--cmd / command 指定被托管的命令,由调用方负责传入当前平台可用的命令:

| 平台 | 推荐命令 | 说明 | |------|----------|------| | Linux / macOS | bash | 也可传 zshshnode -i 等 | | Windows | powershell.exe / cmd.exe | 必须带 .exe 后缀 |

Windows 上 node-pty(ConPTY)对裸命令名不做 PATH 搜索:传 powershellbash 这类不带 .exe 的名字会报 File not found。应传带后缀的可执行名,或完整路径(如 C:\Program Files\Git\bin\bash.exe)。

# Windows:两种可用写法
socket-pty serve --cmd powershell.exe --port 5174
socket-pty serve --cmd cmd.exe --port 5174

连接与协议

端点地址形式

  • Unix domain socketunix:/path/to.sock
  • TCPtcp:127.0.0.1:5174

帧格式

连接建立后,每行一条 JSON-RPC 2.0 报文,以 \n 分隔(换行分帧)。

  • 请求必须包含 idstringnumber);不支持通知(无 id 的请求被拒绝)。
  • 每个请求对应一个响应,响应回显同一个 id
  • params 必须是对象。
请求: {"jsonrpc":"2.0","method":"pty/read","params":{},"id":7}
成功: {"jsonrpc":"2.0","result":{...},"id":7}
失败: {"jsonrpc":"2.0","error":{"code":-32601,"message":"..."},"id":7}

method 一览

| method | params | result(成功) | |--------|--------|----------------| | pty/read | { top? \| bottom? \| start?+end? } | { screen, cursor, size } | | pty/write | { action } | { written } | | pty/wait | { match?, timeout? } | { matched, screen, cursor, size, waitedMs } | | pty/status | {} | { running, exitCode, pid } | | pty/resize | { cols, rows } | {} | | pty/kill | {} | {} |

错误码

| code | 含义 | |------|------| | -32700 | 解析错误(非法 JSON) | | -32600 | 无效请求(结构不合法 / 旧 {op} 协议被拒绝 / 缺 id) | | -32601 | 方法不存在 | | -32602 | 无效参数(如 text/key 互斥、top/bottom 互斥、未知键名) | | -32603 | 内部错误 | | -32000 | 服务端错误 |


数据包格式

以下为每个 method 的完整请求与响应报文。

pty/status — 查询会话状态

→ {"jsonrpc":"2.0","method":"pty/status","params":{},"id":1}
← {"jsonrpc":"2.0","result":{"running":true,"exitCode":null,"pid":1234},"id":1}
  • runningboolean,会话是否运行中。
  • exitCodenumber | null,已退出时为退出码,否则为 null
  • pidnumber,被托管进程的 pid。

pty/read — 读取当前屏幕

→ {"jsonrpc":"2.0","method":"pty/read","params":{},"id":2}
← {"jsonrpc":"2.0","result":{"screen":"root@host:~$\n","cursor":{"row":1,"col":14},"size":{"cols":100,"rows":30}},"id":2}
  • screenstring,当前终端屏幕的纯文本(已去除 ANSI 控制序列,每行尾部空格被去除,多行以 \n 分隔)。语义为终端当前真实画面,非历史日志。
  • cursorobject,光标位置,row/col1-based(示例中 root@host:~$ 占 13 字符,光标在其后,col 为 14)。
  • sizeobject,终端尺寸 { cols, rows }

pty/read 支持截取当前屏的部分行。top/bottomstart+end 为互斥的两组:

→ {"jsonrpc":"2.0","method":"pty/read","params":{"top":3},"id":3}
→ {"jsonrpc":"2.0","method":"pty/read","params":{"bottom":3},"id":4}
→ {"jsonrpc":"2.0","method":"pty/read","params":{"start":2,"end":5},"id":5}
  • top: N — 取顶部 N 行。
  • bottom: N — 取底部 N 行。
  • start/end — 取 1-based 闭区间 [start, end],必须成对给出。
  • 校验:top/bottom 互斥;start/end 必须成对;两组不能混用;越界自动 clamp。

pty/write — 写入数据

action 为单个动作对象或动作数组。

写文本:

→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":{"text":"echo hello"}},"id":6}
← {"jsonrpc":"2.0","result":{"written":11},"id":6}

写按键:

→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":{"key":"Enter"}},"id":7}
← {"jsonrpc":"2.0","result":{"written":1},"id":7}

动作数组(按序执行多个动作):

→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":[{"text":"ls"},{"key":"Enter"}]},"id":8}
← {"jsonrpc":"2.0","result":{"written":5},"id":8}
  • actiontextkey 二选一,不能同时给出;空对象、空数组被拒绝。
  • writtennumber,实际写入的 UTF-8 字节数(数组动作返回合计)。

pty/wait — 等待某段输出出现

→ {"jsonrpc":"2.0","method":"pty/wait","params":{"match":"hello","timeout":6000},"id":9}
← {"jsonrpc":"2.0","result":{"matched":true,"screen":"hello\n","cursor":{"row":2,"col":1},"size":{"cols":100,"rows":30},"waitedMs":45},"id":9}
  • 当前屏幕中按子串匹配 match;未给 match 时只按 timeout 等待一段固定时长。
  • timeout:最大等待毫秒,默认 10000
  • matchedboolean,是否在超时前匹配到。
  • screen:匹配时或超时时的当前屏幕。
  • waitedMs:实际耗时(毫秒)。

超时未匹配:

→ {"jsonrpc":"2.0","method":"pty/wait","params":{"match":"absent","timeout":100},"id":10}
← {"jsonrpc":"2.0","result":{"matched":false,"screen":"...","cursor":{"row":1,"col":1},"size":{"cols":100,"rows":30},"waitedMs":100},"id":10}

pty/resize — 调整终端尺寸

→ {"jsonrpc":"2.0","method":"pty/resize","params":{"cols":120,"rows":40},"id":11}
← {"jsonrpc":"2.0","result":{},"id":11}

pty/kill — 终止会话并关闭端点

→ {"jsonrpc":"2.0","method":"pty/kill","params":{},"id":12}
← {"jsonrpc":"2.0","result":{},"id":12}

发起 pty/kill 后,会话被终止、端点关闭、服务进程退出,当前连接被服务端关闭。

错误响应

所有错误响应为统一形状:

→ {"jsonrpc":"2.0","method":"pty/unknown","params":{},"id":13}
← {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found: pty/unknown"},"id":13}

→ {"jsonrpc":"2.0","method":"pty/write","params":{"action":{"text":"x","key":"Enter"}},"id":14}
← {"jsonrpc":"2.0","error":{"code":-32602,"message":"write 动作内 text 与 key 只能二选一"},"id":14}
  • 错误对象形如 { code, message, data? }code/message上文
  • 正常请求的响应回显请求的 id;无法解析的报文(连 id 都取不到)返回 id: null

会话流转

以下时序描述一个会话从启动到结束的完整流转:

sequenceDiagram
    participant S as 启动方
    participant P as serve 进程
    participant C as 客户端

    S->>P: 指定地址并启动(--socket / --port)
    activate P
    Note over P: 在指定地址监听

    C->>P: 建立 socket 连接
    C->>P: {"jsonrpc":"2.0","method":"pty/status","id":1}
    P-->>C: {"jsonrpc":"2.0","result":{...},"id":1}

    C->>P: {"jsonrpc":"2.0","method":"pty/write","params":{"action":[{"text":"ls"},{"key":"Enter"}]},"id":2}
    P-->>C: {"jsonrpc":"2.0","result":{"written":5},"id":2}

    C->>P: {"jsonrpc":"2.0","method":"pty/wait","params":{"match":"file","timeout":5000},"id":3}
    P-->>C: {"jsonrpc":"2.0","result":{"matched":true,"screen":"...","id":3}}

    C->>P: {"jsonrpc":"2.0","method":"pty/read","params":{},"id":4}
    P-->>C: {"jsonrpc":"2.0","result":{"screen":"...","cursor":{"row":2,"col":1},"size":{"cols":100,"rows":30}},"id":4}

    C->>P: {"jsonrpc":"2.0","method":"pty/kill","params":{},"id":5}
    P-->>C: {"jsonrpc":"2.0","result":{},"id":5}
    Note over P: 会话终止,端点关闭,连接被关闭
    deactivate P
  • 启动:启动方在指定地址监听,地址已知。
  • 连接与请求:客户端连到该地址,发送 JSON-RPC 2.0 请求行,收到对应响应行。
  • 终止pty/kill 终止会话并关闭端点。

真屏幕与 read 截取

  • 屏幕由服务端(端点侧)用 xterm-headless 渲染,是终端的当前真实画面(所见即所得)。
  • pty/read 返回整个当前屏幕(rows 行)。它不是历史日志——滚动出屏外的内容不会出现在该返回值中。
  • 可截取当前屏的部分行(top/bottom/start+end,见数据包格式)。
  • 连接断开后终端仍运行;重新连接后仍可读到该会话的当前画面。

write 支持的键名

pty/writeaction.key 使用以下键名(区分大小写),对应写入的字节序列:

| 类别 | 键名 | |------|------| | 方向 | ArrowUp ArrowDown ArrowLeft ArrowRight | | 编辑 | Enter Tab Backspace Delete Home End PageUp PageDown Insert Escape Space | | 功能 | F1F12 | | 组合 | Ctrl+字母Ctrl+ACtrl+Z)、Shift+字母Alt+字母Alt+EnterCtrl+EnterCtrl+Space 等,可组合修饰符(如 Ctrl+Alt+Del) |

  • 单字符文本可直接用 text 字段。
  • key 无法解析时返回 -32602 无效参数错误。

程序化 API(TypeScript)

createSocketPty

import { createSocketPty } from "@ai-zen/socket-pty";

const server = createSocketPty({
  endpoint: { type: "unix", path: "/tmp/v.sock" }, // 或 { type:"tcp", port:5174, host:"127.0.0.1" }
  command: "bash", // Windows 上请用 "powershell.exe" / "cmd.exe"(见「命令与平台」)
  cols: 100,
  rows: 30,
  cwd: "/path/to/workdir", // 可选
});
const address = await server.listen();

endpoint 必填,地址由调用方指定。

PtyServer 实例方法

| 方法 | 说明 | |------|------| | listen(): Promise<string> | 监听端点并创建会话,返回连接地址 | | address(): string | 实际监听地址 | | session(): IPtySession \| null | 当前会话 | | close({ kill? }): Promise<void> | 关闭端点(kill: true 时终止会话),幂等 |

会话对象方法

回调 server.session 返回 IPtySession,提供同步读屏等能力:

const session = server.session;
await session?.readScreen();                       // 整屏
await session?.readScreen({ bottom: 5 });          // 底部 5 行
await session?.readScreen({ start: 2, end: 6 });   // 第 2..6 行

| 方法 / 属性 | 说明 | |------------|------| | readScreen(sel?) | 读当前屏幕,返回 { screen, cursor, size } | | wait(opts) | 等待输出,返回 { matched, screen, cursor, size, waitedMs } | | write(data): number | 写原始数据,返回 UTF-8 字节数 | | resize(cols, rows) | 调整尺寸 | | kill(signal?) | 终止 | | pid / running / exitCode | 进程信息 |


MCP 适配层

@ai-zen/socket-pty/mcp 提供一套 MCP server(stdio 传输),将终端操作暴露为 MCP 工具。

在 MCP 宿主中注册

{
  "mcpServers": {
    "socket-pty": {
      "command": "npx",
      "args": ["@ai-zen/socket-pty", "mcp"]
    }
  }
}

工具

| 工具 | 参数 | 返回 | |------|------|------| | spawn | command, cwd?, cols?, rows? | address(字符串) |

spawncommand 与 CLI / 程序化接口一致,遵循命令与平台Windows 上应传 powershell.exe / cmd.exe(带 .exe 后缀),传 powershell / bash 会启动失败。 | read | address, top?/bottom?/start?+end? | 屏幕文本 | | write | address, action | 写入成功(含字节数) | | wait | address, match?, timeout? | 匹配结果 + 屏幕 | | resize | address, cols, rows | 成功/失败 | | status | address | { running, exitCode, pid } | | kill | address | 成功/失败 |

spawn 的地址来源

spawn 由 MCP 管理器自分配本机临时端点,并在工具返回中给出该 address

  • Unixunix:/tmp/pty-mcp-<pid>-<时间戳>-<随机>.sock
  • Windowstcp:127.0.0.1:<port>(先探测分配一个空闲端口)

address 由管理器分配并返回给调用方,作为后续工具的 address 参数使用。会话为孤儿进程,常驻直到被 kill

spawn 的自动分配仅存在于 MCP 内部——管理器既分配地址又持有使用,地址始终在其掌握中。对外提供端点(serve / createSocketPty)则必须显式定址。

编程式引入

import { MCPManager, ConnectionPool } from "@ai-zen/socket-pty/mcp";

MCPManager 提供 spawn / read / write / wait / resize / status / kill 方法,经内部 ConnectionPool(按地址复用连接,JSON-RPC 2.0)分发到对应 serve 进程。


会话生命周期

  • 常驻serve/spawn 启动的进程独立常驻,客户端的断开重连不影响其存活与当前屏幕。
  • 连接:一个端点可被多个客户端连接;每个请求独立带 id,按 id 对应响应。
  • 终止pty/kill 终止会话并关闭端点;之后端点不可再连接。
  • 清理:Unix socket 端点关闭时,对应 socket 文件被移除。

已知限制

  • 仅当前屏幕,无历史日志pty/read 返回当前画面(rows 行),不提供滚动出屏外的历史回读。
  • 依赖 node-pty(必需):无降级后端。Linux 需能编译 node-pty(见依赖)。
  • Windows 命令需带 .exe 后缀:node-pty 在 Windows 上不对裸命令名做 PATH 解析,传 powershell / bashFile not found;应传 powershell.exe / cmd.exe 或完整路径(见命令与平台)。
  • 不支持通知:所有请求必须带 id
  • 对外端点必须显式定址serve/createSocketPty 不接受自动分配;MCP 的 spawn 为特例,地址由其自分配并返回。

许可证

MIT