本地 API
本地 API 默认关闭。启用后只监听 127.0.0.1,只读,只能访问当前已启用的资料包。笔记、书签、阅读历史、片段和更正都不对外开放。Crib 自己的搜索和阅读不经过这个接口。
启用
- 打开“设置 → 集成”,在“本地 API”中打开“启用本地 API”。
- “端口”填
0自动选择可用端口(默认),或指定1024–65535中的一个,再点“应用”。 - 启动后,设置页显示“地址”,并提供“复制 Schema 地址”“复制密钥”“更换密钥…”。
关闭开关或退出 Crib 后停止监听。资料库不可用时 API 不会启动,设置页显示原因和“重试”。
发现文件
客户端从用户目录读取地址和密钥,不要缓存自动分配的端口:
~/Library/Application Support/Crib/api/
- status.jsonbase_url、port、pid、instance_id、access_key_file、api_version
- access-key访问密钥
- server.lock正在服务的实例持有这把锁
api/目录权限是0700,两个文件是0600。status.json在服务就绪后才出现,正常关闭时删除。Crib 异常退出可能留下旧的status.json;下次启动时,只要没有实例持有server.lock,就会删掉它。- 密钥文件是符号链接、有多个硬链接、属于其他用户或权限公开时,Crib 拒绝使用。
- “更换密钥…”让旧密钥立即失效。随 App 附带的 MCP 程序自动使用新密钥;手动复制过密钥的工具需要重新复制。
先验证服务端,再发送密钥
Crib 崩溃后,原来的端口可能被其他本地进程占用。发送密钥之前,客户端必须确认对端持有同一把密钥:
- 检查
status.json中的pid进程仍然存在。 - 不带任何凭据请求
GET /auth/challenge?nonce=<nonce>,nonce是 32 个随机字节的 64 位小写十六进制。 - 服务端返回
{"algorithm":"hmac-sha256","proof":"<64 hex>","version":1}。 - 客户端计算
HMAC-SHA256(key = 访问密钥的 ASCII 字节, data = "crib-local-api/server-proof/v1\n127.0.0.1:<端口>\n<nonce>"),用常量时间比较与proof是否相等。相等才发送密钥。
端口写进了证明,占用端口的进程无法把挑战转发给别处的真实 Crib 来冒充。返回 404 的服务端应当拒绝,不能默认信任。
请求规则
/health、/schema、/auth/challenge不需要密钥;其余接口需要Authorization: Bearer <访问密钥>。密钥不要放进查询串。- 只支持
GET。响应是 JSON,带Cache-Control: no-store,发送后关闭连接。 Host必须是127.0.0.1:<端口>或localhost:<端口>。带Origin头的请求返回 403,所以网页里的脚本不能调用它。- 查询串按表单编码解析:
+是空格,字面加号写成%2B。未知参数、重复参数、非法转义都返回 400。
接口
| 路径 | 作用 |
|---|---|
GET /health | 服务状态与 API 版本 |
GET /schema | OpenAPI 描述 |
GET /auth/challenge | 服务端身份证明,见上文 |
GET /docsets/list | 当前搜索世代中已启用的资料包、版本、语言、摘要和修订 |
GET /search | 在已启用的资料中搜索 |
GET /sections/{key} | 读取一个章节的纯文本,必须带 generation |
搜索参数
兼容 Dash 的参数名 query、lang、max_results 分别等同于 text、locale、limit。同时给出一对时,值相同可以接受,值不同返回 400。请求的版本或语言没有安装或启用时返回错误,不会悄悄换成默认版本。
每条结果带 section_key、document_key、path、fragment、docset、pack_digest 等字段,以及两个地址:section_url 是带 generation 的章节接口路径,load_url 是在 Crib 中打开该章节的 crib:// 链接。
响应里的 has_more 和 next_offset 用于翻页;timed_out、cancelled、candidate_truncated 为真时结果不完整,不能当作完整结果使用。
读取章节
GET /sections/{key}?generation=<id>&offset=0&limit=16000
offset和limit按 Unicode 标量计数,不是字节,也不是 UTF-16 单元。默认 16,000,最大 50,000。- 跟随
next_offset继续读,直到它为null。 - 启用、停用或更新资料包后搜索世代会变。用旧 generation 请求返回 409
generation_changed,重新搜索后再读。
时间限制
- 每个请求有 3 秒执行预算。搜索本身超时返回 200 和
timed_out=true,带上已完成的部分结果;准备阶段就耗尽预算返回 504。 - 传输层另有 7 秒处理超时、8 秒连接截止;2 秒内没发完请求头的连接被关闭。最多同时 32 个连接。
错误码
| 状态码 | 含义 |
|---|---|
| 400 | 参数或查询无效 |
| 401 | 缺少密钥或密钥错误 |
| 403 | 来源受限,例如带 Origin 头 |
| 404 | 路径或章节不存在 |
| 409 | 搜索世代已变,或请求的范围没有安装、启用 |
| 503 | 资料库不可用,或请求被取消 |
| 504 | 执行或传输超时 |
响应中的 error 和 message 说明具体原因。
Python 示例
示例从发现文件读取地址和密钥,先验证服务端,再搜索并读取第一条结果。密钥不出现在终端或进程参数里。
与 Dash API 的差异
Crib 与 Dash 8.1.1 的本地 API 都有健康检查、Schema、资料包列表和搜索入口,并兼容 query、max_results 参数名。Crib 不支持 Dash 的 docset_identifiers、search_snippets 和 /docsets/enable_fts,默认范围、结果格式和分页上限也不同。依赖完整 Dash API 的客户端不能直接换成 Crib。