Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Automation Guide

`gcalcli` provides features specifically designed for automation scripts and agents.

## Structured Output (JSON)

Use the `--json` flag to get machine-readable output. This is supported by:

- `list`
- `agenda`
- `search`
- `updates`

### Calendar List Schema (`list --json`)

Returns a list of Calendar objects:

```json
[
{
"id": "user@gmail.com",
"summary": "Work Calendar",
"accessRole": "owner",
"selected": true,
...
}
]
```

### Event Schema (`agenda --json`, `search --json`)

Returns a list of Event objects with ISO 8601 dates:

```json
[
{
"id": "event_id_string",
"summary": "Meeting with Team",
"description": "Discuss Q1 goals",
"start": "2023-10-27T14:00:00-07:00",
"end": "2023-10-27T15:00:00-07:00",
"allday": false,
"location": "Conference Room A",
"htmlLink": "https://calendar.google.com/...",
"calendar": "Work Calendar",
"calendar_id": "user@gmail.com",
"attendees": [{ "email": "boss@example.com", "responseStatus": "accepted" }]
}
]
```

### Notes on Dates

- **Time Events**: `start` and `end` are full ISO 8601 strings including timezone offset.
- **All Day Events**: `start` and `end` are `YYYY-MM-DD` strings. `allday` will be `true`.
2 changes: 1 addition & 1 deletion gcalcli/argparsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ def get_argument_parser():

sub.add_parser(
'list',
parents=[calendars_parser, color_parser],
parents=[calendars_parser, output_parser],
help='list available calendars',
description='List available calendars.',
)
Expand Down
71 changes: 51 additions & 20 deletions gcalcli/gcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,32 +749,59 @@ def _tsv(self, start_datetime, event_list):
output = ('\t'.join(row)).replace('\n', r'\n')
print(output)

def _json(self, start_datetime, event_list):
keys = set(self.details.keys())
keys.update(DETAILS_DEFAULT)

print("[")
def _json_output(self, data):
"""Standard JSON dumper with consistent formatting."""
print(json.dumps(data, default=str, indent=2))

def _serialize_event(self, event):
"""Serialize event to a clean ISO-8601 compatible dict."""
# Use full ISO format for times
start_iso = event['s'].isoformat()
end_iso = event['e'].isoformat()

# Determine if all-day (no time component)
is_allday = is_all_day(event)
if is_allday:
# Force date-only Format YYYY-MM-DD
start_iso = event['s'].strftime('%Y-%m-%d')
# End date is exclusive in API, but usually inclusive in UI.
# Keeping API behavior (exclusive) for machine readability.
end_iso = event['e'].strftime('%Y-%m-%d')

serialized = {
"id": event.get("id"),
"summary": event.get("summary", "(No title)"),
"description": event.get("description", "").strip(),
"start": start_iso,
"end": end_iso,
"allday": is_allday,
"location": event.get("location", "").strip(),
"htmlLink": event.get("htmlLink"),
"status": event.get("status"),
"calendar": event.get("gcalcli_cal", {}).get("summary", "unknown"),
"calendar_id": event.get("gcalcli_cal", {}).get("id", "unknown"),
}

# Add attendees if present
if "attendees" in event:
serialized["attendees"] = [
{"email": a.get("email"), "responseStatus": a.get("responseStatus")}
for a in event["attendees"]
]

return serialized

first = True
def _json(self, start_datetime, event_list):
# Legacy-ish wrapper, but we upgrade it to use the new serializer
data = []
for event in event_list:
if self.options['ignore_started'] and (event['s'] < self.now):
continue
if self.options['ignore_declined'] and self._DeclinedEvent(event):
continue

row = {}
#row['raw'] = event

for key in keys:
if key in HANDLERS:
row[key] = HANDLERS[key].data(event)

if not first:
print(",")
first = False
print(json.dumps(row, default=str))

print("]")
data.append(self._serialize_event(event))

self._json_output(data)

def _PrintEvent(self, event, prefix):

Expand Down Expand Up @@ -1261,6 +1288,10 @@ def _DeclinedEvent(self, event):
if 'self' in a or a['email'] == event['gcalcli_cal']['id'])

def ListAllCalendars(self):
if self.options.get('json'):
self._json_output(self.all_cals)
return

access_len = max(len(cal['accessRole']) for cal in self.all_cals)
access_len = max(access_len, len('Access'))

Expand Down