/meeting-scheduler
Schedule a small meeting end-to-end: resolve attendee emails, check the owner's calendar for the slot, dedup-check, then create + email the Google Calendar invite. The mechanical core only — cross-person availability negotiation stays interactive.
$ npx -y skills add sonichi/sutando --skill meeting-scheduler --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/meeting-scheduler
Context preview
The summary Claude sees to decide when to auto-load this skill.
Schedule a small meeting end-to-end: resolve attendee emails, check the owner's calendar for the slot, dedup-check, then create + email the Google Calendar invite. The mechanical core only — cross-person availability negotiation stays interactive.
SKILL.md
meeting-scheduler.SKILL.mdname: meeting-scheduler
description: "Schedule a small meeting end-to-end: resolve attendee emails, check the owner's calendar for the slot, dedup-check, then create + email the Google Calendar invite. The mechanical core only — cross-person availability negotiation stays interactive."
user-invocable: true
Meeting Scheduler
Encapsulates the ~10 manual steps of "set up a quick meeting" into one command: **resolve attendee emails → check the owner's calendar for the slot → dedup-check → create the event + send invites → report the link.**
**Usage**: `/meeting-scheduler [what to schedule]`
ARGUMENTS: $ARGUMENTS
Scope (read first)
This skill handles the **mechanical** core only. It does **NOT** negotiate availability across people — proposing times, collecting "does 3pm work?", juggling everyone's free/busy stays an **interactive** conversation you have with the owner (and, if asked, the attendees). Once a concrete slot is chosen, this skill does the rote work.
**Authority note.** Creating and sending a calendar invite is an **owner action**. Run the create+send step only when the owner has invoked this skill for a concrete meeting. Never auto-send speculatively — default to a dry-run and show the plan first. The helper enforces this: it defaults to `--dry-run` and only creates with an explicit `--send`.
Inputs
| Input | Required | Notes | |---|---|---| | title | yes | Event summary, e.g. "Sutando sync" | | attendees | yes | Either explicit emails (`--attendees`) or names to resolve (`--resolve`) | | when | yes | Start time — ISO 8601 (`2026-07-25T15:00`) or `today/tomorrow HH[:MM][am/pm]` | | duration | no | Minutes; default 30 | | location | no | Room, address, or a video link | | description | no | Agenda / notes |
Emails must come from a **Gmail lookup** — the identity map (`workspace/.claude-sutando/projects/<slug>/memory/reference_identity_map.md`) resolves a name to a *person* (and their Discord/GitHub ids) but does **not** store email addresses. Use the map to disambiguate *who* is meant; get the address from Gmail.
Run order
Everything below is done by `scripts/schedule_meeting.py`, which shells out to the `gws` (Google Workspace) CLI. You can run it directly, or perform the steps by hand with the `gws` commands shown.
1. Resolve attendee emails (names → emails via Gmail)
For each name, search recent mail and read From/To headers to extract the address:
gws gmail users messages list --params '{"userId":"me","q":"\"Alice\" newer_than:365d","maxResults":10}'
# then, per hit id:
gws gmail users messages get --params '{"userId":"me","id":"<id>","format":"metadata","metadataHeaders":["From","To","Cc"]}'Strip any line containing `keyring` from gws output before parsing JSON. For the **ag2.ai** account, set `GOOGLE_WORKSPACE_CLI_CONFIG_DIR=$HOME/.config/gws-ag2` (the helper's `--account ag2` does this). If a name is ambiguous, confirm with the owner rather than guessing.
2. Check the OWNER's calendar for the slot
Read the owner's events across that day and flag anything whose busy time overlaps `[start, end)` (back-to-back is *not* a conflict; `transparency:transparent` / all-day events don't block):
gws calendar events list --params '{"calendarId":"primary","timeMin":"2026-07-25T00:00:00-07:00","timeMax":"2026-07-26T00:00:00-07:00","singleEvents":true,"orderBy":"startTime"}'3. Dedup-check
From the same day's events, refuse if one already has the same title (case/space-insensitive, ignoring cancelled ones) — prevents a double-booked duplicate invite.
4. Create the event + send invites
Only on explicit owner action (`--send`). `sendUpdates=all` makes Google email the invitations:
gws calendar events insert \
--params '{"calendarId":"primary","sendUpdates":"all"}' \
--json '{"summary":"Sutando sync","location":"Meet link","description":"...","start":{"dateTime":"2026-07-25T15:00:00","timeZone":"America/Los_Angeles"},"end":{"dateTime":"2026-07-25T15:30:00","timeZone":"America/Los_Angeles"},"attendees":[{"email":"a@x.com"},{"email":"b@y.com"}]}'5. Report the event link
Print the `htmlLink` from the insert response back to the owner.
The helper script
# Dry-run (default) — resolve, check, dedup, report. NO changes:
python3 skills/meeting-scheduler/scripts/schedule_meeting.py \
--title "Sutando sync" --when 2026-07-25T15:00 --duration-min 30 \
--resolve "Alice, Bob"
# Create + email invites (owner action):
python3 skills/meeting-scheduler/scripts/schedule_meeting.py \
--title "Sutando sync" --when 2026-07-25T15:00 --duration-min 30 \
--attendees "a@x.com,b@y.com" --location "https://meet.google.com/xxx" --send
# Override a detected conflict/duplicate (owner's explicit call):
# ... --send --force
Flags: `--title --when --duration-min --attendees --resolve --location --description --calendar (default primary) --timezone --account {default,ag2} --gmail-lookback --send/--dry-run --force --self-check --verbose`.
**Fail-safe behavior:**
- Defaults to `--dry-run` — nothing is created or emailed without `--send`.
- On a **conflict** or a same-title **duplicate**, it refuses to create even with
`--send`, unless `--force` is also given (the owner's explicit override).
- If no attendee email is present (none given, none resolved), it stops.
Offline self-check
The pure logic (when-parsing, conflict overlap, dedup, email-pick) is unit-tested without the network:
python3 skills/meeting-scheduler/scripts/schedule_meeting.py --self-check
python3 tests/meeting-scheduler.test.py
Notes / limitations
- **Natural-language dates are intentionally minimal** — ISO 8601 and
`today/tomorrow HH[:MM][am/pm]` only. For "next Tuesday afternoon" etc., resolve it to a concrete ISO time (the owner picks the slot) before calling the helper.
- Free/busy is checked on the **owner's** calendar only — attendee availability is
the interactive part this skill delibera
Read more
name: meeting-scheduler description: "Schedule a small meeting end-to-end: resolve attendee emails, check the owner's calendar for the slot, dedup-check, then create + email the Google Calendar invite. The mechanical core only — cross-person availability negotiation stays interactive." user-invocable: true
Meeting Scheduler
Encapsulates the ~10 manual steps of "set up a quick meeting" into one command: **resolve attendee emails → check the owner's calendar for the slot → dedup-check → create the event + send invites → report the link.**
**Usage**: `/meeting-scheduler [what to schedule]`
ARGUMENTS: $ARGUMENTS
Scope (read first)
This skill handles the **mechanical** core only. It does **NOT** negotiate availability across people — proposing times, collecting "does 3pm work?", juggling everyone's free/busy stays an **interactive** conversation you have with the owner (and, if asked, the attendees). Once a concrete slot is chosen, this skill does the rote work.
**Authority note.** Creating and sending a calendar invite is an **owner action**. Run the create+send step only when the owner has invoked this skill for a concrete meeting. Never auto-send speculatively — default to a dry-run and show the plan first. The helper enforces this: it defaults to `--dry-run` and only creates with an explicit `--send`.
Inputs
| Input | Required | Notes | |---|---|---| | title | yes | Event summary, e.g. "Sutando sync" | | attendees | yes | Either explicit emails (`--attendees`) or names to resolve (`--resolve`) | | when | yes | Start time — ISO 8601 (`2026-07-25T15:00`) or `today/tomorrow HH[:MM][am/pm]` | | duration | no | Minutes; default 30 | | location | no | Room, address, or a video link | | description | no | Agenda / notes |
Emails must come from a **Gmail lookup** — the identity map (`workspace/.claude-sutando/projects/<slug>/memory/reference_identity_map.md`) resolves a name to a *person* (and their Discord/GitHub ids) but does **not** store email addresses. Use the map to disambiguate *who* is meant; get the address from Gmail.
Run order
Everything below is done by `scripts/schedule_meeting.py`, which shells out to the `gws` (Google Workspace) CLI. You can run it directly, or perform the steps by hand with the `gws` commands shown.
1. Resolve attendee emails (names → emails via Gmail)
For each name, search recent mail and read From/To headers to extract the address:
gws gmail users messages list --params '{"userId":"me","q":"\"Alice\" newer_than:365d","maxResults":10}'
# then, per hit id:
gws gmail users messages get --params '{"userId":"me","id":"<id>","format":"metadata","metadataHeaders":["From","To","Cc"]}'Strip any line containing `keyring` from gws output before parsing JSON. For the **ag2.ai** account, set `GOOGLE_WORKSPACE_CLI_CONFIG_DIR=$HOME/.config/gws-ag2` (the helper's `--account ag2` does this). If a name is ambiguous, confirm with the owner rather than guessing.
2. Check the OWNER's calendar for the slot
Read the owner's events across that day and flag anything whose busy time overlaps `[start, end)` (back-to-back is *not* a conflict; `transparency:transparent` / all-day events don't block):
gws calendar events list --params '{"calendarId":"primary","timeMin":"2026-07-25T00:00:00-07:00","timeMax":"2026-07-26T00:00:00-07:00","singleEvents":true,"orderBy":"startTime"}'3. Dedup-check
From the same day's events, refuse if one already has the same title (case/space-insensitive, ignoring cancelled ones) — prevents a double-booked duplicate invite.
4. Create the event + send invites
Only on explicit owner action (`--send`). `sendUpdates=all` makes Google email the invitations:
gws calendar events insert \
--params '{"calendarId":"primary","sendUpdates":"all"}' \
--json '{"summary":"Sutando sync","location":"Meet link","description":"...","start":{"dateTime":"2026-07-25T15:00:00","timeZone":"America/Los_Angeles"},"end":{"dateTime":"2026-07-25T15:30:00","timeZone":"America/Los_Angeles"},"attendees":[{"email":"a@x.com"},{"email":"b@y.com"}]}'5. Report the event link
Print the `htmlLink` from the insert response back to the owner.
The helper script
# Dry-run (default) — resolve, check, dedup, report. NO changes: python3 skills/meeting-scheduler/scripts/schedule_meeting.py \ --title "Sutando sync" --when 2026-07-25T15:00 --duration-min 30 \ --resolve "Alice, Bob" # Create + email invites (owner action): python3 skills/meeting-scheduler/scripts/schedule_meeting.py \ --title "Sutando sync" --when 2026-07-25T15:00 --duration-min 30 \ --attendees "a@x.com,b@y.com" --location "https://meet.google.com/xxx" --send # Override a detected conflict/duplicate (owner's explicit call): # ... --send --force
Flags: `--title --when --duration-min --attendees --resolve --location --description --calendar (default primary) --timezone --account {default,ag2} --gmail-lookback --send/--dry-run --force --self-check --verbose`.
**Fail-safe behavior:**
- Defaults to `--dry-run` — nothing is created or emailed without `--send`.
- On a **conflict** or a same-title **duplicate**, it refuses to create even with
`--send`, unless `--force` is also given (the owner's explicit override).
- If no attendee email is present (none given, none resolved), it stops.
Offline self-check
The pure logic (when-parsing, conflict overlap, dedup, email-pick) is unit-tested without the network:
python3 skills/meeting-scheduler/scripts/schedule_meeting.py --self-check python3 tests/meeting-scheduler.test.py
Notes / limitations
- **Natural-language dates are intentionally minimal** — ISO 8601 and
`today/tomorrow HH[:MM][am/pm]` only. For "next Tuesday afternoon" etc., resolve it to a concrete ISO time (the owner picks the slot) before calling the helper.
- Free/busy is checked on the **owner's** calendar only — attendee availability is
the interactive part this skill delibera
My AI Stand — Realtime by Day, Rewriting Itself by Night. Summon my AI superpower. Voice, vision, screen, meetings, calls when I'm engaged. Learns my patterns, ships its own code when I'm not. Runs across my Macs, interacts with people & their Stands.
Repo: sonichi/sutando
Other skills on sutando.
- /agent-registry
Local Agent Registry — a standalone, dependency-free service that tracks running Claude Code (and other) agent instances. Agents self-register on startup and heartbeat while alive; the Electron overlay and Sutando dashboard read the live list. Use when you need to know which
Open skill - /agent-room-ops
**One skill, multiple tools.** Everything an agent does in a room beyond its task inbox lives here as a tool, so the parity capabilities are self-evidently *one collection* (not N scattered skills). Each tool is a thin **gateway-only** client verb sharing `_gateway.py`; the
Open skill - /audio-transcribe
Transcribes audio files and voice notes to text via Gemini 2.5-flash. Integrates with Slack, Discord, and Telegram bridges so voice clips surface as readable text in tasks.
Open skill - /bot2bot-post
Post a coordination message from this bot to the shared bot2bot channel — @-mentioning a specific peer via --to, auto-mentioning only in single-peer fleets, never guessing.
Open skill - /call-diagnostics
Analyze phone call observability data, detect problems, track them across calls, and recommend systematic repairs.
Open skill - /claude-codex
Bash wrapper around the local Codex CLI for non-interactive runs from inside Sutando (bridges, cron, scripts). For interactive code review or task hand-off from this Claude Code session, prefer the official `/codex:*` plugin commands; this skill is the file-bridge-compatible
Open skill

