Repository navigation
Releases: WireCatLabs/gcal-cli
Releases · WireCatLabs/gcal-cli
Release list
0.10.0
- gcal now runs on cli-core 0.512.0. Nothing changes for gcal's commands or its MCP server: cli-core 0.512.0 renamed
idtooperation_idin its own calendar MCP tools, which gcal does not serve. gcal's agents write through
gcal_write, whoseevents createandevents updatealready takeoperation_idsince 0.9.0, and page
events listthroughgcal_readwithpage.
0.9.0
Changed — may break callers
events create --idandevents update --following --idare now--operation-id, the name every WireCat
write uses for a retry afteroutcome_unknown(WC-CMD-10, WC-OUT-10). The value is the same: theoperation_id
the lost write's error names.--idis refused as an unknown option; change scripts and agent prompts that pass it.- The log of what agents did over
gcal mcpnames the agent that called, not the server (cli-core 0.509.0): the
client's own name, such asclaude-code, where it was alwaysgcal-mcp. A filter ongcal-mcpno longer finds
new rows. gcal now runs on cli-core 0.510.0 and cli-base 0.68.1.
Added
events list --page <n>: the next page of stored events. A list with more events than--limit(100) says
page.next_page, and itsnextstep runs the same query one page on; until now it saidhas_morewith no way to
read further.search events --backend bothasks the store and Google Calendar's own search together. The store's matches
come first, in the store's order, and each one Google also found hasserverin itsfound_by; then the events
only Google found, earliest first. One page with no cursor: it refuses--cursorand--newest, and needs the
network. Before,bothwas offered and always refused.
Fixed
search events --cursorhelp no longer namesnext_offset, a key the answer does not have; the value comes
frompage.next_cursor.
0.8.0
Fixed
- The keyring's file fallback now comes from cli-base 0.68.0 and cli-core 0.507.0, with no workaround of gcal's
own. cli-base fixed the bug gcal worked around: removing the keyring copy after--credential-storage filealso
cleared the file. A sign-in in the private file is read from there and its refreshes stay there; the next
session startthat reaches the keyring moves it back and removes the file copy. Answers are as in 0.7.0, and the
keyring warning still comes once per run.
0.7.0
Changed — may break callers
- A raw
apiwrite that is easy to undo no longer asks.api calendar-events-insert,-patch,-move,
-quickAdd,calendar-calendars-insertand the other methods that change Calendar without deleting areallow,
asevents createandevents updatealready were. A method that deletes (calendar.events.delete,
calendar.calendarList.delete,calendar.acl.delete) and sharing a calendar stayask; emptying, deleting or
giving away a calendar staysdeny, andevents.notify,events.delete.seriesandsharing.notifystill hold on
everyapiwrite. To keep the question, setpermissions.api ask.
Fixed
session start --helpkeeps each option to one short line (WC-CMD-22). What--grant writeand
--grant sharingallow, and when to pick--credential-storage file, moved below the options.
0.6.0
Added
events listfinds events from their earliest start:starts_at>=<date>.starts_atnow takes either side
and a range:starts_at>=2026-11-02,starts_at:[2026-11-02 TO 2026-11-07}, andstarts_at:2026-11-02for the
events of one day. An event that starts exactly at the moment is kept; an all-day event is compared by its date in
your time zone. In 0.5.0starts_at>=…was refused.ends_atis unchanged: an earliest end only, strictly after.
Changed — may break callers
session startfalls back to a private file when the keyring fails, and never asks. It used to
stop withoauth.not-savedand tell you to pass--credential-storage file. Now it signs in, says once on stderr
that it usedcredentials.jsonin the state folder, and answerscredential_storage: "file". The flag still
chooses the file outright; the keyring is still the default and the safer choice.session statusand
session endno longer answerkeyring: "unreachable": the keyring's failure is the stderr warning.- A run with moved folders names its temporary keyring entry after its state folder, where it used to name it after
its config folder; a temporary entry an older run left is no longer read.bin/keyring-deduperemoves it. calendars busytakes the standard field query, and none of its own words.today,week,from:andto:
are refused. The window is the answer's ownstartandend:from:2026-11-02 to:2026-11-06is
start>=2026-11-02 end<=2026-11-06, andtodayisstart>=today end<=today. Withoutstartthe window starts
today, withoutendit lasts seven days, so a barecalendars busyis still this week.calendar:andperson:
stay and may still be repeated. Over MCP the input isquery, no longerwords. The first screen shows
calendars busy --duration-minutes 30.- gcal needs
@wirecat/cli-base0.67 and@wirecat/cli-core0.502, which now buildstore status,
search events <query>and the field queries themselves; the commands, their answers and permission keys are the
same as in 0.5.0.
0.5.0
Added
calendars listtakes a query:synced:,primary:andaccess_role:narrow the list, such as
gcal personal calendars list synced:true.commands infodescribes what each calendar command answers with, and--fieldscan pick from it.
Changed — may break callers
gcal search <words>is nowgcal search events <query>, andgcal statusis nowgcal store status, the
shape the other WireCat tools use (max:search messages <query>,store status). The old paths are gone, with no
alias. Over MCP the command issearch events, and its input isquery(waswords); a store search's next-page
step namesquerytoo. A partial sync's hint points atstore status. The permission keys move with them:
permissions.store.statusandpermissions.search.events;permissions.searchstill covers the search. A config
that still setspermissions.statusis refused withconfig.invalid, which names
gcal <profile> config unset permissions.status.events listtakes only the standard field query, and none of its own words.today,week,from:,to:,
changed:andoverlappingare refused. Write the window as fields:from:2026-11-02 to:2026-11-06is
ends_at>=2026-11-02 starts_at<=2026-11-06, andchanged:2026-10-25ischanged_at>=2026-10-25;calendar:and
person:stay. Over MCP the input isquery("ends_at>=2026-11-02"), no longerwords. What callers lose:todayandweek: write the dates. The query language hastodaybut no word for a day ahead, so the end of a
window is an ISO date.overlappingand theoverlapsfield: no standard term asks for events that clash, so the list no longer finds
them. Comparestarts_atandends_atof the listed events instead.starts_at>=…is refused and names theends_at>=…term to write instead: the store finds events by the moments
they span, so it can bound only the latest start and the earliest end. An event that ends exactly at the
ends_atmoment is left out.- Without a time term the list still starts at the beginning of today.
- Deleting a whole Google calendar and giving it to another account are
denyby default, as emptying one is.
api.calendar.calendars.deleteandapi.calendar.calendars.transferOwnershipwereask, which over MCP runs with no
question. Set either key toaskorallowto let it run. session statusandsession endanswer where no keyring can be reached and no sign-in file is kept. They
failed withoauth.not-saved. Nowsession statussaysstate: missingwithkeyring: unreachable, and
session endsaysended: falsewithkeyring: unreachable: this run can use no sign-in, so there is nothing to
show or end. A sign-in kept with--credential-storage fileis read as before.session startand the commands that
call Google still fail withoauth.not-saved, which troubleshooting explains. A script
that took the failure to mean "keyring out of reach" readskeyringin the answer instead.people listtakes one event asevent:<id>.gcal personal people list 42is refused as a word with no
field; writepeople list event:42. Over MCP the input isquery("event:42"), no longerevent.search --backend serveranswers as the shared search does. Each event'sfound_byis["server"](was
["google"]). The refusals have new reasons: no synced calendar issearch.no-calendars(was
calendars.none-synced),--offlineissearch.offline(wasoffline.refused),--exact,--cursoror
--newestissearch.server-options, and--backend bothissearch.one-backend(wassearch.backend-both).- The calendar commands' help no longer lists
--jsonand--jsonlunder each command, and their MCP tools no
longer takejsonorjsonlinputs. The global--jsonand--jsonlwork as before; an agent that sent
jsonl: truewas refused. config show --botandconfig set|unset --personal|--botare hidden. gcal has no bots, so they had nothing
behind them. They are still accepted, so a script that passes one keeps working.
Fixed
- Calls an agent sends to
gcal mcp serveat the same time each get their own answer. Two calendar reads sent
together could answer one of them empty ({"item":{"value":null}}) or fail it withcommand.crashed, because the
calls shared which command was running and the store it had opened. Each call now keeps its own
(WC-ARCH-22). - An all-day event with no time zone of its own or of its calendar starts at midnight in your computer's time zone.
gcal read it as midnight UTC: on a computer in Madrid it started an hour late, and in New York on the evening before.
Such an event'stimezonenow names the zone it was read in, where it wasnull. An event or calendar that names
its zone is read in that zone, as before. An event already stored keeps its old start until Google sends it again,
after it changes
(WC-TIME-3).
0.4.0
Fixed
- gcal keeps the shared local store at the same version as the other WireCat tools (store version 40). The store
gains the trigram index that notes and documents search uses; gcal has no notes of its own, but an older gcal no
longer leaves a store written by a newer tool without the index entries it should write.
0.3.0
Added
gcal models listandgcal models download <model>fetch the model that search by meaning uses. The search's
hint namedgcal models download e5-small, which did not exist. The model goes into the folder every WireCat tool
shares, so one already downloaded by another WireCat tool is used as it is.events move <event> --to-calendar <id>moves an event, with its id, to another calendar of the same account.
It refuses a calendar you cannot write to, the same calendar, an event Google does not move (focus time, out of
office, working location, birthdays) and an occurrence without--series. Over MCP it is theevents movecommand.events update --followingandevents delete --followingchange or delete "this and all following"
occurrences. An update ends the series just before the occurrence and starts a new series there with the change,
keeping the attendees; a delete only ends the series. A series that ends after a number of times needs--repeat
for the new part.events list changed:<date>lists what changed since a date, from the store: each event sayschange(new,
movedorcancelled) andchanged_at, and a moved one where itwas.events list overlappinglists the meetings that clash, each withoverlaps, the other events. Only events that
take your time count: not cancelled, not all-day, not declined or marked free.
Changed — may break callers
- Creating, changing, moving and answering events go ahead without a question by default.
events.create,
events.update,events.respondand the newevents.moveareallow(wereask); a script no longer needs--yes
for them.events.deletestaysask,--followingincluded. To be asked again, set a key toask, such as
gcal personal config set permissions.events.create ask. Theapi.calendar.events.*keys stop the event writes
once you set them; their built-inaskholds for theapicommands only. - A series update whose new rule ends it sooner also needs
events.delete.series, which isdenyby default. A
newCOUNT, an earlierUNTIL, or a bound on a series that had none removed the later occurrences under
events.updatealone. - A
syncwhere a calendar fails prints nothing on stdout and fails withsync.partial. Its message names each
failed calendar and its error,details.calendarsholds every calendar's result (erroron the failed ones), and
the error says a retry is safe. The calendars that synced are saved. A sync stopped by its time or request budget
still answersstate: "partial"and succeeds. searchanswers a list: the events underitems, withpagein cursor mode, as every other list does. It
answered oneitemholdinghits,has_moreanduntrusted(WC-OUT-4). A script readsitemsin place of
item.hits, andpage.next_cursoris the value for--cursor. Each event names its ownuntrustedfields, and why
meaning was not searched is undermissing.queryandbyare gone; each event still saysfound_by.links reviewanswers its reviews underitems, withoutaccounts: "all". It answered oneitemholding the
list. Link choices are still kept per store, so every account's reviews are listed.search --backend serverrefuses--newest. Google returns its matches in no stated order and gcal reads only
the first page of each calendar, so the newest event could be missing from a "newest first" answer. Its matches
are listed earliest first, and each event'sfound_byis["google"], as a store search writes it.
Fixed
gcal --helpno longer offers a--read-onlyoption, which does not exist.GCAL_READ_ONLY=1is how a run
refuses every change.events deleteof an event already deleted at Google succeeds and removes the stored copy. Google answers such a
delete with 410, and 404 once it no longer shows the event; gcal failed on both and kept the event in the store
until the next sync.- The daily full sync no longer marks events outside its window as deleted. Older and later single events, moved
occurrences and ended series were marked deleted and their events cancelled. A store this damaged heals on its next
sync. - A repeating event moved to another time no longer keeps its occurrences at the old time after an incremental
sync. - A
sync --rescanstopped by its budget no longer leaves the calendars it did not reach on incremental rounds. - A create Google made but the store failed to save names the event's id, so a repeat with
--idfinds it. syncwrites to the store in far fewer transactions, and the daily full round asks only changed series for their
occurrences.events listby time andpeople listread the store faster.- A refusal from Google for a missing or forbidden item names a reason:
google.not-foundorgoogle.denied,
so a script can branch on it instead of the message. - A sign-in cut short by a timeout or Ctrl-C still closes the store it opened.
gcal api call --helpno longer showsDefault: [object Object]for--params. Left out, it still sends no
parameters.gcalstarts in about half the time (gcal --help: 0.40 s before, 0.19 s now, on a laptop with Node 24). The
package ships one bundled program instead of hundreds of modules. A sync also maps each event faster.
Security
search --backend servermarks the right fields as written by others. Itsuntrustedlist namedtitleand
location, while the answer held them underhits, so an agent that follows the list did not treat them as data.
Each event now namestitleandlocationitself.gcal apirefuses an empty,.or..id in a request's address (api.bad-path-id). Such an id made the
address point at another Google endpoint,calendars/primary/events/..at the calendar itself; nothing is sent now.session endin a run with moved folders keeps the owner's sign-in in the system keyring, and says so. A run
withGCAL_CONFIG_DIR,GCAL_STATE_DIRor a movedHOMEuses the keyring entry every run of gcal shares; ending
the session there would have signed the owner out everywhere. It now answerskept: "keyring"and says the run is
still signed in;endedistrueonly when the run had a sign-in of its own to remove. A run in the normal folders
ends the sign-in as before.- A sign-in in a run with moved folders no longer replaces the owner's sign-in in the system keyring. It goes to
the run's own entry,gcal-cli:<config folder>, which the run reads first and itssession endremoves; the run
otherwise reads the owner's entry and never changes it. So a test sign-in with another Google account can no longer
switch the owner's profile.
0.2.0
Added
- The
gcal-cliskill is published on ClawHub aswirecat-gcalafter each release, and tells OpenClaw that it
needsgcaland how to install it (metadata.openclaw:requires.binsand the npm package@wirecat/gcal-cli).
Withgcalmissing from PATH, OpenClaw marks the skill not ready and offers to install the package. Claude Code and
Codex ignore the block.
0.1.1
Fixed
- The agent guide's header (
skills/gcal-cli/SKILL.md) is valid YAML again. Itsdescriptionheld
Calendar: sync, which a YAML parser reads as a nested mapping, so strict skill loaders and the WireCat site's
skills index refused the file. The text is unchanged apart from a dash in place of that colon.