文档 / 本地 API 参考

本地 API 参考

面向 AI Agent 与本地自动化的 HTTP REST API 规范。

概述

Metrix.Bar 在本地回环地址(127.0.0.1:6387)上运行轻量 HTTP REST 服务。该服务专为 AI 编程助手、命令行脚本及自动化工具设计,用于查询、校验、更新与管理 .bar 看板。

  • 服务地址:http://127.0.0.1:6387
  • 鉴权机制:通过请求头 Authorization: Bearer <token> 进行 Bearer Token 验证(GET /v0/health 免鉴权)
  • 完整契约:OpenAPI 规范文件可在 /openapi.json 获取(当前为 v0 版本规范)

本文中的端口 6387 仅为示例,请使用 App 的 Agent 设置中实际显示的地址和端口。

安全防护与状态码

本地 API 针对未授权本地网络与恶意网页跨站请求内置了安全防护:

  1. 严格回环与 Host 头校验:服务仅绑定 127.0.0.1,同时校验 Host 请求头,只接受 127.0.0.1、localhost 或本机回环 IP(含端口)。非回环 Host 直接返回 403 Forbidden。
  2. 阻止浏览器跨站:携带 Origin 头的请求一律拒绝(含 Origin: null,返回 403 Forbidden),不启用 CORS,防御 DNS Rebinding 与浏览器跨站调用。
  3. Bearer Token 校验:除 /v0/health 外,所有请求必须携带在 Settings → Agent 中配置的密钥;缺失或无效 Token 返回 401 Unauthorized。
  4. 请求体上限:单次请求体上限为 2 MiB。
  5. 密钥隔离:系统安全存储(keychain)中的密钥明文绝不回显;输入列表仅报告 is_set: true | false,value 恒为 null。

状态码说明

状态码含义说明
200 OK请求成功返回请求的资源或操作结果。
201 Created创建成功看板创建成功,响应头包含新源码的 ETag。
202 Accepted已接受异步任务已接受并派发(如刷新调度、事件派发),不等待执行结果。
204 No Content处理成功请求已完成,无响应体(如更新 inputs、写入 secret、删除看板)。
400 Bad Request请求错误请求体格式错误、缺少必填字段或传入未声明的 input 参数。
401 Unauthorized未授权缺少或无效 Bearer Token;带 WWW-Authenticate: Bearer realm="Metrix.Bar"。scheme 大小写不敏感,token 精确匹配。
413 Payload Too Large请求体过大请求体超过 2 MiB。
415 Unsupported Media Type不支持的媒体类型源码端点缺失、无效或不支持的 Content-Type。
403 Forbidden禁止访问请求携带 Origin 请求头,或 Host 头部非本机回环地址。
404 Not Found未找到指定的看板不存在,或目标 secret 参数未在 .bar 中声明。
412 Precondition Failed前置条件失败If-Match 乐观并发校验未通过,源码 ETag 不匹配,请重新 GET source 后重试。
422 Unprocessable Entity无法处理的实体.bar 源码解析失败,返回结构化诊断信息 diagnostics。
501 Not Implemented未实现宿主环境未接入对应的应用层能力。

端点列表

所有路由均挂载在 /v0 前缀下:

方法路径描述
GET/v0/health免鉴权健康检查,返回运行状态、运行毫秒数与协议版本。
POST/v0/validate校验 .bar 源码格式与语法,不落盘。
POST/v0/check隔离运行 Worker 并校验视图树,不安装看板。
GET/v0/types返回供编辑器与 Agent 使用的 Worker TypeScript 声明。
GET/v0/dashboards获取所有已安装看板的列表、状态与 ETag。
POST/v0/dashboards根据传入的 .bar 源码创建新看板。
GET/v0/dashboards/:id获取指定看板的运行状态快照与 UI 视图树。
DELETE/v0/dashboards/:id卸载看板并清理其关联的密钥。
GET/v0/dashboards/:id/source获取 .bar 源码文本,并在响应头返回 ETag。
PUT/v0/dashboards/:id/source替换 .bar 源码文本(支持 If-Match 并发校验)。
PATCH/v0/dashboards/:id/settings更新入口、默认与按来源打开方式覆盖(placements、open_mode、open_modes)。
GET/v0/dashboards/:id/inputs获取输入参数声明、当前值及密钥配置状态。
POST/v0/dashboards/:id/inputs更新看板的普通输入参数值。
PUT/v0/dashboards/:id/secrets/:name向系统安全存储写入敏感密钥。
POST/v0/dashboards/:id/refresh触发一次立即刷新周期。
POST/v0/dashboards/:id/reload重读磁盘源码并预检 Worker;预检失败保留旧实例。
POST/v0/dashboards/:id/events/:name向看板 Worker 派发自定义事件。

接口详解

健康检查

GET /v0/health

检查本地服务是否正常启动,无需携带鉴权 Token。返回当前进程运行毫秒数与协议版本号。

bash
curl -fsS http://127.0.0.1:6387/v0/health

响应示例 (200 OK):

json
{
  "status": "ok",
  "uptime_ms": 9900000,
  "protocol_version": "0",
  "app_version": "0.1.0"
}

app_version 为可选字段,仅在应用注入版本号时返回。


校验 .bar 源码

POST /v0/check 仅接受 application/json(可带 charset),请求为 { "source": "...", "inputs": {}, "scheduled": false, "tree": false },仅 source 必填。在隔离 Worker 中执行 render;仅显式传入 scheduled: true 才先执行计划任务。scheduled 与 tree 均默认为 false,tree: true 时返回视图树。预检返回诊断但不安装,网络、存储等外部副作用仍可能发生。GET /v0/types 返回文本格式的 metrixbar.d.ts,供编辑器补全。

外部工具已将源码保存到磁盘时,可携带 Bearer token、无请求体调用 POST /v0/dashboards/:id/reload,预检失败保留旧运行实例。

隔离 fixture 与 PNG 截图

POST /v0/check 可选 fixture 与 screenshot,树和截图来自同一次 Worker render。fixture 提供人工测试数据,整个 fixture JSON 最大 2 MiB,拒绝未知字段;now 必填且为 RFC 3339 时间,冻结 UTC 时钟。输入按声明默认值 → fixture.inputs → 请求 inputs 合并,并保持类型与 secret 输入校验。

json
{
  "source": "<bar ...>...</bar>",
  "scheduled": true,
  "tree": true,
  "fixture": {
    "now": "2026-06-01T12:00:00Z",
    "inputs": {},
    "secrets": { "token": "fixture-token" },
    "files": { "/fixture/value": "42" },
    "icons": {
      "lucide:zap": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"m13 2-3 8H4l7 12 3-8h6Z\"/></svg>"
    },
    "http": [{ "url": "https://metrics.example.com/value", "body": "42" }]
  },
  "screenshot": { "theme": "dark", "width": 320, "height": 240, "scale": 2 }
}

http 精确匹配 method(默认 GET)、完整 URL 及可选 request_body,status 默认 200,body 是原始响应字符串。远程模块与 Image/Avatar 资源也使用 fixture HTTP;资源在原 Worker 关闭前通过相同权限与身份请求头加载。files 按绝对虚拟路径匹配,仍检查声明权限;secrets 只读取 fixture 值,不读系统密钥。命名 Icon(或无 src/svg 的 Image.name)必须在 icons 中提供 SVG 或图片 data URI,每项 1..256 KiB;缺失图标报错,不读 Assets、磁盘缓存或真实 Iconify 网络。内联 SVG 与图片 data URI 保持可用。fixture 模式禁用 socket;未匹配 HTTP、文件、secret 或图标必定产生诊断,即使脚本捕获错误也不能通过。fixture 不得含真实凭据。未传 fixture 时仍可能执行声明权限允许的真实 I/O。

省略 screenshot 时不生成 PNG。提供时 theme 必须为 light 或 dark;width/height 为 DIP,省略使用 panel 尺寸(无声明时 320×240),每边 1..2048;scale 默认 1、范围 0.5..4,最终像素总量不超过 16 Mi。成功响应包含 screenshot: {mime_type:"image/png", width:640, height:480, data_base64:"..."},宽高为实际 PNG 像素。使用内存绘图并等待资源,不截取桌面;资源收集失败返回 resources(未匹配 fixture 还返回 fixture),绘图失败返回 screenshot 诊断,HTTP 200、valid:false,无截图。参数错误返回 400。loopback、Bearer、Origin 和 secret 隔离规则继续生效。

POST /v0/validate

验证 .bar 源码语法与规范合法性,不写入系统。解析器采用单错误快速失败机制,diagnostics 最多包含一条诊断信息。

bash
curl -fsS -X POST http://127.0.0.1:6387/v0/validate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/plain" \
  --data-binary @dashboard.bar

校验通过示例 (200 OK):

json
{
  "valid": true,
  "diagnostics": []
}

校验失败示例 (200 OK):

json
{
  "valid": false,
  "diagnostics": [
    {
      "severity": "error",
      "code": "inputs/input@type",
      "message": "unknown input type \"color\"",
      "path": "inputs/input@type"
    }
  ]
}

看板管理

GET /v0/dashboards

列出当前所有看板概要。状态(status)枚举值为 idle、loading、ok 或 error。若看板定义中包含可选元数据(version、icon、description、license、homepage、docs),非空时将一并返回。

bash
curl -fsS -H "Authorization: Bearer $TOKEN" http://127.0.0.1:6387/v0/dashboards

响应示例 (200 OK):

json
{
  "dashboards": [
    {
      "id": "github-repo",
      "title": "GitHub Repo",
      "status": "ok",
      "protocol_version": "0",
      "revision": 3,
      "etag": "0123456789abcdef",
      "version": "1.0.0",
      "description": "Track GitHub repository stars, forks, and activity."
    }
  ]
}

POST /v0/dashboards

根据传入的 .bar 源码创建并安装新看板。请求体支持 text/plain、application/octet-stream 或 JSON {"source": "..."}。返回创建后的看板摘要(包含 protocol_version 及 <bar> 中声明的非空可选元数据)。

bash
curl -fsS -X POST http://127.0.0.1:6387/v0/dashboards \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/plain" \
  --data-binary @dashboard.bar

响应示例 (201 Created): 响应头携带 ETag: "0123456789abcdef"。

json
{
  "id": "github-repo",
  "status": "loading",
  "title": "GitHub Repo",
  "protocol_version": "0",
  "etag": "0123456789abcdef"
}

若源码解析失败,返回 422 Unprocessable Entity 并携带 diagnostics。

GET /v0/dashboards/:id

获取看板快照与最近一次成功渲染的 UI 视图树。在 API 返回的视图树中,所有事件处理函数(如 onClick)统一输出为 null。

bash
curl -fsS -H "Authorization: Bearer $TOKEN" http://127.0.0.1:6387/v0/dashboards/github-repo

响应示例 (200 OK):

json
{
  "id": "github-repo",
  "status": "ok",
  "updated_at": "2025-01-01T00:00:00Z",
  "revision": 3,
  "inputs": {
    "repository": "golang/go"
  },
  "docs": "配置 GitHub 仓库路径即可开始监控。",
  "view": {
    "tray": {
      "title": "★ 1.2k"
    },
    "panel": {
      "type": "Column",
      "props": { "padding": 16, "gap": 12 },
      "children": [{ "type": "Text", "props": { "text": "egoist/mygo", "bold": true } }]
    }
  }
}

当 status 为 error 时,响应中会包含 error 字段记录最近一次运行错误;docs 为来自 <docs> 的纯文本配置说明(为空时不返回);view 保留上一次成功渲染的视图树(若尚未成功渲染过则为 null)。

GET /v0/dashboards/:id/source

获取原始 .bar 源码文本。响应头包含 RFC 9110 引号格式的 ETag(源码 SHA-256 前 16 位十六进制字符)。

bash
curl -fsS -H "Authorization: Bearer $TOKEN" -i http://127.0.0.1:6387/v0/dashboards/github-repo/source

PUT /v0/dashboards/:id/source

全量替换 .bar 源码。请求体支持 text/plain、application/octet-stream 或 JSON {"source": "..."}。新源码的 id 必须与路径中的 :id 一致。

支持通过 If-Match 进行乐观并发控制:传入 GET source 返回的带引号 ETag、逗号分隔的带引号标签列表,或单独的 * 匹配任意已存在看板。采用强比较,弱标签始终不匹配;裸标签和畸形列表返回 400。只有缺失该头才跳过标签检查,仍保护并发文件编辑。

bash
curl -fsS -X PUT http://127.0.0.1:6387/v0/dashboards/github-repo/source \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/plain" \
  -H 'If-Match: "0123456789abcdef"' \
  --data-binary @updated.bar

更新成功响应 (200 OK): 响应头返回新源码的 ETag。

json
{
  "id": "github-repo",
  "updated": true
}

ETag 不匹配响应 (412 Precondition Failed):

json
{
  "error": "etag mismatch"
}

源码解析失败响应 (422 Unprocessable Entity):

json
{
  "error": "validation failed",
  "diagnostics": [
    {
      "severity": "error",
      "code": "bar@id",
      "message": "source id \"other-repo\" does not match dashboard \"github-repo\"",
      "path": "bar@id"
    }
  ]
}

DELETE /v0/dashboards/:id

卸载指定看板,停止运行实例并清理其保存在系统安全存储中的密钥。.bar 源码文件仍保留在磁盘上。

bash
curl -fsS -X DELETE -H "Authorization: Bearer $TOKEN" http://127.0.0.1:6387/v0/dashboards/github-repo

响应示例 (204 No Content):无响应体。


配置与密钥

GET /v0/dashboards/:id/inputs

获取看板所有输入参数的声明与当前值。仅 secret 类型的 value 恒为 null,通过 is_set: true | false 反映配置状态;file 类型通过此鉴权接口正常返回本地文件路径字符串。

bash
curl -fsS -H "Authorization: Bearer $TOKEN" http://127.0.0.1:6387/v0/dashboards/github-repo/inputs

响应示例 (200 OK):

json
{
  "inputs": [
    {
      "name": "repository",
      "type": "string",
      "required": true,
      "label": "Repository",
      "help": "owner/repo",
      "value": "egoist/mygo",
      "is_set": true
    },
    {
      "name": "token",
      "type": "secret",
      "required": false,
      "label": "GitHub Token",
      "value": null,
      "is_set": true
    }
  ]
}

POST /v0/dashboards/:id/inputs

更新普通输入参数。请求体必须为 {"inputs": {...}} 包装对象。更新后会触发异步刷新。

输入值类型必须与声明匹配(string/file 为字符串,bool 为布尔值,integer 为整数,number 为有限数值)。不允许在此更新 secret 类型参数(须走专门的 secrets 接口),传入未声明的参数将返回 400 Bad Request。

bash
curl -fsS -X POST http://127.0.0.1:6387/v0/dashboards/github-repo/inputs \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inputs": {"repository": "golang/go"}}'

响应示例 (204 No Content):无响应体。

PUT /v0/dashboards/:id/secrets/:name

将敏感密钥写入系统安全存储(keychain)并触发看板刷新。目标参数必须在 .bar 中声明为 type="secret",否则返回 404 Not Found。密钥不进入普通看板记录、快照或 API 响应;看板脚本可读取凭据用于认证。

请求体为 JSON 格式 {"value": "..."}:

bash
curl -fsS -X PUT http://127.0.0.1:6387/v0/dashboards/github-repo/secrets/token \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "ghp_xxxxxxxxxxxx"}'

响应示例 (204 No Content):无响应体。


事件与控制

POST /v0/dashboards/:id/refresh

向调度器派发刷新命令,触发看板 Worker 重新拉取数据。返回 202 Accepted 表示已接受请求,不等待异步渲染完成。

bash
curl -fsS -X POST -H "Authorization: Bearer $TOKEN" http://127.0.0.1:6387/v0/dashboards/github-repo/refresh

响应示例 (202 Accepted):无响应体。

POST /v0/dashboards/:id/events/:name

向看板 Worker 的 event(name, payload) 导出函数派发自定义事件。请求体为任意 JSON 值(可为空,空时 payload 为 null)。事件处理完成后会自动提交重新渲染。

bash
curl -fsS -X POST http://127.0.0.1:6387/v0/dashboards/github-repo/events/notify \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"action": "toggle-mode"}'

响应示例 (202 Accepted):无响应体。

PATCH /v0/dashboards/:id/settings

请求体接受 placements、open_mode 和 open_modes,至少提供一个字段:

  • placements:menubar、sidebar-left、sidebar-right、sidebar-bottom 的任意无重复数组,或 null。
  • open_mode:默认打开方式覆盖,值为 flyout、dialog 或 null。
  • open_modes:按来源配置的打开方式对象,或 null:
    • 省略(missing):保持现有的按来源例外覆盖不变;
    • null 或 {}:清除全部 5 个来源的例外覆盖,所有来源恢复跟随默认打开方式;
    • 传入对象时整份替换所有来源的例外覆盖(非增量 merge)。支持的 key 仅限 menubar、sidebar-left、sidebar-right、sidebar-bottom、shortcut;value 仅限 flyout、dialog。未知 key、非法值或错误类型返回 400。

省略字段保持原设置,null 清除对应字段的用户覆盖并恢复作者默认值,placements: [] 禁用全部视觉入口。各项覆盖独立保存,官方和第三方看板使用同一契约。

未知字段、错误类型、未知或重复位置、无效打开模式,以及空对象均返回 400;旧 show_in_menu_bar 字段不再接受。缺失看板返回 404,操作冲突返回 409,持久化失败返回 500,未接入设置回调返回 501。

配置入口、默认打开方式与按来源打开方式(例如菜单栏与右侧 Sidebar 入口,默认 Flyout,而快捷键与右侧 Sidebar 使用 Dialog):

bash
curl -fsS -X PATCH http://127.0.0.1:6387/v0/dashboards/github-repo/settings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"placements":["menubar","sidebar-right"],"open_mode":"flyout","open_modes":{"shortcut":"dialog","sidebar-right":"dialog"}}'

响应(200 OK):返回更新后的最终有效设置。无入口时 placements 保持 [],不会返回 null;open_modes 始终返回完整的五个来源有效解析模式对象,永不为 null。

json
{
  "id": "github-repo",
  "placements": ["menubar", "sidebar-right"],
  "open_mode": "flyout",
  "open_modes": {
    "menubar": "flyout",
    "sidebar-left": "flyout",
    "sidebar-right": "dialog",
    "sidebar-bottom": "flyout",
    "shortcut": "dialog"
  }
}

其他请求体:

修改默认打开方式:

json
{ "open_mode": "flyout" }

禁用全部视觉入口:

json
{ "placements": [] }

恢复默认入口(不改变打开方式与快捷键):

json
{ "placements": null }

恢复默认打开方式(不改变按来源例外或入口):

json
{ "open_mode": null }

清除全部按来源例外覆盖(所有来源恢复跟随默认打开方式):

json
{ "open_modes": null }

同时恢复默认入口、默认打开方式并清除所有来源例外:

json
{ "placements": null, "open_mode": null, "open_modes": null }

程序化面板操作(如 $metrix.panel.open() / toggle())遵循最终有效的默认打开方式(open_mode),{open} 返回结构不变。

源码端点(validate、创建和源码替换)必须显式声明 Content-Type:text/plain、application/octet-stream,或包含 { "source": "..." } 的 application/json;允许 charset 等媒体类型参数。缺失或不支持的类型返回 415,JSON 格式错误返回 400,请求体超过 2 MiB 返回 413。