# pios

**Local-first AI coding workspace for pi**  
**面向 pi coding agent 的本地优先 AI 编程工作台**

**English / 中文** · [简体中文文档](https://github.com/xing-shuyin/pios/blob/main/README.zh-CN.md)

[![npm version](https://img.shields.io/npm/v/pios?color=cb3837&logo=npm)](https://www.npmjs.com/package/pios)
[![Node.js](https://img.shields.io/node/v/pios?logo=node.js&logoColor=white)](https://nodejs.org/)
[![License](https://img.shields.io/github/license/xing-shuyin/pios)](LICENSE)

> **Turn pi into a complete browser-based coding cockpit.**  
> **让 pi 不只是聊天机器人，而是一个可观察、可操作、可持续运行的编程工作台。**

`pios` is a full-featured web interface for the [pi coding agent](https://pi.dev). It runs the pi SDK in the server process and connects your browser directly to the agent's live work: streaming answers, thinking blocks, tool calls, files, images, terminal sessions and Git changes.

它把 AI 编程中最重要的工作流放在同一个界面里：**对话、文件、终端、Git、模型配置和任务复核**。数据默认保留在本机，服务默认只监听 loopback，适合个人开发、远程工作站和长期运行的项目会话。

Requires **Node.js ≥ 22.19** and a configured pi environment.

## Why pios?

普通 AI chat UI 只能看到答案；`pios` 让你看到并控制完整的 coding loop：

- **Observe / 可观察** — Streaming responses, thinking blocks, tool calls and bash output with live status.
- **Act / 可操作** — Attach a file or selected lines, steer a running turn, use the terminal, and work with Git without leaving the browser.
- **Keep context / 保持上下文** — Multiple conversations can run per project; switch away and come back without losing the running task.
- **Verify / 可复核** — Set a goal and let an independent review session check the final response and `git diff`, then feed back actionable corrections.
- **Own your setup / 掌控环境** — Configure models, system prompts, skills, extensions and reusable presets from the UI; provider credentials stay on the server.
- **Run anywhere / 随处运行** — One command locally, or install as a macOS launchd agent, Linux systemd unit or Windows scheduled task.

## Core workflow

1. **Open a project / 打开项目** — Start `pios` in any workspace and use the browser as your coding cockpit.
2. **Ask and inspect / 对话并检查** — Follow the agent's reasoning, tool calls and file changes as they happen.
3. **Operate / 直接操作** — Preview files, select exact lines, attach images, run commands in the built-in terminal, and review Git status or diff.
4. **Guide / 持续引导** — Send a steer while a turn is running, edit and re-ask an earlier question, or keep several project conversations running in parallel.
5. **Review / 自动复核** — Lock a goal, review the result with a separate model, and iterate until the work passes or reaches the configured limit.

## Features

**Chat**

- Streaming agent chat over WebSocket — the pi SDK runs in-process; events are pushed as snapshots (60 ms throttled) and the browser renders them.
- Thinking blocks, tool-call cards and bash outputs with live status (running → finished · waiting for the model · duration).
- **补充 (steer)** — send a follow-up while the agent is replying; it is queued and injected as soon as the current turn's tool calls settle (the "Interrupt" equivalent of the pi CLI).
- **Slash commands** — `/` opens a command picker (built-in / extension / template / skill); built-ins include `/new /model /compact /cwd /thinking /resume`, plus `/help` (command list) and `/copy` (copy last reply).
- **Multiple conversations per project** — each conversation gets its own agent runtime and keeps running in the background after you switch away; the "Running conversations" list shows stream progress and lets you switch back.
- **Edit & re-ask** — fork any past question into a new branch and re-prompt; the original conversation stays untouched.
- Long threads auto-collapse messages older than 30 into lazy summary rows (click to expand).
- Question navigation — a floating rail plus per-question tags to jump between questions.

**Files, images & attachments**

- Three attachment modes: `inline` (≤12 KB), `reference` (path only), `lines` (selected ranges) — over-limit ones degrade automatically.
- Paste / drag-drop / upload images — resized client-side and sent as image content when the model supports vision (warning otherwise).
- **Vision bridge** — when the current model is text-only, images are transcribed into text evidence by an auto-discovered vision model (cached per batch; model & on/off configurable in Settings).
- Attach arbitrary files without a workspace path — stored in a global uploads dir, inlined when small, referenced by absolute path otherwise.
- File preview — line numbers, click/drag/Shift selection (add to chat as `lines`), GBK fallback decoding, binary hex view, media preview over HTTP with Range support, and a download button.
- Live file tree — the server watches the listed directory (fs.watch) and re-lists on change; oversized directories show a truncation warning.

**Terminal & Git**

- Built-in terminal (xterm.js + node-pty) with per-client PTY management; Windows auto-selects Git Bash (busybox fallback).
- **Source control (Git) panel** — status / branch / diff / untracked files via a hidden query terminal; commit, switch branch, push and pull run in the visible terminal and auto-switch to the terminal view.

**Models & settings**

- Theme switching — pick a theme in the top bar; each theme is a full standalone stylesheet (default white + a bundled dark). See [Themes](#themes) for how to add your own or contribute one.
- Model management — edit `models.json` in the UI and set per-provider API keys (keys/headers never leave the server).
- Thinking level per model (only the levels the model actually supports are shown).
- First-run setup wizard.
- Settings panel — system prompt (append or replace), toggle skills/extensions on/off with immediate effect, save/apply/delete settings presets, and vision-bridge model & switch.

**Goal mode**

- Goal bar — set a target with a review model, max rounds and a lock switch.
- Goal wizard ("AI 提炼") — turns a raw request into a concrete goal through a guided questionnaire.
- Automatic review loop — after each turn an independent review session checks the goal against the final text and `git diff HEAD`; on fail the feedback is injected as steer until it passes (or the round cap is hit).

**Background tasks**

- Background-task panel — servers launched by the agent are detected via port snapshots and listed (port/pid/name); stop one or kill all.
- Tool watchdog — a tool call running over 20 minutes is aborted automatically.
- **Stop bash command only** — abort a running bash tool without killing the conversation.

**Safety & operations**

- Loopback-only by default; set `PI_WEB_HOST=0.0.0.0` for LAN / containers.
- WebSocket Origin/Host same-authority check — cross-origin pages are rejected (403); `PI_WEB_ALLOW_ORIGINS` whitelist for reverse proxies.
- Quiesce drain mode via a local control socket (`server status|quiesce|unquiesce`).
- Credentials stay server-side — provider headers are never sent to the browser.
- Sound alerts, Chinese/English UI, and a recent-projects list (click to switch workspace).

**Deploy & update**

- Foreground, global npm install, macOS launchd, Linux systemd, Windows Task Scheduler, and a desktop shortcut (`server shortcut`).
- In-app self-update — checks the npm registry, installs and auto-restarts the service.

## Screenshots

![Settings panel](https://raw.githubusercontent.com/xing-shuyin/pios/main/assets/shot1.png)

![Built-in terminal](https://raw.githubusercontent.com/xing-shuyin/pios/main/assets/shot2.jpeg)

![Chat interface](https://raw.githubusercontent.com/xing-shuyin/pios/main/assets/shot3.jpeg)

![Git source control panel](https://raw.githubusercontent.com/xing-shuyin/pios/main/assets/shot4.jpeg)

## Install / 安装

Requires **Node.js 22.19+**.

```bash
npm install -g pios       # recommended / 推荐：全局安装
pios                      # start the workspace / 启动工作台
```

Prefer a one-off run? Use `npx pios` — suitable for trying the latest release without a global install.

**npm ≥ 12:** npm 12+ may block dependency install scripts by default. `node-pty` is a native module, so allow its script during installation:

```bash
npm install -g --allow-scripts=node-pty,@google/genai,protobufjs pios@latest
```

## Start / 启动

```bash
pios                                           # foreground, http://localhost:8787
PORT=9000 PI_WEB_CWD=/path/to/project pios     # custom port / workspace
pios --no-browser                              # headless server / 无需自动打开浏览器
```

On first launch, open the setup wizard to configure your pi environment and models.  
首次启动后，根据引导配置 pi 环境和模型即可开始使用。

## Stop

- **Foreground**: press `Ctrl+C` in the terminal running it.
- **As a service**: `pios server stop` (stops the instance; auto-start stays until `server uninstall`).

## Update

```bash
npm i -g pios@latest     # upgrade to the latest published version
pios server restart      # restart the service to apply it (foreground: restart manually)
```

## Uninstall

```bash
npm uninstall -g pios
```

Uninstalling does **not** delete your chats — session data lives in
`<cwd>/.pi-web` (or `PI_WEB_DATA_DIR`) and survives uninstall/upgrade.

## Run as a system service (auto-start on boot)

```bash
pios server install --port 9000 --cwd /path/to/project   # install + start
pios server status                     # running? auto-start?
pios server restart                    # restart (applies config/version changes)
pios server stop                       # stop (auto-start stays)
pios server start                      # start again
pios server uninstall                  # remove the service entirely
pios server shortcut                   # desktop one-click launch icon
pios server quiesce                    # drain: refuse NEW chats/messages, let running ones finish
pios server unquiesce                  # reopen admission
```

`server status` also shows live stats via a local control socket (version,
PID, quiesce state, connected browsers, running conversations) — the same
socket drives `quiesce`/`unquiesce`.

- **macOS** → launchd agent (no sudo), logs to `/tmp/pios.log` / `.err`
- **Linux** → systemd unit (`systemctl enable --now`), logs via `journalctl -u pios -f`
- **Windows** → Task Scheduler logon task (hidden PowerShell window, no black console)

Options: `--port` (default 8787), `--cwd` (workspace), `--data-dir` (sessions),
`--name` (custom service name). Rerunning `server install` with new options
regenerates the config and restarts the service — that's how you change its
port/cwd.

## Themes

Each theme is a **complete standalone stylesheet** — a self-contained CSS file (no CSS-variable extraction, no base file to include). Picking a theme swaps the whole file, so any theme works with every build.

Built-in themes ship in the npm package (`themes/`); the only bundled theme is the light `white.css`. The theme picker lives in the settings panel (⚙ → 外观 → 主题); the current choice is stored per browser in `localStorage`.

### Using a theme

Just pick it in the top bar — built-in and user themes are merged in the same menu. User themes win over built-ins on the same id.

### Providing a theme locally (no GitHub needed)

Any CSS file dropped into your **data-dir themes folder** shows up in the theme menu automatically — no restart, no rebuild:

1. Find your data dir (default `~/.pi-web`, override with `PI_WEB_DATA_DIR`).
2. Create `<dataDir>/themes/` and drop your stylesheet in: e.g. `~/.pi-web/themes/my-theme.css`.
3. Reload the page and pick it in the top bar. The **file name (without `.css`)** is the theme id shown in the menu.

```
~/.pi-web/
└── themes/
    └── my-theme.css          # appears in the menu as "my-theme"
```

Easiest way to write one: copy `themes/white.css` and change the `:root` colors plus any hardcoded values — the file must be **self-contained**. Notes:

- The **terminal follows the theme** — set the `--term-*` variables (terminal ANSI palette + `--term-bg`) in your `:root` and both the xterm canvas and its padded container adapt automatically (see the defaults in `themes/white.css`).
- Syntax-highlight colors (`highlight.js`'s `github-dark.css` is bundled) must be overridden in your theme file or code will be unreadable — see the `.hljs` overrides at the bottom of `themes/white.css` for the pattern.
- Theme ids must match `^[A-Za-z0-9_-]+$` (no dots/slashes — path-traversal guard on the server).

### Contributing a theme to the repository (GitHub)

Want your theme shipped to everyone? Open a pull request at [github.com/xing-shuyin/pios](https://github.com/xing-shuyin/pios):

1. Fork the repo and clone it.
2. Create your theme as `themes/<id>.css` — a **self-contained** stylesheet. Copy `themes/white.css` as the starting template.
3. Verify locally: run `npm run dev`, then use the settings panel theme picker — your theme must be listed and render correctly (chat cards, code blocks, tool-call cards, git/terminal panels).
4. Commit (`git add themes/<id>.css`) and open the PR. The `themes/` folder is already in the npm package `files` whitelist, so once merged and released, `npm i -g pios` will ship your theme to everyone.

Rules for merged themes: the file must be a single self-contained CSS file, be a full standalone theme (no base file to import), set the `--term-*` variables for a readable terminal, and override `.hljs` syntax colors for readable code.

## Security

- **Loopback-only by default** — the server binds `127.0.0.1` and is not
  reachable from the network unless you explicitly set `PI_WEB_HOST=0.0.0.0`
  (e.g. LAN access when explicitly configured).
- **WebSocket origin check** — browser pages connecting to `/ws` must present
  an `Origin` whose hostname **and port** match the request `Host`;
  cross-origin pages are rejected with 403. Non-browser clients (no `Origin`)
  are unaffected. Add `PI_WEB_ALLOW_ORIGINS=http://your-host:port` for
  reverse-proxy setups.
- **Quiesce** — `server quiesce` refuses new prompts/forks/session resumes
  until you `server unquiesce`; in-flight runs finish cleanly (useful before
  upgrades/backups).
- **Credentials stay server-side** — provider `headers` (which may carry
  `Authorization` / API keys) are never sent to the browser; the model
  management UI edits everything else and the server preserves the headers.

## Reverse proxy (nginx)

Serve pios behind nginx on the same host (it binds loopback only, so a
same-machine reverse proxy is the supported remote-access path — no
`PI_WEB_HOST=0.0.0.0` needed):

```nginx
# pios on 127.0.0.1:8787, exposed as https://your-host/pi/
server {
    listen 443 ssl;
    server_name your-host;
    # ssl_certificate ... / ssl_certificate_key ...

    # App entry at a sub-path (strips the /pi/ prefix)
    location /pi/ {
        proxy_pass http://127.0.0.1:8787/;
        proxy_http_version 1.1;
        # $http_host keeps the port — the server's origin check compares the
        # full authority (hostname AND port). $host would drop it and get 403.
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # WebSocket — MUST forward Host identically or the upgrade is 403'd
    # (page loads, but chat/terminal keep reconnecting)
    location /ws {
        proxy_pass http://127.0.0.1:8787;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # Absolute-path assets/API the built frontend requests (root, not /pi/)
    location /assets/  { proxy_pass http://127.0.0.1:8787; }
    location = /favicon.svg           { proxy_pass http://127.0.0.1:8787; }
    location = /favicon-streaming.svg { proxy_pass http://127.0.0.1:8787; }
    location = /api/file   { proxy_pass http://127.0.0.1:8787; }
    location = /api/health { proxy_pass http://127.0.0.1:8787; }
}
```

Key points:

- **`Host` must be `$http_host`** (keeps the port) on both `/pi/` and `/ws` —
  the origin check compares hostname **and** port. `proxy_set_header Host $host`
  or leaving it unset (defaults to the upstream `127.0.0.1:8787`) both fail with 403.
- **Same-origin works automatically**: as long as the browser's `Origin` equals
  the forwarded `Host` (it does through a plain proxy), no
  `PI_WEB_ALLOW_ORIGINS` is needed. Only set it when the browser origin differs
  from the Host the server sees (e.g. a TLS-terminating proxy that changes the
  port).
- **No `proxy_protocol` unless you really need real client IPs**: it makes
  nginx reject every connection that does not send a PROXY header, which
  breaks direct LAN access and any non-frp clients. With frp, drop
  `transport.proxyProtocolVersion` from the proxy config unless nginx listens
  with `proxy_protocol` too.
- **LAN access without a proxy**: just set `PI_WEB_HOST=0.0.0.0` (and a
  firewall rule) — or put the whole server block above on port 80/443.

Full working example (with an frp tunnel): `deploy/nginx-pios.conf`.

## License

MIT

