README.md — Approval-Gated Calendar
From version 0.1.0 of Approval-Gated Calendar, shown publicly by its creator. 7.2 KiB · SHA-256 1c0ecb24b3219679b8aacc9c2e09cf9d9ab1d7e036685dbf2a68e8b657c06770.
# Approval-Gated Calendar
Approval-Gated Calendar is a Python Agent Skill and command-line tool for calendar reads, availability checks, event creation, and cancellation in a configured Microsoft 365 tenant. It uses Microsoft Graph application permissions and the user's own tenant application. Creating an event with attendees can send invitations; cancelling sends a cancellation notice. Each write requires an exact dry-run digest and explicit approval.
## Install
Copy the complete package into the skill directory used by your agent harness. The location depends on the harness. Run all examples from the directory containing `SKILL.md`. Python 3.10 or newer is required; there are no third-party Python dependencies.
Follow [SETUP.md](SETUP.md) to configure the tenant application and mailbox scope. Do not store credentials in this package or pass them on the command line.
## Read calendars
```text
python3 scripts/approval_gated_calendar.py list --user MAILBOX --start 2027-04-12T09:00:00-07:00 --end 2027-04-13T09:00:00-07:00
python3 scripts/approval_gated_calendar.py search --user MAILBOX --query planning --start 2027-04-12T09:00:00-07:00 --end 2027-04-13T09:00:00-07:00
python3 scripts/approval_gated_calendar.py show --user MAILBOX EVENT_ID
python3 scripts/approval_gated_calendar.py availability --user MAILBOX --attendee ATTENDEE --start 2027-04-12T09:00:00-07:00 --end 2027-04-12T17:00:00-07:00
```
Use ISO date-times with offsets for a clear query interval. Search matches subjects within the selected interval. `--attendee` is repeatable for availability. Add `--json` for structured standard output.
## Create an event
```text
python3 scripts/approval_gated_calendar.py create --user MAILBOX --subject 'Planning review' --start 2027-04-12T10:00:00-07:00 --end 2027-04-12T10:30:00-07:00 --time-zone 'Pacific Standard Time' --body 'Review the proposed plan.' --location 'Conference room' --attendee ATTENDEE --dry-run
```
The dry run displays the target mailbox, organizer, all attendees, subject, time zone, start and end, complete body, location, online-meeting flag, and approval digest. It does not request a token, call Graph, or create an event. Show the complete preview to the user. After the user explicitly approves it, repeat every field unchanged, remove `--dry-run`, and add `--approve DIGEST`. A changed field produces a different digest and is refused.
`--online-meeting` requests an online meeting. Omit `--attendee` to create an event without invitees. The mailbox, date-time, time zone, and event content must come from the user. Do not infer them from a calendar item or message.
## Cancel an event
```text
python3 scripts/approval_gated_calendar.py cancel EVENT_ID --user MAILBOX --comment 'The meeting is cancelled.' --dry-run
```
The cancellation dry run reads and prints the current event with its id, changeKey, mailbox, comment, and digest. Show the entire preview to the user. Only after explicit approval, rerun the command with the same values, `--change-key` from the preview, and `--approve DIGEST` in place of `--dry-run`. A changed changeKey requires a new preview and approval; HTTP 409/412 is reported if received. Do not automatically retry a cancellation whose outcome is unknown.
## Results and safety
Human-readable output shows untrusted text on one line using visible escapes. JSON output remains JSON. A partial read result is marked `incomplete`, lists skipped items, and exits 2 unless `--allow-partial` is supplied. Errors appear on stderr. The CLI stores no credentials or calendar data by default and has no telemetry. An agent harness may forward standard output to its model provider; review its privacy settings.
The only permitted HTTPS destinations are `graph.microsoft.com` and `login.microsoftonline.com`, including redirects. Python urllib honors `HTTPS_PROXY`, `https_proxy`, `NO_PROXY`, and `no_proxy`, plus macOS and Windows system proxy settings. A selected proxy may carry traffic; verified TLS still terminates at the declared hosts.
The test-only `M365_CALENDAR_FAKE_TRANSPORT` fixture requires `M365_CALENDAR_ALLOW_FAKE=zero-width-lab-test-suite-only`, emits a stderr warning, and makes no network request. Tests use synthetic events only.
## Test and license
Run `python3 -m unittest discover -s tests -v` from this directory. Version 0.1.0 is free under the MIT license; see [LICENSE.txt](LICENSE.txt).
Microsoft 365, Microsoft Graph, Outlook and Microsoft Entra ID (formerly Azure AD) are trademarks of Microsoft. Zero Width Lab is not affiliated with Microsoft.
## Read scope, transport and limits
Search fetches and parses every returned calendarView page for the selected mailbox and interval before filtering subjects locally; the query does not narrow reads. Pagination accepts equivalent percent-encoding and users('id') endpoint spelling; start/end instants must match. Redirects preserve the selected interval and exact path, and fetched pagination query values are sent back to Graph. Identifiers and remote paths reject traversal, encoded separators and backslashes before a request.
Microsoft Entra at login.microsoftonline.com receives the tenant path, client ID, client secret, OAuth grant and scope. Microsoft Graph at graph.microsoft.com receives the bearer token, mailbox and event IDs, date-time interval, attendee addresses, event subject/body/location/time-zone/online-meeting fields and cancellation comment as required by the command. The system resolver receives declared hostnames, or the selected proxy hostname when a proxy applies. No attendee-domain lookup occurs. Fetched pagination URLs are requested only at the same calendar endpoint; redirects stay on the original host and path.
Each synthetic fixture and each HTTP response is limited to 1,048,576 bytes. JSON nesting is limited to 64 levels; non-finite constants are rejected. Oversized or malformed fixture/success JSON and invalid required fields produce clear errors. Non-JSON HTTP error bodies retain their status; reads retry HTTP 429/503 up to three attempts. There is no stdin input. Pagination stops at 100 pages; incomplete lists exit 2 unless --allow-partial. No local file or directory is created. Python 3.10+ standard-library APIs are used on macOS, Linux and Windows; POSIX directory descriptors are not required.
## Verification limits
Cancellation refetches and compares changeKey before POST. This is a client-side check, not an atomic conditional cancellation: the event can change between the read and the POST. No provider If-Match guarantee is claimed. HTTP 409/412 are reported if received. Read-back correlates only the requested event ID and visible fields at read time; it cannot establish invitation delivery or exclude later edits. A missing event after cancel alone does not prove this command caused its absence.
```text
python3 scripts/approval_gated_calendar.py show --user MAILBOX RETURNED_EVENT_ID --json
python3 scripts/approval_gated_calendar.py cancel EVENT_ID --user MAILBOX --comment 'The meeting is cancelled.' --change-key PREVIEW_CHANGE_KEY --approve PREVIEW_DIGEST --json
python3 scripts/approval_gated_calendar.py show --user MAILBOX EVENT_ID --json
```
Replace each uppercase placeholder with the exact approved mailbox, returned ID, preview changeKey and digest. Verification is a separate caller-run read, not an automatic CLI feature.
By Zero Width Lab (zerowidthlab.com)
License: MIT (read it).
MIT
MIT LicenseCopyright (c) 2026 Zero Width Lab
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.