{"id":44730207,"url":"https://github.com/itspriddle/ical-guy","last_synced_at":"2026-04-14T03:00:59.089Z","repository":{"id":338645614,"uuid":"1158601102","full_name":"itspriddle/ical-guy","owner":"itspriddle","description":"Modern Swift CLI for querying macOS calendar events","archived":false,"fork":false,"pushed_at":"2026-02-17T22:39:15.000Z","size":171,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-18T00:36:43.831Z","etag":null,"topics":["calendar","cli","ical","ical-parser","macos","swift"],"latest_commit_sha":null,"homepage":"https://priddle.net/oss/ical-guy/","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/itspriddle.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":null,"dco":null,"cla":null}},"created_at":"2026-02-15T16:34:00.000Z","updated_at":"2026-02-17T21:29:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/itspriddle/ical-guy","commit_stats":null,"previous_names":["itspriddle/ical-guy"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/itspriddle/ical-guy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itspriddle%2Fical-guy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itspriddle%2Fical-guy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itspriddle%2Fical-guy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itspriddle%2Fical-guy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/itspriddle","download_url":"https://codeload.github.com/itspriddle/ical-guy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itspriddle%2Fical-guy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31779947,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["calendar","cli","ical","ical-parser","macos","swift"],"created_at":"2026-02-15T18:12:23.274Z","updated_at":"2026-04-14T03:00:59.083Z","avatar_url":"https://github.com/itspriddle.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ical-guy\n\n[![CI](https://github.com/itspriddle/ical-guy/actions/workflows/ci.yml/badge.svg)](https://github.com/itspriddle/ical-guy/actions/workflows/ci.yml)\n[![Swift 6.0+](https://img.shields.io/badge/Swift-6.0+-FA7343.svg)](https://swift.org)\n[![macOS 14+](https://img.shields.io/badge/macOS-14+-000000.svg)](https://developer.apple.com/macos/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003e I'm not your buddy, guy.\n\nA modern Swift CLI for querying macOS calendar events and reminders. Built with EventKit as a replacement for the now-unmaintained icalBuddy.\n\nSupports text and JSON output, ANSI colors, meeting detection (Zoom, Google Meet, Teams, WebEx), reminders, and TOML configuration.\n\nRequires macOS 14 (Sonoma) or later.\n\n## Install\n\n### Homebrew\n\n```\nbrew install itspriddle/brews/ical-guy\n```\n\n### From source\n\n```\ngit clone https://github.com/itspriddle/ical-guy.git\ncd ical-guy\nmake install\n```\n\n### From GitHub release\n\nDownload the universal binary from the [releases page](https://github.com/itspriddle/ical-guy/releases), extract, and move to your PATH:\n\n```\ntar -xzf ical-guy-v0.13.0-macos-universal.tar.gz\nmv ical-guy /usr/local/bin/\n```\n\n## Usage\n\nOn first run, macOS will prompt for calendar access. Grant it in **System Settings \u003e Privacy \u0026 Security \u003e Calendars**. Reminder commands will separately prompt for reminders access (**Privacy \u0026 Security \u003e Reminders**).\n\n### Output format\n\nOutput format is auto-detected: **text** when stdout is a terminal, **JSON** when piped. Override with `--format`:\n\n```sh\n# Force JSON output to terminal\nical-guy events --format json\n\n# Force text output when piping\nical-guy events --format text | less -R\n```\n\nColors are enabled by default and adapt to terminal capabilities (truecolor, 256-color, 16-color). Disable with `--no-color` or by setting the `NO_COLOR` environment variable.\n\n### List calendars\n\n```\n$ ical-guy calendars\nHome (calDAV, iCloud)\nUS Holidays (subscription, Subscribed Calendars)\n```\n\n### Query events\n\n```\n$ ical-guy events\n```\n\nWith no options, returns today's events:\n\n```\nSunday, Feb 15, 2026\n  11:00 AM - 12:00 PM  Galentine's Brunch  [Family]\n  2:00 PM - 3:00 PM  Team Standup  [Work]\n    Meeting: https://meet.google.com/abc-defg-hij\n    Attendees:\n      - Alice Smith \u003calice@example.com\u003e (accepted)\n      - Bob Jones \u003cbob@example.com\u003e (you)\n    Recurs: Every weekday\n```\n\n### Events options\n\n```\n$ ical-guy events --help\nOPTIONS:\n  --format \u003cformat\u003e           Output format: json or text (auto-detects based on TTY).\n  --no-color                  Disable colored output.\n  --group-by \u003cmode\u003e           Group output: none, date, or calendar.\n  --show-empty-dates          Show dates with no events (implies --group-by date).\n  --from \u003cfrom\u003e               Start date (ISO 8601, 'today', 'tomorrow', 'yesterday', 'today+N', or natural language).\n  --to \u003cto\u003e                   End date (same formats as --from).\n  --include-calendars         Only include these calendars (comma-separated titles).\n  --exclude-calendars         Exclude these calendars (comma-separated titles).\n  --include-cal-types         Only include these calendar types (comma-separated).\n  --exclude-cal-types         Exclude these calendar types (comma-separated).\n  --exclude-all-day           Exclude all-day events.\n  --limit \u003climit\u003e             Maximum number of events to output.\n  --template \u003cpath\u003e           Path to a .mustache template file for event rendering.\n  --time-format \u003cpattern\u003e     Time format string (ICU pattern, e.g. \"HH:mm\" for 24-hour).\n  --date-format \u003cpattern\u003e     Date format string (ICU pattern, e.g. \"yyyy-MM-dd\").\n  --show-uid                  Show event UIDs in text output.\n  --bullet \u003cstring\u003e           Bullet prefix for each event (e.g. \"→ \").\n  --separator \u003cstring\u003e        Separator between events (e.g. \"---\").\n  --indent \u003cstring\u003e           Indentation for detail lines (default: 4 spaces).\n  --truncate-notes \u003cn\u003e        Truncate notes to N characters (0 = no limit).\n  --truncate-location \u003cn\u003e     Truncate location to N characters (0 = no limit).\n  --max-attendees \u003cn\u003e         Max attendees in text output (0 = no limit).\n```\n\n### Grouping\n\nEvents can be grouped by date or calendar using `--group-by`:\n\n```sh\n# Group by date (auto-activates for multi-day ranges)\nical-guy events --from today --to today+3\n\n# Explicitly group by calendar\nical-guy events --from today --to today+7 --group-by calendar\n\n# Show empty dates (implies --group-by date)\nical-guy events --from today --to today+7 --show-empty-dates\n\n# Flat list with no grouping\nical-guy events --group-by none\n```\n\nGrouping auto-detection: multi-day ranges default to `--group-by date`, single-day ranges default to `--group-by none`. The `--show-empty-dates` flag implies `--group-by date`.\n\nReminders can also be grouped by list:\n\n```sh\nical-guy reminders list --group-by calendar\n```\n\n### Calendar type filtering\n\nFilter events by calendar type using `--include-cal-types` / `--exclude-cal-types`. Valid types: `local`, `calDAV`, `exchange`, `subscription`, `birthday`, `icloud`.\n\n```sh\n# Only show calDAV calendar events\nical-guy events --include-cal-types calDAV\n\n# Exclude birthday and subscription calendars\nical-guy events --exclude-cal-types birthday,subscription\n\n# Only iCloud-sourced calendars (calDAV calendars with iCloud source)\nical-guy events --include-cal-types icloud\n```\n\nThe `icloud` type is a virtual alias that matches calDAV calendars with an iCloud source. Type matching is case-insensitive.\n\nThese flags are also available on `conflicts` and `free` commands and can be set in the config file.\n\n### Meetings\n\nThe `meeting` command group provides quick access to meetings with detected video call URLs:\n\n```sh\n# Show current meeting\nical-guy meeting now\n\n# Show next upcoming meeting\nical-guy meeting next\n\n# Open current meeting URL in browser\nical-guy meeting open\n\n# Open next meeting URL in browser\nical-guy meeting open --next\n\n# Open in a specific browser\nical-guy meeting open --browser \"Google Chrome\"\n\n# List today's meetings (events with video call URLs)\nical-guy meeting list\n```\n\nMeeting subcommands support `--include-calendars` and `--exclude-calendars` for filtering, and `--format`/`--no-color` for output control (except `meeting open`).\n\n#### Browser selection\n\n`meeting open` supports per-vendor browser overrides via the config file and a `--browser` CLI flag. Priority: `--browser` flag \u003e vendor-specific config \u003e `[browsers] default` config \u003e system default.\n\n```toml\n[browsers]\ndefault = \"Safari\"\nmeet = \"Google Chrome\"\nzoom = \"Safari\"\nteams = \"Microsoft Edge\"\nwebex = \"Google Chrome\"\n```\n\nVendor keys (`meet`, `zoom`, `teams`, `webex`) correspond to the detected meeting URL type. See Configuration below.\n\n### Conflicts\n\nThe `conflicts` command detects double-booked events in a date range:\n\n```sh\n# Check today for conflicts\nical-guy conflicts\n\n# Check the next week\nical-guy conflicts --from today --to today+7\n\n# Include all-day events in conflict detection\nical-guy conflicts --include-all-day\n\n# Filter by calendar\nical-guy conflicts --include-calendars Work\n```\n\nText output groups conflicts by day:\n\n```\nMonday, Feb 16, 2026\n  CONFLICT (2 events, 10:00 AM - 11:30 AM)\n    10:00 AM - 11:00 AM  Team Standup  [Work]\n    10:30 AM - 11:30 AM  1:1 with Alice  [Work]\n\nFound 1 conflict.\n```\n\nEvents are automatically excluded from conflict detection if they are canceled, marked as \"free\" availability, or if you declined the invitation. All-day events are excluded by default (opt in with `--include-all-day`).\n\n### Free time\n\nThe `free` command finds open time slots within working hours for deep work planning:\n\n```sh\n# Today's free time\nical-guy free\n\n# Free time for the next 5 days\nical-guy free --from today --to today+5\n\n# Free time from now (clips today to remaining time)\nical-guy free --from now\n\n# Custom working hours and minimum slot duration\nical-guy free --work-start 08:00 --work-end 18:00 --min-duration 60\n\n# Filter by calendar\nical-guy free --exclude-calendars \"US Holidays\"\n```\n\nText output shows free slots with duration tiers:\n\n```\nMonday, Feb 16, 2026  (3h 30m free)\n  9:00 AM - 10:00 AM  1h  [focus]\n  11:30 AM - 2:00 PM  2h 30m  [deep work]\n\nSummary: 3h 30m free across 1 day\nWorking hours: 9:00 AM - 5:00 PM, minimum slot: 30 minutes\n```\n\nDuration tiers: **deep work** (2h+), **focus** (1-2h), **short** (30-60m), **brief** (\u003c30m). The same scheduling filters as `conflicts` are applied (canceled, free-availability, and declined events are excluded).\n\nDefaults can be configured in the TOML config file (see Configuration).\n\n### Birthdays\n\nThe `birthdays` command lists upcoming birthdays from the Contacts birthday calendar:\n\n```sh\n# Upcoming birthdays (next 30 days)\nical-guy birthdays\n\n# Birthdays in a specific range\nical-guy birthdays --from today --to today+90\n\n# Limit results\nical-guy birthdays --limit 5\n\n# JSON output\nical-guy birthdays --format json\n```\n\nText output groups birthdays by date:\n\n```\nMonday, Feb 16, 2026\n  John Smith\n  Jane Doe\n\nThursday, Feb 19, 2026\n  Bob Jones\n```\n\n### Reminders\n\nThe `reminders` command group provides read-only access to macOS Reminders:\n\n```sh\n# List incomplete reminders (default)\nical-guy reminders\n\n# List completed reminders\nical-guy reminders list --completed\n\n# List all reminders (completed and incomplete)\nical-guy reminders list --all\n\n# Filter by due date range\nical-guy reminders list --from today --to today+7\n\n# Filter by reminder list\nical-guy reminders list --include-lists \"Work,Shopping\"\nical-guy reminders list --exclude-lists \"Birthdays\"\n\n# Sort and limit\nical-guy reminders list --sort-by priority --limit 10\n\n# List available reminder lists\nical-guy reminders lists\n```\n\nText output shows a checkbox, title, list name, due date, and priority:\n\n```\n[ ] Buy groceries  [Shopping]  due: Feb 20, 2026  !high\n[ ] Call dentist  [Personal]  due: Feb 22, 2026\n[x] Send report  [Work]\n```\n\nReminder subcommands support `--format`/`--no-color` for output control.\n\n### Week number\n\nThe `week` command prints the Calendar.app week number for a given date:\n\n```sh\n# Current week number (zero-padded)\n$ ical-guy week\n08\n\n# Week number for a specific date\n$ ical-guy week 2026-03-01\n\n# Offset by weeks\n$ ical-guy week --next 2\n$ ical-guy week --prev 1\n\n# Start/end dates of the week (Sunday and Saturday)\n$ ical-guy week --start-date\n$ ical-guy week --end-date\n\n# Unpadded week number\n$ ical-guy week --no-pad\n\n# Full JSON output\n$ ical-guy week --format json\n{\n  \"endDate\" : \"2026-02-21\",\n  \"startDate\" : \"2026-02-15\",\n  \"week\" : 8,\n  \"year\" : 2026\n}\n```\n\n### Examples\n\n```sh\n# Today's events (default)\nical-guy events\n\n# This week\nical-guy events --from today --to today+7\n\n# Tomorrow, work calendar only, no all-day events\nical-guy events --from tomorrow --to tomorrow --include-calendars Work --exclude-all-day\n\n# Specific date range\nical-guy events --from 2024-03-15 --to 2024-03-22\n\n# JSON output piped to jq\nical-guy events --format json | jq '[.[] | select(.meetingUrl != null) | {title, meetingUrl}]'\n\n# Next 5 events\nical-guy events --from today --to today+30 --limit 5\n\n# Open current meeting in browser\nical-guy meeting open\n\n# Upcoming birthdays in the next 90 days\nical-guy birthdays --from today --to today+90\n\n# High priority reminders due this week\nical-guy reminders list --from today --to today+7 --sort-by priority\n\n# All reminders as JSON piped to jq\nical-guy reminders list --all --format json | jq '[.[] | select(.isCompleted == false)]'\n\n# Check for conflicts this week\nical-guy conflicts --from today --to today+7\n\n# Find free time for deep work today\nical-guy free --from now --min-duration 60\n\n# Free time for the week as JSON\nical-guy free --from today --to today+5 --format json\n\n# This week's events grouped by calendar\nical-guy events --from today --to today+7 --group-by calendar\n\n# Show empty dates in a range\nical-guy events --from today --to today+7 --show-empty-dates\n\n# Reminders grouped by list\nical-guy reminders list --group-by calendar\n```\n\n### Meeting URL extraction\n\nThe `meetingUrl` field is automatically populated when a Google Meet, Zoom, Microsoft Teams, or WebEx URL is found in an event's `url`, `location`, or `notes` fields (checked in that priority order). If no meeting URL is detected, the field is `null`.\n\n### Date formats\n\n| Format | Example | Description |\n|---|---|---|\n| `today` | `--from today` | Start of today |\n| `tomorrow` | `--from tomorrow` | Start of tomorrow |\n| `yesterday` | `--from yesterday` | Start of yesterday |\n| `today+N` | `--to today+7` | N days from today |\n| `today-N` | `--from today-3` | N days before today |\n| `now` | `--from now` | Current date/time |\n| ISO 8601 | `--from 2024-03-15` | Specific date |\n| Natural language | `--from \"june 10 at 6pm\"` | English date phrases (e.g. \"next friday\", \"march 1, 2026\") |\n\n### JSON output\n\nWhen using `--format json` (or piping), events include rich structured data. The JSON structure depends on the `--group-by` mode:\n\n**Flat (default, `--group-by none`):** A JSON array of event objects.\n\n**Grouped by date (`--group-by date`):** An array of date group objects:\n\n```json\n[\n  {\n    \"date\": \"2024-03-15\",\n    \"events\": [...]\n  }\n]\n```\n\n**Grouped by calendar (`--group-by calendar`):** An array of calendar group objects:\n\n```json\n[\n  {\n    \"calendar\": { \"id\": \"...\", \"title\": \"Work\", ... },\n    \"events\": [...]\n  }\n]\n```\n\nEach event object contains:\n\n```json\n{\n  \"id\": \"E3A4B5C6-...\",\n  \"title\": \"Team Standup\",\n  \"startDate\": \"2024-03-15T14:00:00Z\",\n  \"endDate\": \"2024-03-15T14:30:00Z\",\n  \"isAllDay\": false,\n  \"location\": \"Conference Room B\",\n  \"notes\": \"Weekly sync\",\n  \"url\": null,\n  \"meetingUrl\": \"https://meet.google.com/abc-defg-hij\",\n  \"meetingVendor\": \"meet\",\n  \"calendar\": {\n    \"id\": \"A1B2C3D4-...\",\n    \"title\": \"Work\",\n    \"type\": \"calDAV\",\n    \"source\": \"iCloud\",\n    \"color\": \"#1BADF8\"\n  },\n  \"attendees\": [\n    {\n      \"name\": \"Alice Smith\",\n      \"email\": \"alice@example.com\",\n      \"status\": \"accepted\",\n      \"role\": \"required\",\n      \"isCurrentUser\": false\n    }\n  ],\n  \"organizer\": {\n    \"name\": \"Bob Jones\",\n    \"email\": \"bob@example.com\"\n  },\n  \"recurrence\": {\n    \"isRecurring\": true,\n    \"description\": \"Every weekday\"\n  },\n  \"status\": \"confirmed\",\n  \"availability\": \"busy\",\n  \"timeZone\": \"America/New_York\",\n  \"creationDate\": \"2024-01-15T10:00:00Z\",\n  \"lastModifiedDate\": \"2024-03-10T08:30:00Z\"\n}\n```\n\nReminders with `--group-by calendar` produce an array of list group objects:\n\n```json\n[\n  {\n    \"list\": { \"id\": \"...\", \"title\": \"Shopping\", ... },\n    \"reminders\": [...]\n  }\n]\n```\n\n## Configuration\n\nical-guy supports an optional TOML config file at `~/.config/ical-guy/config.toml` (or `$XDG_CONFIG_HOME/ical-guy/config.toml`). CLI flags always take precedence over config values.\n\n```toml\n[defaults]\nformat = \"text\"                          # \"text\" or \"json\"\nexclude-all-day = false\ninclude-calendars = [\"Work\", \"Personal\"]\nexclude-calendars = [\"US Holidays\"]\ninclude-cal-types = [\"calDAV\"]           # filter by calendar type\nexclude-cal-types = [\"subscription\"]\ngroup-by = \"date\"                        # \"none\", \"date\", or \"calendar\"\nshow-empty-dates = true\n\n[text]\nshow-calendar = true\nshow-location = true\nshow-attendees = true\nshow-meeting-url = true\nshow-notes = false\nshow-uid = false\n\n[free]\nmin-duration = 30                        # Minimum free slot in minutes\nwork-start = \"09:00\"                     # Working hours start (HH:MM)\nwork-end = \"17:00\"                       # Working hours end (HH:MM)\n\n[browsers]\ndefault = \"Safari\"                       # Default browser for meeting open\nmeet = \"Google Chrome\"                   # Google Meet\nzoom = \"Safari\"                          # Zoom\nteams = \"Microsoft Edge\"                 # Microsoft Teams\nwebex = \"Google Chrome\"                  # WebEx\n\n[templates]\ntime-format = \"HH:mm\"                   # ICU time format (default: \"h:mm a\")\ndate-format = \"yyyy-MM-dd\"              # ICU date format (default: \"EEEE, MMM d, yyyy\")\nevent = \"{{startTime}} - {{title}}\"     # Inline Mustache template for events\nevent-file = \"my-template.mustache\"     # External template file (overrides inline)\ndate-header = \"{{#bold}}=== {{formattedDate}} ==={{/bold}}\"\ncalendar-header = \"{{#calendarColor}}{{title}}{{/calendarColor}}\"\nbullet = \"→ \"                           # Bullet prefix for each event\nindent = \"  \"                           # Indentation for detail lines\nseparator = \"---\"                       # Separator between events\ntruncate-notes = 80                     # Truncate notes to N characters\ntruncate-location = 40                  # Truncate location to N characters\nmax-attendees = 10                      # Max attendees in text output (0 = no limit)\n```\n\nTemplate files are loaded from `~/.config/ical-guy/templates/` (relative paths) or from absolute paths. CLI `--template` flag takes precedence over `event-file`, which takes precedence over `event` inline.\n\n### Templates\n\nText output uses [Mustache](https://mustache.github.io/) templates. Customize event rendering with inline templates in `config.toml`, external `.mustache` files, or the `--template` CLI flag.\n\nAvailable template variables:\n\n| Variable | Description |\n|---|---|\n| `{{title}}` | Event title |\n| `{{startTime}}` / `{{endTime}}` | Formatted start/end time |\n| `{{startDate}}` / `{{endDate}}` | Formatted start/end date |\n| `{{relativeStart}}` / `{{relativeEnd}}` | Relative time (e.g. \"in 30 minutes\") |\n| `{{location}}` | Event location |\n| `{{notes}}` | Event notes |\n| `{{meetingUrl}}` | Detected meeting URL |\n| `{{status}}` | Event status (e.g. \"confirmed\") |\n| `{{availability}}` | Availability (e.g. \"busy\") |\n| `{{id}}` | Event UID |\n| `{{calendar.title}}` | Calendar name |\n| `{{calendar.color}}` | Calendar hex color |\n| `{{organizer.name}}` / `{{organizer.email}}` | Organizer info |\n| `{{recurrence.description}}` | Recurrence rule |\n\nBoolean sections for conditional rendering:\n\n| Section | Description |\n|---|---|\n| `{{#isAllDay}}...{{/isAllDay}}` | All-day events |\n| `{{#isRecurring}}...{{/isRecurring}}` | Recurring events |\n| `{{#hasLocation}}...{{/hasLocation}}` | Has location |\n| `{{#hasMeetingUrl}}...{{/hasMeetingUrl}}` | Has meeting URL |\n| `{{#hasAttendees}}...{{/hasAttendees}}` | Has attendees |\n| `{{#showCalendar}}...{{/showCalendar}}` | Display toggle (config-controlled) |\n\nANSI formatting lambdas (disabled with `--no-color`):\n\n| Lambda | Description |\n|---|---|\n| `{{#bold}}text{{/bold}}` | Bold text |\n| `{{#dim}}text{{/dim}}` | Dimmed text |\n| `{{#calendarColor}}text{{/calendarColor}}` | Calendar's color |\n\nIterate attendees with `{{#attendees}}...{{/attendees}}`, using `{{name}}`, `{{email}}`, `{{status}}`, and `{{{displayString}}}` inside the loop. Attendee overflow variables (when `--max-attendees` truncates): `{{attendeesTotalCount}}`, `{{#hasAttendeesOverflow}}`, `{{attendeesOverflowCount}}`.\n\nExample templates:\n\n```mustache\n{{! Minimal one-line }}\n{{startTime}} {{title}}\n\n{{! Detailed with relative time }}\n{{#bold}}{{title}}{{/bold}} — {{relativeStart}}\n{{#hasLocation}}  @ {{location}}{{/hasLocation}}\n```\n\nSee `man ical-guy` for the full template reference.\n\n## Development\n\nRequires Swift 6.0+ and Xcode (for running tests).\n\n```\nmake help\n\n  build        Build debug binary\n  release      Build release binary\n  universal    Build universal (arm64 + x86_64) release binary\n  test         Run tests\n  clean        Remove build artifacts\n  install      Install to PREFIX (default: /usr/local)\n  uninstall    Remove installed binary\n  deps         Install dependencies via Homebrew\n  lint         Run SwiftLint\n  format       Run swift-format\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitspriddle%2Fical-guy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fitspriddle%2Fical-guy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitspriddle%2Fical-guy/lists"}