From 64f9ad1cfd781c5302d50ded6b307e1a1d42d1e8 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 07:30:15 -0700 Subject: [PATCH] docs: rewrite README around usage State that Gmail is the only supported API, install the package and its peers with bun, and add a quickstart that attaches the gmail tools to an agent, a tool reference with approval gates, and the sidecar-bundle and interchange manifest entries. Development and live Gmail checks move to CONTRIBUTING.md. --- CONTRIBUTING.md | 47 ++++++++++++++++++++ README.md | 114 +++++++++++++++++++++++++++++++----------------- 2 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..38cff30 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,47 @@ +# Contributing + +## Development + +```bash +bun install +bun run typecheck +bun run typecheck:live +bun run test +bun run build +``` + +The tests inject `fetch` implementations and do not call Gmail. + +## Live Gmail checks + +Use a dedicated Gmail fixture account and a Desktop OAuth client granted only +`gmail.modify`. The read smoke requires a query that matches exactly one +one-message thread: + +```bash +GMAIL_LIVE_TEST=1 \ +GMAIL_LIVE_CLIENT_ID='...' \ +GMAIL_LIVE_CLIENT_SECRET='...' \ +GMAIL_LIVE_FIXTURE_QUERY='subject:(interchange-gmail-e2e-fixture)' \ +bun run test:live +``` + +The full live suite has been verified against a dedicated Gmail fixture account. +It exercises every Gmail tool: + +- `gmail_search_threads` finds the one-message fixture thread. +- `gmail_get_thread` and `gmail_get_message` retrieve its metadata. +- `gmail_list_labels` lists the account labels. +- `gmail_create_draft` and `gmail_list_drafts` create and find a disposable + draft, which the suite then deletes. +- `gmail_label_message` and `gmail_unlabel_message` add and remove `STARRED` + on the fixture message. +- `gmail_label_thread` and `gmail_unlabel_thread` add and remove `STARRED` + on every message in the fixture thread. + +Add `GMAIL_LIVE_MUTATION_TEST=1` to run the draft and label checks. The suite +deletes its draft, restores the fixture labels, and never sends email. + +The first run opens Google OAuth and stores the refresh token in the ignored +`.local/gmail-live-token.json` file. Set `GMAIL_LIVE_TOKEN_FILE` when using a +different token file. Credentials and message contents are not logged. diff --git a/README.md b/README.md index 37ecf70..9d16104 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,96 @@ # @corbits/google-tools -Google API clients and Interchange tools, starting with Gmail. Authentication -is supplied through the Interchange `gmail-api` mediated credential; the -package never accepts or returns raw tokens. +Gmail tools for AI agents: search, read, label and draft over the Gmail REST API, authenticated by an HTTP credential that signs each request so the agent never holds an OAuth token. A tool pack for the Interchange sidecar that also runs standalone. -## Requirements +## Why @corbits/google-tools? -Node.js 24 or newer (`engines.node >=24`). +1. **No tokens in the agent.** Tools call Gmail through a credential the host resolves at run time. The OAuth token never enters the model context or tool arguments. +2. **Human approval on every tool.** Every tool uses `approval: "ask"`, so Interchange pauses each call until a person approves it or saves an always-allow grant. No tool can send mail. +3. **Grant-gated in Interchange.** The package declares the `gmail-api` credential and the `gmail.modify` scope it needs. The hub (Interchange's control plane) stores the credential, and a grant (a record of which agent may use it) decides which agents get it. + +It covers Gmail only, not other Google APIs. ## Install ```bash -bun add github:corbitsdev/google-tools +bun add @corbits/google-tools @intx/agent @intx/types ``` -## Working on it +Requires Node.js 24 or newer. -```bash -bun install -bun run typecheck -bun test -bun run build +## Quickstart + +Gives an Interchange agent the Gmail tools: + +```ts +import { defineAgent } from "@intx/agent"; +import { gmail } from "@corbits/google-tools/sidecar-bundle"; + +export const inboxAgent = defineAgent({ + id: "inbox-agent", + systemPrompt: "Triage my Gmail inbox and draft replies for me to review.", + tools: [gmail], + capabilities: [], + inference: { sources: [{ provider: "ollama", model: "gpt-oss:20b" }] }, +}); ``` -The tests inject `fetch` implementations and do not call Gmail. +When the agent runs, the sidecar resolves the `gmail-api` credential the hub +granted it and passes it to the tools. The token stays out of the agent. -## Live Gmail checks +## Where it fits -Use a dedicated Gmail fixture account and a Desktop OAuth client granted only -`gmail.modify`. The read smoke requires a query that matches exactly one -one-message thread: +[Interchange](https://github.com/faremeter/interchange) runs AI agents as principals (accounts that hold their own identity, permissions and credentials). -```bash -GMAIL_LIVE_TEST=1 \ -GMAIL_LIVE_CLIENT_ID='...' \ -GMAIL_LIVE_CLIENT_SECRET='...' \ -GMAIL_LIVE_FIXTURE_QUERY='subject:(interchange-gmail-e2e-fixture)' \ -bun run test:live -``` +- Runs in the sidecar, the runtime that hosts each agent and its tools. +- Registers its tools with `defineTool` from + [`@intx/agent`](https://github.com/faremeter/interchange/tree/main/packages/agent). +- Reads the Gmail credential from the runtime capabilities in + [`@intx/types`](https://github.com/faremeter/interchange/tree/main/packages/types). +- Pairs with + [`@corbits/oauth-core`](https://github.com/corbitsdev/corbits-oauth-core), + which obtains the Google credential the hub stores. + +## Tools + +| Tool | Purpose | +| ----------------------- | --------------------------------------------------- | +| `gmail_search_threads` | Search the mailbox and return thread summaries. | +| `gmail_get_thread` | Fetch one thread with selected message detail. | +| `gmail_get_message` | Fetch one message with selected detail. | +| `gmail_list_labels` | List the mailbox labels. | +| `gmail_create_draft` | Create a draft, optionally as a reply. Never sends. | +| `gmail_list_drafts` | List drafts with Gmail-query filtering. | +| `gmail_label_message` | Add labels to a message. | +| `gmail_unlabel_message` | Remove labels from a message. | +| `gmail_label_thread` | Add labels to every message in a thread. | +| `gmail_unlabel_thread` | Remove labels from every message in a thread. | + +### Approvals + +Every tool declares `approval: "ask"`, including the read-only ones, because +Gmail is third-party data. Interchange pauses each call until a person +responds. Allowing once runs that call; always allowing saves an `allow` +grant for that tool on that agent, so later calls run without asking. + +## Using with Interchange -The full live suite has been verified against a dedicated Gmail fixture account. -It exercises every Gmail tool: +The sidecar loads tools from the `./sidecar-bundle` export. Its `gmail` export +builds the tools from the `capabilities` the sidecar puts in the agent's +environment. -- `gmail_search_threads` finds the one-message fixture thread. -- `gmail_get_thread` and `gmail_get_message` retrieve its metadata. -- `gmail_list_labels` lists the account labels. -- `gmail_create_draft` and `gmail_list_drafts` create and find a disposable - draft, which the suite then deletes. -- `gmail_label_message` and `gmail_unlabel_message` add and remove `STARRED` - on the fixture message. -- `gmail_label_thread` and `gmail_unlabel_thread` add and remove `STARRED` - on every message in the fixture thread. +The package's `package.json` tells Interchange what to load: -Add `GMAIL_LIVE_MUTATION_TEST=1` to run the draft and label checks. The suite -deletes its draft, restores the fixture labels, and never sends email. +- `interchange.tools` points at `./dist/sidecar-bundle.js`. +- `interchange.credentials` asks for a credential named `gmail-api` with the + `https://www.googleapis.com/auth/gmail.modify` scope. -The first run opens Google OAuth and stores the refresh token in the ignored -`.local/gmail-live-token.json` file. Set `GMAIL_LIVE_TOKEN_FILE` when using a -different token file. Credentials and message contents are not logged. +To connect it, store the user's Google OAuth credential on the hub as +`gmail-api` and grant it to the agent. The sidecar resolves it for the tools at +run time. ## License -LGPL-2.1-only. See [LICENSE](./LICENSE). +LGPL-2.1-only. See +[LICENSE](https://github.com/corbitsdev/google-tools/blob/main/LICENSE). +Development notes are in [CONTRIBUTING.md](https://github.com/corbitsdev/google-tools/blob/main/CONTRIBUTING.md).