Skip to main content

heinzel-google

Google Docs, Sheets, Drive, Calendar and Gmail: the documents (read), calendar and mail (read and write) capabilities (ADR 0028): a first-party Heinzelmännchen plugin. It is independently released and installed explicitly:

hz system plugin add google --confirm             # a compatible plugin release; run it again to upgrade
hz system plugin remove heinzel-google

It is the default provider for Google Workspace, not the only possible one: workflows ask for documents, calendar and mail, and any other plugin that provides those capabilities can be bound instead, in configuration.

How it reaches Google

Through the open-source Google Workspace CLI, gws (0.22 or newer): it signs in with Google, keeps its own credentials (encrypted, with the key in the OS keychain, by default) and calls Google's APIs. This plugin never sees a token. It runs gws <service> <resource> <method> --params <JSON> --format json as an argument list (never through a shell), with the executable resolved to an absolute path, from the root directory (so no project's .env reaches gws), with an allowlisted environment (never Heinzelmännchen's own credentials or other secrets; of gws's own variables only GOOGLE_WORKSPACE_CLI_CONFIG_DIR, _KEYRING_BACKEND, _CLIENT_ID, _CLIENT_SECRET and GOOGLE_WORKSPACE_PROJECT_ID, not its token, credentials-file, sanitizing or log variables), a timeout that stops gws and anything it started, and at most 10 MB of output read. Why gws: ADR 0042.

What leaves this machine, and where to: file ids, the Drive query (the modified-time window and the --name text) the calendar window and the mail search, to Google's API hosts (www.googleapis.com for Drive and Calendar, docs.googleapis.com, sheets.googleapis.com, gmail.googleapis.com); gws also fetches API descriptions from Google and refreshes its sign-in at oauth2.googleapis.com. Nothing goes anywhere else: gws 0.22 documents no telemetry or update check, and none was found in it.

Set up

Install gws (it needs Node.js), give it an OAuth client once (gws auth setup, which needs gcloud, or a Desktop OAuth client as its README describes), then let setup do the rest:

npm install -g @googleworkspace/cli
hz setup google         # finds gws, signs it in, binds documents, calendar and (if you say so) mail
hz system doctor        # finds gws, checks its version and its stored sign-in, from files; Google isn't contacted

By hand, setup is:

gws auth login --scopes https://www.googleapis.com/auth/documents.readonly,https://www.googleapis.com/auth/spreadsheets.readonly,https://www.googleapis.com/auth/drive.metadata.readonly,https://www.googleapis.com/auth/calendar.readonly
hz system config set capabilities.documents google
hz system config set capabilities.calendar google

Mail is opt-in: setup asks, defaulting to no, and then signs in again with Gmail's read-only scope added (,https://www.googleapis.com/auth/gmail.readonly), which lets the plugin read all of your mail; then hz system config set capabilities.mail google.

Writes are opt-in too. Setup asks whether hz may change your calendar and your mail, defaulting to no; only then does the sign-in ask for https://www.googleapis.com/auth/calendar.events (create, move, cancel and answer events) and https://www.googleapis.com/auth/gmail.modify (keep drafts, send them, file conversations; it reads too, so it replaces gmail.readonly, and it can't delete mail for good). Heinzelmännchen writes only from publish operations that show you what they will do and run after you confirm; a chat's agent only proposes them (ADR 0059). Without the scope, a write fails with the gws auth login that adds it.

These are the narrowest read-only scopes for what the adapters do; drive.metadata.readonly lists and identifies files and never reads their contents (Google classes it as restricted, which matters only for published OAuth clients). Bindings go in your user configuration, never in a project hz.toml, which Heinzelmännchen ignores for them.

Optional settings, in [google] or HZ_GOOGLE__<FIELD>: gws_path, the absolute path of gws when it isn't on PATH (only absolute PATH entries are searched; both are ignored in a project hz.toml: a repository someone else controls must not choose the program that reads your data), and timeout, the seconds each gws run may take (default 60, also ignored in a project hz.toml).

Is it connected?

Bound automatically to documents and calendar while it is the only installed adapter of them, ADR 0055; mail never is, so Gmail stays opt-in: hz setup google asks. hz system connector list shows whether gws has a sign-in, from its files alone; hz system connector test google asks Drive who is signed in (gws drive about get, 15 s at most), and only when you run it.

Use

Workflows ask for documents and calendar, never for Google:

from pydantic import AwareDatetime

from heinzel import Calendar, Document, Documents, Event, Module, Window

module = Module("prep", "One-on-one preparation.", requires=["documents:read", "calendar:read"])


@module.operation
def week(calendar: Calendar, since: AwareDatetime, until: AwareDatetime) -> list[Event]:
    """This week's events."""
    return calendar.events(Window(since=since, until=until))


@module.operation
def notes(documents: Documents) -> Document:
    """The running notes, as Markdown."""
    return documents.read("https://docs.google.com/document/d/1AbCdEfGhIjKlMnOpQrStUvWxYz/edit")
Method Capability gws command
read(ref) documents:read drive files get (metadata), then docs documents get with includeTabsContent or sheets spreadsheets get with includeGridData
events(window) calendar:read calendar events list on primary, singleEvents, showDeleted, paged by pageToken
messages(query, window) mail:read gmail users messages list with Gmail's search, then messages get (metadata) for each
read(message) mail:read gmail users messages get (full)
free_busy(people, window) calendar:read calendar freebusy query for primary and each address
create_event(event) calendar:write calendar events insert on primary with an id of hz's own (no duplicate on a retry), sendUpdates as the draft says, a Meet link when asked
update_event(event, change) calendar:write calendar events get, then patch (only your events, or ones whose guests may change them)
cancel_event(event, notify) calendar:write calendar events get, then delete (only your events; decline others')
respond(event, response) calendar:write calendar events get, then patch of your own attendee entry
thread(thread) mail:read gmail users threads get (full), without drafts, Spam and Trash
draft(draft) mail:read gmail users drafts get (full)
create_draft(draft) mail:write gmail users drafts create with a plain-text MIME message; a reply with threadId, In-Reply-To and References
send_draft(draft, seen) mail:write gmail users drafts get, then send if its message is still the one you saw
relabel(thread, add, remove) mail:write gmail users threads modify with label ids; not Spam, Trash, Drafts or Sent
  • Documents are a Docs, Sheets or Drive link (https://docs.google.com/document/d/<id>/..., .../spreadsheets/d/<id>/..., https://drive.google.com/file/d/<id>/..., .../open?id=<id>) or a bare file id; links to any other host are refused before gws runs, and a Drive shortcut is followed once. A Doc comes back as Markdown-flavoured text, every tab under its title: headings, lists, bold and italic, links, tables; person chips by name, never their email, and mail links by their text; titles and link targets can't start a line of their own; images and drawings are left out. A Sheet comes back as one ## <sheet> table per sheet with the values as people see them. revision is the Doc's revision id or the Sheet's Drive version. Uploaded Word or Excel files are refused: open them as a Google Doc or Sheet first.
  • Events are your primary calendar's, every occurrence of a recurring event, cancelled ones included, by start. Attendees are display names and emails, without meeting rooms; the workflow packet builder drops the emails before anything reaches an agent's prompt. conference_id is the Zoom meeting number when the event has a Zoom link (in its conferencing data, location or description), else the Meet code, so the meetings capability can find the event's meeting; conference links are kept only on the conferencing services' own hosts (Meet, Zoom, Teams, Webex), without their query, where passcodes live. A display name that is an address is dropped.
  • Mail is your Gmail: messages takes the shared query syntax (from:<address or domain> subject:<text> label:<name>) and a window. The window and the sender go into Gmail's search and the label as its id; the subject is matched by the plugin, because Gmail's subject: matches whole words only. Results come oldest first, at most 200 messages a window (one gws run each). read gives the text part, or the HTML part as text, in the part's own charset, without attachments, and only the headers that carry no addresses. Senders and recipients are names and emails, parsed before any encoded name is decoded, so a display name can't pose as an address; the workflow packet builder drops the addresses before anything reaches an agent's prompt. A message longer than 2 million characters, or with more than 500 MIME parts or 2 million characters of HTML, is refused, not cut; Spam and Trash are not read; reading one window's messages may take at most five minutes.
  • Writes change only what the person confirmed, through publish operations. Ids are checked before they reach gws; a write that times out or fails without Google refusing it is not retried and says to look in Google first; a rate-limited one is retried. A recurring series' own id is refused: one occurrence changes at a time. Drafts are built with Python's email package from plain addresses and one-line subjects, so nothing can add a header.
  • Text is data. Every string returned passes heinzel.clean_text (no control, invisible or bidirectional characters).
  • Bounds. One gws run prints at most 10 MB, one call reads at most 100 pages or 25 MB, a Doc's text at most four times its JSON, tables and tabs nest at most 8 deep, a Sheet at most a million cells; past that, an error says to narrow the window or that the file is too large. Rate limits are honoured: a rate-limited call is retried after 1, 2 and 4 seconds, then fails saying when to retry.
hz google info                  # this plugin, its version, the gws it uses, the scopes and APIs
hz google files --days 3        # Docs and Sheets you can open that changed in the last three days
hz system capability list       # who reads what, through which adapter and hosts

Errors say what to do: no gws names the install; not signed in or an expired sign-in names the exact gws auth login command above (doctor names hz setup google); a missing scope or a disabled API says which; a file the account can't see says so. Token-shaped strings in anything gws says are redacted before it is shortened.

Development and release commands: repository guide.

Migration provenance: heinzel, before the 0.9 plugin-boundary migration.

Metadata

Release files for heinzel-google 0.9.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for heinzel-google 0.9.1
File Size Uploaded Provenance Actions
heinzel_google-0.9.1.tar.gz 151.7 kB Without provenance Details for heinzel_google-0.9.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for heinzel-google 0.9.1
File Interpreter ABI Platform Actions
heinzel_google-0.9.1-py3-none-any.whl Python 3 none any Details for heinzel_google-0.9.1-py3-none-any.whl

Total release size: 201.5 kB

Release files / heinzel_google-0.9.1.tar.gz

Download URL heinzel_google-0.9.1.tar.gz
Size 151.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d21862ba257c16e6c50ebed1bacd646458d7b5f436cb58592e40d56ccc544f43
BLAKE2b-256 checksum
How to use checksums
04f10f2c49c664d70d03653f1c9365ef0215727969ddc9eb92d6d7add649155d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / heinzel_google-0.9.1-py3-none-any.whl

Download URL heinzel_google-0.9.1-py3-none-any.whl
Size 49.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ef7a9090e9f3c8803f3b6546d6629614e637f25346f766f697827a0da132e81a
BLAKE2b-256 checksum
How to use checksums
51e4c0e4073c53bdbd4139969b431a1d506a9b335c84ad10460c1e2b7253bcf4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for heinzel-google 0.9.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.9.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page