内容
# plane-client
一个小型的、无依赖的Python客户端、命令行界面(CLI)和MCP服务器,用于[Plane](https://plane.so)项目管理API。
Plane是一个优秀的问题跟踪器,并且可以自行托管 —— 但其REST API有一些缺陷,对于使用临时`curl`或`requests`代码与其交互的人来说,这些缺陷会造成麻烦:JSON响应中的控制字符、每个令牌的速率限制、来自撇号的400错误、分页字段谎称是否有下一页、服务器端过滤器被静默忽略、评论按最新优先顺序到达,以及带有`200`状态码的身份验证代理HTML页面。`plane-client`将这些变通方法编码一次,以便您不必重新发现它们。
这里的所有内容都仅限于标准库 —— 没有`requests`,没有第三方HTTP堆栈,也不需要审计此存储库以外的任何内容。Python 3.9+。
## 安装
从源代码安装:
```bash
git clone <此存储库>
cd plane-client
pip install .
```
或者,对于您计划编辑的检出:
```bash
pip install -e .
```
运行时没有依赖项。(如果存在`pandoc`,则使用它进行Markdown→HTML转换,否则使用最小的内置转换器 —— 但它从未被要求。)
## 配置
客户端绑定到单个Plane项目,并从环境中读取其配置:
| 变量 | 必需 | 含义 |
| --- | --- | --- |
| `PLANE_BASE` | 是 | Plane实例URL,例如`https://plane.example.com` |
| `PLANE_WORKSPACE` | 是 | 工作区代号 |
| `PLANE_PROJECT` | 是 | 项目UUID |
| `PLANE_PAT` | 以下两者之一 | 个人访问令牌(值) |
| `PLANE_PAT_FILE` | 以下两者之一 | 包含令牌的文件路径 |
如果同时设置了`PLANE_PAT`和`PLANE_PAT_FILE`,则`PLANE_PAT`优先。`PLANE_PAT_FILE`是保持令牌不进入shell历史记录和进程环境的好方法 —— 将其指向一个`600`模式的文件即可。
```bash
export PLANE_BASE="https://plane.example.com"
export PLANE_WORKSPACE="您的工作区"
export PLANE_PROJECT="00000000-0000-0000-0000-000000000000"
export PLANE_PAT_FILE="$HOME/.config/plane/token"
```
## 快速入门
### Python
```python
from plane_client.core import PlaneClient
plane = PlaneClient.from_env()
issue = plane.get("PROJ-42") # 按标识符、裸数字或UUID
print(issue["name"], "->", plane.url_for(issue))
plane.comment("PROJ-42", "看起来不错 —— 立即发布。别@我。")
plane.set_state("PROJ-42", "进行中") # 状态按名称解析,无需UUID
for i in plane.list(state="待办"): # 客户端过滤,完全分页
print(i["sequence_id"], i["name"])
```
每个问题引用 —— `get`、`comment`、`set_state`等 —— 都接受以下三种格式之一,因此您可以传递您手头的任何内容。
### 命令行界面
该软件包安装了一个`plane`控制台命令。每个动词都映射到一个客户端方法,并且每个方法都支持 `--json` 以获取机器可读的输出。
```bash
plane get PROJ-42 --json
plane list --state "进行中" --json
# 评论正文是一个位置参数;使用 "-" 从stdin读取,
# 或 "@path" 从文件中读取。
plane comment PROJ-42 "干得好"
plane comment PROJ-42 - < notes.md
plane comment PROJ-42 @release-notes.md
plane comments PROJ-42 --json
plane set-state PROJ-42 "完成"
plane create --title "调查 flaky 测试" --body-file ./body.md
plane patch-comment PROJ-42 <comment-uuid> "原地编辑"
```
## MCP 服务器
该软件包附带一个[MCP](https://modelcontextprotocol.io)服务器(标准输入/输出上的标准JSON-RPC),为Claude Code和Claude Desktop等支持MCP的AI客户端提供类型化的工具:
| 工具 | 功能 |
| --- | --- |
| `plane_get` | 按引用获取单个问题 |
| `plane_list` | 列出问题,可选按状态名称过滤 |
| `plane_comment` | 发布评论 |
| `plane_list_comments` | 读取问题评论线程(最新优先) |
| `plane_latest_comment` | 获取问题的最新评论 |
| `plane_set_state` | 将问题移动到命名状态 |
| `plane_create` | 创建问题 |
直接运行:
```bash
python -m plane_client.mcp_server
```
### 注册到 Claude Code
在 `~/.claude.json` 的 `mcpServers` 块中添加一个条目。服务器读取与客户端相同的环境变量,通过 `env` 传递 —— 因此存储库中永远不会有秘密:
```json
{
"mcpServers": {
"plane": {
"type": "stdio",
"command": "python3",
"args": ["-m", "plane_client.mcp_server"],
"env": {
"PLANE_BASE": "https://plane.example.com",
"PLANE_WORKSPACE": "您的 工作区",
"PLANE_PROJECT": "00000000-0000-0000-0000-000000000000",
"PLANE_PAT_FILE": "/home/您/.config/plane/token"
}
}
}
}
```
## Plane API 的陷阱
这是值得阅读的部分。每个都是 Plane API 的真实行为,客户端都会吸收这些行为,这样您的代码就不必处理它们。
- **JSON响应中的控制字符。** Plane在`description_html` / `comment_html`字段中回显原始控制字符。`json.loads`拒绝该响应,即使写入本身成功。客户端在解析前会剥离这些控制字符 —— 并且关键是不会重试解析失败,因为写入已经发生,重试会重复应用它。
- **速率限制(429),带有重试/不重试区分。** Plane强制执行每个令牌每分钟的预算,并在超出时返回`429`。与控制字符的情况不同,`429`意味着请求*没有*落地,因此客户端会退回并重试。两种故障模式看起来很相似,但需要相反的处理;客户端都能正确处理。
- **撇号/引号 400错误。** 通过字符串插值组装的请求正文在包含撇号或引号时返回`400`。客户端使用`json.dumps`从头到尾构建每个有效载荷,因此任意文本 —— 缩写、引号字符串、代码片段 —— 都可以安全地通过网络传输。
- **谎称有最后一页的游标分页。** 列表端点通过游标分页,`next_cursor`字段*即使在最后一页*也存在。仅信任`next_cursor`会导致无限循环。真正的“是否有更多?”信号是`next_page_results`布尔值,客户端会检查它(如果未发送,则回落到裸`next` URL)。
- **服务器端过滤器被静默忽略。** 一些列表过滤器 —— 特别是按`state`过滤问题列表 —— 被服务器接受然后忽略,返回整个项目而不是您请求的子集。客户端不信任服务器,而是传递过滤器(以防Plane将来启用它)*和*在客户端对结果进行后过滤,因此`list(state="待办")`仅返回处于该状态的问题。
- **三种引用格式。** 问题可以通过其UUID、裸序列号(`42`)或项目标识符形式(`PROJ-42`)来寻址。所有三种格式都透明地解析。因为Plane还忽略服务器端的`sequence_id`过滤器,所以解析裸数字或`PROJ-N`引用意味着分页问题列表并在客户端匹配;客户端缓存解析结果,以便您只需支付一次费用。
- **评论按最新优先顺序返回。** Plane的问题`/comments/`端点按*最新优先*顺序返回评论。执行`list_comments(ref)[-1]`以获取“最新评论”(回执,状态心跳)的代码会默默地获取*最旧*的评论。改用`latest_comment(ref)`,它收集线程并返回具有最大`created_at`的评论,而不是信任服务器的(未记录的)排序;CLI的`plane comments`列表按旧到新排序,因此最新的是始终最后一行。
- **带有`200`的HTML页面在Plane前面。** 当Plane位于身份验证/反向代理(例如OAuth2代理)或短暂维护时,请求可以以HTML登录/维护页面返回,带有HTTP `200` —— 而不是您请求的JSON。将该页面视为“0结果”是危险的故障:轮询循环看到空列表并得出结论,没有工作。客户端改为将其显示为硬`PlaneParseError`(2xx正文,在剥离控制字符后不是JSON),CLI退出状态非零,因此代理/维护页面不会伪装成空成功响应。
## 示例:Plane上的协调循环
[`examples/open_engine/`](examples/open_engine/)是一个示例,展示了如何使用此客户端运行一个小型自主多代理“编码循环”,以**Plane作为协调基板** —— 一个自定义状态的状态机,一个标签作为自动化工作者的轮询门,评论回执作为审计跟踪,以及父字段用于依赖门控工作。这是说明性的;请将状态和ID适应您自己的项目。
## 与Plane无关
这是独立的、社区建设的客户端。它不是由Plane(makeplane / plane.so)生产、认可或支持的。“Plane”是其各自所有者的商标。
## 许可证
MIT。