关于 when-free
when-free 是面向会议协调的本地空闲时间工具,可读取日历订阅或 ICS 文件,并根据指定日期和时段生成空闲时间,也能解析粘贴的邀约内容。
主要功能
- 01读取 Google、Outlook 和 iCloud 日历
- 02合并多个日历
- 03从消息中提取日期和时段
- 04按时长与时区筛选时段
- 05提供 CLI、MCP 和 HTTP 接口
适用场景
- 01回复会议邀约并提供可用时间
- 02汇总工作与个人日历的空档
- 03为脚本输出 JSON 空闲时间
- 04查看其他时区的可用时段
MCP 相关
MCP 服务向客户端提供 `free_slots` 和 `check_calendars`,基于已配置日历返回空闲时间和日历读取状态。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx when-freeWHENFREE_CALENDARS密钥Private iCal addresses of your calendars, comma-separated. Optional if you saved them with `whenfree add`.WHENFREE_TZTime zone for answers, like Europe/London. Default: the computer's.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
3收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| to | string | 否 | 日期范围的最后一天,YYYY-MM-DD。 |
| days | string | 否 | 指定日期,使用逗号分隔。ISO 日期最可靠:"2026-10-05, 2026-10-06"。也支持:"Thu 1 Oct, Fri 2 Oct"、"tomorrow"、"next week"。 |
| from | string | 否 | 日期范围的第一天,YYYY-MM-DD。默认值:今天。 |
| hours | string | 否 | 提供时段的部分,格式为 HH:MM-HH:MM,例如 "10:00-16:00",或 "morning"、"afternoon"、"evening"。默认值:用户配置的时段。 |
| message | string | 否 | 用于读取建议日期和时间的文本,例如询问可用时间的邮件。会读取明确日期;如果没有明确日期,则读取相对于今天的表达:"tomorrow"、"Thursday"、"next Tuesday"、"next week"、"any afternoon"。 |
| timezone | string | 否 | 答案使用的时区,例如 "Europe/London"。默认值:用户的时区。 |
| weekends | boolean | 否 | 在时间范围中包含周六和周日。默认值:false。 |
| min_minutes | integer | 否 | 返回时段的最短时长,单位为分钟。将其设为会议时长。默认值:用户的设置,通常为 60。 |
| all_day_busy | boolean | 否 | 将全天事件视为忙碌。默认值:false。 |
| include_busy | boolean | 否 | 同时返回每天占用时间的事件及其标题。标题属于隐私信息:仅在用户想查看时询问。默认值:false。 |
| buffer_minutes | integer | 否 | 每个事件前后预留的空闲时间。默认值:用户的设置,通常为 15。 |
详细介绍
when-free
有人问你什么时候有空。它会读取你的日历,并给出答案,让你可以直接粘贴。

- 粘贴消息,获取空闲时段。 它能读懂“下周二或周三,上午 10 点到下午 4 点”,并针对指定日期和时段给出答案。
- 支持 Google、Outlook 和 iCloud 日历,也支持任何
.ics文件。还可以同时使用多个日历。 - 保护隐私。 它在你的电脑上运行,无需账号或 API 密钥,也不会修改你的日历。
- 也能通过 AI 工具使用: Claude、Cursor 和其他 MCP 客户端、Open WebUI、n8n、Shortcuts、脚本。
- 也能在其他设备上使用: 手机、平板和手表、Home Assistant(以及通过它使用 Alexa、Google Home、Siri),还有 macOS、Linux 和 Windows 的菜单栏或状态栏。
目录
立即试用 · 添加日历 · 日常使用 · 解析消息 · 通过其他工具使用 · 其他设备 · 隐私 · 设置 · 忙碌时段的判定方式 · 限制 · 问题排查 · 收录位置
一分钟内试用
使用 uv,无需预先安装:
uvx when-free demo
要保留它(需要 Python 3.11 或更高版本):
uv tool install when-free # or: pipx install when-free
whenfree demo
在使用 Claude Desktop?可以跳过以上步骤:安装一键扩展。
whenfree demo 会生成下周的模拟日历,并据此回答招聘人员的消息。它不会读取你的任何数据,也不会保存任何内容:
> Hi! Thanks for applying. Could you share a few times on Tuesday, Wednesday or Thursday next week,
> between 10am and 4pm? The interview takes about an hour.
Free between 10:00 and 16:00 (Europe/London), slots of 60+ minutes, 15-minute buffer around events:
- Tue 13 Oct: 11:45–12:45, 14:15–16:00
- Wed 14 Oct: 12:15–14:15
- Thu 15 Oct: 13:15–14:45
添加日历
-
复制日历的私有地址。 这是一个以
.ics结尾的链接:日历 查找位置 Google Calendar 设置 → 点击左侧的日历 → 集成日历 → iCal 格式的私有地址 Outlook 设置 → 日历 → 共享日历 → 发布日历 → ICS 链接 iCloud 日历 → 点击日历旁的共享图标 → 公开日历( webcal://链接也可以)其他 任意 .ics链接,或导出的.ics文件 -
运行
whenfree add并粘贴地址。 粘贴时不会显示该地址。系统会先检查,只有可用时才会保存:$ whenfree add Paste the address and press Enter (it is not shown): Added 'personal': 412 events, 9 block time in the next 14 days. Saved in /Users/you/.config/when-free/config.toml, readable only by you. Now run: whenfree -
运行
whenfree。 你会看到接下来几个工作日的空闲时间。
在 Mac 上,pbpaste | whenfree add 可以直接从剪贴板读取地址。
请保密该地址。 它就像密码:任何拥有它的人都能读取该日历。只将它提供给
whenfree add,不要用于其他任何地方,例如聊天、Shell 命令或代码仓库。如果地址泄露,请在日历设置中重置。
Google Calendar 分步说明
请在浏览器中操作,因为手机应用不会显示该地址。
- 打开 Google Calendar 设置:点击齿轮图标,然后点击设置。
- 在左侧栏的我的日历的设置下,点击所需的日历。使用你自己姓名命名的日历就是接收邀请的日历。
- 向下滚动到集成日历,复制iCal 格式的私有地址。地址以
basic.ics结尾。不要选择“iCal 格式的公开地址”,它只适用于你已设为公开的日历。 - 运行
whenfree add,并在出现提示时粘贴地址。
找不到“私有地址”?工作和学校账号可能已将其关闭。请改为导出日历(设置 → 导入和导出 → 导出),解压后运行 whenfree add ~/calendars/work.ics。导出的是快照,因此日历变更后需要重新导出。
添加多个日历
只有当你添加的每个日历都显示为空闲时,该时段才会被视为空闲。
whenfree add --name work # a second calendar: asks for its address
whenfree add ~/calendars/family.ics # an exported file instead of an address
whenfree check # can every calendar be read?
这些日历保存在 ~/.config/when-free/config.toml 中,每个日历对应一个 [[calendar]] 块,其中包含 name 和 url 或 path。你可以手动编辑该文件。whenfree init 会创建一个空文件。
日常使用
| 需求 | 运行 |
|---|---|
| 查看接下来几个工作日的空闲时间 | whenfree |
| 查看消息中提到的日期和时间 | pbpaste | whenfree --message -(Mac 剪贴板)或 whenfree --message invite.txt |
| 查看下周的下午时段 | whenfree --days "next week" --hours afternoon |
| 指定日期 | whenfree --days "Thu 15 Oct, Fri 16 Oct" |
| 指定日期范围 | whenfree --from 2026-10-12 --to 2026-10-16 |
| 仅显示足以安排 90 分钟会议的时段 | whenfree --min 90 |
| 查看每天的占用情况 | whenfree --busy |
| 以其他时区显示结果 | whenfree --tz America/New_York |
| 获取脚本所需的 JSON | whenfree --format json |
| 复制结果 | whenfree ... | pbcopy(只会复制时段行) |
只有时段行会输出到标准输出。上下文信息会输出到标准错误,因此使用 | pbcopy 时,复制的内容正好可以粘贴到回复中。
阅读消息
--message 可以从普通文本中找出日期和时间。它能理解:
- 各种常见写法的日期:
Thu 1 Oct·Thursday the 1st of October·Monday, October 5th·5 October·2026-10-05·Wednesday 30th - 时间:
between 10:00am and 4:00pm·10:00–16:00·9-5pm·2 to 4 pm - 未给出日期时使用的词语(这里假设今天是 10 月 1 日星期四):
| 写法 | 解读为 |
|---|---|
today · tomorrow · the day after tomorrow | 10 月 1 日星期四 · 10 月 2 日星期五 · 10 月 3 日星期六 |
Thursday or Friday | 即将到来的周四和周五,包含今天:10 月 1 日星期四、10 月 2 日星期五 |
this Tuesday · next Tuesday | 本周的星期二(已经过去,因此会被排除并注明)· 下周的星期二,即 10 月 6 日星期二 |
next week · this week · the week after next | 对应周的工作日,从今天起算 |
Tuesday or Wednesday next week | 10 月 6 日星期二、10 月 7 日星期三 |
any afternoon · Friday morning · evening | 12:00–17:00 · 09:00–12:00 · 17:00–20:00,除非指定了具体时间 |
它总会告诉你如何解读日期,方便你对照消息进行核查。以下规则可以避免它猜测:
- 明确写出的日期优先。 如果“Next week:”这样的标题下方写着“Wednesday 30th, Thursday 1st”,不会因此再加上整整一周。
- **“Good morning”是问候语,**不是时间。
- **已经过去的日期会被排除并注明,**绝不会悄悄忽略。“Wednesday 30th”指最近的一个日期为 30 日的星期三,因此,来自上周的消息会被解读为上周。
- 相对日期以今天为准。 对于较早的消息,如果其中写着“tomorrow”,请使用
--days手动提供日期。
when-free 不会连接你的邮箱。你可以粘贴消息、通过管道传入消息,或将消息保存到文件中。
让你自己的语言模型阅读消息
如果你自己运行模型,也可以让模型来阅读。将以下内容添加到设置文件中:
[extract]
command = ["ollama", "run", "llama3.2"] # the prompt is sent on standard input
# command = ["my-cli", "ask", "{prompt}"] # or placed where {prompt} is
该命令会接收消息,并且必须输出 JSON:{"days": ["2026-10-05"], "hours": "10:00-16:00", "minutes": 60}。如果失败,则会改用模式匹配。只有在你进行配置后才会运行;使用 --no-extract 可在单次运行中跳过它。消息会发送给该命令所连接的对象,因此处理私人文本时请使用本地模型。
在其他工具中使用
所有方式都会给出相同的答案,只进行读取,并且除非另有要求,否则不会包含事件标题。
| 你的工具 | 用法 | 设置 |
|---|---|---|
| Claude Desktop | 一键扩展 | 双击 |
| Claude Code,搭配知道何时使用它的技能 | 插件 | 两条命令 |
| Claude Code、Cursor、VS Code,以及任何 MCP 客户端 | MCP 服务 | 一行 |
| Raycast,右键点击消息(macOS) | 使用方法 | 几分钟 |
| Open WebUI、n8n、Shortcuts、Raycast,以及任何能调用 URL 的工具 | 本地 HTTP 服务 | whenfree serve |
| 使用函数调用的自有 Agent | Schema 和调用 | 两条命令 |
| Python | 库 | import whenfree |
Claude Code 插件
该插件将 MCP 服务与一项技能打包在一起,用来告诉 Claude 何时使用它:当你询问自己的空闲时间、粘贴询问何时可以见面的消息,或在它提出时间之前。
/plugin marketplace add YauhenBichel/when-free
/plugin install when-free@when-free
然后添加一次日历(如果你尚未添加):uvx when-free add
MCP:Claude、Cursor 和其他助手
claude mcp add when-free -- uvx when-free mcp # Claude Code
对于 Cursor 和其他 MCP 客户端,将以下内容添加到其服务列表中。uvx 首次运行时会自动获取 when-free,因此无需安装:
{
"mcpServers": {
"when-free": { "command": "uvx", "args": ["when-free", "mcp"] }
}
}
如果你使用 uv tool install 安装,请改用 "command": "whenfree", "args": ["mcp"]。它也已列入 MCP registry,标识为 io.github.YauhenBichel/when-free,并收录于 mcprush 和 M8ven Trust Index。
然后只需询问:“这是招聘人员发来的消息。我能参加其中哪些时间?”助手会调用 free_slots,并根据你的实际日历作答。
| 工具 | 功能 |
|---|---|
free_slots | 每天的空闲时间。接受 days,或 from 和 to,或 message,以及 hours、min_minutes、buffer_minutes、timezone、weekends、all_day_busy、include_busy |
check_calendars | 检查每个日历是否可读取,并提供事件数量。不包含详情或地址 |
如果工具无法完成任务,助手会收到可读的错误信息(“无法读取日历 ‘work’”),绝不会猜测。
HTTP:Open WebUI、n8n、Shortcuts 和脚本
whenfree serve # http://127.0.0.1:8765, only reachable from this computer
curl -H "Authorization: Bearer $(cat ~/.config/when-free/token)" \
'http://127.0.0.1:8765/free_slots?days=next%20week&hours=afternoon'
- 使用 JSON 请求体向
/free_slots发送POST请求,或使用带查询字符串的GET请求。/check_calendars也相同。 - 返回内容为
{"ok": true, "text": "...", "data": {...}},或状态码为 400 的{"ok": false, "error": "..."}。 /openapi.json提供 OpenAPI 描述。要设置 Open WebUI、n8n 或 Shortcuts,请参阅配置指南。- 令牌会在首次运行时创建,并保存在
~/.config/when-free/token中,只有你能读取。也可以通过WHENFREE_TOKEN设置自己的令牌。每次工具调用都需要令牌。 - 发往此计算机以外任何主机的请求都会被拒绝。网页只能从你允许的来源调用它:
whenfree serve --allow-origin http://localhost:3000。使用--host 0.0.0.0时,网络中的其他设备也可以访问;此时只有令牌提供保护,因此仅在你信任的网络中使用。
函数调用执行环境
whenfree schema # the tool definitions: name, description, inputSchema
whenfree schema --format openai # the same, as {"type": "function", "function": {...}}
whenfree call free_slots --args '{"days": "next week", "hours": "10:00-16:00"}'
whenfree call 会输出 {"ok": true, "text": "...", "data": {...}},或 {"ok": false, "error": "..."},并以退出代码 1 结束。参数也可以通过标准输入传入。
Python
from whenfree import api
result = api.find_free(api.Query(days="next week", hours="10:00-16:00", min_minutes=45))
for day in result["days"]:
print(day["label"], day["free"]) # for example: Mon 12 Oct [['10:00', '11:45'], ['13:15', '16:00']]
find_free 返回普通的字典和列表,可直接用于 json.dumps;遇到问题时会抛出 api.Problem,其中的消息可安全显示。传入 reader= 可自行提供日历文本,而不从日历中获取。
在你的其他设备上
| 设备 | 开始使用 | 指南 |
|---|---|---|
| iPhone、iPad、Android、手表 | whenfree serve --lan,然后运行 whenfree devices add "My phone" 并扫描二维码 | 手机和平板 |
| Home Assistant、Alexa、Google Home、Siri、忙碌指示灯 | whenfree mqtt --broker mqtt://homeassistant.local | 智能家居和语音 |
| macOS 菜单栏、Waybar、i3blocks、Polybar、GNOME、Windows 托盘 | whenfree now --format xbar(或 waybar、i3blocks 等) | 桌面状态栏 |
$ whenfree now
Busy until 15:30 · next free 15:45–17:00
设备可以查看你是否有空以及你的空闲时段,但看不到事件标题。每部手机都有独立的令牌,你可以撤销令牌;无法读取的日历会显示为“未知”,绝不会显示为“空闲”。
隐私
- 它在你的电脑上运行。 它只会获取你的日历订阅源,不会获取其他内容。它从不写入日历,也从不发送消息。
- 你的日历地址会保密。 地址不会被打印出来,你粘贴时也不会显示,而且会存储在只有你能读取的文件中。错误信息会指出日历名称(“个人”),而不会显示其地址。
- 助手只能看到空闲时间,看不到你的事件。 除非助手使用
include_busy请求,否则不会包含事件标题;该选项的说明也会告知模型标题属于隐私信息。没有任何工具可以添加、更改或删除内容,也没有工具会接收日历地址;日历地址只会从你这里传给whenfree add。
设置
除日历外,所有设置都是可选的。设置存放在 ~/.config/when-free/config.toml 中,命令行标志的优先级高于文件中的设置。
| 设置 | 默认值 | 标志 | 含义 |
|---|---|---|---|
timezone | 你系统的时区 | --tz | 答案使用的时区,例如 Europe/London |
hours | 09:00-18:00 | --hours | 你提供的时段。也可以是 morning、afternoon、evening |
min_minutes | 60 | --min | 值得提供的最短时段 |
buffer_minutes | 15 | --buffer | 每个事件前后预留的空闲时间 |
weekends | false | --weekends | 在日期范围内包含星期六和星期日 |
all_day_busy | false | --all-day-busy | 全天事件会占用整天 |
days_ahead | 7 | 未指定日期时显示的工作日数量 | |
me | [] | 你的电子邮件地址,这样你拒绝的邀请就不会占用时间 | |
[[calendar]] | --calendar | name 和 url 或 path。每个日历使用一个区块;whenfree add 会写入这些内容 | |
[extract] command | 无 | --no-extract | 使用你自己的模型读取消息的命令 |
环境变量
适用于脚本、容器和 MCP 客户端配置。全部为可选项。
| 变量 | 含义 |
|---|---|
WHENFREE_CALENDARS | 以逗号分隔的日历地址或路径。替代文件中的列表。敏感信息:见下文 |
WHENFREE_CONFIG | 设置文件的路径 |
XDG_CONFIG_HOME | 未设置 WHENFREE_CONFIG 时查找设置文件的位置:$XDG_CONFIG_HOME/when-free/config.toml,否则为 ~/.config/when-free/config.toml |
WHENFREE_TZ | 答案使用的时区。优先级高于文件中的 timezone |
TZ | 你的系统时区;仅在以上两项都未指定时使用 |
WHENFREE_TOKEN | whenfree serve 使用的令牌,用于替代令牌文件。敏感信息 |
WHENFREE_NOW | 用作“现在”的 ISO 日期时间,以便重现一次运行 |
WHENFREE_CALENDARS 中包含密码。 私密日历地址的持有者都能读取该日历。请像保护 API 密钥一样保护此变量:不要将其放入共享的 shell 配置文件、已提交的 MCP 配置、CI 日志和屏幕截图中。whenfree add 写入的设置文件只有你能读取,因此在可行时请改用该文件。
什么情况算作忙碌
| 日历中的内容 | 是否占用时间? |
|---|---|
| 普通事件 | 是,且前后都会预留缓冲时间 |
| 标记为 空闲 的事件 | 否 |
| 已取消的事件 | 否 |
你拒绝的邀请(你的地址在 me 中) | 否 |
| 全天事件 | 否,除非 all_day_busy = true。此时会占用时间,除非该事件标记为“空闲” |
| 重复事件 | 每次重复都占用时间;跳过的日期除外,移动过的事件则按移动后的时间计算 |
经过缓冲时间后,短于 min_minutes 的时段不会被提供。对于今天,计算从下一个 15 分钟刻度开始,而不是从过去的时间开始。
限制
- 它只读取,不会预约。 它绝不会写入日历或发送消息。
- 数据新鲜度取决于日历源。 Google 刷新私密地址需要一些时间,因此刚刚添加的事件可能不会显示。运行
whenfree --busy查看它读取到的内容。 - 要么读取所有日历,要么一个也不显示。 如果有一个日历无法读取,就不会显示任何时段,因为忙碌时间可能会被误认为空闲。
- 重复事件。 支持展开每日、每周、每月(按日期或“第一个星期一”“最后一个星期五”)和每年的规则,以及间隔、结束日期、次数、跳过的日期和已移动的实例。不支持较少见的规则(
BYSETPOS、周数)。对于这些规则,首个日期会被标记为占用,并附有说明。 - 时区取自事件。如果时区数据库不认识某个时区名称(某些 Outlook 导出会出现这种情况),则会按你所在的时区读取。
如果遇到问题
| 你看到的内容 | 处理方法 |
|---|---|
No calendar is configured | 运行 whenfree add。如果你手动编辑了文件,则 url 行仍为空,或文件尚未保存;消息会指出文件名 |
could not read the calendar 'personal' (...) | 地址不完整,或不是私密地址;如果能够判断,消息会说明原因。使用复制按钮重新复制 |
returned a web page, not a calendar 或 did not return iCalendar data | 该链接不是日历源。它应以 .ics 结尾 |
Nothing was saved | whenfree add 无法读取日历,因此没有任何更改。修正地址后重新运行 |
| Google 设置中没有“私密地址” | 你的组织已将其关闭。请改为导出日历:参见Google Calendar,分步指南 |
| 刚添加的事件没有显示 | 日历源刷新需要一些时间。参见限制 |
| 会议没有标记为占用 | 运行 whenfree --busy 查看读取到的内容。已拒绝的邀请、标记为“空闲”的事件和全天事件不会标记为占用;参见哪些情况会被视为忙碌 |
whenfree check | 随时检查每个日历是否都能读取 |
收录位置
- 官方 MCP 注册表,名称为
io.github.YauhenBichel/when-free - PyPI,名称为
when-free - Glama、MCP Servers Live 和 MCP Changefeed,这些平台会收录 MCP 服务
如果你在某处使用了它,或撰文介绍了它,请提交 issue,相关内容就会收录在这里。
开发
uv run --with pytest pytest -q # a few seconds; no network
测试使用 tests/data/sample.ics 中的小型日历和固定时钟(WHENFREE_NOW)。代码按组件分组:
| 组件 | 包含内容 | 可使用 |
|---|---|---|
core/ | ical、slots、status、render、errors:事件、忙碌时间、空闲时段、当前状态和措辞。不负责输入或输出 | 无 |
messages/ | dates、extract:从消息中读取的日期和小时 | core |
settings/ | config:设置文件和环境变量 | 无 |
calendars/ | sources:从地址获取或从文件读取的日历 | core、settings |
api.py | find_free、status、check_calendars:所有前端共用的唯一入口 | 以上四项 |
agents/ | tools、mcp:提供给 AI Agent 的工具和 MCP 服务 | api、core |
web/ | server:供工具和手机使用的 HTTP 前端 | api、agents、devices、calendars、core、settings |
devices/ | widgets、pairing、feed、qr、mobile/:状态栏、手机配对、日历订阅源、手机页面 | api、core、settings |
smarthome/ | mqtt、homeassistant:通过 Home Assistant 发现机制发布 MQTT 消息 | api、core |
cli/ | main、demo:whenfree 命令 | 任意组件 |
如果某个组件导入了不允许使用的组件,tests/test_structure.py 就会报错,从而确保领域代码不依赖任何前端。
请参阅 CONTRIBUTING.md。
许可证
Apache-2.0。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。