Command reference
oko calendar connect
Connects one of the user's own calendars: the macOS system calendars, Google Calendar, Microsoft 365, a CalDAV collection, or an ICS subscription.
Invocation#
oko calendar connect {eventkit|google|microsoft|caldav|ics} [--url U] [--username N] [--password-stdin] [--client-id ID] [--client-secret-stdin] [--skarbiec-item ITEM] [--id ID] [--label L] [--request-access] [--no-open] [--redirect-port PORT]Inputs and options#
- eventkit: --request-access is the only path that triggers the one-time macOS consent dialog; without granted access the command refuses instead of prompting.
- google / microsoft: --client-id (or OKO_GOOGLE_CLIENT_ID / OKO_MICROSOFT_CLIENT_ID) names the installation's own OAuth client; the PKCE loopback flow prints the sign-in URL, opens the default browser unless --no-open, and waits for the redirect.
- google: Google issues installed-app clients a client secret and requires it at code exchange; pipe it with --client-secret-stdin, set OKO_GOOGLE_CLIENT_SECRET, or keep it in the Skarbiec item as client_secret. --client-secret with a value is refused: a secret in argv is readable by every process on the host. Microsoft public clients need none.
- caldav: --url names the collection and --username with --password-stdin carry Basic-auth credentials; ics: --url names the subscription.
- --skarbiec-item points the account at a Skarbiec bundle item (fields: url, username, password for caldav/ics; client_id, refresh_token for google/microsoft); sync resolves them at run time, and a google/microsoft item that already holds a refresh_token connects with no browser flow at all.
- --redirect-port pins the loopback port the OAuth redirect URI names, instead of taking any free one. This is what lets the browser run on another machine: the redirect URI is otherwise unknown until the flow is already running, and an encrypted reverse forward (stado host forward-local --remote-port P --local-port P) cannot be opened for a port nobody can name in advance. Google's installed-app clients accept any loopback port, so pinning one keeps the redirect URI valid.
- Driving the sign-in on another machine, once the OAuth client carries a loopback redirect URI, is four commands: stado host forward-local <host> oko-oauth --remote-port P --local-port P to bridge that host's loopback back to this one; oko calendar connect google --skarbiec-item <item> --redirect-port P --no-open, which prints the sign-in URL and waits; stado host weles-browser-task <host> --url <that URL> --objective @objective.txt --session-label oko-calendar --allow-login to open it where the browser runs; then oko calendar sync and oko calendar list. Close the bridge afterwards with stado host forward-close <host> oko-oauth. The objective should instruct the browser agent to stop and report rather than act if a two-factor push, device approval, passkey or SMS verification is requested.
- Google Calendar can also be connected with no OAuth client at all: oko calendar connect ics --url <private iCal address> subscribes to the calendar's own secret address, which needs no client registration, no consent screen and no browser. It is read-only, which is the whole of what sync and list need.
- Which client to use: a first-party client belongs in your organization's own Google Cloud project, under its OAuth consent brand. Its application type must be **Desktop app**: Google grants installed-app clients the loopback exemption, accepting any loopback port without registering each one, which is what makes --redirect-port work and removes the redirect-URI problem for oko and skrzynka in one step. A Web-type client has none of that and must register every redirect URI literally. Check that the client id you configure belongs to that project — the number before the first hyphen is the owning project's number — because a client owned by an unrelated project mints grants through somebody else's consent screen, and nothing in oko asserts otherwise because OKO_GOOGLE_CLIENT_ID has no committed default. No Google Cloud API can add a loopback redirect URI to an existing client — the only OAuth-client APIs are IAP's, whose clients are locked to IAP and expose no redirect-URI field — so the correct client is created once in the Console.
- --id and --label override the derived account id and display label.
Output and state effect#
- Writes the account to the user's own ~/.oko/calendar/accounts.json (owner-only, OKO_CALENDAR_DIR override); with --skarbiec-item only the pointer is stored and no secret is written. Oko ships no provider credentials — Skarbiec is an optional home for the account's secrets on machines that have it.
- For google / microsoft with --skarbiec-item: if the OAuth flow completes and produces a refresh_token, the token is written to the vault item, making the grant durable across machines. Subsequent connections from any machine with vault access connect with no browser flow.
- The code the redirect carries is traded for tokens by oko calendar exchange --token-url U, which posts the form it reads on standard input only to Google's or Microsoft's own token endpoint and prints the access token, refresh token and expiry. When no redirect arrives, oko calendar landing --url U opens the authorize address as a browser would and prints the address it ended on, whose authError names why the provider refused.
Refusals and safe exits#
- google/microsoft without a client id refuse naming the flag and environment variable and stating that the OAuth client belongs to your installation.
- eventkit without granted access and without --request-access refuses naming the flag and the System Settings path; a declined dialog fails with: calendar access was not granted.
- caldav/ics without --url or a resolvable pointer, and a duplicate account id, refuse with their exact sentences.
- --redirect-port that cannot be bound refuses with the kernel's reason and never falls back to another port: the number was requested so that forwards or redirect URIs could be prepared for it, and quietly binding a different one produces a flow that waits forever for a redirect delivered somewhere else.
- A redirect URI the OAuth client has not registered no longer times out silently. Google refuses it inside the browser, so no redirect ever reaches the listener; the flow now asks Google why and refuses naming the client id, the redirect URI it presented, that the client has no loopback redirect URI registered, and the single fix: register a loopback redirect URI for that client, or issue a Desktop app client, which accepts any loopback port. OKO_OAUTH_REDIRECT_PATH names the path the client registered, since Google matches the path exactly and ignores the port.
- If a grant is written to a vault item and the write fails, the command refuses with the exact sentence naming the item and the field, followed by Stado's own refusal. The write goes to the vault SKARBIEC_VAULT_FILE names, or else through Stado (stado credentials put ITEM --field refresh_token --route … --consumer oko-calendar-client --grant-file …, the value on standard input) under the calendar consumer's own grant: Skarbiec replaces that one field of the existing item and keeps the rest. The consumer needs rotate:<item>#refresh_token beside its read grant (skarbiec grant issue oko-calendar-client --capabilities <its current capabilities>,rotate:<item>#refresh_token --replace-capabilities); without it Skarbiec answers 403 naming the item and field. An item that does not exist yet, or one a credential lifecycle or Weles controls, is refused, because a rotation never creates or takes over an item.