读者:直接对 Shiftly 数据做 CRUD 的 AI 会话、脚本与开发者。 只读这一份文档即可安全操作;发生冲突时以本文档为准(代码变更须同步更新此处)。
- 本地文件是唯一真相源。数据根目录(下称
<root>)= 环境变量SHIFTLY_ROOT→(App 记忆的目录)→ 从可执行文件向上找data/config.json。 - 三种操作方式,推荐顺序:① MCP 工具(会话内已注册
shiftlyserver 时) ②shiftlyCLI ③ 直接编辑文件(遵守 §5 守则)。 - 写数据不会自动更新 Apple Calendar:改完调
sync_now/shiftly sync now, 或让用户在 App 里点 Sync。 - 日期一律
YYYY-MM-DD;时间HH:MM(24 小时制);跨午夜班次 end ≤ start 表示次日结束。
文件(相对 <root>) |
谁可以写 | 作用 |
|---|---|---|
data/config.json |
人 / AI / App | 排班规则、班次类型、日历名、日志目录 |
data/swaps.json |
人 / AI / App / 同步回读 | 换班记录 |
data/leave.json |
人 / AI / App / 同步回读 | 请假区间 |
data/holidays.json |
人 / AI / App | 公共假期(不排班) |
data/overrides.json |
同步回读(AI 可删条目=撤销) | 单日时间覆盖 |
data/manual_shifts.json |
同步回读(AI 可删条目=撤销) | 日历里手建的单次班 |
data/pay.json |
人 / AI / App | 薪资模型 |
data/routine.json |
人 / AI / App | 一键上班流程步骤 |
data/meta.json |
只读(同步引擎写) | 最近同步状态 |
data/sync_state.json |
禁止手改(引擎私有) | 事件映射 |
data/last_sync_report.json |
禁止手改(引擎私有) | 最近同步报告 |
data/readback_log.json |
禁止手改(引擎私有) | 回读日志(App 内撤销用) |
<log_dir>/dd-mm-yy.md |
人 / AI / App | 工作日志,一班一文件(见 §2) |
<meetings_dir>/dd-mm-yy | hh-mm/ |
人 / App / Scripto | 会议:录音 dd-mm-yy.mp4 + Scripto 字幕 *.{en,zh}.srt(见 §2.5) |
App 的 Settings → Reset 删除上表 data/ 下的全部 Shiftly 文件并重置应用偏好;
data/ 中的非 Shiftly 文件、工作日志与 Apple Calendar 事件一律保留。
首次设置选定一个文件夹后,Shiftly 自动创建:
<所选文件夹>/
├── app/
│ ├── data/ ← 数据根(ShiftlyPaths.root = <所选文件夹>/app)
│ └── meetings/ ← 会议录音
├── logs/ ← 工作日志
└── notes/ ← 快速笔记
已是数据根的文件夹(含 data/config.json)被原样接管(旧布局兼容)。四个位置均可在 Settings → Storage 单独迁移:迁移 = 移动全部既有内容,目标冲突时整体中止、绝不覆盖, 原路径不留文件。Settings → Reset 依据这些路径清除全部 Shiftly 内容(数据文件白名单、 可识别的日志/笔记文件、可识别的会议文件夹;无法识别的文件保留)。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["calendar_name", "event_title", "default_start_time", "default_end_time", "rules"],
"properties": {
"config_version": { "type": "integer", "enum": [1, 2] },
"calendar_name": { "type": "string", "minLength": 1 },
"event_title": { "type": "string", "minLength": 1 },
"default_start_time": { "type": "string", "pattern": "^\\d{1,2}:\\d{2}$" },
"default_end_time": { "type": "string", "pattern": "^\\d{1,2}:\\d{2}$" },
"setup_completed": { "type": "boolean" },
"history_csv": { "type": "string" },
"log_dir": { "type": "string" },
"rules": {
"type": "array",
"items": {
"type": "object",
"required": ["effective_from", "workdays"],
"properties": {
"effective_from": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"workdays": { "type": "array", "items": { "enum": ["MO","TU","WE","TH","FR","SA","SU"] } },
"shift_type": { "type": "string" }
}
}
},
"shift_types": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "label", "start", "end"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"label": { "type": "string", "minLength": 1 },
"start": { "type": "string", "pattern": "^\\d{1,2}:\\d{2}$" },
"end": { "type": "string", "pattern": "^\\d{1,2}:\\d{2}$" }
}
}
}
}
}语义:
- 某天生效的规则 =
effective_from ≤ 当天的最后一条(按日期排序)。 改排班永远是追加/同日替换,绝不清除历史规则(历史规则保证过去的工作记录正确)。 - 某天的班次时间:
overrides.json的单日覆盖 > 规则shift_type对应类型的时间 > 默认时间。 shift_types[].end ≤ start表示跨午夜(次日结束);写入 shift_types 时把config_version提到 2。log_dir缺省~/Documents/ShiftlyLogs。
示例:
{
"config_version": 2,
"calendar_name": "Shifts",
"event_title": "Work Schedule",
"default_start_time": "10:00",
"default_end_time": "18:30",
"setup_completed": true,
"log_dir": "/Users/me/Documents/ShiftlyLogs",
"shift_types": [
{ "id": "day", "label": "白班", "start": "09:00", "end": "17:00" },
{ "id": "night", "label": "夜班", "start": "22:00", "end": "06:00" }
],
"rules": [
{ "effective_from": "2026-07-01", "workdays": ["MO", "TU"], "shift_type": "day" },
{ "effective_from": "2026-08-01", "workdays": ["WE", "FR"], "shift_type": "night" }
]
}{ "type": "array", "items": { "type": "object",
"required": ["from_date", "to_date"],
"properties": { "from_date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"to_date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" } } } }{ "type": "array", "items": { "type": "object",
"required": ["start_date", "end_date"],
"properties": { "start_date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"end_date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" } } } }语义:换班把 from_date 的班移到 to_date(按顺序应用,可链式);请假区间含双端,
请假在换班之后应用(换入请假日的班会被吞掉)。日历侧删除事件会回读为单日请假
(start_date == end_date)。
{ "type": "array", "items": { "type": "object",
"required": ["start_date", "end_date", "name"],
"properties": { "start_date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"end_date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"name": { "type": "string" } } } }语义:区间含双端,单日假期 start_date == end_date。节假日取消规则生成的班 (在换班之前应用,因此明确换到节假日的班仍生效;请假仍最后应用)。 工作历史 Day N 计数同样排除节假日。App 可从任一日历(如订阅的节假日日历) 一键导入——跨多天的全天事件成为一个区间,起始日已存在的条目不覆盖。
{ "type": "array", "items": { "type": "object",
"required": ["date", "start", "end"],
"properties": { "date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"start": { "pattern": "^\\d{1,2}:\\d{2}$" },
"end": { "pattern": "^\\d{1,2}:\\d{2}$" } } } }{ "type": "array", "items": { "type": "object",
"required": ["date", "start", "end", "source"],
"properties": { "date": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"start": { "pattern": "^\\d{1,2}:\\d{2}$" },
"end": { "pattern": "^\\d{1,2}:\\d{2}$" },
"source": { "type": "string" } } } }两者主要由日历回读产生(用户在 Apple Calendar 改时/新建)。AI 通常不新增;
删除某条目 = 撤销该回读(App 的 Sync Report 卡片有同款按钮)。每日期至多一条。
source: "import" 的条目来自 Settings 的历史导入:所选日历今天之前的事件按真实
起止时间落盘;全天事件用 config 默认班时,重复事件的每次过去发生各记一天,
同日多个定时事件合并为一段;已有日期一律不覆盖(重复导入幂等)。
{
"type": "object",
"required": ["version", "base_currency", "rates"],
"properties": {
"version": { "type": "integer", "enum": [1] },
"base_currency": { "type": "string", "minLength": 3, "maxLength": 3 },
"rates": {
"type": "array", "minItems": 1,
"items": { "type": "object", "required": ["effective_from", "hourly"],
"properties": { "effective_from": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
"hourly": { "type": "number", "exclusiveMinimum": 0 } } }
},
"display_rates": { "type": "object", "additionalProperties": { "type": "number" } },
"unpaid_break_minutes": { "type": "integer", "minimum": 0 }
}
}语义:金额一律 base_currency 记账;班次适用费率 = effective_from ≤ 班次日期 的最新段;
早于首段的班次计 0(App 标橙提示)。display_rates 是手工维护的显示换算乘数
(1 base = N 显示币),本地维护,永不联网获取。调薪 = 追加一段,勿改历史段。
工时按真实起止(跨午夜足额),班次归属开始日。示例:
{
"version": 1,
"base_currency": "AUD",
"rates": [ { "effective_from": "2026-01-01", "hourly": 32.5 } ],
"display_rates": { "CNY": 4.7, "USD": 0.66 }
}{ "type": "array", "items": { "type": "object",
"required": ["kind", "value"],
"properties": {
"kind": { "enum": ["app", "url", "path", "command", "sync", "log"] },
"value": { "type": "string" },
"args": { "type": "array", "items": { "type": "string" } },
"enabled": { "type": "boolean", "default": true } } } }语义:按序执行已启用步骤,单步失败不阻断。app = open -a value(有 args 时
-n … --args …,如 Ghostty 的 --working-directory=…);url 仅限 http(s);
path 打开文件/文件夹(~ 展开);command 经 /bin/zsh -lc 执行(用户自写,
信任级同 shell 配置);sync = 一次日历同步;log = 今日日志追加打卡行。
{ "last_sync_at": ISO8601, "last_sync_status": "success"|"error", "last_sync_error": string? }
——由同步引擎写入,用于判断最近一次同步是否成功。
sync_state.json:班次 ↔ 日历事件的映射与指纹。手改会导致重复建事件或误删。 文件损坏/丢失时引擎自动降级重认领。last_sync_report.json/readback_log.json:同步报告与回读日志(App 内撤销依据)。
- 位置:
config.log_dir(缺省~/Documents/ShiftlyLogs),文件名dd-mm-yy.md平铺(2026-07-19 →19-07-26.md,与班次日期一一对应);旧版YYYY/YYYY-MM-DD.md布局的文件仍可读且续写在原文件(同一天绝不分裂成两个文件)。 - 默认日期(GUI 快速记录与 CLI 未传
--date时):今天有班记今天; 今天无班则记最近一个工作日(周日复盘落进周六的班日志)。 - 快速笔记:独立文件
dd-mm-yy | 标题.md(存放在config.notes_dir, 缺省<log_dir>/notes;标题中的/ : \替换为-), 仅含 frontmatter,正文归用户;文件名中的|是笔记标记, 永不计入工作日志(搜索/月历标记只看日志)。 - 新文件自动带 frontmatter;已存在的文件 Shiftly 只追加、绝不重建:
---
date: 2026-07-17
shift: day # 规则班次类型 id;无班次 = none;日历手建 = manual
hours: 8.5 # 当日计划工时;无班次 = 0
tags: []
---
- 12:30 快记条目(`- HH:MM 内容` 为快记约定格式,正文其余部分自由)- 正文是自由 Markdown,任何编辑器可改;App 的搜索会命中 frontmatter 与正文。
- 目录:
config.meetings_dir(缺省~/Documents/ShiftlyMeetings)。每次录音生成dd-mm-yy | hh-mm/(开始时间戳;macOS 文件名冒号保留,故用hh-mm), 内含录音dd-mm-yy.mp4(AAC)。 - 转录/翻译由 App 无界面调用 Scripto:
cd <config.scripto_dir> && uv run scripto-cli run <音频> --format srt [--translate --target <config.translate_target|zh>]; 字幕<名>.<lang>.srt由 Scripto 写在录音旁,App 只读——列表、播放与按时间戳 高亮均在 App 内完成。 config.repo_dir(可选):Shiftly 仓库检出路径,供 设置 → 更新 的 「重装并重启」使用(用检出内dist/Shiftly.app原地替换当前 App 并重启); 缺省自动探测(App 位于检出内,或scripto_dir的同级目录)。
二进制:随 App 打包在 /Applications/Shiftly.app/Contents/MacOS/shiftly-cli
(或仓库 ShiftlyApp/.build/*/shiftly)。输出恒为 JSON(--json 兼容接受)。
错误 {"error": "…"} 到 stderr;退出码:0 成功 / 2 参数或配置问题 / 3 资源不存在或权限 / 1 其他。
环境:SHIFTLY_ROOT 指数据目录(不设则用 App 记忆目录/向上查找)。
| 命令 | 参数 | 输出(成功) |
|---|---|---|
schedule show |
— | {default_start_time, default_end_time, rules[], shift_types[]} |
schedule set |
--workdays MO,TU --from D [--shift-type id] [--start HH:MM --end HH:MM] |
{rules[]}(upsert 后全量) |
swap add |
--from D --to D |
{swaps[]} |
swap list |
— | {swaps[{index,…}]} |
swap remove |
<index> |
{swaps[]} |
leave add / list / remove |
--start D --end D / — / <index> |
{leave[…]} |
holiday add / list / remove |
--start D [--end D] [--name S] / — / <index> |
{holidays[…]} |
shifts list |
--from D --to D |
{shifts[{date,kind,start,end,hours}]} |
pay report |
--month YYYY-MM |
{month,currency,total_hours,total_amount,has_unrated_shifts,items[]} |
log append |
"text" [--date D] |
{date,path} |
log show |
[--date D] |
{date,content} |
log path |
[--date D] |
{date,path,exists} |
report hours |
--period week|month |
{period,from,to,shift_count,total_hours,shifts[]} |
routine show |
— | {routine[{kind,value,args?,enabled}]} |
routine run |
— | {results[{kind,value,success,message?}]}(sync 步骤提示改用 sync now) |
sync now |
[--window next_month] |
{created,updated,deleted,readbacks,converged}(需已在 App 授权日历) |
示例:
export SHIFTLY_ROOT=/path/to/shiftly-data
shiftly-cli shifts list --from 2026-07-01 --to 2026-07-31
shiftly-cli swap add --from 2026-07-22 --to 2026-07-24
shiftly-cli sync nowpackages/mcp-server/(node + zod,stdio),每个工具 = 一次 CLI 调用;
工具面共 16 个:get_schedule / set_schedule / list_shifts / add_swap /
add_leave / list_overrides / remove_override / list_holidays / add_holiday /
remove_holiday / pay_report / log_append / log_read / routine_show /
routine_run / sync_now。详见 packages/mcp-server/README.md。
注册示例(SHIFTLY_ROOT 指数据目录;CLI 路径缺省自动探测,也可用 SHIFTLY_CLI 指定):
claude mcp add shiftly -e SHIFTLY_ROOT=/path/to/shiftly-data \
-- node /绝对路径/shiftly/packages/mcp-server/index.js- 优先用 MCP / CLI——它们校验输入并保证格式;直接改文件是兜底手段。
- 改前读全文件;写回时保留所有未知字段(顶层与嵌套都算),2 空格缩进。
- 只改 §0 表中标注可写的文件;
sync_state.json等私有文件一律不碰。 config.rules只允许追加或同日替换,不得删除历史规则(未来日期的规则可删)。pay.rates只追加新段,不改动历史段。- 写完提醒(或代为执行)同步:
shiftly-cli sync now/ MCPsync_now, 否则日历不会更新;App 开着时界面可能需要点 Refresh 才反映外部改动。 - 日志文件:可自由编辑正文;保留 frontmatter 块;快记条目用
- HH:MM 内容格式追加。