[English](./README.md) ｜ 中文

# pi-compact-tui

一组面向日常使用的 [Pi](https://pi.dev/) 扩展：精简 TUI、补充状态信息，并提供命令审计、输入历史、会话回顾、subagent、联网搜索等能力。

![演示](./assets/demo.gif)

## 安装

```bash
pi install npm:pi-compact-tui
# 或
pi install git:github.com/saltfishpr/pi-compact-tui
```

所有插件的配置文件统一放在 `~/.pi/agent/extensions/` 目录下，首次使用时会自动生成默认配置，无需手动创建。

## 插件

### Bash 命令审计（`pi-bash-audit`）

拦截 Pi 执行的 bash 命令，分两级防护：

1. **规则匹配**：内置一份"只读命令白名单"（如 `ls`、`cat`、`git status` 等），命中的直接放行；也可以用规则强制放行、强制确认或交给模型审计。
2. **模型审计**：规则未放行的命令交给所选聊天模型或 classifier 判断风险，支持 [JEV](https://openrouter.ai/typesafe/jev-1.13) 等决策模型。高风险命令需要人工确认；使用 classifier 时，所选类别的概率低于配置阈值也需要确认。

![命令审计](./assets/audit.png)

**配置**：运行 `/audit` 选择聊天模型或 classifier 并启用审计。聊天模型还需选择推理档位，classifier 会跳过这一步。也可以直接编辑 `~/.pi/agent/extensions/bash-audit.json`：

```json
{
  "enable": true,
  "model": "anthropic/claude-sonnet-4-5",
  "thinkingLevel": "off",
  "timeoutMs": 10000,
  "minProbability": 0.9,
  "rules": [
    { "command": "pnpm", "args": ["install"], "action": "allow" },
    { "command": "rm", "args": ["-rf"], "action": "prompt" }
  ]
}
```

字段说明：

- `enable`：是否启用自动模型审计和自定义规则，默认为 `false`；通过 `/audit` 完成选择后自动启用。关闭时仅内置只读白名单自动放行，其余命令需要确认。
- `model`：审计模型，格式 `<provider>/<model-id>`，支持聊天模型和 classifier。未配置或模型不可用时，需要审计的命令改为人工确认。已有聊天模型配置继续有效。
- `thinkingLevel`：`off` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`，仅对聊天模型生效。
- `timeoutMs`：模型审计超时时间，单位为毫秒，默认为 `10000`。
- `minProbability`：classifier 判定为低、中风险时自动执行所需的最低概率，取值 `0` 到 `1`，默认为 `0.9`，不影响聊天模型审计。高风险始终需要确认。classifier 审计消息展示风险类别与概率，不生成解释。
- `rules`：自定义命令规则，默认为空数组，匹配方式见下文。

使用 JEV 时，先在 Pi 中配置 OpenRouter 认证或设置 `OPENROUTER_API_KEY`，然后在 `/audit` 中选择 `openrouter/typesafe/jev-1.13`，或将配置中的 `model` 改为该值。

**审计规则**

自定义规则按数组顺序检查，第一条匹配且未命中例外的规则生效；没有匹配规则时，使用内置只读命令白名单，其余命令交给模型审计。自定义规则优先于内置白名单。

每条规则包含：

- `command`：精确匹配命令名，例如 `pnpm`；不会自动将 `/usr/bin/pnpm` 视为 `pnpm`。
- `args`：按顺序匹配解析后的参数，不是匹配整段命令文本。省略或设为 `[]` 时匹配任意参数。
- `except`：可选的例外数组，格式为 `[{ "args": ["..."] }]`。任一例外匹配时，跳过本规则并继续检查后续规则，不代表直接拒绝执行。
- `action`：`allow`（放行）/ `prompt`（人工确认）/ `auto`（模型审计；没有可用模型时改为人工确认）。

`args` 和每个例外的 `args` 使用相同的参数模式：

| 模式                   | 匹配方式                                                                                                |
| ---------------------- | ------------------------------------------------------------------------------------------------------- |
| 字面量，如 `"install"` | 精确匹配一个参数；整个数组均为字面量时按前缀匹配，因此 `["install"]` 也匹配 `install --frozen-lockfile` |
| `"*"` 或 `"--*=*"`     | 匹配一个参数，`*` 可代表该参数内任意长度的文本                                                          |
| `"/正则表达式/"`       | 用正则表达式匹配一个完整参数，不支持尾部 flags                                                          |
| `"**"`                 | 匹配零个或多个参数                                                                                      |

只要数组包含通配符或正则表达式，就必须匹配完整参数列表；需要允许额外参数时，显式添加 `"**"`。例如 `["**", "-rf", "**"]` 匹配任意位置出现的独立参数 `-rf`，但不匹配 `-fr` 或其他等价写法。

下面的 `rules` 示例允许一般的 `pnpm install`，但将带 `--ignore-scripts` 的调用交给模型审计：

```json
[
  {
    "command": "pnpm",
    "args": ["install"],
    "except": [{ "args": ["install", "**", "--ignore-scripts", "**"] }],
    "action": "allow"
  },
  { "command": "pnpm", "args": ["install"], "action": "auto" },
  { "command": "rm", "args": ["**", "-rf", "**"], "action": "prompt" }
]
```

管道或组合命令会综合各部分的结果，优先级为 `prompt` > `auto` > `allow`。即使命中 `allow`，写入重定向、后台执行等额外 shell 行为仍可能使整段命令进入模型审计；动态命令或参数也不会直接按这些规则放行。

**审计范围**：此扩展仅审计主会话的 bash 工具调用，不覆盖 subagent 的 bash 工具调用，也不覆盖 subagent 配置中的 `!` 命令。`!` 命令由独立的命令授权控制，详见下方 Subagent 的“命令授权”说明。

### 清屏新会话（`pi-clear-command`）

提供 `/clear` 命令，开启全新会话并继承当前模型和推理档位。

### 精简布局（`pi-compact-layout`）

自定义输入框边框和底部状态栏（footer），把模型、用量、Git 分支等信息放在你需要的位置。支持多行 footer，以及输入框的右上、左下、右下三个位置；Pi 的活动状态嵌入左上边框，保留原生滚动提示、快捷键和边框颜色。

**配置**：编辑 `~/.pi/agent/extensions/compact-layout.json`，运行 `/reload` 生效。默认配置如下：

```json
{
  "separator": " ",
  "footer": [
    {
      "left": [
        "pwd",
        { "kind": "element", "value": "branch", "prefix": "(", "suffix": ")" },
        "sessionName"
      ],
      "right": [
        "status:codex-stats",
        "status:deepseek-stats",
        "status:opencode-go-stats",
        "status:zai-stats",
        "cacheHitRate",
        "cost",
        "context"
      ]
    },
    { "left": ["extensionStatuses"], "right": [] }
  ],
  "editor": {
    "topRight": [
      { "kind": "element", "value": "provider", "prefix": "(", "suffix": ")" },
      "model",
      { "kind": "element", "value": "thinkingLevel", "prefix": "• ", "suffix": "" }
    ],
    "bottomLeft": [],
    "bottomRight": []
  }
}
```

**布局字段**：

- `footer`：每个对象对应一行，`left` 和 `right` 数组分别靠左、靠右显示。设为 `[]` 隐藏 footer，没有内容的行自动隐藏。
- `editor`：`topRight`、`bottomLeft`、`bottomRight` 分别对应输入框右上、左下、右下。设为 `{}` 清空这三个位置，但不影响 Pi 的活动状态和滚动提示。
- `separator`：同一数组中非空元素之间的分隔文本，默认为一个空格。

数组顺序就是显示顺序，元素不自带圆点分隔符或装饰括号。可以在任意位置使用下表中的元素；移动元素即可更换位置，重复添加则重复显示。省略整个 `footer` 或 `editor` 字段时保留该部分默认布局；提供字段时替换整个布局，不与默认值合并，未填写的 `left`、`right` 或边框位置为空。空间不足时，内容会截断或隐藏；Pi 的活动状态和滚动提示优先显示。

**可用元素**：

| 元素                                  | 显示内容                                                                            |
| ------------------------------------- | ----------------------------------------------------------------------------------- |
| `pwd`、`branch`、`sessionName`        | 当前会话目录、Git 分支、会话名                                                      |
| `provider`、`model`、`thinkingLevel`  | 当前 provider、模型、推理档位（如 `high` 或 `off`）；推理档位仅对支持推理的模型显示 |
| `inputTokens`、`outputTokens`         | 会话累计输入、输出 token 数                                                         |
| `cacheReadTokens`、`cacheWriteTokens` | 会话累计缓存读取、写入 token 数                                                     |
| `cacheHitRate`                        | 最近一次模型响应的输入缓存命中率                                                    |
| `cost`                                | 会话累计费用，订阅模型带有 `(sub)` 标记                                             |
| `context`                             | 上下文占用比例和窗口大小，启用自动压缩时带有 `(auto)` 标记                          |
| `extensionStatuses`                   | 未单独指定位置的其他扩展状态                                                        |
| `status:<key>`                        | 指定扩展状态，例如 `status:codex-stats`；需要提供该状态的扩展已启用                 |

例如，将 `status:codex-stats` 从 footer 移到 `editor.bottomRight`，即可在输入框右下显示 Codex 用量。单独指定位置的状态不会再出现在 `extensionStatuses` 中，扩展提供的颜色保持不变。订阅用量和账户余额由本包的 `pi-provider-stats` 提供，没有对应状态时不显示。

需要装饰元素时，使用 `{ "kind": "element", "value": "branch", "prefix": "(", "suffix": ")" }`。`value` 可以使用上表中的任意元素，包括 `status:<key>`。`prefix`、`suffix` 均可省略，默认为空字符串，仅在元素有内容时显示。默认布局通过这种配置显示 `(branch)`、`(provider)` 和 `model • thinkingLevel`；直接使用字符串元素时不带装饰。

数组中也可以加入 `{ "kind": "literal", "value": "|" }` 显示自定义单行文本。`color` 是可选项，使用时填写 Pi 主题的前景色名称，例如 `dim`、`accent`、`warning`；未填写或名称无效时按 `dim` 显示。

### 输入历史（`pi-history`）

按 `Shift+↑` / `Shift+↓` 在输入框中翻阅历史输入，跨会话持久保存（保留最近 100 条）。用 `Ctrl+C` 清空输入框时，草稿也会被保存，可随时召回。

### 模型专属指令（`pi-model-instructions`）

按当前模型将对应 Markdown 文件追加到 system prompt，保留原有全局和项目 `AGENTS.md` 指令。运行 `/model-instructions` 可预览命中文件，并查看最近一次注入记录。

**配置**：编辑 `~/.pi/agent/extensions/model-instructions.json`：

```json
{
  "rules": [
    {
      "provider": "anthropic",
      "model": "claude-*",
      "file": "model-instructions/claude/AGENTS.md"
    },
    {
      "provider": "openai",
      "model": "gpt-*",
      "file": "model-instructions/gpt/AGENTS.md"
    }
  ]
}
```

- `provider` 和 `model` 匹配当前选择的 provider 与模型 ID，包括虚拟模型。匹配区分大小写，仅 `*` 表示通配符。`openai` 和 `openai-codex` 是不同的 provider。
- 按顺序选择第一条匹配规则，精确规则应放在前面。需要默认文件时，在末尾添加 `"provider": "*", "model": "*"` 的规则。默认配置没有规则。
- `file` 相对配置文件目录解析，因此第一个示例读取 `~/.pi/agent/extensions/model-instructions/claude/AGENTS.md`。也支持绝对路径和 `~/`。文件正文按纯文本读取，文件名可以是任意 Markdown 文件名。

切换模型、修改配置或文件后，从下一次 run 生效；一次 run 包含其工具调用及后续模型请求。未匹配、空文件或加载失败时，本轮不追加模型专属指令；加载错误会报告，旧文件内容不会复用。切换指令会保留对话历史及此前指令的影响，需要全新上下文时请开启新会话。本 extension 不作用于本包的 subagent 子会话。

### 会话回顾（`pi-recap`）

会话空闲时在输入框上方自动生成一句简短的回顾（当前在做什么、下一步是什么），提交新输入或恢复工作后自动清除；也可以随时运行 `/recap` 手动生成。

**配置**：编辑 `~/.pi/agent/extensions/recap.json`：

```json
{
  "model": "anthropic/claude-sonnet-4-5",
  "thinkingLevel": "off",
  "idle": "30s"
}
```

- `model` / `thinkingLevel`：生成回顾用的模型与推理档位，不填则使用当前会话模型。
- `idle`：空闲多久后触发回顾，支持数字（毫秒）或 `"30s"`、`"1m"` 等时长字符串，最小 5 秒。


### Subagent（`pi-subagent`）

为 Pi 提供 `agent` 工具，可把独立子任务委托给在隔离上下文中运行的子代理。内置 `explore`（探索）、`planner`（规划）和 `reviewer`（代码审查），你也可以添加自己的。

**添加子代理**：在 `~/.pi/agent/agents/`（全局）或 `.pi/agents/`（项目）下放置 Markdown 文件，文件名即子代理名：

```markdown
---
description: 审查代码变更，输出问题清单
model: anthropic/claude-sonnet-4-5
effort: low
maxTurns: 30
---

!git status --short

这里写子代理的系统提示词……
```

- `description`（必填）：告诉主模型何时使用该子代理。
- `model` / `effort` / `maxTurns`：可选，不填时继承当前会话模型；`maxTurns` 默认 50。
- `!command`：可选，写在代码块外、行首的 shell 命令。子代理启动前在当前项目目录执行，输出会提供给子代理。

**配置**：编辑 `~/.pi/agent/extensions/subagent.json`，可关闭功能、限制并发数，或按子代理名覆盖其 `model` / `effort` / `maxTurns`：

```json
{
  "enabled": true,
  "maxConcurrent": 4,
  "agents": {
    "explore": { "effort": "low" }
  }
}
```

**命令授权**：执行配置中的 `!` 命令前，可选择拒绝、仅本次允许或信任此版本。在 TUI 中运行 `/subagent-trust` 可搜索可用配置，用 Enter/Space 切换信任、Ctrl+A 全选、Ctrl+X 清空，最后用 Ctrl+S 一次保存全部修改，或用 Esc 放弃修改。命令自动维护 `subagent.json` 中的 `trustedProfiles`；信任绑定文件路径和内容哈希，适用于所有工作目录，文件变化后需要重新授权。当前列表未显示的其他项目配置不受影响。此授权仅针对 `!` 命令，不限制子代理后续使用工具。

### 使用提示（`pi-tips`）

每次新开会话时在顶部展示一条随机使用技巧（如快捷键、常用命令），支持多语言。

### Git 自动信任（`pi-trust-git`）

根据项目 Git `origin` 远程地址自动信任项目，跳过 Pi 的手动确认。

**配置**：编辑 `~/.pi/agent/extensions/trust.json`，命中任一名单即自动信任：

```json
{
  "domains": ["github.com"],
  "usernames": ["saltfishpr"]
}
```

- `domains`：信任的远程域名（如 `github.com`、`gitlab.com`）。
- `usernames`：信任的远程路径首段（如 `github.com/saltfishpr/xxx` 中的 `saltfishpr`）。

### 订阅额度与余额（`pi-provider-stats`）

在状态栏展示当前模型的 provider 用量：

- **openai-codex**：订阅限流窗口（5h / 7d 剩余额度和相对重置时间）。
- **deepseek**：账户余额。
- **opencode-go**：订阅的 5 小时 / 每周剩余额度和相对重置时间（`5h` / `7d`）。每月额度（`1mo`）默认隐藏，可设置 `showMonthly: true` 开启。使用 Pi 中已配置的 OpenCode Go API Key，布局状态键为 `status:opencode-go-stats`。
- **zai-coding-cn**（智谱）：账户余额。

**配置**：编辑 `~/.pi/agent/extensions/provider-stats.json`，按需关闭某个 provider、隐藏订阅重置时间或指定余额币种：

```json
{
  "providers": {
    "openai-codex": { "showResetTime": false },
    "opencode-go": { "showMonthly": false },
    "deepseek": { "enabled": true, "currency": "CNY" }
  }
}
```

未配置的 provider 默认启用；订阅重置时间默认仅在对应窗口的剩余额度低于 20% 时展示。

### 联网搜索（`pi-web-search`）

提供 `web_search` 工具，用于查询需要时效性或外部验证的信息。结果包含标题、URL 和摘要，方便引用来源。注册 search provider 后，工具自动启用。

**配置**：在 `~/.pi/agent/extensions/web-search.json` 中用 `provider` 指定默认 provider ID；`maxResults` 指定默认返回数量，范围 1～10，默认为 5。调用 `web_search` 时可覆盖这两个值；未配置默认 provider 时，调用必须指定 `provider`。

接入其他搜索服务的方法见[自定义 provider 开发指南](packages/pi-web-search/docs/PROVIDERS.zh.md)。

#### BigModel

内置 BigModel 联网搜索 provider。要启用它，在 `~/.pi/agent/extensions/web-search.json` 中填写 API Key：

```json
{
  "provider": "bigmodel",
  "maxResults": 5,
  "providers": {
    "bigmodel": {
      "apiKey": "your-api-key",
      "searchEngine": "search_std"
    }
  }
}
```

`searchEngine` 可选 `search_std`（默认）、`search_pro`、`search_pro_sogou` 或 `search_pro_quark`。

#### DeepSeek

内置 [DeepSeek 联网搜索](https://api-docs.deepseek.com/zh-cn/api/search/) provider，默认使用 Pi 中已配置的 DeepSeek 凭据。要启用它，在 `~/.pi/agent/extensions/web-search.json` 中配置：

```json
{
  "provider": "deepseek",
  "maxResults": 5
}
```

可通过 `providers.deepseek.apiKey` 显式指定 API Key，优先于 Pi 中的凭据。若要保留其他默认 provider，同时启用 DeepSeek，在 `providers` 中添加 `"deepseek": {}` 即可。

查询词须为 1～128 个 Unicode 字符，且不能全为空白；支持 `site:`、`-site:`、引号等搜索语法。结果优先使用长摘要，缺失时回退到短摘要。

**从 Brave 或 Tavily 迁移**：内置的 Brave、Tavily provider 已移除。原有的 `provider: "brave"` 或 `provider: "tavily"` 配置不会启用搜索。请改用 BigModel 或 DeepSeek，或按上面的开发指南注册自定义 provider。

## License

MIT
