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

pi-sysmon

v0.7.0

Published

bottom-style braille system monitor charts (CPU / memory / network / LLM token throughput) for the pi coding agent

Readme

pi-sysmon

pi 里显示 bottom 风格的 braille 折线图

CPU · 内存 · 网络 · Tokens —— 用盲文点阵字符画的实时历史曲线

test license

150 列宽下的默认四图。曲线由盲文字符绘制,y 刻度叠印在绘图区内侧、 时间标签嵌在下边框里。

English | 简体中文 | X @zzjcoo

特性

  • 真正的折线图,不是进度条或 sparkline —— 每个字符编码 2×4 个盲文点子像素,等效把终端分辨率放大 8 倍
  • 零依赖 —— 只用 Node 内置模块 + pi 的公开扩展 API
  • 直接读 /proc —— 不需要 systeminformation / pidusage 之类的包
  • 默认四图 —— CPU / 内存 / 网络 / Tokens(LLM 吞吐速率); Tokens 图实时显示本 pi 进程与模型 API 之间的输出 token 速率, 并与 pi 自身在状态栏显示的 ⚡ 23.0 t/s (avg) 相互印证
  • CPU 温度曲线 —— CPU 图以第二条(红色)曲线绘制温度,自带 0–100°C 刻度与 CPU% 共享绘图区 (右上角 100° 标注副轴刻度,标题栏同步显示当前 °C 读数,参照 Tokens 图 TPS/缓存率双轴的实现)。 Linux 读取 /sys/class/hwmon + /sys/class/thermal;macOS 无免 root 系统 API, 探测用户自装的 osx-cpu-temp / istats 辅助命令 —— 无可用源时图表安静降级为单曲线原样
  • 带坐标轴 —— y 轴刻度、x 轴线、时间窗标签,复刻 bottom 的排版
  • y 轴顶端 = 整窗真实最高值 —— 60s 窗口内任何一处的高度都能直接用顶端刻度读出来, 刻度绝不撒谎(配合 PI_SYSMON_SCALE_WINDOW=<1 还能开启尖峰后自动回落)
  • 自动剔除本机环回/容器流量 —— lo / veth* / docker* / br-* 不计入网速 (实测:本机推 30 MB/s 环回流量时,网速图只报物理网卡的 178 KB/s)
  • 响应式并排 —— 终端宽时四张图并排一行,变窄自动降级成 2×2 / 1 列竖排,不会把曲线挤成噪声
  • Tokens 图分上下行 —— 读数与 pi 自己的状态栏同口径( 上行 input / 下行 output / R 缓存读),两边数字可以直接对照;上行是精确值,速率是估算值(带 ~
  • 图表利用率高 —— 刻度叠在绘图区内侧、时间标签嵌进边框。同样的块高下 绘图面积比「刻度独占列 + 轴线独占行」的旧排版大 ~71% (50 列宽、8 行高:168 格 → 288 格)
  • 读数写在边框标题栏里(默认)—— 复用那行本来就有的 填充,零成本、不遮曲线;也可用 PI_SYSMON_LABEL=box 换回 bottom 的右上角浮框
  • 状态持久化 —— 显示偏好(模式 / 位置)与全局开关默认值记在配置文件里、重启保留;会话级的开关记在会话自身里
  • 多种显示模式 —— 图表(默认)/ 一行文字 / 整块底部;fullscreen TUI 下右下角有可点击标签, 一次点击即可在 chart ⇄ line 间切换
  • 纯 Linux 友好,其他平台不崩 —— 非 Linux 上采集自动退化为零值

截图

四张图各自的读数都写在边框标题栏里(复用那行本来就有的 填充, 不遮任何曲线)。y 轴刻度叠印在绘图区内侧、时间标签嵌在下边框里

这两处省下的空间是可量化的。按同样的块高(8 行)、块宽 50 列比

| 排版 | 每块 chrome | 绘图区 | | --- | --- | --- | | 旧(刻度独占 5 列 + 轴线独占 1 行) | 4 行 | 42 × 4 = 168 | | 现在(叠印 + 边框兼任轴) | 2 行 | 48 × 6 = 288 |

增益拆开是两个因数相乘:宽度 42 → 48(+14%,刻度不再占列)× 行数 4 → 6(+50%,省下的两行还给数据)≈ +71%

窄终端自动降级(95 列)

终端变窄时排成 2×2,再窄就变 1 列竖排。断点在 96 列: ≥ 96 是四图并排一行(上方那张),< 96 就降到 2×2(这张是 95 列)。 bottom 在同样宽度下会把四张图硬挤成一行(每张 16 列左右),曲线就不可读了。

Tokens 图的上下行

┌ Tokens ─ ~58t/s ·92% ⌀87% ◔42% ─┐

读数刻意不与 pi 自己的状态栏重复:widget 模式(chart / line)下图表就挂在其旁边, 而 pi 状态栏本身常驻显示 上行 input / 下行 output / R 缓存读 —— 这三个计数在这里就省去了。只有 /sysmon footer 模式(它替换了 pi 的状态栏, 连同其 token 读数一起消失)才会重新显示它们。几个细节:

  • 主曲线只画下行速率。两个方向的时间形状完全不同(上行是一次性整块上传、 下行是逐字流式,实测比例约 516:1),画同一根轴上会将下行压成 0.2% 高度。
  • ~ 只加在速率上 —— 它是从流式增量估算的;累计值来自 provider 的 精确 usage,所以不带 ~。哪个数字可信,一眼能看出来。
  • 块变窄时按重要度逐段丢弃:速率 → 瞬时命中率 → 累计命中率 → 上下文用量 → (footer 模式:上行 → 下行 → 缓存读)。

上下文用量:◔N%

◔N% 读数(半满圆 —— “上下文窗口有多满”)与 pi 自己状态栏显示的是同一个数字 (来自 ctx.getContextUsage(),pi 对活跃会话的估算,含 system prompt、工具结果、 compaction 边界),所以两边可以直接对照。它从图表自己的 token 累计值重新推导: pi 的估算考虑了那些累计值看不到的东西。

颜色沿用 pi footer 的阈值:平常 muted,超过 70% 转黄(warning),超过 90% 转红(error)。 未知读数(还没有模型,或 /compact 刚结束、下一轮 LLM 回复前 pi 还报不出百分比) 降级为不显示,绝不会显示成假的 ◔0%

缓存命中率:第二根曲线

缓存命中的 prompt token 计费远低于全新输入,所以命中率是唯一能回答“缓存到底有没有起作用” 的数字。Tokens 块从两个视角展示它:

  • ⌀N% —— 会话累计命中率(黄色曲线)。 读作“平均”;每次消息落地时从会话累计值 重新计算,因此曲线是阶梯状 —— 这是诚实的:底层数字是 provider 精确上报的总量, 两个消息之间没有更细的信息可画。
  • ·N% —— 瞬时命中率(仅标题栏)。最近一轮自己的 cacheRead / (cacheRead + input) 比例;这才是可据以行动的数字 —— 累计平均值会把一轮未命中缓存的请求藏很久。

黄色曲线有自己固定的 0–100% 量程,预先映射到 TPS 轴上且不参与 y 轴量程计算: 90% 的命中率永远渲染为绘图区高度的 90%,哪怕 3000 t/s 的流式尖峰把 TPS 轴顶抬高。 刻度列仍然只显示 TPS —— 靠颜色绑定(黄色曲线 ⇄ 读数)区分两根线。

两个读数只在 provider 上报过缓存读(R)之后才出现;从未碰过缓存的会话会原样退回 之前的单曲线块。line 模式同样显示 ·N%/⌀N%(门控条件完全一致),并且 token 组 在重要度顺序中排到了 NET 之前 —— line 放不下时整组从尾部丢弃,而 token 读数比网络 速率更难从屏幕其他地方找回。

安装

用 pi 安装(推荐)

# 从 npm 装最新版
pi install npm:pi-sysmon

# 或锁定具体版本
pi install npm:[email protected]

# 或直接装 git 源
pi install git:github.com/zzjcool/pi-sysmon

重启 pi 即可看到曲线,默认就是开启的。升级用 pi update npm:pi-sysmon, 卸载用 pi remove npm:pi-sysmon

从 git clone 装:单文件(手动安装里最简单)

git clone https://github.com/zzjcool/pi-sysmon && cd pi-sysmon && npm install
npm run build:single    # 生成 dist/pi-sysmon.ts
cp dist/pi-sysmon.ts ~/.pi/agent/extensions/pi-sysmon.ts

从 git clone 装:目录形式(多文件)

git clone https://github.com/zzjcool/pi-sysmon
cp -r pi-sysmon ~/.pi/agent/extensions/pi-sysmon

⚠️ 目录形式必须放在子目录里。pi 的自动发现会把 extensions/ 下的每个 .ts 都当成扩展加载,平铺多个文件会导致 braille.ts 被当作扩展而整个加载失败。

不安装先试用

pi -e npm:pi-sysmon

使用

/sysmon                    开 / 关(**只影响当前会话**)
/sysmon on | off           显式开 / 关(只影响当前会话)
/sysmon global on | off    设置**新会话**的默认开关(同时立即作用于当前会话)
/sysmon chart              图表模式(默认)
/sysmon below|above        图表 / line 放编辑器下方(默认)/ 上方(持久化)
/sysmon line               一行文字模式(显示 `CPU … TOK …` 的那一行;别名 `status`)
/sysmon footer             用图表替换整个底部(可以比 widget 模式更高)

chart / line互斥的显示模式(点某个模式名 = 切过去并打开,不会把监控关掉); on / off / above / below 与模式正交。

点击切换模式(仅 fullscreen TUI)

在 pi 的 fullscreen TUI 模式下(--tui-mode fullscreen,或在 /settings 里切 TUI mode), 面板右下角会出现一个可点击的 [line] / [chart] 小标签 —— 点一下就在 chart 和 line 之间切换,不用敲命令:

┌ CPU ─ 12% ───────────────────────┐
│ ⡿⢸⣿⡇ ...                       │
└ 60s ──────────────────────── 0s ┘
                              [line]
  • 点击走的是和 /sysmon chart / /sysmon line 同一条路径 —— 模式切换作为全局显示偏好持久化, 会话级开关状态完全不受影响。
  • line 模式下标签和文字同行(行高仍严格为 1 行 —— 标签的列从指标文字里借,指标照旧 从尾部整段丢弃)。
  • fullscreen 下图表用自己的行预算支付 chip 那一行,面板总行数不会超过 WIDGET_MAX_ROWS
  • 标签以外的点击不会被吞:在面板上拖拽选中文字的行为和以前完全一样。
  • regular 模式(默认)完全不接管鼠标 —— 那时终端拥有回滚区 —— 所以不渲染标签, 面板行为和以前一模一样。请用 /sysmon chart | line 切换。

为什么标签没有悬停高亮:tmux/zellij/screen 下 pi 只开启 button-motion 鼠标上报(没有 move 事件),所以标签必须在没有任何悬停反馈的情况下也看起来能点。

会话级 vs 全局级

/sysmon on|off会话级的:写在当前会话自己的 entry 里,所以 /resume 回到这个会话时 开关和离开时一模一样,而其它会话、以后新开的会话都不受影响 —— 在某个项目里关掉, 只在这个会话里关掉。

/sysmon global on|off新会话的默认值写进 <configDir>/pi-sysmon.json, 并立即作用于当前会话(否则「以后默认关」却眼睁睁看着图表还在跑,会让人以为命令坏了)。

启动时的优先级:会话选择 → --sysmon → 全局默认 → 内置默认(开)。 模式(chart/line/footer)与位置(above/below)仍是全局显示偏好, 继续写在配置文件里 —— 否则每开一个会话都要重新选一遍图表。

line 模式显示什么

一行、按重要度降序、放不下时从尾部整段丢弃(不会切出 ↑1.0 这种半截数字):

CPU 12%  MEM 60% 37G  NET ↑592K/s ↓34K/s  TOK ~0t/s ↑5.7k ↓89 R2.7k
└─ 系统指标 ─────────────────────────┘ └─ LLM token ──────────────────┘
  • CPU / MEM / NET 与图表模式的对应块同源、同配色;
  • TOK 是 LLM token 吞吐:~<速率>(由流式 delta 估算,所以带 ~) 加会话累计的 ↑input ↓output RcacheRead(来自 message_end精确 usage, 逐字对齐 pi footer 的 ↑↓R 口径,可以直接和底部那行对照);
  • 无快照(非 Linux / /proc 不可读)时仍会输出 TOK 段 —— 它是唯一不依赖 /proc 的指标。

linewidget 实现而不是 setStatus,所以它和图表一样遵循 above/below/sysmon below 后图表与 line 会出现在同一个位置。 (setStatus 的内容永远被 pi 内建 footer 渲染,位置钉死在底部,改不了。)

图表按终端宽度响应式排版(以默认四图为例):

| 终端宽度 | 布局 | | --- | --- | | ≥ 96 列 | 4 张图并排一行(共 8 行) | | 48 – 95 列 | 2 列 × 2 行 | | < 48 列 | 1 列竖排 |

行数按 widget 模式默认预算计算(PI_SYSMON_CHART_HEIGHT=6WIDGET_MAX_ROWS=18); footer 模式预算更大(40 行),行数会更多。

每张图最少需要 24 列(边框 2 + 绘图区 22)。刻度是叠印在绘图区上的、 不占独立列,所以这个下限比旧排版(刻度独占 5 列)低不少。 比这更窄时 bottom 会把 N 张图硬挤成一团(50 列时每张只有 16 列,曲线已不可读), 本项目改为降级排列。

列数还会随块数自适应(块数由 PI_SYSMON_TOKENS / PI_SYSMON_DISKS 决定): 三图时 ≥ 72 列即并排(旧行为不变);块数 ≥4 时跳过 3 列档,避免排成 「3+1」那种第二组只有一个块加一整行空格子的形态。

配置

| 环境变量 | 默认 | 说明 | | --- | --- | --- | | PI_SYSMON_INTERVAL | 1000 | 采样间隔(毫秒,下限 500) | | PI_SYSMON_POINTS | 60 | 历史点数(覆盖 PI_SYSMON_WINDOW 换算出的点数) | | PI_SYSMON_CHART_HEIGHT | 6 | 每张图的绘图行数(不含上/下边框那 2 行) | | PI_SYSMON_LABEL | title | 读数位置:title(边框标题栏)/ box(右上角浮框)/ both / none | | PI_SYSMON_WINDOW | 60 | 横轴时间窗长度(秒) | | PI_SYSMON_SCALE_WINDOW | 1(= 量程窗 == 显示窗) | 速率图量程取样比例1 = y 轴顶端为整窗真实最高值;设 <1(如 1/6)开启「尖峰过去约 10s 后自动回落」(此时超出量程的尖峰会显示为 + 标记) | | PI_SYSMON_MODE | chart | 初始模式(chart / line / footer) | | PI_SYSMON_PLACEMENT | belowEditor | chart / line 挂在编辑器下方(默认)还是上方belowEditor / aboveEditor(也可用 /sysmon below / /sysmon above 随时切换,会持久化;footer 模式不受影响) | | PI_SYSMON_TOKENS | 开 | LLM token 吞吐图。设 0 回到旧的三图形态 | | PI_SYSMON_DISKS | — | 设 1 再加一块磁盘 I/O 图(第 5 块) |

开关有两个作用域,所以存两处:

  • <configDir>/pi-sysmon.jsonconfigDir 默认 ~/.pi/agent)存 modeplacement 以及全局默认值 enabled —— 由 /sysmon global on|off 写入;
  • 当前会话的开关是会话 entry,由 /sysmon on|off 写入, 因此 /resume 这个会话时能恢复,而又不会影响其它任何会话。

--sysmon 命令行开关是「本次启动强制打开」—— 它盖过全局默认,但不盖过会话里 手动输入的 /sysmon off(那是更具体的表达)。

配置兼容: 早期只带 enabled: falsepi-sysmon.json(会话级开关之前的形态) 会被读作「全局默认关」,即从未动过开关的会话默认关 —— 正好等价于旧版该字段表达的行为。 想要回到「默认开」,执行 /sysmon global on

发布内容包括什么

pi-sysmon 是一个 pi package:扩展入口写在 package.jsonpi 字段里, 运行时除 pi 本身外不需要任何第三方 dependencies(两个 pi 包是可选 peerDependencies, 由 pi 自己提供)。npm tarball 里包含 src/docs/、两份 README、changelog 与 license —— 也就是 pi install npm:pi-sysmon 拉取的内容。

实现说明

图表没有用任何 TUI 绘图库,而是自己合成字符。核心是 braille 点阵

每个 braille 字符(U+2800–U+28FF)对应 8 个可独立点亮的点,排成 2 列 × 4 行:

(0,0) (1,0)      bit0  bit3
(0,1) (1,1)  →   bit1  bit4
(0,2) (1,2)      bit2  bit5
(0,3) (1,3)      bit6  bit7

所以一个 width × height 的字符区域,实际分辨率是 2*width × 4*height。 把数据点线性映射到子像素坐标,再用 Bresenham 连成线,就能得到平滑的折线。

这与 bottom 的做法一致(ratatui 的 Marker::Braille)。我们对齐了 bottom 的这些参数:

| 项 | bottom | 本项目 | | --- | --- | --- | | 绘制字符 | Marker::Braille(2×4 子像素) | 同 | | 连线算法 | Bresenham | 同 | | 百分比图 y 轴 | 固定 0 .. 100.5 | 同 | | 动态图 y 轴 | 窗口内最大值 × 1.5(留白) | 同 | | 网格线 | 无,只有轴线 + y 刻度 + 两端时间标签 | 同 | | 默认采样间隔 | 1000 ms | 同 | | 每张图 | Block 边框,标题嵌在上边框里 | 同 | | y 刻度位置 | 独立列(占 5 列宽) | 叠印在绘图区内侧(不占列) | | y 刻度数量 | 百分比 2 个、速率 4 个 | 只标顶端 1 个(带单位) | | x 时间标签 | 单独占一行 | 嵌在下边框里(省 1 行) | | x 轴线 | 单独占一行 | 由 0 基线兼任(省 1 行) | | 右上角读数框 | 覆盖画在绘图区右上角,空间不够就整块消失 | 同(阈值算法不同,见下) | | 宽度不够时 | 把 N 张图硬挤到一行(50 列时每张 16 列) | 降级成 2 列 / 1 列 | | 横轴默认窗口 | 60 s | 同 | | 自动量程回落 | 滞后计数后降档(net_auto) | 默认不回落(顶端 = 整窗最高值);可选 PI_SYSMON_SCALE_WINDOW=1/6 开启 |

架构

src/
├── metrics.ts      # 采集层:读 /proc/{stat,meminfo,net/dev,diskstats} + os.loadavg
├── state.ts        # 决策层:会话级 vs 全局开关的优先级 + `/sysmon` 参数解析(纯函数)
├── braille.ts      # 渲染层:数据 → braille 点阵 → 字符行(纯函数,无副作用)
├── tokens.ts       # 估算层:LLM 流式增量 → token 数(纯函数 + 闭包 meter)
├── blocks.ts       # 组装层:历史 + 快照 → MetricBlock[](纯数据 → 纯数据)
├── chart-panel.ts  # 布局层:响应式列数、边框、刻度、浮动读数框、并排拼接
└── index.ts        # 扩展层:pi 生命周期、命令、配置持久化、定时刷新

依赖方向单向:index → {chart-panel, blocks, metrics}blocks → chart-panelchart-panel → brailleblocks → tokensbraille.tstokens.ts 不依赖任何其它本模块

分层原则:braille.ts纯函数(输入数值数组,输出字符串数组),因此可以脱离 pi 单独测试与复用;metrics.ts 只负责读数,不关心怎么显示。

与 bottom 的排版是怎么对齐的

不是靠肉眼调参数,而是读 bottom/ratatui 的源码把算法抄准,再用受控宽度的真实 btm 抓帧逐字符核对(┌ CPU ─ 1.91 1.80 2.17 ───┐ 那种)。 例如:

  • x 轴线不延伸到 y 轴那一列 —— ratatui 的 Chart::layout 在放下 y 轴后执行了 x += 1
  • 左下时间标签的末位落在 y 轴列上 —— labels_alignment = Left 时首个 x 标签的区域是 [chart_left, graph_left)(左含右不含)再右对齐;
  • y 刻度位置dy = i * (plotH - 1) / (n - 1)(索引 0 在底部)。

三处有意偏离

  1. 读数默认写在边框标题栏里PI_SYSMON_LABEL=title),而不是 bottom 的右上角浮框。 标题栏那一行本来就要写块名,剩下的 填充是纯装饰 —— 拿来放读数零成本, 且不遮任何曲线。bottom 的浮框是覆盖画在绘图区右上角的,会真的吃掉一块绘图区。 想换回 bottom 那种浮框就设 PI_SYSMON_LABEL=box

  2. 浮框显隐阈值(仅在 box/both 模式生效)。bottom 用 hidden_legend_constraints 这套按比例的阈值(Network 是 9/10 × 3/4),但它是按 40+ 列宽的图校准的, 套到本项目 20~30 列的块上会导致读数框永远不显示。 本项目改成「放得下(legendW <= plotW)且浮框下面还留得出一行曲线(legendH < rows)」—— 后者是为了避免浮框下边框与绘图区底部的 0% 基线叠成一条双横线。

  3. 读数是分级片段,块窄时从尾部逐段丢弃。比如 Network 的顺序是 瞬时速率 → 累计流量,窄块下先丢累计流量,保住更重要的瞬时速率。

测试

npm test          # 167 项单元测试(braille 13 + layout 74 + tokens 31 + state 26 + extension 23)
npm run typecheck # tsc strict
npm run check     # 两者都跑

test/braille.test.ts 覆盖 braille 位映射(对照 Unicode 标准逐点验证)、坐标映射、 输出尺寸、边界输入(空数据 / 全零 / 单点 / NaN / Infinity / 极小宽度)。

test/layout.test.ts 覆盖响应式布局(列数断点、列宽之和恒等于总宽、行数恒定) 以及两条会让 pi 崩掉/错位的硬约束:对 8..220 列全宽度 × 多个高度与块数组合, 断言每行可见宽度不越界、行数与布局声明一致 —— 这两条是扫全宽度而不是抽查几个宽度。

line 模式也在这里扫全宽度:plainLineSegs + renderStyledLine 在 8..220 列下 渲染宽度必须恰好等于声明宽度(多一列就 pi 退出),且窄到 1 列也不抛异常、 CPU 段永远保留。

test/tokens.test.ts 覆盖 token 估算(英文 chars/4、CJK 逐字、emoji 算一个、 非字符串防御)、每秒桶的排空语义(关闭期积压不得变成假尖峰), 以及 fmtTps / tokenAxis宽度上界(标题栏读数越界会让 pi 退出)。

test/state.test.ts 覆盖开关优先级链(会话选择 → --sysmon → 全局默认 → 内置默认开)、 会话 entry 的倒序扫描,以及 /sysmon 参数解析(globally 不能被当成 global 子命令; 未知输入必须被拒绝,而不是默默 toggle)。

test/extension.test.ts 用桩化的 pi API + 临时配置目录驱动真实的 index.ts, 把这次设计的核心作用域规则钉死:/sysmon off 不得碰配置文件; /sysmon global off 既写默认值、又立即作用于当前会话;新会话继承默认值、 而 /resume 恢复会话自己的选择;headless 下 /sysmon global off 仍然两者都落盘。

验证方法:真机抓帧才是唯一可信的

单元测试能捉住几何与坏值,但捉不住打包/加载类问题 —— 例如扩展在真实 pi 里根本没被加载、或 bundle 残留相对 import。 本项目用 pty 启动真实 pi、用 pyte 回放屏幕来验证, 这一步捉到过多次「单测全绿但真机不显示」的事故。

开发时踩过的坑

这些是实际踩到并修复的,记下来避免重犯:

  1. 宽度算错会让 pi 直接崩溃退出。 pi 的渲染器发现某行超过终端宽度会抛 uncaughtException 并退出。自定义组件必须用 truncateToWidth() 截断, 且不能用 String.slice()(它按字节算,会把 ANSI 转义也算进去)。
  2. 面板高度变化会挪动编辑器,破坏鼠标选区。 若组件在有/无数据时行数不同, 编辑器会上下位移,导致「选中文字后复制不了」。所以高度必须恒定, 无数据时也要占满同样的行数。
  3. 多文件平铺安装会让 pi 启动失败。 见上文安装说明。
  4. yMax <= 0 或数据含 NaN 会算出 NaN 坐标,非空断言 ! 会掩盖这个 问题并在运行时崩溃。所有坐标都要做有限性检查。
  5. 不同扩展的 setStatus 共享同一行,窄终端上会互相挤压截断。

贡献

欢迎 issue 和 PR。跑 npm run check 确保测试与类型检查通过。

也欢迎在 X 上找我:@zzjcoo

致谢

本项目受 bottombtm)启发。

pi-sysmon 最初的想法就是「把 btm 那种终端里的 braille 折线图搬进 pi」。 不只是视觉上的致敬 —— 本项目把 bottom 当作行为基准,去读它的源码、 用受控宽度抓它的帧,逐字符核对排版:

  • 盲文点阵(Marker::Braille,2×4 子像素)与 Bresenham 连线
  • 百分比图固定 0 .. 100.5、动态图取窗口最大值 × 1.5 的留白
  • y 刻度位置 dy = i * (plotH - 1) / (n - 1)(索引 0 在底部)
  • x 轴线不延伸到 y 轴那一列(ratatui Chart::layoutx += 1
  • 左下时间标签的末位落在 y 轴列上(labels_alignment = Left 的半开区间再右对齐)

也正因为它是个成熟工具,我们才看得出哪里不该照抄 —— 比如本项目把读数放进边框标题栏(bottom 是画在右上角浮框,会吃掉一块绘图区), 以及宽度不够时选择降级排列而不是把 N 张图硬挤成 16 列。 这些偏离都在上文「与 bottom 的排版是怎么对齐的」里逐条记了原因。

感谢 Clement Tsang 和 bottom 的贡献者们。

本项目的另一个前提是 pi 提供的扩展 API —— setWidgetplacementmessage_update 的流式事件、Theme 取色, 没有这些就没有这个扩展。

许可证

MIT —— 随便用。

本项目的灵感与排版算法参照来自 bottom(MIT 许可),但没有复制它的代码: 所有实现都是按它的可观察行为重写的。