400-100-5265

预约演示

Kindle 变成 AI 桌宠屏

2026-06-23

这件事的起点其实特别简单:桌上有一台 Kindle Paperwhite,吃灰很久了。

扔了舍不得,继续放抽屉里又有点浪费。它屏幕不大,性能也不强,但 e-ink 常显、不刺眼、耗电低,这几个特点放在今天反而挺稀缺。

前阵子我刚折腾过 Codex 桌面宠物,让一个小宠物在电脑里显示 Agent 的运行状态。那次有个判断后来越想越成立:桌面宠物不是装饰品,它更像一个轻量状态层。于是很自然地冒出一个想法:既然 Codex 里可以养桌面宠物,那吃灰的 Kindle 能不能变成一块实体桌宠屏?

一开始真没想做什么严肃的监控系统,只是想把这台 Kindle 用起来,让它在桌面上有点存在感。后来才发现,Claude Code 和 Codex 的状态刚好很适合放上去:谁在跑,谁闲着,在哪个项目里,抬头看一眼就知道。

实拍:Kindle 上单独显示 Codex 当前状态和最近任务。

所以这篇不是“我为了效率发明了一个看板”的故事。

更准确地说,是我想让一台吃灰 Kindle 重新回到桌面上,顺手把它做成了 AI 桌宠的第二块屏。

一、先有一台吃灰 Kindle

Kindle 最尴尬的地方是:它还挺好,但你就是不怎么用。

看书有手机、iPad、微信读书;拿来当主力屏幕又太慢。它很难再成为一个中心设备。

但如果换个思路,不让它当中心,只让它承担一个很窄的任务:常显、低干扰、放在桌角。这个位置反而很适合 Kindle。

它不像第二屏那样不断吸引你点开消息,也不像手机那样一亮就把注意力拽走。它慢、黑白、刷新不频繁,天然适合展示那种“不急,但一直有用”的信息。

这和桌面宠物的思路其实是一脉相承的。

桌面宠物的价值不在于多一个可爱的东西,而是把 Agent 的状态从日志、终端、IDE 插件里拿出来,变成余光就能感知到的小反馈。Kindle 只是把这个状态层从电脑屏幕里拿出来,放到了一块真实的 e-ink 屏上。

很多旧设备不是没用了,只是它不该再承担主力设备的任务。给它一个足够窄的位置,它就能重新变得好用。

这个判断在旧手机、旧平板、树莓派小屏上也成立。关键是别指望它重新变成生产力中心,而是找一个足够克制的单点任务。

二、把状态搬到实体屏上

既然起点是“桌宠的延伸”,第一版设计就不想做成那种枯燥的监控面板:一堆数字、一堆进度条、再配几条日志。

Kindle 的纸质感屏幕,适合放点更轻、更有陪伴感的东西。

最后定下来的是:两只像素小宠物,一只代表 Claude,一只代表 Codex。

实拍:Claude 和 Codex 两只像素宠物,加上下面的系统状态。

它们头顶各有一个小气泡,显示当前状态,比如:

  • running
  • idle
  • sleeping
  • 当前项目名
  • 最近任务摘要

后来发现这俩工具的社区里本来就有很适合像素化的形象。我从社区项目 rullerzhou-afk/clawd-on-desk 里拿到了两套风格很接近的 GIF,再用 Python Pillow 自动转成 32x32 黑白像素图,直接喂给前端。

这一步比想象中重要。

如果只是写 Claude running / Codex idle,看两天就会腻。但换成两个小宠物之后,这块屏幕突然有了点桌面摆件的感觉。它不是强提醒,不制造压力,只是在那里安静地告诉你:谁还醒着,谁已经睡了。

这里有一个很小但很实际的工程判断:状态展示不是越详细越好。

如果一块屏幕放在桌角,用户不会认真阅读它。它更像路边的红绿灯,靠形状、位置、少量文字完成信息传递。信息密度太高,反而会把它从“环境感知”拖回“需要阅读的界面”。

我最后保留的状态层级大概是这样:

层级 内容 作用
第一层 宠物状态 一眼知道 Agent 是否活跃
第二层 项目名 / 最近任务 知道它在忙什么
第三层 Mac 系统状态 顺手看资源占用
放弃项 详细日志 / token / 长文本 太重,不适合 Kindle

这也是整个方案的基调:它不是控制台,也不是监控大屏,只是一块低干扰状态屏。

三、真正的坑在 Kindle 浏览器

我原本以为,现在的 Kindle 浏览器再差,也应该是个能用的现代浏览器。

真机一跑才发现,想多了。

它基本像 2014 年左右的 WebKit。JavaScript 很多时候跑不动,CSS Grid 不支持,连一些继承样式都不稳定。

给 Kindle 做前端,最重要的心法是:假装你回到了 2010 年。

SVG 很漂亮,但 Kindle 直接白屏

第一版宠物是用 SVG 一个个像素拼出来的,需要 JS 在客户端循环渲染。在 Mac 浏览器里看完美,推到 Kindle 上,一片空白。

这个问题并不复杂,但很典型:现代浏览器能跑,不代表老设备能跑。

最后的解法很朴素:服务端直接生成 PNG。

我用 Pillow 把 32x32 sprite 用 Image.NEAREST 放大到 256x256,Flask 通过 /pet/claude.png 直接返回图片字节。Kindle 只需要会 `` 标签就行。

示意代码大概是这样:

from io import BytesIO
from flask import Flask, send_file
from PIL import Image

app = Flask(__name__)

def render_pet_png(path: str, scale: int = 8):
    img = Image.open(path).convert("L")

    # 转成黑白,避免 Kindle 上灰阶表现不可控
    img = img.point(lambda p: 255 if p > 128 else 0)

    w, h = img.size
    img = img.resize((w * scale, h * scale), Image.NEAREST)

    buf = BytesIO()
    img.save(buf, format="PNG")
    buf.seek(0)
    return buf

@app.route("/pet/claude.png")
def claude_pet():
    return send_file(
        render_pet_png("assets/claude_32.png"),
        mimetype="image/png",
        max_age=0
    )

这类方案不优雅,但稳定。

很多时候,给弱设备做页面,最好的优化不是压缩 JS,也不是 polyfill,而是别让它执行复杂逻辑。能在服务端算掉的,就在服务端算掉。

CSS Grid 很现代,但 Kindle 不认

期望的布局是两只宠物左右并排。我一开始用了 display: grid,Mac 上看好端端的。Kindle 上,两只宠物纵向堆成一列,每只占满整行。

第一反应是退回 table 布局。

我知道,2020 年之后还用 table 做布局多少有点逆时代。但在 Kindle 浏览器上,table 一度是唯一能保证横排的方案。

这种场景里,技术洁癖没什么意义。目标设备不支持,规范再漂亮也没用。

table 能救命,也会制造新坑

修完前两个坑之后,又发现宠物卡片的右边和下面 SYSTEM 卡片的右边对不齐,差了 17 像素。

debug 半天才发现:Kindle 上 table 的宽度计算有点诡异,不完全按父容器内容区宽度算,而是更接近按外层 border-box 处理,相当于忽略了父容器的 padding。于是 table 比旁边的 div 宽出一截。

最后的解法是彻底放弃 table,改成:

  • 外层容器固定宽度
  • 子元素 float:left
  • 两个宠物卡片各占 50%
  • 全局使用 box-sizing:border-box
  • 中间共享一根边框,避免重复边框导致宽度误差

核心 CSS 大概这样:

* {
  box-sizing: border-box;
}

.container {
  width: 560px;
  margin: 0 auto;
  padding: 16px;
}

.pet-row {
  width: 100%;
  overflow: hidden; /* 清除 float */
  border: 2px solid #000;
}

.pet-card {
  float: left;
  width: 50%;
  height: 300px;
  text-align: center;
  padding: 12px;
}

.pet-card.left {
  border-right: 2px solid #000;
}

这事挺讽刺:你以为你在做一个 AI 状态看板,最后真正让你破防的是老浏览器布局。

越是跑在老设备上的东西,越不能相信“现代前端理所当然能用”。

四、状态数据不用 API

我没有调用 Claude Code 或 Codex 的 API,也没有从 CLI 里硬解析输出。

原因很简单:状态看板要稳定,依赖越少越好。

Claude Code 和 Codex 本来就会把会话写到本地 JSONL,我直接读这些文件。它们不一定是官方承诺的稳定接口,但相比模拟终端输出、hook CLI 进程、或者依赖一堆外部 API,本地文件扫描在这个场景下反而是更稳的选择。

大致链路是这样:

流程图 - Kindle 变成 AI 桌宠屏

这里的关键不是“技术先进”,而是链路短。

Kindle 只是展示层,它不负责复杂计算;后台服务只读本地文件,做轻量解析;前端尽量是静态 HTML PNG 图片。整个系统没有登录态,没有第三方服务,没有 WebSocket,也不需要 Kindle 跑复杂 JS。

示意代码可以很简单:

import json
import glob
from pathlib import Path
from datetime import datetime

def read_last_jsonl_line(pattern: str):
    files = sorted(glob.glob(pattern), key=lambda p: Path(p).stat().st_mtime, reverse=True)
    if not files:
        return None

    latest = files[0]

    last_line = None
    with open(latest, "r", encoding="utf-8") as f:
        for line in f:
            if line.strip():
                last_line = line

    if not last_line:
        return None

    try:
        return json.loads(last_line)
    except json.JSONDecodeError:
        return None

def detect_agent_status(pattern: str):
    event = read_last_jsonl_line(pattern)

    if not event:
        return {
            "status": "idle",
            "project": "-",
            "updated_at": "-"
        }

    ts = event.get("timestamp") or event.get("created_at")
    project = event.get("cwd") or event.get("project") or "-"

    return {
        "status": "running",
        "project": Path(project).name if project != "-" else "-",
        "updated_at": ts or datetime.now().strftime("%H:%M")
    }

实际实现里还要做一些兜底:

  • JSONL 文件不存在时显示 idle
  • 文件被写入中时避免解析半行
  • 路径字段不稳定时做多字段兼容
  • 最近更新时间太久时自动降级为 sleeping
  • 任务摘要过长时截断,避免 Kindle 页面撑开

我没做的是“Claude Code / Codex 使用额度”。

这个功能一开始其实很想放。Kindle 上能直接看到还剩多少额度,确实很实用。但折腾了一圈发现,Claude Code 和 Codex 的 CLI 目前都没有一个我能放心长期依赖的稳定接口。

Claude Code 里的 /usage 更像 CLI 内部能力,不适合作为外部服务长期依赖的公开接口;Codex 这边也没有看到足够稳定的额度 API。

所以 v1 只能放弃额度显示。

这个取舍有点可惜,但我觉得是对的。状态看板最怕变成另一个需要维护的系统。为了一个不稳定数字,引入 fragile parser、定时命令、异常重试,最后很可能比它带来的价值还重。

对这块屏幕来说,核心问题不是“我还能跑多久”,而是“它们现在有没有在跑”。

工具越轻,越适合常驻。

五、顺手塞进 Mac 状态

宠物显示完之后,Kindle 屏幕下半部还有一大块空白。索性把 Mac 的系统状态也放上去:

指标 读取方式
CPU psutil.cpu_percent()
内存 psutil.virtual_memory().percent
磁盘 (total - free) / total
温度 smctemp -c,主要用于 Apple Silicon
网络 psutil.net_io_counters() 两次采样差
开机时长 time.time() - psutil.boot_time()

这部分没有做得很复杂,因为 Kindle 的刷新频率也不适合做实时监控。

我最后采用的是定时刷新页面,而不是 WebSocket 或长连接。比如每 30 秒刷新一次:


很土,但在 Kindle 上很好用。

这也是一个典型的工程权衡:

方案 优点 问题 是否适合 Kindle
WebSocket 实时性好 老浏览器支持不稳,连接维护复杂 不适合
AJAX 轮询 局部刷新,体验好 依赖 JS,兼容性风险高 勉强
Meta Refresh 极其简单 整页刷新,不够优雅 适合
手动刷新 最省电 需要人操作 不适合常显

如果是现代浏览器,我大概率会用 SSE 或 WebSocket。但 Kindle 这个环境下,简单比优雅重要。

系统状态代码也没必要写成复杂监控 agent,一个 Python 函数就够:

import time
import shutil
import psutil
import subprocess

_last_net = None
_last_time = None

def get_temperature():
    try:
        output = subprocess.check_output(["smctemp", "-c"], timeout=1)
        return output.decode("utf-8").strip()
    except Exception:
        return "-"

def get_system_status():
    global _last_net, _last_time

    cpu = psutil.cpu_percent(interval=0.1)
    memory = psutil.virtual_memory().percent

    disk = shutil.disk_usage("/")
    disk_percent = round((disk.total - disk.free) / disk.total * 100, 1)

    now = time.time()
    net = psutil.net_io_counters()

    if _last_net and _last_time:
        seconds = max(now - _last_time, 1)
        up = (net.bytes_sent - _last_net.bytes_sent) / seconds
        down = (net.bytes_recv - _last_net.bytes_recv) / seconds
    else:
        up = down = 0

    _last_net = net
    _last_time = now

    uptime_seconds = int(time.time() - psutil.boot_time())

    return {
        "cpu": cpu,
        "memory": memory,
        "disk": disk_percent,
        "temperature": get_temperature(),
        "network_up": format_speed(up),
        "network_down": format_speed(down),
        "uptime": format_uptime(uptime_seconds),
    }

def format_speed(value):
    if value > 1024 * 1024:
        return f"{value / 1024 / 1024:.1f} MB/s"
    if value > 1024:
        return f"{value / 1024:.1f} KB/s"
    return f"{value:.0f} B/s"

def format_uptime(seconds):
    hours = seconds // 3600
    minutes = seconds % 3600 // 60
    return f"{hours}h {minutes}m"

这里的状态不是为了排障,而是为了“桌面余光感知”。

CPU 飙高、温度异常、网络在跑,一眼能看到就够了。真要排查问题,还是回到 Activity Monitor、htop、日志系统这些工具里。

别把小屏做成大屏,这是这类项目里很容易踩的坑。

六、架构越简单越能活

最后整个方案其实很朴素:

流程图 - Kindle 变成 AI 桌宠屏

技术栈没有什么花活:

  • Python
  • Flask
  • Pillow
  • psutil
  • 一点非常保守的 HTML/CSS
  • Kindle 自带浏览器

如果非要总结设计原则,我会把它压成几条:

  1. 让 Kindle 只负责展示
  2. 让服务端承担渲染和兼容性
  3. 前端不用现代能力,能不用 JS 就不用
  4. 状态来源尽量本地化
  5. 信息密度控制住,不要做成监控大屏

这类东西最怕一开始就奔着“平台化”去。

比如加用户系统、加插件机制、加主题市场、加远程同步、加移动端控制。听起来都挺合理,但会把一个本来半天能跑起来的小工具,变成一个需要长期维护的产品。

个人项目尤其要警惕这种膨胀。

很多工具不是死在难做,而是死在做太多。

七、旧设备复用的关键是降级预期

这次折腾 Kindle,最大的感受不是“电子墨水屏多适合 AI”,而是旧设备复用需要先降级预期。

Kindle 的屏幕很好,但浏览器很老;它适合常显,但不适合交互;它省电,但刷新慢;它有实体存在感,但显示能力很有限。

这些限制听起来像缺点,但如果任务选对了,反而会变成产品约束。

AI 工具现在越来越多,状态也越来越碎。Claude Code 在跑一个任务,Codex 在另一个项目里改代码,终端里还有别的命令。很多时候你不需要一个完整 dashboard,只需要知道:它们是不是还在工作。

把这个状态放在 Kindle 上,有点像给 AI Agent 一个实体化的呼吸灯。

它不抢注意力,也不要求你处理。你抬头看一眼,心里有数,然后继续干自己的事。

这可能就是这类小工具最舒服的地方:它没有试图改变工作流,只是在原来的工作流旁边补了一层很轻的反馈。

如果手上也有一台吃灰 Kindle,这个方向值得试试。但别一上来就做复杂系统。先让它显示一个状态、一张图、一个刷新中的小宠物。只要它重新回到桌面上,而不是躺在抽屉里,这个项目就已经赢了一半。

创作声明:本内容包含AI辅助创作,观点仅供参考。