内容
# dayone-headless
一款**无头、无需 Mac、只读**的 [Day One](https://dayoneapp.com)
日记客户端,以 **CLI 和 MCP 服务器**的形式发布。它可以在 [Bun](https://bun.sh)
运行的任何地方(Linux / macOS)运行,因此可以放在家庭实验室容器中,并可由
AI 代理或命令行远程访问。
> **非官方、只读、个人使用**。与 Day One 或 Automattic 无关。该程序读取
Day One 私有的、未公开的 Web API,访问您的账户 — 该 API 可能会更改或停止工作。
## 为什么存在
Day One 没有公开的远程读取 API。现有的所有集成(官方 MCP、`dayone` CLI、
社区 MCP 服务器)都读取 Day One 的 **本地 Core Data SQLite 存储在安装了该应用程序的 Mac 上**
— 没有始终在线的 Mac 就无法使用。
唯一可以**读取**端到端加密日记的曲面是 **Day One 网页应用**:它从私有 REST 后端获取密文并
在客户端使用 JavaScript 解密 **,`dayone-headless` 在可移植的 TypeScript 中重新实现了读取路径
— 纯 HTTPS + WebCrypto 重新实现了 Day One 的 E2EE,没有浏览器 — 并通过本地解密的**镜像**以干净的 CLI 和 MCP 服务器公开。
## 功能
- **只读 MCP 服务器** — `get_stats`、`list_journals`、`list_tags`、`search_entries`、`list_entries`、`sample_entries`、`get_entry`、`get_entries`、`on_this_day`,以及媒体工具。分析级:`get_stats`映射十年无需读取任何字;`sample_entries`返回确定性、均匀分层的语料库切片以供下一步阅读。每个结果都带有`synced_at`,以便代理知道数据的新鲜度。
- **CLI** — 相同的读取操作加上`sync`和`doctor`健康检查。
- **无需 Mac,无需浏览器生产** — 发布的摄入器是纯 HTTPS + 我们的加密。Docker 镜像中不包含 Chromium。
- **增量、完整性感知同步** — 首次同步是完整的;之后,只有服务器修订更改的条目会被重新获取和重新解密。每个条目的失败都会将尝试标记为已降级,而不会推进最后的完整新鲜时间戳,并且在下次运行时将重试未更改的修订。
- **全文搜索**,支持 CJK 查询(SQLite FTS5),具有 CJK 能力的子字符串回退,以便中文/日语/韩语查询正确回忆(FTS5 的`unicode61`分词器不会分割 CJK 词 — 请参见下面的*搜索*)。
- **便携、解耦的镜像** — 一个类似于 Day One JSON 导出的本地 SQLite DB。它也是您的便携备份,并且不会丢失任何无法建模的内容(每一行都保留了`raw`列中的原始来源)。
- **来自环境变量或秘密文件的机密** — 直接值和 Docker 样式的`_FILE`输入是互斥的,从不记录,从不提交;CI 和 pre-commit 中运行的秘密扫描。
## 快速入门
需要 [Bun](https://bun.sh) ≥ 1.3。
```bash
git clone <your-fork-url> dayone-headless
cd dayone-headless
bun install
cp .env.example .env # 然后填写以下机密
chmod 600 .env # 仅所有者:它包含主密钥和帐户身份验证
```
在`.env`中设置最低限度:
- `DAYONE_ENCRYPTION_KEY` — 您的 Day One 加密密钥,格式为`D1-<userId>-<code…>`(在应用程序中:设置 → 加密;这是您输入到网页应用程序的密钥)。
- 身份验证 — 既可以是`DAYONE_API_TOKEN`,也可以是`DAYONE_EMAIL` + `DAYONE_PASSWORD`(客户端会从这些信息中自行生成令牌)。
- `DAYONE_DEVICE_ID` — 推荐;固定一个 32 位十六进制值,以便重复运行注册为同一个设备,而不是每次都注册为新设备。
然后构建镜像并使用它:
```bash
bun run sync # 获取 + 解密 + 写入 data/mirror.db
bun run cli doctor # 配置 + 镜像健康检查
bun run cli journals
bun run cli search "coffee" 10
bun run cli on-this-day # 或:on-this-day 12-25
bun run mcp # 启动只读 MCP 服务器(stdio)
```
Docker 路径(始终在线的服务):
```bash
docker compose up -d # 在配置文件的机密提供后;参见部署
```
参见[部署](#部署)以获取容器详细信息以及如何安全地公开它。
## 命令
单个`daytwo`调度器(`src/serve/cli.ts`;也是`daytwo`二进制文件):
| 命令 | 它执行的操作 |
|---|---|
| `daytwo sync` | 获取、解密并写入镜像(需要环境变量)。 |
| `daytwo sync-status` | 显示最新尝试是否正在运行、已完成、已降级或失败,并带有最后尝试/最后完成的时间戳和聚合失败条目计数。 |
| `daytwo media-fetch [uuid]` | 获取并解密附件**字节**到`data/media/`(所有或一个条目)。 |
| `daytwo mcp` | 运行只读 MCP 服务器。默认为 stdio;如果设置了`DAYONE_MCP_PORT`,则为流式 HTTP。 |
| `daytwo doctor` | 配置、镜像健康状况和本地纯文本权限检查(报告机密**存在/形状**,而非值)。 |
| `daytwo doctor --fix-permissions` | 在检查诊断后显式收紧现有的镜像/缓存路径。 |
| `daytwo journals` | 列出带有条目计数和新鲜度的日记。 |
| `daytwo stats <year\|month\|journal> [filters]` | 语料库地图:每个桶的条目计数、日期范围和文本量。任何纵向问题都很容易进行第一次调用 — 不会读取任何条目文本。 |
| `daytwo sample [n] [--stratify-by year\|month\|none] [filters]` | 确定性、仅限元数据的分层覆盖样本(默认 48,最大 200) — 均匀分布在年份上以供纵向阅读。 将 uuid 提供给`get`/`get_entries`. |
| `daytwo search <q> [limit] [filters]` | 全文搜索(支持 CJK),可选地由`list`过滤器缩小范围。 |
| `daytwo list [filters]` | 结构化浏览 — 按过滤器/分页/文本查询进行筛选。 `--include-text` 返回带有显式截断元数据的限定正文; `--order-by date\|length\|editing_time`. |
| `daytwo tags` | 所有带有条目计数的标签,按使用频率排序靠前。 |
| `daytwo get <uuid> [--rich-text] [--raw]` | 一个精选的条目。 `--rich-text` / `--raw` 添加了重字段。 |
| `daytwo media <uuid>` | 附加到条目的媒体元数据(永远不是字节)。 |
| `daytwo media-file <id>` | 将媒体标识符解析为其缓存字节路径(在`media-fetch`后)。 |
| `daytwo on-this-day [MM-DD]` | 某个月-日的条目(默认为今天)。 |
`list`过滤器都是可选的,并且一起使用 AND 操作:
```
daytwo list --journal <name> --tag <name> --starred \
--from <ISO> --to <ISO> --place <substr> \
--limit <n> --offset <n> \
--include-text --max-chars-per-entry <n> --max-total-chars <n>
```
`--from`/`--to` 绑定`creation_date`(两者都包含;一个裸`YYYY-MM-DD` `--to`涵盖整个日期)。 `--place` 是对 / 地点 / 国家/地区的不区分大小写的子字符串。 最新优先;使用 `--limit`/`--offset` 分页。 列表输出包含 `page_info`,带有 `returned`、`has_more` 和 `next_offset`;这样可以避免猜测一个页面是否涵盖了筛选后的结果集。
相同的标志缩小了`search`,因此关键字和结构可以组合:
```
daytwo search "coffee" --from 2021-01-01 --to 2021-12-31 --journal Trips
```
通过`bun run src/serve/cli.ts <cmd>`、`daytwo`二进制文件或软件包脚本运行:`bun run sync | mcp | cli | import | check | lint | format | typecheck | test`.
镜像存储**媒体元数据**;附件**字节**按需通过`media-fetch`获取到内容寻址的 gitignored 缓存(`data/media/<md5>`,使用`DAYONE_MEDIA_DIR`覆盖)。它是幂等的(已缓存的文件在不下载的情况下被跳过),每个文件在写入前都会进行 md5 验证,因此永远不会缓存错误的解密。
默认情况下,解密的本地状态仅限所有者:镜像数据库及其 SQLite WAL/SHM 边车,以及缓存的媒体文件,都以`0600`创建;它们的专用目录为`0700`。进程在创建它们之前安装了限制性的`077` umask,并且普通打开的文件权限不会添加所有者权限。 `daytwo doctor`还会检查项目本地的`.env`(如果存在),并诊断整个缓存而不打印媒体标识符;使用显式的`--fix-permissions`选项来修复这些现有的路径。
## 搜索
`search_entries` 是基于一个查询字符串的混合搜索:
- **拉丁查询** 通过 **SQLite FTS5** — 相关性排名、`snippet()`高亮、短语/`AND`/`OR`/`NOT`/前缀运算符。
- **CJK 查询** (任何包含中文/日语/韩语字符的术语)会回退到 **`LIKE` 子字符串**扫描:每个空格分隔的术语必须出现在正文中(`AND`组合),最新优先,并带有手动构建的片段。
为什么要进行分割,坦率地说:FTS5 的`unicode61`分词器不会分割 CJK — 它将一串表意文字视为一个标记 — 因此`MATCH '咖啡'`仅匹配那些确切的 2 个字符运行为一个完整的“单词”的条目,遗漏了几乎所有内容。 三元组分词器也不是修复程序;主要的中文搜索术语是 2 个字符的单词,低于三元组分词器的 3 个字符的最低限度。 子字符串匹配是短 CJK 术语的唯一正确召回路径,并且在这种语料库大小下,`LIKE`扫描是毫秒级的。 权衡之处在于 CJK 结果按日期排序,而不是相关性,并且布尔/短语运算符仅适用于拉丁查询。 结构化过滤器(日记/标签/日期/地点/星标)在这两个路径上都相同。
## MCP 用法
`daytwo mcp` 使用 [Model Context Protocol](https://modelcontextprotocol.io)。
默认情况下,它通过 **stdio** 提供服务(对于一个本地客户端,它会启动进程);
设置`DAYONE_MCP_PORT`以提供 **流式 HTTP** 代替(对于始终在线的服务)。
HTTP 服务器默认绑定到环回地址 **loopback `127.0.0.1`** (`DAYONE_MCP_HOST`覆盖它)
— 始终将其保留在身份验证代理后面。
HTTP 边界需要一个确切的`Host`允许列表,并提供三种明确的身份验证模式:`none`表示字面意义上的环回绑定,`static`表示高熵承载者(不是 OAuth),`cloudflare-access`验证由 Cloudflare 注入的 Access JWT 断言。 主机允许列表是路由验证,不是身份验证;启动拒绝`none`通配符、LAN、容器网络或公共绑定。 确切的`Origin`允许列表单独阻止浏览器重新绑定;默认拒绝所有浏览器源。
请参见[docs/deployment.md](docs/deployment.md)在启用远程访问之前。
将其添加到_stdio_ MCP 客户端(例如 Claude Desktop / Claude Code)大致如下:
```json
{
"mcpServers": {
"daytwo": {
"command": "bun",
"args": ["run", "/absolute/path/to/dayone-headless/src/serve/cli.ts", "mcp"],
"env": { "DAYONE_MIRROR": "/absolute/path/to/data/mirror.db" }
}
}
}
```
读取 MCP 进程只需要镜像,并且可以选择自己的传输凭证 — 永远不需要 Day One 加密密钥或上游帐户凭证。
公开的工具(均为只读):
- `get_sync_status` — 检查完整性分析:最新尝试状态、聚合失败条目计数和单独的最后尝试/最后完成时间戳。 运行/降级/失败尝试永远不会推进`last_complete_at`;`running`也可能意味着前一个进程在完成之前被中断。
- `get_stats` — 语料库地图:条目计数、日期范围和每个桶的文本量(与`list_entries`相同的过滤器)。 任何纵向问题都很容易进行第一次调用 — 不会读取任何条目文本。
- `sample_entries` — `get_stats`的确定性、仅限元数据的补充,用于纵向阅读。 `get_stats`显示语料库的**形状**;`sample_entries`返回它的均匀、可复制的切片以供阅读。 它在层面上分配`n`个条目(默认 48,最大 200) — 按年(默认)、月或`none` — 按比例分配到每个层级的大小,给每个非空层级至少一个,同时允许预算(按时间顺序排列,最旧优先),并在每个层级内按`(creation_date, uuid)`顺序选择均匀间隔的条目。 没有随机性:相同的镜像和参数始终返回相同的 uuid,按时间顺序排列。 结果是普通的仅限元数据摘要(`uuid`、`creation_date`、`journal`、`tags`、`starred`、`text_length`、`place_name`)没有正文或片段 — 将 uuid 直接提供给`get_entries`. 相同的日记/标签/星标/日期/地点过滤器限定样本范围。
- `list_journals` — 日记 + 条目计数 + `synced_at`.
- `list_tags` — 每个标签及其条目计数,按使用频率排序靠前。
- `search_entries` — 关键字搜索正文(支持 CJK,请参见*搜索*),可选地由与`list_entries`相同的过滤器缩小范围;每个命中返回 uuid、日期、地点、日记、标签、`text_length`和片段。
- `list_entries` — 结构化浏览:按日记/标签/日期范围/地点/星标进行筛选,分页。 `include_text` 返回正文边界;默认为每个条目 4000 个 Unicode 字符和整个页面 24,000 个字符。 每个正文报告`text_truncation`,而`page_info`报告`returned`、`has_more`和`next_offset`。 `order_by` = `date` | `length` | `editing_time`. 补充`search_entries`.
- `get_entry` — 一个精选的条目,作为一个精选的、精简的对象(类型化字段 + 内联媒体元数据 + `text_length`);`include_rich_text`/`include_raw`添加了重字段。
- `get_entries` — 批量精选读取最多 50 个 uuid。 正文默认为每个条目 12,000 个 Unicode 字符,整个批次 60,000 个字符;每个项目报告确切的原始/返回计数以及截断预算。 未知 uuid 返回`missing`. 重型富文本/原始字段保留在单个条目的`get_entry`上,因此批处理不能无声地将它们乘以无限的响应;旧批量标志被迁移消息拒绝。 与`sample_entries`配对进行纵向阅读:采样 uuid,然后在这里读取它们。
- `get_entry_media` — 附加到条目的媒体元数据(永远不是字节)。
- `get_media` — 一个附件的解密**字节**,按标识符(小照片内联为图像;较大的/其他文件仅返回元数据,从不服务器本地路径)。 从缓存中提供服务 — 使用`media-fetch`填充它;永远不会获取或解密。
- `on_this_day` — 匹配某个月-日跨越年份的条目。
读取结果保留兼容的顶级`synced_at`(最后一个完整的快照),并添加`sync_status`对象。 如果最新尝试是降级,成功更新的条目仍然可用,失败的修订状态待重试,并且调用者可以避免将镜像视为完整。 吸入器记录`running`之前网络、解密或条目写入,因此并发读取器和崩溃后读取器永远不会将正在进行的局部镜像视为完整的快照。
## 部署
Compose 堆栈定义了两个服务共享一个镜像卷:
- **`sync`** — 在间隔运行 REST 吸入器(`DAYONE_SYNC_INTERVAL`,默认为 1h;增量,因此便宜)。
- **`mcp`** — 一个始终在线的只读 MCP 服务器,通过流式 HTTP 提供服务,绑定到容器通配符并仅发布到主机**环回 `127.0.0.1:8477`**。 配置静态或 Cloudflare Access 身份验证后启动它;`none`被拒绝,因为进程绑定不是环回。
镜像是多阶段、版本固定的`oven/bun`构建并且**没有浏览器**。 最小根入口点仅接受固定的非符号`/run/secrets/<known-name>`挂载并复制到私有 tmpfs,因此所有者文件在 Linux 主机 UID 任意位置都有效;然后以`bun`启动应用程序并具有空能力边界集和`no-new-privileges`。 Compose 将每个根文件系统设置为只读,限制 PID,使用服务特定的就绪检查,并将上游凭据仅映射到 `sync`。
**永远不要公开暴露 MCP 端口**。它读取您的整个解密日记;任何人访问它都可以读取所有内容。 使用身份验证代理前端它 - 例如 [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/) 隧道 - 常见的自托管 MCP 桥接模式。 Compose `mcp` 服务接收不到 Day One 上游凭证(只有 `sync` 获取)。 为了深度防御,选择明确的 MCP 身份验证模式并配置确切的主机/源允许列表。
完整的演练(环境变量/秘密处理、固定设备 ID、吸入间隔、Cloudflare Access、备份): [docs/deployment.md](docs/deployment.md)。
架构
唯一规则:**解耦数据摄入与服务**。三个部分之间具有稳定的契约:
```
[ 摄入层 ] -- [ 解密镜像 ] <--读取-- [ 命令行界面 / MCP 服务层 ]
获取 + 解 稳定契约:纯 TypeScript + bun: (REST,或浏览器, Day 导出格式 零 / 加密依赖
或 JSON 导出) 在 SQLite 中 携式命令行界面/MCP1. **服务层**/serve/…`) — 纯 TypeScript + `bun:sqlite`,对 Day One、网络或加密一无所知。
2. **镜像** — 类似 Day One 的 JSON 导出格式的 SQLite 结构 (`src/types.ts`,
`src/serve/db/schema.sql`)。媒体仅包含元数据;按需获取字节。
3. **摄入层** (`src/ingest/…`) — 生产环境的 **REST 摄入层** (纯 HTTPS +
我们的加密方式),一个 **JSON 导出导入器** (经过验证的手动故障转移和独立的一致性预言机),以及一个实验性的、未经验证的 **浏览器摄入层**。所有摄入层都生成导出格式,因此服务层永远不会更改。
详细信息以及保留两个摄入层的理由:
[docs/architecture.md](docs/architecture.md)。协议/加密参考:
[docs](docs/protocol.md)。
## 验证解密
REST 摄入层自己的加密方式的正确性是经过验证的,而不是被假设的:通过 REST 路径解密的相同条目与 **独立预言机** 进行比较 — 从 Day One 的官方 JSON 导出构建的镜像 — 并且在每个解密关键字段(条目文本、富文本、标签、媒体标识符、标志和定时条目日期)上必须完全相同。这是编解码器交叉验证。
`scripts/conformance.ts` 在两个本地镜像上运行比较(两者都忽略了 git — 真实的日志数据从未进入仓库):
```bash
# 预言机:将手动导出的 JSON 日志导入到其自己的镜像中
DAYONE_MIRROR=exports/mirror-export.db bun run import path/to/Journal 主体:REST 同步镜像
bun run sync
# 比较 — 如果有任何关键差异,则退出非零
bun run conformance data/mirror.db exports/mirror-export.db在导出和实时 REST 源之间合法字段(坐标浮点精度、仅导出反向地理编码的地点名称、媒体 `type` 命名、全天条目时区锚定)被归类为 **良性**,并且仅用于信息报告。相同的检查在 `test/conformance.test.ts` 中运行,当设置 `CONFORMANCE_REST_DB` / `CONFORMANCE_EXPORT_DB` 时,并且在 CI 中跳过,因为那里没有真实数据。
## 安全
`dayone-headless` 解密您的 **全部** 日志。机密仅来自直接环境值或其 `_FILE` 伴侣,并且从不记录或提交、导出和浏览器配置文件都 git;读取是唯一的路径。完整威胁模型和部署规则:
[SECURITY.md](SECURITY.md)。
## 贡献
开发设置、项目布局、如何添加摄以及一致性预言机 [CONTRIBUTING.md](CONTRIBUTING.md)。工具链:Bun、TypeScript、
[Biome](https://biomejs.dev) (lint + 格式)、 [ook](https://lefthook.dev) git 钩子、 [gitleaks](https://gitleaks.io)、GitHub Actions CI + 发布。
## 许可证
MIT — 请参阅 [
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器