Skip to content
Back to skills

Walnut Calendar

ASecurity

Read and edit the user's real calendars (macOS Calendar: iCloud, Google, Exchange accounts) through Walnut's /api/calendar REST endpoints. Use when asked about the user's schedule, meetings, or availability, or to create, move, retime, or delete calendar events. Covers listing events in a date range, enumerating calendars, and write-back that syncs to the cloud provider via macOS.

  • 32 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 1, 2026
developmentgobashapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add EvanZhang008/open-walnut --skill walnut-calendar --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Walnut Calendar?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Walnut Calendar
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/evanzhang008-walnut-calendar/badge)](https://www.skillsdirectory.com/skills/evanzhang008-walnut-calendar)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: walnut-calendar
description: >-
  Read and edit the user's real calendars (macOS Calendar: iCloud, Google,
  Exchange accounts) through Walnut's /api/calendar REST endpoints. Use when
  asked about the user's schedule, meetings, or availability, or to create,
  move, retime, or delete calendar events. Covers listing events in a date
  range, enumerating calendars, and write-back that syncs to the cloud
  provider via macOS.
---

# Walnut Calendar API

Walnut exposes the Mac's calendars (every account added in macOS System
Settings → Internet Accounts: iCloud, Google, Exchange, …) over REST. Edits
write back through macOS EventKit, so a change made here shows up in Google
Calendar / iCloud within seconds — no separate login or OAuth involved.

Base URL: `http://localhost:3456/api/calendar` (Walnut server).

## Date format contract (IMPORTANT)

All dates are **tz-less local ISO** — the server's local wall time, never a
`Z` or `+hh:mm` suffix:

- Timed: `2026-08-05T09:00:00`
- All-day / date-only: `2026-08-05`

## Read

```bash
# Events in a date range (inclusive days)
curl -s 'http://localhost:3456/api/calendar/events?from=2026-08-03&to=2026-08-09'
# → { "events": [ { "id", "title", "start", "end", "allDay", "calendarId",
#      "calendarName", "accountName", "color", "location?", "readonly?",
#      "status?", "selfStatus?" } ],
#     "sources": [ { "id": "eventkit", "available", "enabled", "reason?",
#      "message?", "lastRefresh?" } ] }

# Add fresh=1 when a stale answer would be worse than a slow one (~0.25s extra):
# it bypasses the read cache and re-reads macOS.
curl -s 'http://localhost:3456/api/calendar/events?from=2026-08-27&to=2026-08-27&fresh=1'

# List the calendars themselves (to pick a create target)
curl -s 'http://localhost:3456/api/calendar/sources'
# → { "sources": [...], "calendars": [ { "id", "title", "account", "color",
#      "readonly", "hidden" } ] }
```

- If `sources[0].available` is `false`, read `reason`/`message` — e.g.
  `permission-denied` means the user must grant Calendar access in System
  Settings → Privacy & Security → Calendars. `GET /events` still returns
  `{ events: [] }` in that case rather than erroring.
- Event ids of recurring occurrences look like `<baseId>#<epoch>`; treat the
  whole string as opaque and **URL-encode it** in paths (`#` → `%23`).

### Cancelled and declined events (read this before reporting a schedule)

Two optional fields say whether a meeting is actually happening. Both are
absent for ordinary personal events, which means "nothing to report" — never
read a missing field as "confirmed".

| field | values | meaning |
|---|---|---|
| `status` | `confirmed`, `tentative`, `canceled` | the meeting's own state |
| `selfStatus` | `pending`, `accepted`, `declined`, `tentative`, `delegated` | the user's response to the invite |

`status: "canceled"` events are **still returned**. An invitation the organizer
cancelled stays in the macOS store (often re-titled `Canceled: …`) until someone
processes the cancellation, so the API reports it and marks it rather than
quietly dropping it. When summarising a day, exclude `status: "canceled"` and
`selfStatus: "declined"` entries from "what you have on", or call them out as
cancelled/declined — do not present either as a meeting the user is attending.

### Freshness

`sources[0].lastRefresh` is when Walnut last read macOS. Reads are served from a
short-lived cache (default 60s), and macOS syncs Exchange/Google on its own
schedule on top of that, so a change made in Outlook seconds ago may not be
visible yet. Use `fresh=1` to skip Walnut's cache; if an event still looks wrong
after that, the lag is macOS↔provider sync, not Walnut, and the fix is on the
Mac (open Calendar.app and press ⌘R).

## Write

Only calendars with `readonly: false` accept writes (a write to a read-only
calendar returns 409).

```bash
# Create (end defaults are NOT applied server-side — always send end)
curl -s -X POST http://localhost:3456/api/calendar/events \
  -H 'Content-Type: application/json' \
  -d '{"calendarId":"<id from /sources>","title":"Dentist",
       "start":"2026-08-05T15:00:00","end":"2026-08-05T16:00:00"}'
# All-day: use date-only start/end (end inclusive) and "allDay": true.

# Move / retime / rename (start AND end required; PATCH only that occurrence
# for recurring events)
curl -s -X PATCH 'http://localhost:3456/api/calendar/events/<url-encoded id>' \
  -H 'Content-Type: application/json' \
  -d '{"start":"2026-08-06T15:00:00","end":"2026-08-06T16:00:00","title":"Dentist (moved)"}'

# Delete (only that occurrence for recurring events). Confirm with the user
# before deleting anything you did not just create.
curl -s -X DELETE 'http://localhost:3456/api/calendar/events/<url-encoded id>'
```

## Error codes

| HTTP | code | meaning |
|---|---|---|
| 400 | `usage` | bad params / date format |
| 403 | `permission-denied` | macOS Calendar access not granted (Full Access) |
| 404 | `not-found` | event id doesn't resolve |
| 409 | `readonly` | target calendar is read-only |
| 503 | `disabled` / `cloud` / `not-configured` | source off in Settings, cloud companion (no Mac calendars), or helper unavailable |
| 502 | other | EventKit helper failure — surface `message` to the user |

## Tips

- The Walnut web calendar (`/calendar`) live-updates after any write — no
  refresh call needed on your side.
- To answer "what's on my schedule", query a generous range and filter by
  `calendarName`/`accountName` yourself if the user means one account.
- Move = keep the duration: compute `new end = new start + (old end − old start)`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.