{"id":51277361,"url":"https://github.com/threefish-ai/give-me-a-break","last_synced_at":"2026-06-29T22:01:23.425Z","repository":{"id":366756137,"uuid":"1277571781","full_name":"ThreeFish-AI/give-me-a-break","owner":"ThreeFish-AI","description":"Give me a break.","archived":false,"fork":false,"pushed_at":"2026-06-25T04:13:21.000Z","size":759,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-25T05:06:28.912Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ThreeFish-AI.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-23T02:26:16.000Z","updated_at":"2026-06-25T01:45:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ThreeFish-AI/give-me-a-break","commit_stats":null,"previous_names":["threefish-ai/timeout","threefish-ai/give-me-a-break"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ThreeFish-AI/give-me-a-break","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThreeFish-AI%2Fgive-me-a-break","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThreeFish-AI%2Fgive-me-a-break/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThreeFish-AI%2Fgive-me-a-break/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThreeFish-AI%2Fgive-me-a-break/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ThreeFish-AI","download_url":"https://codeload.github.com/ThreeFish-AI/give-me-a-break/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThreeFish-AI%2Fgive-me-a-break/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34944147,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-29T22:01:20.968Z","updated_at":"2026-06-29T22:01:23.413Z","avatar_url":"https://github.com/ThreeFish-AI.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Give me a break 🍅\n\n**macOS 菜单栏强制作息应用**\n\n在自定义工作时段内执行「工作 / 强制休息」节律，休息时**遮罩全部显示器**并**播放舒缓音效**（内置粉噪音 + 可选 QQ 音乐联动）；接入 **Google 日历**会议，会议计为工作时间但**休息自然后延**。\n\n![macOS](https://img.shields.io/badge/macOS-14%2B-000000) ![Swift](https://img.shields.io/badge/Swift-SPM-F05138) ![License](https://img.shields.io/badge/license-MIT-blue)\n\n\u003c/div\u003e\n\n## 设计哲学\n\n熵减：用「上下文驱动、最小干预、循证工程」原则，对抗每天作息的无序。完整设计与循证调研见 [设计文档](./docs/give-me-a-break-design.md)，协作规约见 [AGENTS.md](./AGENTS.md)。\n\n## 核心能力\n\n- **强制排版作息**：工作窗口内每累计 N 分钟（默认 50）→ 强制休息 M 分钟（默认 10）。\n- **全屏遮罩**：休息时遮罩所有显示器（`CGShieldingWindowLevel`，压过菜单栏/Dock/全屏），按 Esc 需**二次确认**才可提前结束（软强制，留逃生阀）。\n- **休息音效**：进入休息播放**自定义音频**（在设置选择本地 mp3/m4a/aac/wav/flac 等文件，循环播放，取代内置粉噪音；文件不打包不分发，仅以本地路径引用，缺失/不可用自动回退粉噪音）或内置**粉噪音**（AVAudioEngine 实时合成，零音频文件、可靠）；可选叠加联动 QQ 音乐（经系统 Now Playing 路由的 CGEvent 媒体键）。结束休息自动停止。均可在设置中配置。\n- **Google 日历门控**：会议计为工作时间，但休息推迟到会议结束。例：工作 30min 后接 30min 会议 → 连续工作 60min，会议结束才开始 10min 休息。\n- **工作日志（认知闭合仪式）**：自然休息前弹一个轻量输入框，花 30 秒写下「刚刚完成了什么 + 可选下一步」，让大脑真正放下再休息（循证：Leroy 注意力残留 / Stubblebine 插值日记）。记录按时间段落盘，菜单「工作日志…」一键生成今日/本周/月报 Markdown，支持复制与导出。永不阻塞休息：回车提交 / Esc 跳过 / 关窗放行 / 到点自动放行（等待时长可在设置调整，默认 3 分钟）；亦可设「永久等待」让窗口停留至手动操作，或在设置关闭整个环节；「立即休息」不弹。\n- **运动记录 + 综合报告**：与工作日志对称——**休息自然结束时**弹轻量输入框，记录这段休息里做的微运动（如胯下击掌 / 提膝击掌 / 深蹲 / 俯卧撑），每条含「运动时段 + 若干（类型 × 数量）」；提前结束（Esc）与被会议、下班打断均不弹。运动记录与工作日志一并汇入菜单「综合报告…」，按**周 / 月 / 季 / 年**合成原生层级报告（工作 Top N + 周期分布、运动按类型聚合 + 明细），支持复制与导出 Markdown。\n- **健壮性**：AFK/睡眠暂停累加（不回灌）、崩溃恢复（fast-forward）、多屏热插拔、状态持久化。\n\n## 架构总览\n\n```mermaid\nflowchart LR\n    subgraph Inputs[\"输入源\"]\n        T[\"DispatchSourceTimer\u003cbr/\u003e1Hz\"]\n        SLP[\"NSWorkspace\u003cbr/\u003esleep/wake\"]\n        IDLE[\"CGEventSource\u003cbr/\u003eidle\"]\n        CAL[\"LiveCalendarProvider\u003cbr/\u003eEKEventStoreChanged\"]\n    end\n    subgraph Engine[\"LiveGiveMeABreakEngine（单一事实源）\"]\n        ADV[\"advance\u003cbr/\u003e累加器\"]\n        EVAL[\"evaluate\u003cbr/\u003e纯函数 FSM\"]\n        TR[\"transition\u003cbr/\u003e幂等副作用\"]\n    end\n    subgraph Ctrls[\"三大集成\"]\n        OV[\"LiveOverlayController\"]\n        MU[\"LiveMusicController\"]\n        CAP[\"LiveCalendarProvider\"]\n    end\n    T --\u003e Engine; SLP --\u003e Engine; IDLE --\u003e Engine; CAL --\u003e Engine\n    ADV --\u003e EVAL --\u003e TR\n    TR --\u003e OV; TR --\u003e MU; CAL --\u003e CAP\n    classDef input fill:#2d5a3a,color:#fff,stroke:#7fc98a\n    classDef core fill:#1f3a5a,color:#fff,stroke:#7fa8c9\n    classDef ctrl fill:#5a4a2d,color:#fff,stroke:#c9b27f\n    class T,SLP,IDLE,CAL input\n    class Engine,ADV,EVAL,TR core\n    class OV,MU,CAP ctrl\n```\n\n**正交分解**（[详细 FSM](./docs/give-me-a-break-design.md#调度引擎)）：`evaluate` 是零时间依赖纯函数（注入虚拟时钟可测）；`LiveGiveMeABreakEngine` 仅汇聚输入 + 调用纯函数 + 幂等分发；三大 Controller 各封装系统 API 与权限。三模块：`GiveMeABreakEngine`（纯核心）、`GiveMeABreakIntegrations`（AppKit/EventKit/CGEvent）、`GiveMeABreak`（@main 壳）。\n\n## 下载与安装（Release 资产）\n\n本版本为 **MVP 正式发布（GA）**：功能完备，但 macOS / Windows 双端产物**均未做代码签名 / 公证**（代码签名公证与 Windows 真机验收将在后续版本补齐），首次启动需按下方说明手动放行。从 [Releases](https://github.com/ThreeFish-AI/give-me-a-break/releases) 下载对应平台 zip。\n\n**macOS**（`give-me-a-break-*-macos.zip`）：ad-hoc 签名、未公证，首次打开会被 Gatekeeper 拦截。解压后将 `GiveMeABreak.app` 拖入 `/Applications`，在终端执行一次去隔离即可正常启动（macOS 15 起已无右键「打开」旁路）：\n\n```bash\nxattr -dr com.apple.quarantine /Applications/GiveMeABreak.app\n```\n\n**Windows 10/11 x64**（`give-me-a-break-*-win64.zip`）：自包含、未签名。SmartScreen 提示时点「更多信息 → 仍要运行」；因低级键盘钩子（`WH_KEYBOARD_LL`）+ `SendInput` 媒体键，可能被 Defender / 360 / 火绒**误报**，需手动放行。解压后运行 `GiveMeABreakShell.exe`。\n\n## 环境要求\n\n- **macOS 14+**（Sonoma 及以上，本机实测 macOS 26.5.1）。\n- **Swift 工具链**：Command Line Tools 即可（`swift --version` ≥ 6.0）。**无需安装完整 Xcode**——本工程用 Swift Package Manager + `Makefile` 手工装配 `.app`。\n- `codesign` / `xcrun notarytool` 随 Command Line Tools 附带。\n\n## 构建与运行\n\n```bash\n# 装配 GiveMeABreak.app（ad-hoc 签名 + Hardened Runtime）并运行\nmake run\n\n# 或分步\nmake build      # swift build -c release\nmake app        # 装配 .app + codesign + 清 quarantine\nopen GiveMeABreak.app\n\n# 单元测试（自建运行器，CLT 无 XCTest）\nmake test\n```\n\n**快速验证遮罩/音乐/工作日志**（⚠️ 会遮罩你的屏幕约 15 秒）：\n\n```bash\nGIVEMEABREAK_DEBUG=1 .build/release/GiveMeABreak\n# → 8s 工作 → 工作日志输入框（DEBUG 旁路 15min 门控）→ 回车/跳过 → 全屏遮罩 + 拉起 QQ 音乐 → 15s 后自动退出遮罩 + 暂停音乐\n```\n\n## 权限授予（首次运行，全部由你在系统设置手动完成）\n\nGive me a break 是**非沙盒**应用（沙盒会阻断媒体键与日历自动化）。运行后请在「系统设置 → 隐私与安全性」依次授予：\n\n| 权限                         | 用途                            | 触发时机         |\n| ---------------------------- | ------------------------------- | ---------------- |\n| **辅助功能 (Accessibility)** | CGEvent 合成媒体键控制 QQ 音乐  | 首次启动弹引导窗 |\n| **完全日历访问**             | EventKit 读取 Google 日历会议   | 首次启动请求     |\n| **自动化 (Automation)**      | 仅当未来启用 AppleScript 回退时 | 按需             |\n\n\u003e Agent 不得绕过任何权限授予——均由用户在系统设置完成（同构于 [浏览器验证协议](./.agents/browser-validation.md) 的登录态红线）。\n\n## QQ 音乐与 Google 日历准备\n\n- **休息音效（默认开）**：内置**粉噪音**无需任何准备，开箱即用。可选联动 **QQ 音乐**：安装至 `/Applications/QQMusic.app` 并授予辅助功能权限（其注册为系统 Now Playing 应用，故媒体键可路由控制；**不可** AppleScript 脚本化，已二进制验证）。两者均可在设置中开关。\n- **应用图标**：装配自动生成（`leaf.fill` 绿叶 + teal 渐变 squircle，方案 A）。\n- **Google 日历**：在「系统设置 → Internet 账户」添加 Google 账户并启用日历 → 经 CalDAV 同步至 macOS 日历 → EventKit 自动可见（**无 OAuth 代码**，复用 OS 登录态）。\n\n## 配置\n\n配置文件：`~/Library/Application Support/com.aurelius.givemeabreak/config.json`（缺失则用默认；旧版配置自动平滑迁移）。默认即用户所述作息：\n\n```json\n{\n  \"schemaVersion\": 5,\n  \"workWindows\": [\n    { \"start\": { \"hours\": 9 }, \"end\": { \"hours\": 12 } },\n    { \"start\": { \"hours\": 13, \"minutes\": 40 }, \"end\": { \"hours\": 18 } }\n  ],\n  \"workIntervalSeconds\": 3000,\n  \"restDurationSeconds\": 600,\n  \"afkThresholdSeconds\": 180,\n  \"ambientSoundEnabled\": true,\n  \"controlQQMusic\": true,\n  \"workLogEnabled\": true,\n  \"restMusicPath\": null,\n  \"workLogPromptTimeoutSeconds\": 180\n}\n```\n\n工作日志单独持久化为 `work-log.json`（同目录），schema 见 [shared/work-log.schema.json](./shared/work-log.schema.json)；报告生成（今日/本周/月报 Markdown）见菜单「工作日志…」。运动记录单独持久化为 `exercise-log.json`（同目录）；与工作日志合成的综合报告（周/月/季/年 Markdown）见菜单「综合报告…」。\n\n可在**设置窗口**图形化编辑（即时保存 + 引擎热更新，无需手动改 JSON）。菜单栏显示「英文状态 + 倒计时」（如 `Work 23′` / `Break 8′`），下拉菜单提供「立即休息 / 设置 / 工作日志 / 补录工作日志 / 综合报告 / 补录运动记录 / 退出」；「开机自启」已迁入设置窗口的「一般」分组。\n\n## 验证\n\n- **单元测试**：`make test`（77 用例，\u003c1s）覆盖 FSM 谓词优先级、工作示例（30+30→60→10）、AFK 冻结、睡眠不回灌、fast-forward、区间合并、工作日志记录/报告/补录、运动记录/综合报告（周/月/季/年）、onPostBreak 触发不变量、配置迁移（v3→v6）等。详见 [设计文档](./docs/give-me-a-break-design.md#测试矩阵)。\n- **端到端**（真机，三权限 + QQ 音乐 + Google 账户）：`GIVEMEABREAK_DEBUG=1` 观察遮罩/音乐周期；正常时段等待 50min 触发；日历建会议验证推迟。\n\n## 已知限制（透明披露）\n\n- **强制休息无法阻止 force-quit**：Cmd-Opt-Esc / `kill` 始终可终止——这是 macOS 设计，非恶意软件。软强制提供摩擦而非硬锁。\n- **QQ 音乐联动依赖外部条件**：媒体键控 QQ 音乐需 (a) 已安装 `/Applications/QQMusic.app`、(b) 已授辅助功能权限、(c) QQ 音乐注册为 Now Playing，任一不满足即静默失败（toggle 语义还可能在播放中误暂停）。**故默认叠加内置粉噪音**作为可靠休息音效——无论 QQ 音乐是否可用都有声。失败原因见 Console.app 日志（`[GiveMeABreak][music]`）。详见 [issue #3](./.agents/issue.md)。\n- **日历过滤近似**：「仅 Google」靠 `.calDAV` 源过滤；若有其他 CalDAV 账户（Yahoo/Fastmail）会被纳入。\n- **macOS 26 `canBecomeKey`**：遮罩面板设为可成为 key 以收 Esc；beta 期有崩溃报告，需目标版本实机回归（已预置 [issue](./.agents/issue.md)）。\n\n## 项目结构\n\n```\n├── Package.swift                  # SPM：GiveMeABreakEngine / GiveMeABreakIntegrations / GiveMeABreak 三目标\n├── Sources/\n│   ├── GiveMeABreakEngine/             # 纯 Foundation：FSM + evaluate 纯函数 + 模型 + 持久化\n│   ├── GiveMeABreakIntegrations/       # AppKit/EventKit/CGEvent：遮罩 + 音乐 + 日历 + 心跳 + 装配\n│   └── GiveMeABreak/                   # @main 壳 + AppDelegate\n├── tests/                         # 自建测试运行器（Harness + Cases + main）\n├── docs/give-me-a-break-design.md         # 设计文档（FSM + IEEE 引用）\n├── Makefile / Resources/          # .app 装配 + Info.plist + entitlements\n└── .agents/                       # 协作文档（knowledge-map / issue / 引用规范）\n```\n\n## Windows 移植（进行中）\n\nmacOS 是当前主版本。Windows 版采用 **C#/.NET 8 WPF 重写**（非 Swift 直编——77% 代码绑定 Apple 专有框架，Windows 物理不存在，详见 [`docs/windows-port-design.md`](./docs/windows-port-design.md) §2 循证）。\n\n**进展**（Swift `Sources/` 零改动，C# 平行重写）：\n\n- **Phase 0 ✅**：`windows/GiveMeABreakEngine/` C# 重写纯核心 + `shared/` 黄金 fixture（两端共用同一份 JSON 保证 FSM 不漂移），xUnit 25 + Swift 47 双端全绿。\n- **Phase 1 ✅**：`windows/GiveMeABreakEngine.Win32/`（net8.0 互操作层，18 测试 macOS 本地可验证）+ `windows/GiveMeABreakShell/`（net8.0-windows WPF 最小壳：NAudio 粉噪音 + SendInput 媒体键 + H.NotifyIcon 托盘）。**验证靠 CI**（macOS 无法运行 Windows-only 代码）：L1 双平台 net8.0 测试、L2 壳编译、L3 headless 烟测。\n- **Phase 2 ✅**：全屏强制遮罩（`WS_EX_TOPMOST` + `WH_KEYBOARD_LL` soft-force + Esc 双语义，§5 妥协设计）；CI 验证接入闭环，真实覆盖/键盘拦截/Esc 双语义归真机验收。\n- **Phase 3 ✅**：日历门控（Microsoft Graph + MSAL 设备码 + 条件注入降级，解析层/缓存/Provider mock 12 测试可验）；OAuth 授权与真实会议数据归 Windows 真机验收（CI 无账户）。\n- **Phase 4 ✅**：CI 多平台 Release（`release.yml` 3-job matrix，打 tag 即同时发布 macOS + Windows 双 asset）；Windows 暂无签名（SmartScreen 告知，签名留后续 Azure Trusted Signing/证书单独 workflow）。\n\n**Windows 构建**（需 Windows + .NET 8 SDK，macOS 无法编译 WPF 工程）：\n\n```powershell\ndotnet build windows/GiveMeABreakShell/GiveMeABreakShell.csproj -c Release\ndotnet publish windows/GiveMeABreakShell/GiveMeABreakShell.csproj -c Release -r win-x64 --self-contained -o ./publish\n.\\publish\\GiveMeABreakShell.exe\n```\n\n配置文件：`%APPDATA%\\com.aurelius.givemeabreak\\`（与 macOS 同 schema）。**真机验收限制**：托盘图标、粉噪音出声、QQ 音乐联动、全屏遮罩覆盖/键盘拦截需在 Windows 真机验收（CI 无 explorer shell/音频设备/QQ 音乐/桌面会话）；CI 验证接入闭环与不崩。\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n## License\n\nMIT © [ThreeFish-AI](https://github.com/ThreeFish-AI)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreefish-ai%2Fgive-me-a-break","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthreefish-ai%2Fgive-me-a-break","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreefish-ai%2Fgive-me-a-break/lists"}