Skip to content

feat(calendar): events remind, a reminder before each meeting for a timer to run - #2367

Merged
leemour merged 1 commit into
mainfrom
feat/calendar-remind
Nov 2, 2026
Merged

leemour merged 1 commit into
mainfrom
feat/calendar-remind

Conversation

@leemour

@leemour leemour commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Task

Close the last gcal gap against gcalcli remind. Research IDEA-15 asks for a message to the owner before a meeting, with the meeting brief.

Why

cron or a systemd timer can run one short command every few minutes. Running that command needs no long-lived process and no new background framework. The command is events remind. It is in cli-core so that every calendar CLI gets it.

What

  • events remind [--due-within 10m] [--dry-run] [-- <program> [args…]].
    • It syncs first, unless --offline.
    • It takes the timed events that start within the window and are not cancelled, not declined, and not focus, out-of-office or working-location blocks.
    • It prints each event, or runs the owner's program once per event.
  • Security
    • The program is spawned with shell: false.
    • The event goes as JSON on stdin. Only <PREFIX>_EVENT=event:<id> and <PREFIX>_PROFILE are added to its environment.
    • A program still running after 60 s gets SIGTERM, then SIGKILL.
    • events.remind.hook is in CALENDAR_WRITE_KEYS and CALENDAR_WRITE_DEFAULTS as deny, because --yes answers ask.
    • The read-only lock refuses the program form.
    • The command is mcp: false.
  • State
    • A 0600 checkpoint file per profile at checkpoints/<profile>/events-remind.json, keyed by event id and start, so a moved meeting is reminded again. It keeps one day of history.
    • A file instead of a store table: a table would need store version 44, which reaches every CLI.
    • A lock (takeLock) keeps overlapping runs from reminding twice.
    • An event whose program fails is not kept. The next run retries it (remind.hook-failed).
  • A failed sync goes into meta.issues (sync.failed / sync.partial), and the run reminds from the store.
  • ownsTime() is taken out of the next-meeting rule in prepare.ts and shared with isMeeting.
  • Bumps cli-base to 0.73.0 for the verb remind (feat(vocabulary): the verb remind cli-base#443). The peer range is now >=0.73.0 <0.74.0.

Testing

  • New tests in src/calendar/cli/remind.test.ts use the fake source, the memory store and a fake clock. They cover:
    • each event is told once, and a moved event is told again;
    • --offline and --dry-run;
    • a failed sync;
    • the window bounds;
    • the hook is denied by default and refused under the read-only lock;
    • stdin JSON and env, with a hostile $(touch pwned) title that does not run;
    • a failed hook is retried;
    • a missing program answers exit 127;
    • the command is off MCP.
  • pnpm lint, typecheck, the full test and docs:check pass locally.

🤖 Generated with Claude Code

…imer to run

`events remind` closes the last gap against gcalcli's `remind`. Cron or a
timer runs it every few minutes. It syncs first unless --offline, then takes
the events that start within --due-within (10m by default) and take the
owner's time. It prints each one, or after `--` runs the owner's program once
per event.

- The program starts with no shell. The event goes as JSON on its stdin, and
  only ids reach its environment (<PREFIX>_EVENT, <PREFIX>_PROFILE). An
  invitation's text is a stranger's, so it never reaches a command line.
  A program still running after 60 s is stopped.
- events.remind.hook defaults to deny, not ask: --yes answers ask, and
  anything that can type a command can add it. The read-only lock refuses
  the program at any level. The command is never an MCP tool.
- What was reminded is kept per profile in a 0600 checkpoint file, one key
  per event and start, so a moved meeting is reminded again. A failed program
  leaves its event for the next run. A lock keeps two runs from reminding the
  same event twice.
- A failed sync is reported in meta.issues, and the run still reminds from
  the store.
- cli-base 0.73.0 adds the verb `remind`. The peer range is now
  >=0.73.0 <0.74.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leemour
leemour force-pushed the feat/calendar-remind branch from 24f8b5d to 080ff47 Compare November 2, 2026 00:46
@leemour
leemour merged commit 4142528 into main Nov 2, 2026
38 checks passed
@leemour
leemour deleted the feat/calendar-remind branch November 2, 2026 00:51
leemour added a commit to WireCatLabs/gcal-cli that referenced this pull request Nov 2, 2026
….0 (#85)

## Task
Close the last gap against gcalcli `remind`: remind the owner before a
meeting. This is research IDEA-15, a Telegram message to the owner
before a meeting.

## Why
gcalcli users run `gcalcli remind` from cron. gcal had no equivalent.
The command lives in cli-core (WireCatLabs/cli-core#2367, released in
0.543.0). This PR mounts it, bumps the dependencies and documents it.

## What
- Bumps `@wirecat/cli-core` 0.540.0 → 0.543.0 and `@wirecat/cli-base`
0.72.0 → 0.73.0. cli-base 0.73.0 adds the verb `remind`.
- `gcal <profile> events remind [--due-within 10m] [--dry-run] [--
<program> [args…]]`:
  - It syncs first, unless `--offline`.
- It tells once about each event that starts soon and takes the owner's
time.
- It prints the event, or runs the owner's program once per event. The
program gets the event as JSON on stdin and `GCAL_EVENT` /
`GCAL_PROFILE` in its environment.
- **Security**
- No shell runs the program, and the invitation's text never reaches its
command line.
- `permissions.events.remind.hook` is `deny` until the owner allows it.
`--yes` does not lift it.
  - `GCAL_READ_ONLY` refuses the program form.
  - No MCP tool offers the command.
- **Docs**
- `docs/usage.md`, a new section "Get reminded before a meeting": the
hook contract, a notify-send hook, a Telegram hook, and cron / systemd
user timer / launchd / Task Scheduler setups.
  - `docs/security.md`, roadmap, skill, CHANGELOG Unreleased.
  - The generated `docs/commands.md` and `docs/dev/test-matrix.md`.

## Testing
- New test in `src/calendar.test.ts` over the fake Google API, with only
`Date` faked. It covers:
  - the dry run;
  - each event told once;
  - the hook denied by default;
- after `config set … allow`, the hook gets JSON on stdin and
`GCAL_EVENT`.
- These pass locally: `pnpm lint`, `typecheck`, `test`, `docs:check`,
`generate:check`, `contracts:check`, `lint:commands`, `test:matrix` (0
missing). Some suites timed out once under machine load (load average
about 30) and passed when run again alone.

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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