Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pi-mobile

从手机控制本地 Mac 上的 pi coding agent —— 仿 codex-mobile。

浏览器即 UI(PWA,可加到主屏幕)。后端复用 pi 的 SDK(RpcClient + SessionManager), 所以会话、模型、思考等级全部和你终端里的 pi 完全一致、同一份会话文件

能做什么

  • 列出所有项目 / 会话(按项目分组,最近活跃在前)
  • 打开任意历史会话,查看完整对话(含 thinking / 工具调用 / 工具结果)
  • 发消息驱动 agent,流式显示回复
  • 运行中「停止」(abort)
  • 续聊直接写回原会话文件,和桌面端 pi 共享

架构

手机浏览器 (PWA)
   │  WebSocket (实时事件流) + HTTP (REST: 列表/历史)
   ▼
Mac 上的 pi-mobile server (Node, 复用 pi SDK)
   ├─ SessionManager.listAll()      列项目/会话
   ├─ SessionManager.open().buildSessionContext()  读历史
   ├─ RpcClient (每会话一个, 空闲回收)  prompt/steer/abort/set_model...
   │     └─ spawn `pi --mode rpc --session <file>`
   └─ token 鉴权 (defence-in-depth)
  • src/sessions.js — 只读:列会话、读历史并扁平化成前端友好结构
  • src/rpc-pool.js — 按会话路径管理 RpcClient 实例,多 viewer 共享,空闲超时回收
  • src/ws-bridge.js — WebSocket ↔ RpcClient 命令/事件桥接
  • src/server.js — HTTP(REST + 静态)+ WS 入口 + token + 加载/分发插件
  • src/plugins/ — 后端插件(index.js 加载器 + 各插件目录),见「插件」
  • web/ — 零构建原生前端(index.html / app.js / manifest)
  • web/plugins/ — 前端插件(loader.js + 各插件目录)

前置:pi agent

pi-mobile 不是独立的 agent,它是 pi 这个本地 coding agent 的远程外壳。 先把 pi 装好、能在终端正常跑起来,pi-mobile 才有东西可驱动。

1. 安装 pi

npm install -g @earendil-works/pi-coding-agent
pi --version          # 验证,应输出版本号(本项目基于 0.79.4)

2. 配置模型 / API key

pi 支持几十种 provider(Anthropic / OpenAI / Gemini / DeepSeek / Moonshot / …), 通过环境变量提供 key。任选其一即可,例如:

export ANTHROPIC_API_KEY=sk-ant-...        # Claude
#
export OPENAI_API_KEY=sk-...               # GPT

完整变量列表见 pi --help。也可以用 pi config 打开 TUI 启用/管理资源。

3. 终端里先跑一次

cd ~/your-project        # 进到要让 agent 工作的目录
pi                       # 交互模式,发个 "hi" 确认能正常对话

pi 的会话按工作目录存在 ~/.pi/agent/sessions/--项目路径--/<时间戳>_<uuid>.jsonl。 pi-mobile 读写的就是这些文件 —— 所以手机和终端是同一份会话,两边可无缝切换。

pi 常用操作(终端)

操作 命令
交互模式 pi
带初始 prompt pi "帮我重构这个函数"
指定模型 pi --model anthropic/claude-opus-4-7pi --model sonnet:high
继续上次会话 pi -c
选择历史会话恢复 pi -r
非交互、跑完即退 pi -p "总结这个仓库"
只读模式(禁文件修改) pi --no-tools-nt
列出可用模型 pi --list-models

这些在 pi-mobile 里的对应:选会话 = -r,发消息 = 交互,切模型的后端命令已就绪(前端 UI 待接)。

快速开始(局域网)

npm install
npm start

启动后会打印手机访问地址和二维码链接:

│  phone:  http://192.168.x.x:4180/?token=xxxxxxxx
└─ QR:     https://api.qrserver.com/.../?data=...   # 浏览器打开即可扫码

手机连同一个 WiFi,扫码或直接输地址即可。token 持久化在 ~/.pi-mobile/token,重启不变。

⚠️ 这是个能在你 Mac 上跑命令的 agent 的远程入口。token 不要外泄;公共 WiFi 下优先用 Tailscale(见下)。

远程访问(Tailscale,推荐)

出门也能连、全程加密,比裸 token 暴露在公网安全得多:

  1. Mac 装 Tailscale:brew install --cask tailscale,打开 App 登录你的账号。
  2. 手机装 Tailscale App,登录同一账号
  3. 拿到 Mac 的 tailnet IP:tailscale ip -4(形如 100.x.x.x)。
  4. 手机浏览器访问 http://100.x.x.x:4180/?token=...

server 本来就监听 0.0.0.0,无需改动。

开机自启(launchd)

bash deploy/install-launchd.sh

会自动填好路径、加载 agent,崩溃/登录自动拉起。

tail -f ~/.pi-mobile/server.log                 # 日志
cat ~/.pi-mobile/token                          # 当前 token
launchctl unload ~/Library/LaunchAgents/com.pi-mobile.server.plist   #

环境变量

变量 默认 说明
PI_MOBILE_PORT 4180 监听端口
PI_MOBILE_HOST 0.0.0.0 监听地址
PI_MOBILE_TOKEN 持久化生成 覆盖 token
PI_MOBILE_IDLE_MS 600000 会话空闲多久回收 RpcClient 子进程
PI_MOBILE_PLUGINS workflow 启用的插件,逗号分隔;设为空字符串则全部停用(见下「插件」)

插件

pi-mobile 的核心只做「列会话 / 看历史 / 驱动 agent」。其余功能(如工作流步骤条)都是 插件:自包含、可整目录删除、用环境变量开关,不改核心代码即可扩展。

启用 / 停用

PI_MOBILE_PLUGINS(逗号分隔,默认 workflow):

npm start                                  # 默认启用 workflow
PI_MOBILE_PLUGINS= npm start               # 全部停用
PI_MOBILE_PLUGINS=workflow,foo npm start   # 启用多个

启动日志会打印当前启用的插件;前端通过 GET /api/plugins 拿到清单后动态加载。

内置插件

插件 作用
workflow 顶部步骤条,可视化多 Agent 工作流进度(规划 › 规划评审 › 构建 › 代码评审 › 验证)。数据来自会话文件里 pi subagent 运行时持久化的 vstack-subagents:runtime-state 记录,前端每 ~3s 轮询刷新。

写一个新插件

一个插件 = 后端目录 src/plugins/<name>/ + 前端目录 web/plugins/<name>/,名字一致。

后端 src/plugins/<name>/server.js(default export):

export default {
  name: "<name>",                       // 必须与目录名一致
  // 注册 HTTP 路由:pathname -> async(ctx) => { status, body } | undefined
  routes: {
    "/api/<name>": async ({ url, isSessionPath }) => {
      const p = url.searchParams.get("path");
      if (!p || !isSessionPath(p)) return { status: 400, body: { error: "bad path" } };
      return { status: 200, body: { /* ... */ } };
    },
  },
  // 可选:就地装饰 /api/session 响应
  async decorateSession(session, sessionPath) {
    session.myField = await compute(sessionPath);
  },
};

ctx = { req, url, isSessionPath, send } 由核心注入;鉴权(token)已在分发前统一完成。

前端 web/plugins/<name>/client.js(default export):

export default {
  name: "<name>",
  init(host) { /* 一次性:注入 style、挂载 UI */ },
  onSessionOpen(sess, data) { /* data 是 /api/session 全量响应(含 decorateSession 注入的字段) */ },
  onSessionClose() { /* 切/关会话:清理 UI、停定时器 */ },
  onAgentEvent(ev) { /* 转发的 RpcClient 事件:turn_end / agent_end / ... */ },
};

host(核心提供给插件的稳定能力,传入 init):

方法 说明
host.el(tag, cls?, text?) 创建 DOM 元素
host.$(id) getElementById
host.api(path) 带 token 的 fetch → JSON
host.getCurrentSession() 当前会话 { path, cwd, name }null
host.slot(name) 命名挂载点容器,目前有 "belowHeader"(顶栏正下方)
host.showSheet(title, text) 复用底部弹层显示一段文字

隔离性:init 与各生命周期钩子都被 try/catch 包裹,单个插件报错只打 warning,不影响核心。 静态资源由核心统一托管 web/plugins/**,无需额外配置。

参考实现见 src/plugins/workflow/web/plugins/workflow/

验证脚本

node scripts/verify-sdk.mjs    # 阶段0: SDK 连通性(列会话 + 一次 prompt 往返)
node scripts/ws-test.mjs       # 阶段2: WS 往返(需先在 4181 起 server)

已知限制 / TODO

  • ✅ 切换模型 / 切思考等级:底部 meta 栏点 model / 🧠 pill 弹出选择器即可切换。
  • fork / 新建会话:后端命令已就绪(ws-bridge),前端 UI 尚未接入。
  • 工具批准(approval):当前依赖 pi 默认行为,未做手机端逐次批准。
  • 图片输入未接。

License

MIT © Ebispongebob — 见 LICENSE

About

Control your local pi coding agent from your phone — a codex-mobile-style web app reusing pi's SDK

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages