Skip to main content
Glama

when-free

Which days and times am I free? Someone asks for your availability. This reads your calendars and prints the slots you can offer, ready to paste into the reply.

tests License: Apache-2.0

A recruiter writes: "Please share your availability for Thursday 1st, Friday 2nd, Monday 5th, Tuesday 6th or Wednesday 7th, between 10:00am and 4:00pm." You copy the message and run one command:

$ pbpaste | whenfree --message -
Free between 10:00 and 16:00 (Europe/London), slots of 60+ minutes, 15-minute buffer around events:

- Thu 1 Oct: 12:15–16:00
- Fri 2 Oct: 10:15–13:45
- Mon 5 Oct: 10:00–14:45
- Tue 6 Oct: 10:00–14:45
- Wed 7 Oct: 10:15–12:00, 13:00–16:00

Dates and hours were read from the message by pattern matching. Check them against the message.

The five lines go to standard output and everything else to standard error, so whenfree ... | pbcopy copies exactly what you paste into your answer.

It runs on your own machine. It needs no account, no API key and no Google Cloud project, has no dependencies beyond Python, and sends nothing anywhere.

Install

Python 3.11 or newer.

pipx install git+https://github.com/YauhenBichel/when-free      # or: uv tool install git+https://github.com/YauhenBichel/when-free

Related MCP server: Chronary

Set up

Copy the private iCal address of your calendar, then run:

$ whenfree add
Where the private address of a calendar is:
  Google Calendar: Settings, your calendar, Integrate calendar, "Secret address in iCal format"
  Outlook: Settings, Calendar, Shared calendars, Publish a calendar, the ICS link
  iCloud: Calendar, the share icon next to the calendar, Public Calendar

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

It reads the calendar first and saves it only if that worked. The address is not shown as you paste it and is never printed afterwards. On macOS, pbpaste | whenfree add takes it straight from the clipboard.

Calendar

Where the address is

Google Calendar

Settings → your calendar → Integrate calendar → Secret address in iCal format

Outlook

Settings → Calendar → Shared calendars → Publish a calendar → the ICS link

iCloud

Calendar → the share icon next to the calendar → Public Calendar. A webcal:// address works as it is

Anything else

Any .ics address, or a path to an exported .ics file

Google Calendar, step by step

Do this in a browser. The phone app does not show the address.

  1. Open Google Calendar settings: the gear icon, then Settings.

  2. In the left column, under Settings for my calendars, click the calendar you want. The one with your own name is where invitations arrive.

  3. Scroll down to Integrate calendar and copy Secret address in iCal format. It ends in basic.ics. Do not take "Public address in iCal format": that one only works for a calendar you have made public.

  4. Run whenfree add and paste it when asked.

Give the address to whenfree add and to nothing else: not to a chat with an assistant, not to a shell command (--calendar with an address stays in your shell history), not to a repository.

More calendars, and doing it by hand

A slot is free only if it is free in every calendar you add.

whenfree add --name work                 # a second calendar: asks for its address
whenfree add ~/calendars/family.ics      # an exported file instead of an address
whenfree init                            # only creates the settings file, for you to edit

The settings file is ~/.config/when-free/config.toml, with one [[calendar]] block per calendar: a name, and a url or a path. whenfree add writes those blocks and leaves the rest of the file as it is.

At any time, check that every calendar can be read:

$ whenfree check
Settings: /Users/you/.config/when-free/config.toml   time zone: Europe/London
  ok  personal: 412 events, 9 block time in the next 14 days

If it does not work

What you see

What to do

No calendar is configured

Run whenfree add. If you edited the file by hand, the url line is still empty or the file was not saved; the message names the file

could not read the calendar 'personal' (...)

The address is incomplete or is not the secret one; the message says which when it can tell. Copy it again with the copy button

returned a web page, not a calendar or did not return iCalendar data

The address is not a feed. It should end in .ics

Nothing was saved

whenfree add could not read the calendar, so the settings file is unchanged. Fix the address and run it again

There is no "Secret address" in Google's settings

Work and school accounts can have it switched off by the administrator. Export the calendar instead (Settings → Import & export → Export), unzip it, and run whenfree add ~/calendars/work.ics. An export is a snapshot: export again when your calendar changes

An event you just added is missing

The feed is refreshed with a delay. See Limits

whenfree check is fine but a meeting does not block time

Run whenfree --busy to see what was read. An invitation you declined, an event marked Free and a whole-day event do not block; see What counts as busy

The address is a password. Anyone who has it can read that calendar. whenfree never prints it, whenfree add does not show it as you paste, the settings file is readable only by you, and an error names the calendar, not its address. If the address leaks, reset it in your calendar's settings.

Use

whenfree                                              # the next 7 working days
whenfree --from 2026-10-05 --to 2026-10-09            # a range
whenfree --days "Thu 1 Oct, Fri 2 Oct, Mon 5 Oct"     # specific days
whenfree --hours 10:00-16:00 --min 90                 # only 90-minute slots, in part of the day
whenfree --message invite.txt                         # days and hours from a message saved to a file
pbpaste | whenfree --message -                        # the same, from the clipboard (macOS)
whenfree --busy                                       # also show what blocks each day
whenfree --format json                                # for scripts
whenfree --calendar ~/Downloads/calendar.ics          # one calendar, without a settings file

Reading a message

--message reads the days and the daily window out of ordinary text by pattern matching. It understands explicit dates in the ways people write them:

Thu 1 Oct · Thursday the 1st of October · Monday, October 5th · 5 October · 2026-10-05 · Wednesday 30th

and hours such as between 10:00am and 4:00pm, 10:00–16:00, 9-5pm, 2 to 4 pm.

A bare "Wednesday 30th" is read as the date nearest to today that is both a Wednesday and a 30th, so a message from last week resolves to last week. Days already past are left out of the answer and named on standard error, never dropped silently.

It does not read "next week" or "any afternoon". For those, give the days yourself with --days or --from and --to. It always says how the dates were read, because you should check them against the message before you reply.

when-free does not connect to your mailbox. You paste the message, pipe it in, or save it to a file.

Reading a message with your own model

If you run a language model yourself, it can do the reading instead. Add to the settings file:

[extract]
command = ["ollama", "run", "llama3.2"]          # the prompt is sent on standard input
# command = ["my-cli", "ask", "{prompt}"]        # or placed where {prompt} is

The command receives the message and must print JSON: {"days": ["2026-10-05"], "hours": "10:00-16:00", "minutes": 60}. If it fails or prints something else, pattern matching is used. Nothing runs unless you configure it, and --no-extract skips it for one run. The message is sent to whatever that command talks to, so use a local model for private text.

Use it from an assistant or an agent

The same answer is available to programs in three ways. All of them read only, return the same data, and leave event titles out unless asked.

As an MCP server

whenfree mcp is a Model Context Protocol server on standard input and output, with no extra install.

claude mcp add when-free -- whenfree mcp                 # Claude Code

For Claude Desktop, Cursor and other MCP clients, add it to their server list:

{
  "mcpServers": {
    "when-free": { "command": "whenfree", "args": ["mcp"] }
  }
}

Then ask in ordinary words: "Here is the recruiter's message. Which of those times can I do?" The assistant calls free_slots and answers from your real calendar.

Tool

What it does

free_slots

Free time ranges per day. Takes days, or from and to, or a message to read the days from; plus hours, min_minutes, buffer_minutes, timezone, weekends, all_day_busy, include_busy

check_calendars

Whether each calendar can be read, with event counts. No details, no addresses

A tool that cannot do its job returns an error the assistant can read ("could not read the calendar 'work'"), never a guess.

From a function-calling harness

If your harness registers tools from JSON schemas and runs commands, it needs two things:

whenfree schema                    # the tool definitions: name, description, inputSchema
whenfree schema --format openai    # the same, as {"type": "function", "function": {...}}
whenfree call free_slots --args '{"days": "2026-10-05, 2026-10-06", "hours": "10:00-16:00"}'

whenfree call prints one JSON object: {"ok": true, "text": "...", "data": {...}}, or {"ok": false, "error": "..."} with exit code 1. The arguments can also come on standard input.

From Python

from whenfree import api

result = api.find_free(api.Query(days="2026-10-05, 2026-10-06", hours="10:00-16:00", min_minutes=45))
for day in result["days"]:
    print(day["label"], day["free"])          # Mon 5 Oct [['10:00', '14:45']]

find_free returns plain dicts and lists, ready for json.dumps, and raises api.Problem with a message that is safe to show.

What an agent can and cannot see

  • Free time: yes. That is the point.

  • Event titles: only on request. include_busy is off by default, and its description tells the model that titles are private.

  • Calendar addresses: never. They are not in any tool result or error.

  • Changing anything: no. There is no tool that writes.

What counts as busy

In your calendar

Blocks time?

An ordinary event

Yes, plus the buffer on both sides

An event marked Free

No

A cancelled event

No

An invitation you declined (your address in me)

No

A whole-day event

No, unless all_day_busy = true. Then yes, unless it is marked Free

A repeating event

Each occurrence, with skipped dates skipped and moved occurrences where they were moved to

A slot shorter than min_minutes after the buffers are applied is not offered. For today, the day starts at the next quarter hour, not in the past.

Settings

All optional except a calendar. Command-line flags win over the file.

Setting

Default

Flag

Meaning

timezone

your system's

--tz

The zone the answer is given in, like Europe/London

hours

09:00-18:00

--hours

The part of the day you offer

min_minutes

60

--min

Shortest slot worth offering

buffer_minutes

15

--buffer

Kept free before and after every event

weekends

false

--weekends

Include Saturday and Sunday in a date range

all_day_busy

false

--all-day-busy

Whole-day events block the day

days_ahead

7

Working days shown when you give no dates

me

[]

Your addresses, so declined invitations do not block

[[calendar]]

--calendar

name and url or path. One block per calendar; whenfree add writes them

[extract] command

none

--no-extract

A command that reads a message with your own model

Environment variables, for scripts and containers: WHENFREE_CONFIG (path to the settings file), WHENFREE_CALENDARS (comma-separated addresses or paths, replacing the file's list), WHENFREE_TZ.

Limits, stated plainly

  • It reads; it does not book. It never writes to a calendar and never sends a message.

  • It is as fresh as the feed. Google refreshes the secret iCal address with some delay, so an event added a minute ago may not be there yet. Run whenfree --busy to see what it saw.

  • Recurrence rules. Daily, weekly, monthly (by date, or "first Monday", "last Friday") and yearly rules are expanded, with intervals, end dates, counts, skipped dates and moved occurrences. Rarer rules (BYSETPOS, week numbers) are not. For those the first date is blocked and a note says so. Nothing is guessed.

  • Every calendar or none. If one calendar cannot be read, no slots are printed, because busy time would look free.

  • Time zones come from the events. A zone name the tz database does not know (some Outlook exports) is read in your own zone.

Development

uv run --with pytest pytest -q        # under a second; no network

The tests use a small calendar in tests/data/sample.ics and a fixed clock (WHENFREE_NOW). See CONTRIBUTING.md.

Licence

Apache-2.0.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Calendar API purpose-built for AI agents. Exposes tools to manage agents, calendars, and events, find meeting times, run scheduling proposals, set availability rules, manage webhooks, and subscribe to iCal feeds.
    54
    65 npm
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides read-only access to calendar events from iCal feeds, enabling agents to query schedules, search events, and check availability via natural language.
    AGPL 3.0