主人的一天,写成 JSONL。
蜗牛的家是自己长出来的。
Stilday 里装的,也是你自己过出来的日子。
一个 macOS App,把一天收下来,Go + MyGo。
侧栏是「事件」+「采集器」+ 一份倒序的日历 + 设置:
- 事件:汇总历史上所有事件,按天倒序;点一张卡片进入那一天。
- 采集器:插件列表,每个插件有自己的启用开关、每日采集时间和插件 设置(例如相册的同步范围);顶部是系统每日分析时间,右侧只 放分析任务;改动即时保存。
- 日历:标题行只写列表顶部的年份,右边是「⌃ 今天 ⌄」;中间「今天」 进入今天的界面,上下箭头按年跳。月份只写「10 月」,年份只出现在标题行 和旧年份的分组里。侧栏默认宽档(380),右边缘点一下或拖一下切到窄档 (320)。点日历上的任意日期,进入的是同一种界面。
- 某一天:左边是资料,中间是事件,右边是对话。对话固定 370,可拖
右侧分隔线在 240–480 之间调;资料和事件自由拖,素材 73 / 事件 27;拖
资料/事件那条时对话宽度不变;日历侧栏默认宽档(380),右边缘点一下
或拖一下切到窄档(320);右上角「重置布局」回到 370 / 73 / 宽侧栏。
- 资料:每张照片的大图缩略图;单击选中、方向键换一张、空格在应用内预览 1600px JPEG(预览里左右键继续换,空格或 Esc 关闭);当天合集现在显示在 第一条分析用户消息里;最下面是 manifest.json
- 事件:当天 AI 写出的事件时间流
- 日历从 2020 年到现在,本月在最上面;过去年份默认收起成一行年份, 点一下展开,数据多的年份旁边有天数;当前月、滚动位置和选中日期所在年份 会自动展开;整条列表是虚拟滚动的,82 个月也不会一次画完。
用 MyGo 的原生 UI 写成:一个 Go 二进制,没有 webview,没有前端构建。
- 设置里选存储目录(只选一次)
- 工作目录:每天的采集素材、合集、对话
- 产物目录:每年一份 JSONL,一行一个事件
- 照片不需要选目录:从 macOS 照片图库按拍摄时间自动取
- 采集器:相册照片(全局)
- 采集器是插件:名字、描述、子文件夹、图标、执行函数,以及自己的
View - 全局配置在侧栏「采集器」页:相册同步最近 N 天、启用状态、采集时间、 相册删除策略、每日分析时间;每个插件都可以画自己的配置 UI
- 每个插件的设置写在存储根目录的
plugins/<插件 id>/settings.json,不跟 应用自己的~/.stilday/config.json混在一起;老版本的collectors.json第一次启动时会被搬进去,原文件留成collectors.json.migrated - 每个采集器只写自己的子文件夹;执行前先清空它——相册例外:「相册删除」 默认开着,只增不删,相册里删掉的照片这边留着,重跑只追加新照片
- 采集器是插件:名字、描述、子文件夹、图标、执行函数,以及自己的
- 系统统一给每个采集器、每一天算 SHA256 指纹:采集器文件夹的文件
路径/大小/内容 + 影响采集结果的配置。状态这类生成物不进指纹
- 目前只有「相册照片」一个,写
previews/、thumbs/、sheets/和 根目录的manifest.json;不再保存原图副本 - 只增不删时按 PhotoKit 的 asset 标识 判重(没有标识的来源退回拍摄秒 + 原图尺寸)。收集完按拍摄时间重排,序号就是文件名:新照片插进中间时,后面 的照片跟着改号(只改名,不重新编码,不动内容)
- 「同步最近 N 天」逐天执行;当天的状态和手动执行都在采集器页
- 每天 22:00 同步最近 N 天,只把指纹和上次分析不一致的天标成待分析; 第二天 10:00,批量启动这些待分析的天。两个定时任务各跑各的,没有先后 依赖;没有待分析的天就不处理,不调用模型
- 采集器页会显示「有变化 / 待分析」,并可以手动「启动所有」
- 目前只有「相册照片」一个,写
- 中间:对话
- 第一条用户消息不是技术指令,而是一句由临时 Pi 根据当天截图生成的、给朋友的开场白
- 生成期间先在聊天里显示截图和三个点的输入动效,拿到后直接发送
- 当天全部合集作为图片附件随这条消息发给模型,聊天里也全部显示
- 每张照片的 manifest 还带相册、收藏、地点、相机/镜头/ISO/光圈/快门/焦距、Live Photo/截图/全景/HDR/景深、编辑/连拍等元数据
- 模型可以调用:
day_overview:再看一遍合集read:直接读previews/012.jpg看某一张的细节stilday_save_day({entries}):保存当天的日志(全量,第二次调用会替换当天已有的记录)
- 每次分析都清空旧对话和旧事件,新开一个 Pi 会话
- 日期页:资料 / 事件 / 对话
- 资料(第一列,比事件宽):照片的大图缩略图;单击选中,方向键换一张,
空格在窗口里预览
previews/的 1600px JPEG,预览里左右键继续换、空格 或 Esc 关闭;选中项会随方向键滚进视野。当天合集在第一条分析用户消息 里,不再占用资料栏,点一下也进同一个预览层(多张合集时 ← → 切换)。 照片的收集和整理在「采集器」页。 - 事件(第二列):当天的事件卡片流,按时间正序
- 对话(第三列,固定 370):顶部是带外框的头像 + 玻璃胶囊(Stilday ›), 整块不可见区域都可点,弹出中间的 system prompt 弹窗;下面是消息列表和 输入框;分析/重新分析也从这里的输入条发起
- 两条分隔线:左边那条自由调资料/事件(默认 73 / 27),拖它时对话 宽度不变;右边那条调对话宽度(240–480),资料/事件按比例分剩下的; 日历侧栏右边缘切换窄/宽两档;右上角的「重置布局」回到 370 / 73 / 宽侧栏
- 资料(第一列,比事件宽):照片的大图缩略图;单击选中,方向键换一张,
空格在窗口里预览
- 对话输入
- 输入框默认一行,随文字长到五行;Enter 发送,Shift+Enter 换行
- 左边圆按钮是「分析这一天 / 重新分析这一天」:空白的一天直接开始; 已有事件或对话时会先确认,删掉当天的事件和对话,重新整理照片并重新 生成;右边圆按钮是发送
- 空对话就是空白的(像短信),不放开始按钮;资料/事件列为空时显示居中 的图标、标题和说明,分析只从输入条发起
- 自己说的话是蓝色气泡靠右,AI 的 Markdown 平铺在左侧;工具调用是灰色小条
- 采集器状态
- 侧栏「采集器」是全局页面:中间是插件设置,每个插件有自己的自定义 UI 和「同步最近 N 天」/「运行今天」;右侧是「采集计划」和「分析任务」
- 插件列表里的每一行都是一个插件:启用/禁用开关、每天几点自动执行、 「立即执行」,然后才是这个插件自己的设置;相册的同步范围、启用状态和 采集时间写进插件自己的 settings.json,每日分析时间写进 config.json, 没有保存按钮
- 列表顶部是「每日分析」:到这个时间就批量启动右侧待执行的任务(默认 10:00);没有任务就不处理,也不等采集器;行末的「立即执行」马上跑这一批
- 每个插件执行结束都会重新计算待分析的列表;采集器的「立即执行」会先停掉 正在跑的分析(扫描会换掉素材),再开始扫描,停掉的那天回到「待分析」里, 等下一个每日分析时间。采集和分析没有先后依赖,只是共用同一个执行槽、不并行
- 右侧「分析任务」:分析中、排队中、待分析、已取消、最近完成;每天一行, 可以「取消」待分析/排队的任务,取消或失败的可以「重新启动」
- 任务栏顶部是三个图标按钮:启动所有、取消所有、清除已取消(完成的自动离开列表); 对应状态没有任务时按钮变浅禁用。取消所有会停掉当前 并取消等待的任务;启动所有把等待的任务改成现在开始
- 同一时间只做一件事:手动发消息、手动分析、立即执行采集、定时采集和 分析任务共用一个会话槽,谁在跑其他入口就禁用或等它结束,不会互相插队; 槽被占着时发的消息会立刻显示,等当前 turn 结束后先发出去,再跑下一天的分析
- 分析运行中,侧栏底部会出现「分析中 · 队列 N」;手动对话进行中显示 「对话中」;正在回复的那天在日历上有强调环
- 每行只显示图标、名字、描述和上次状态;图库路径、子目录这些内部细节不露出来
- 「立即执行」会先清空该采集器的子文件夹,再重新收集;相册在「只增不删」 开着时不删已有的预览,只补新照片,并重新拼合集
- 每个启用的插件按自己的每日时间执行(相册默认 22:00);扫描只把变动的天 标出来,出现在右侧待执行;到「每日分析」的时间批量启动,跟采集器没有 依赖关系
- 设置页只留存储、模型、Pi 和 MCP 查询;没有常驻的「取消 / 保存」,改动后 右上角才出现一个玻璃勾
- 每个插件的配置在
<存储根>/plugins/<id>/settings.json;每个采集器每天的 运行状态(状态、指纹、是否已分析)写在当天的collectors.json,同时 汇总进manifest.json;指纹由插件自己算,默认是「文件夹 + 配置种子」的 SHA256
- 事件
- 所有历史事件,按处理日倒序,每天一个分组标题
- 点卡片进入那一天,并在「事件」里定位到它
- 可以搜索所有事件
设置里只选一个根目录,Stilday 在里面分两棵树:
<存储目录>/
plugins/photos/
settings.json 相册插件自己的设置(同步范围、相册删除、执行时间)
schedule.json 调度器自己的记录:每个 job 上次运行的时间
work/
2026/10/23/ 每天的工作目录:没采到任何东西的天不建
manifest.json 当天采集器、照片、文件的总清单
collectors.json 每个采集器当天的状态、指纹、是否变化、已分析指纹
previews/ 1600px JPEG 预览(模型读取用)
thumbs/ 360px 界面缩略图
sheets/01.jpg 编号合集
chat.jsonl 对话记录
system.md 当天给模型的系统提示
.pi/ Pi 会话
artifacts/
2026.jsonl 当年的产物:一行一个事件
工作目录按年/月分组,只有真的有照片、对话或事件的天才存在;产物每年一个文件; 换存储目录不会搬走旧文件。
事件格式:
{"occurred_at":"2026-10-23T14:05:00+08:00","updated_at":"2026-10-24T09:12:00+08:00","day":"2026-10-23","kind":"activity","summary":"在 Blue Bottle 写了两个小时代码"}| 字段 | 含义 |
|---|---|
occurred_at |
事件发生时间,RFC 3339,带本地时区 |
updated_at |
这条记录最后一次写入/更新时间 |
day |
这一行被处理/归入的日期;重新分析时按它整体替换 |
kind |
事件类型:observation、activity、food、health、plan、interest |
summary |
一句描述 |
设置页打开「MCP 查询」后,Stilday 会在 http://127.0.0.1:7845/mcp 上开一个
MCP 端点(只监听本机,端口可改)。支持 streamable HTTP 的 AI 客户端填这个
地址就能查主人的日常记录:做过的事、行程安排、吃喝、身体、兴趣和观察。
- 服务名:
stilday-daily-log(显示名「Stilday 主人的日常记录」) - 工具:
search_events,参数from、to(YYYY-MM-DD,含首尾,按day过滤)、kinds、text(不区分大小写的子串)、order(desc默认 /asc)、limit(默认 50,最多 500) - 返回
occurred_at、day、kind、summary
服务跑在 Stilday 里:关掉窗口也还在,退出应用才停。数据直接从年度 JSONL 读。
对话由 Pi 驱动(RPC 模式),照片整理不需要 Pi。
第一次使用:
- 在设置页的 Pi 卡片点「下载 Pi v1.4.0」。Stilday 会从 Pi 的 GitHub
release 下载当前平台的 standalone binary(约 30–45 MB),校验 SHA256,
解压到应用私有目录:
不需要 Node,也不需要系统里预装
~/Library/Application Support/stilday/pi/1.4.0/pi。 - 在设置页的模型卡片选 OpenCode Go 的模型,并填 API key(在
opencode.ai 获取):
- Provider:
opencode-go - Model:在「模型」弹窗里搜索选择,例如
deepseek-v4.1-flash - 环境变量:
OPENCODE_API_KEYkey 存在 macOS Keychain,不写进配置文件。保存后直接对话即可,不需要 终端登录。
- Provider:
- 也可以选择「在终端登录」:打开下载好的 Pi,输入
/login,登录信息在~/.pi/agent,Stilday 共用。
STILDAY_PI可以指定另一个pi,优先于下载的版本- 模型列表从 OpenCode Go 的
/inference/go/v1/models拉取(x-api-key), Pi 的本地 catalog 只补显示名;provider.go里加一项就能接其他 provider - Pi 的版本固定在
piVersion(piinstall.go),应用升级时一起更新
Stilday 启动 Pi 时只加载自己的扩展(pi/stilday.ts,运行时写到
~/.stilday/pi/),只开放三个照片/输出工具,并关掉其他扩展、技能、MCP 和项目
上下文文件。
- 默认用 官方 PhotoKit:首次同步时编译一个很小的 Swift 助手
(
Stilday Photos.app),用PHAsset.creationDate和PHImageManager导出原图数据来生成预览;macOS 会弹一次照片图库权限 - 需要 Xcode Command Line Tools 的
swiftc;助手只编译一次,之后复用 - 如果 PhotoKit 不可用或被拒绝,退回读取
~/Pictures/Photos Library.photoslibrary的originals STILDAY_PHOTOS_LIBRARY可以指向另一个照片图库- 照片库里的文件,Spotlight 的
kMDItemContentCreationDate常常是导入/整理 日期,不是拍摄日期(会把 2016 年的照片放进 2026 年) - 照片按拍摄时间归日,导入时间只作兜底:
- 拍摄时间:PhotoKit 的
PHAsset.creationDate(官方拍摄日期);助手不可用时 用sips的 EXIF/creation,仍拿不到再退回导入时间 - 照片按拍摄时间归到某一天;没有 EXIF 时依次退回 Spotlight 内容日期和文件日期
- 云相册拿不到拍摄日期时,导入日期是合理的兜底
- 拍摄时间:PhotoKit 的
sips读 HEIC 和照片库元数据,也生成预览- 其他平台只保证 JPEG / PNG
- 一天最多均匀采样 120 张,避免几千张照片撑爆上下文
- 合集是给模型的总览:6×5 格、每格 300×225;放不下才生成多张
- 不保存原图副本。
previews/是 1600px 的 JPEG,thumbs/是 360px 的 界面缩略图;HEIC 也在生成预览时转成 JPEG - 细节不另做工具:模型用
read打开每张照片的read路径,也就是previews/里的 1600px JPEG manifest.json是当天资料的完整索引:每张照片的拍摄时间、原图尺寸、 预览字节数、格式、所在合集和格子;它把合集格子和照片双向映射起来,还 列出所有预览/缩略图/合集文件(files)
一个照片条目大致是:
{
"index": 1,
"preview": "previews/001.jpg",
"thumb": "thumbs/001.jpg",
"read": "previews/001.jpg",
"captured_at": "2026-10-23T08:41:41+08:00",
"sheet": 1,
"sheet_file": "sheets/01.jpg",
"cell": 1,
"cell_row": 1,
"cell_col": 1,
"width": 1179,
"height": 2556,
"bytes": 977643,
"format": "png"
}合集里还有 cells: [{photo, row, col}],和照片的 sheet/cell 对应;
files 把当天所有资料文件摊平,方便人和脚本直接扫。
照片只从照片图库读取,工作目录里只保留 1600px JPEG 预览和 360px 缩略图;
合集和模型用 read 打开的预览会通过 Pi 发给模型提供商。
不要把照片图库指向你不想上传的内容。
Pi 以你的用户身份运行。Stilday 只开放 day_overview、read、stilday_save_day
三个工具。
Stilday 有固定时间的采集(相册默认 22:00)和每日分析,所以关掉窗口不等于退出:
- Dock 图标跟着窗口:窗口开着时是正常应用,Dock 里有图标,Cmd+Tab 也能找到;关掉窗口收进后台后切成 accessory,Dock 图标消失,只剩菜单栏的蜗牛。
- 关窗口 = 收起来:窗口隐藏,进程继续跑,到点照常采集、排队分析;菜单栏的蜗牛图标一直在。
- 菜单栏图标:打开窗口、直接跳今天、立即采集、开机自动启动开关、退出。
- 点 Dock 图标 / 再启动一次:都会把窗口叫回来。Stilday 只允许一个实例,第二次启动不会开第二个进程。
- 开机自动启动:设置页「启动与后台」里打开(菜单栏菜单里也有同一项)。由登录项在后台启动时,只出现菜单栏图标,不弹窗口。
- 隐藏期间没有窗口渲染,所以调度由一个 30 秒的定时器驱动(
keepScheduleTicking),不依赖界面在画。
go tool mygo dev # 开发,热重载(保存即重建、重启)
make dev-app # 构建 dev 版并打开:dev 图标/identifier、inspector,不监听文件
make release # 发版:测试、构建 dmg、打 tag、推送、建 GitHub release
go tool mygo build # 打包成 .app / .dmg
go test # 不打开窗口,直接测试视图和协议
go run ./tools/icon # 重新生成 App 图标和菜单栏图标(都是矢量画的)要求 Go 1.27+。go.mod 通过 replace 指向同级的 ../mygo 检出。
mygo.config.ts 由 Node 22.6+ 或 Bun 求值;STILDAY_DEV=1 把它切成 dev 身份
(名字、identifier、图标、.mygo/dev),make dev-app 用的就是这个。
mygo dev 不用那个变量,它自己会加 Dev 和 .dev。dev 版有自己的一份配置
(~/.stilday-dev/config.json,建议的存储是 ~/Stilday-dev),不会碰到正式版的数据。
make release 的版本号取自 mygo.config.ts 的 version:发版前先把版本改好并提交,
工作区要干净、当前在 main、v<version> 还没打过 tag。它会跑 go test ./...、
go tool mygo build 出 dmg,打 tag、推 main 和 tag,再用
gh release create --generate-notes 建 release(需要 gh 已登录)。没有
Developer ID 时是 ad-hoc 签名,首次打开要右键 → 打开。
应用设置写在 ~/.stilday/config.json(STILDAY_HOME 可改;dev 版是
~/.stilday-dev/config.json),由设置页维护。
API key 不在这里,存在 macOS Keychain:
{
"storage_dir": "~/Stilday",
"photo_start": "09:00",
"photo_end": "22:00",
"max_photos": 120,
"provider": "opencode-go",
"model": "deepseek-v4.1-flash",
"collect_time": "22:00",
"auto_time": "10:00"
}- ✅ 日历侧栏:2020 到现在、年份收起/展开、虚拟滚动、选中日期、回今天
- ✅ 全局事件(历史倒序)+ 每天 资料 / 事件 / 对话
- ✅ 资料栏:合集 + 大图缩略图 + manifest 映射;单击选中、方向键切换、 空格预览(应用内预览层,预览图按需解码);对话固定 370、资料/事件 自由拖、两条分隔线、日历侧栏窄/宽两档、重置布局
- ✅ 单行输入框(Enter 发送,Shift+Enter 换行,长文自动长高)
- ✅ 工作目录按年/月分组;产物每年一个 JSONL
- ✅ 照片图库读取、按 EXIF 拍摄时间过滤、采样、6×5 编号合集、预览/缩略图
- ✅ 全局采集器页:插件列表(启用/禁用、每日时间、立即执行)、每日分析时间、 右侧分析任务、启用/禁用、立即执行、改动即时保存
- ✅ 官方 PhotoKit 照片读取(Swift 助手,首次编译),失败退回 originals
- ✅ 采集器统一 SHA256 指纹、变化检测;每个插件按自己的时间采集,每日分析 时间只批量启动右侧待执行、还没分析的天,不等采集器
- ✅ 相册「只增不删」开关(默认开):相册里删掉的照片,已经收集的留着; 重跑只加新照片,按 asset 标识判重,然后按拍摄时间重排、文件重新编号
- ✅ 后台运行:关窗口只隐藏,菜单栏蜗牛图标(打开 / 今天 / 立即采集 / 退出)、 开机自动启动、单实例
- ✅ 事件卡片时间流、重新分析、三个工具、AI 结果写回年度 JSONL
- ✅ 采集器页的后台分析任务列表:分析中 / 排队 / 待分析 / 已取消(可重新启动), 运行中侧栏有状态条;顶部三个玻璃图标按钮管全部任务,没有对应状态时禁用
- ✅ OpenCode Go:模型在设置页的弹窗里搜索选择,API key 存 macOS Keychain
⚠️ PhotoKit 助手需要 Xcode Command Line Tools;首次使用会弹照片权限⚠️ 目前只有「相册照片」一个采集器;屏幕 / 剪贴板 / 日历 / 文件 / 浏览 / 终端 还没接入,但加到defaultCollectors就行⚠️ 需要 OpenCode Go 的 API key(或在终端登录 Pi);没有凭证时对话会报错
下一步:
- 接入第二个采集器(剪贴板或 shell 最简单)
- 采集器独立开关、安装/卸载
- 分析任务的指定天数 / 更多筛选(取消、重新启动已做)
- 跨天自动刷新日历与产物
- 每周 / 每月回顾
- 结果镜像到当天工作目录,方便整包带走
按职责分层:根目录是 package main(窗口和所有页面),没有 UI 依赖的领域
放在 internal/,Pi 协议在 agent/。边界和「改代码前要守住的规则」见
docs/architecture.md。
| 文件 / 包 | 内容 |
|---|---|
main.go |
窗口 |
app.go |
应用状态、窗口壳、换天、产物载入 |
sidebar.go / calendar.go |
侧栏导航、年份步进、日历行与采集标记 |
day.go / panels.go |
一天的头部与三栏;资料、事件、分析动作 |
board.go |
全局事件:所有事件、倒序、按天分组、搜索 |
chat.go / chat_store.go |
一天的会话状态、transcript 读写 |
chat_agent.go / chat_analysis.go / chat_prompt.go |
Pi 进程生命周期、事件流;按天分析入口;系统提示与 stilday_save_day |
analysis.go / schedule.go |
待分析天到调度器任务、被占槽时的排队;每日 job 注册 |
chat_message.go / chat_composer.go |
消息与工具行、输入条与发送 |
greeting.go |
开场白的一次性 Pi 会话 |
internal/artifact |
事件行格式、年度 JSONL 读写与旧格式迁移 |
config.go / clock.go |
配置默认值/校验、存储布局、时段与日期换算 |
collector.go |
采集器接口、全局/按天状态、指纹、manifest |
collector_photos.go / photos.go / photo_*.go |
相册插件、照片扫描与预览、合集、EXIF/PhotoKit |
collector_runner.go |
采集运行、进度、自动采集、待分析判定 |
internal/schedule |
每日 job;按天分析任务的状态机(排队/取消/重新启动) |
collectorpage.go |
采集器页、插件自定义 UI、分析任务栏 |
settings.go |
设置页与保存、存储目录选择 |
pi.go / piinstall.go / models.go / provider.go |
Pi 运行时与下载、模型列表、Provider 定义 |
photokit.swift / photokit_darwin.go / photokit_other.go |
PhotoKit 助手源码、安装调用与非 macOS 桩 |
chrome.go / kind.go / icons.go |
主题、页面壳、玻璃控件、事件标签与图标 |
agent/rpc.go |
Pi RPC 客户端(JSONL 协议) |
pi/stilday.ts |
Pi 扩展:三个工具 |
docs/ai-integration.md |
Pi 集成的调研与取舍 |
docs/architecture.md |
分层、数据流、重构后要守住的规则 |
shot_test.go |
STILDAY_SHOTS=1 go test -run TestShots 生成截图到 /tmp |
tools/icon |
应用图标生成器:go run ./tools/icon 重写 resources/icon.png 与 icon-dev.png;矢量绘制,按 Apple 的 824 圆角网格,icon-dev.png 是同一个蜗牛换成 accent 蓝,go tool mygo dev 的 Dock 图标靠它区分 |