Skip to content

feat(calendar): free/busy, repeating events, timed/all-day switch - #13

Merged
leemour merged 6 commits into
mainfrom
feat/calendar-roadmap
Oct 27, 2026
Merged

leemour merged 6 commits into
mainfrom
feat/calendar-roadmap

Conversation

@leemour

@leemour leemour commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Task

The owner chose gcal's three roadmap items next, then a budgeted, resumable sync after the first live sync (8 calendars) died at the 30 s deadline and lost the round. The shared commands are in cli-core (WireCatLabs/cli-core#2112); this is the Google adapter.

What

  • calendars busy: googleCalendarSource.freeBusy over freebusy.query. It works with calendar.readonly (Freebusy: query lists calendar.readonly among its scopes). Busy times are clipped to the window. An id Google will not show comes back with its reason (notFound) and is listed in unknown, never as free. Nothing is stored.
  • --repeat: the rule goes to recurrence. A repeating all-day event's start and end carry timeZone; Google requires it for recurring events (Events resource, start.timeZone).
  • Timed ↔ all-day: events.patch merges start, and clearing dateTime would need a null that cli-base's generated request check refuses. So the writer fetches the event and replaces it with events.update (If-Match = the synced etag, conferenceDataVersion=1, supportsAttachments=true). This is the get-then-update that Events: update documents for partial changes, and it needs no cli-base release. A plain time change stays a patch. writeKeys("update") now also names api.calendar.events.update.
  • Fakes written from the API reference: freeBusy answers and a full-replace PUT. Tests: each path, both switch directions, a conflict replaces nothing, conference data, files and a passcode line in the description round-trip untouched, EXDATE kept on a rule change, busy in two time zones.
  • Budgeted, resumable sync (cli-core #2118, #2125, released in 0.465.0): sync stops before its deadline (5 min at a terminal, 45 s over MCP; --timeout overrides), saves after every page, and the next run goes on. requestMs: 1_500 on the app identity sets the request budget, (deadline − margin) / 1.5 s; it is an estimate until a live run measures it. --max-pages lowers it, --rescan starts over. A partial answer has meta.complete: false and a next step (gcal personal sync, same flags) as the last line in text and as command + args for an agent. A run that moved nothing gets no auto-run step, only a hint. Top-level gcal personal status shows per-calendar progress (WC-LIFE-14 shape).
  • Stale page token: a 400 or 410 on a request that carries a pageToken is a reset, so the engine restarts that calendar's round once. Google states no lifetime for a page token (events.list, sync guide). The fake answers Google's documented 400 body for an unknown page token.
  • bin/gcal-live-check loops sync until state: complete, printing each run's progress, with a cap of 40 runs (GCAL_LIVE_SYNC_RUNS).
  • Pins @wirecat/cli-core 0.465.0 and @wirecat/cli-base 0.64.0 exactly. The cli-base#381 lint overrides and the baselines 0.64's vocabulary covers are gone. Tests follow cli-core 0.462's skill envelope.
  • Docs: usage, design, skill, CHANGELOG Unreleased, generated command reference and test matrix.

Follow-up

Not tested against live Google

  • The switch on one occurrence of a repeating event.
  • { date, timeZone } on a repeating all-day event.
  • That the replace keeps a real Meet link and Drive files.

bin/gcal-write-check can cover these on a test calendar.

Testing

On the released cli-core 0.465.0 and cli-base 0.64.0: ci:quick, lint, typecheck, test:coverage, generate:check, contracts:check, test:matrix (177 tested, 0 missing), docs:check, package:check, standards:check, security:no-leak, all passing.

Not run: bin/gcal-live-check against real Google (needs the owner's sign-in).

🤖 Generated with Claude Code

@leemour
leemour marked this pull request as ready for review October 27, 2026 19:08
leemour and others added 5 commits October 27, 2026 20:13
…r Google

- calendars busy: googleCalendarSource.freeBusy over freebusy.query (calendar.readonly
  allows it), busy times clipped to the window, an id Google will not show reported with
  its reason; mounted through cli-core's shared command (deps.freeBusy).
- events create|update --repeat: the rule goes to `recurrence`; a repeating all-day
  event's start and end carry timeZone, which Google requires for recurring events.
- Timed <-> all-day: the writer fetches the event, refuses if its etag is not the synced
  one, and replaces it with events.update (conferenceDataVersion=1, supportsAttachments)
  instead of a patch that would need `start.dateTime: null`. An update also checks
  api.calendar.events.update.
- Fakes from the API reference: freeBusy answers, a full-replace PUT. Tests for each
  path, both switch directions, the EXDATE kept on a rule change, busy in two time zones.
- Lint overrides for the words in cli-base#381 (unreleased); usage, design, skill and
  changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…412; prove the replace keeps conference data and files

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….465.0)

- Pin @wirecat/cli-core 0.465.0 and @wirecat/cli-base 0.64.0 exactly; drop the
  lint overrides and baselines cli-base 0.64's vocabulary makes stale.
- requestMs 1.5 s on the app identity: an estimate for one events.list page or
  one series' events.instances until a live run measures it.
- A page token Google refuses on resume (400 or 410 with a pageToken) is a
  reset, so the engine starts that calendar's round over once; Google states
  no lifetime for page tokens (events.list reference).
- A partial sync answers meta.complete false and lifts the engine's next step
  into the envelope: `gcal <profile> sync` with the same flags as the last line
  in text, the command and args for an agent.
- `status` (top-level) shows each calendar's progress; `sync --rescan` starts
  over.
- bin/gcal-live-check loops sync until complete, printing progress, at most 40
  runs (GCAL_LIVE_SYNC_RUNS).
- Tests follow cli-core 0.462's skill envelope and fix a test type error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leemour
leemour force-pushed the feat/calendar-roadmap branch from 19acfa0 to 2bba257 Compare October 27, 2026 19:14
@socket-security

socket-security Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​wirecat/​cli-base@​0.63.0 ⏵ 0.65.097 +4100100 +196 +170

View full report

@leemour

leemour commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Not merging yet. Everything in this PR passes, but CI's cross-CLI audit (bash scripts/audit.sh in validation / ci / check, run https://github.com/WireCatLabs/gcal-cli/actions/runs/40567341392) fails with 20 new same-path-contract findings. They come from the cli-base 0.64.0 pin, which cli-core 0.465.0's peer range requires: cli-base 0.64 adds gcal to the all parity group (commands, doctor, skill, mcp, upgrade, search, notes, meetings) and declares no differences (cli-base CHANGELOG 0.64.0, "cli-dev audit compares gcal…").

The findings are differences that already exist between the other CLIs. gcal now joins them: doctor/mcp/search/skill summaries, mcp --http/--port/--public-url/--revoke (gcal has its own stdio-only MCP server), doctor --online, and search options. gcal cannot fix them alone. They are more than the parity group's override budget (10). Declaring them belongs in cli-base's PARITY_GROUPS, or gcal moves to cli-core's mcpCommand and the shared summaries. Both are decisions for the owner or the coordinator, not for this PR.

All ten checks the brief named pass locally on cli-core 0.465.0 and cli-base 0.64.0, as do standards, bun, prose and version in CI.

🤖 Generated with Claude Code

leemour added a commit to WireCatLabs/cli-base that referenced this pull request Oct 27, 2026
…l the others converge (#391)

## Task

Coordinator decision (option B from the cli-core fix-wave-A lane):
unblock gcal-cli#13's release audit without declaring differences.
Tracking: #390.

## Why

cli-base 0.64.0 put gcal into the `all` parity group.
`same-path-contract` names every compared CLI in its target and cannot
be baselined, so gcal's audit
(WireCatLabs/gcal-cli#13 (comment))
blocks on 20 differences in `doctor`, `mcp`, `search` and `skill`. These
already exist among tg, max, wa, memo, drive and zoom as published, and
only their releases on cli-core's shared commands can remove them.

## What

- `all` loses gcal.
- New group `gcal-shared`: every CLI, including gcal, over `commands`,
`upgrade`, `notes`, `meetings`. That is what gcal already shares
identically; `skill` stays out because drive's summaries still differ.
- The other CLIs' audits do not change: a path is compared across every
member of every group that covers it.
- CHANGELOG Unreleased → Fixed.

## Testing

- `pnpm ci:quick`, `vitest run` (901 passed), `pnpm docs:check`.
- Local `cli-dev audit --for gcal` with this build, against the
published tg, max, wa, memo, drive and zoom and gcal-cli main's dist:
exit 0, "0 new that block" (gcal-cli#13 CI showed 20 with 0.64.0).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
leemour added a commit to WireCatLabs/cli-base that referenced this pull request Oct 27, 2026
## Task
Release `cli-dev audit`'s new parity groups
([#391](#391)) so gcal-cli's
release audit stops blocking
[gcal-cli#13](WireCatLabs/gcal-cli#13).

## Why
`## Unreleased` holds a "Changed — may break callers" section (handbook
rule changes, and `cli-dev lint` now fails on stale `serve`/`revoke`
baseline entries), so the minor is raised: 0.64.0 → 0.65.0 (WC-REL-7).

## What
`package.json` version and the changelog heading only.

## Testing
- `pnpm docs:check`, `pnpm lint` pass.
- Dry run: the built `cli-dev audit` from this branch over gcal-cli#13's
`dist` and the six published CLIs gives `audit: 7 CLIs, 420 findings, 0
new that block`; `cli-dev lint` on gcal gives `0 new`, no stale entries.
- The "Template" check fails on #391 too: cli-template lists `mcp serve`
and `mcp revoke` in its unknown-verb baseline, which this release makes
stale. That is the expected follow-up for cli-template, not a fault
here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
leemour added a commit to WireCatLabs/cli-core that referenced this pull request Oct 27, 2026
## Task
Move cli-core to `@wirecat/cli-base` 0.65.0, so gcal-cli can take it:
its release audit
([gcal-cli#13](WireCatLabs/gcal-cli#13)) passes
only with cli-base 0.65's parity groups
([cli-base#391](WireCatLabs/cli-base#391)).

## Why
The CLIs pin cli-base exactly, and cli-core's peer range `>=0.64.0
<0.65.0` does not allow 0.65; a CLI on 0.65 would get two copies of
cli-base.

## What
- `@wirecat/cli-base` 0.64.0 → 0.65.0. Peer range → `>=0.65.0 <0.66.0`
(WC-REL-8), as in #2115.
- `lint/baseline/unknown-verb.json`: `bot mcp serve`, `mcp serve`, `mcp
revoke`, `replies consents revoke` removed. cli-base 0.65 has `serve`
and `revoke` in the vocabulary and `cli-dev lint` fails on stale
entries.
- `docs/dev/STANDARD.md` regenerated (`pnpm standard:docs`).
- Changelog under "Changed — may break callers".

## Testing
`pnpm ci:quick`, `pnpm docs:check`, `pnpm lint` locally; CI on this PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
cli-base 0.65 compares gcal only on the commands it already shares
(cli-base#391), so the release audit no longer blocks on the 20
same-path-contract differences the other CLIs carry. cli-core 0.469 is the
first that accepts cli-base 0.65. docs/commands.md regenerated: config
settings and skill show follow cli-core 0.467-0.468. No lint baseline or
override entry went stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leemour
leemour merged commit 8957321 into main Oct 27, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant