Skip to content

Commit 523d9ec

Browse files
committed
feat: add gmail hooks wizard
1 parent aeb5455 commit 523d9ec

10 files changed

Lines changed: 1332 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Custom model providers: `models.providers` merges into `~/.clawdis/agent/models.json` (merge/replace modes) for LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.
88
- Group chat activation modes: per-group `/activation mention|always` command with status visibility.
99
- Gateway webhooks: external `wake` and isolated `agent` hooks with dedicated token auth.
10+
- Hook mappings + Gmail Pub/Sub helper (`clawdis hooks gmail setup/run`) with auto-renew + Tailscale Funnel support.
1011

1112
### Breaking
1213
- Config refactor: `inbound.*` removed; use top-level `routing` (allowlists + group rules + transcription), `messages` (prefixes/timestamps), and `session` (scoping/store/mainKey). No legacy keys read.

docs/configuration.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,27 @@ Enable a simple HTTP webhook surface on the Gateway HTTP server.
318318
Defaults:
319319
- enabled: `false`
320320
- path: `/hooks`
321+
- maxBodyBytes: `262144` (256 KB)
321322

322323
```json5
323324
{
324325
hooks: {
325326
enabled: true,
326327
token: "shared-secret",
327-
path: "/hooks"
328+
path: "/hooks",
329+
presets: ["gmail"],
330+
transformsDir: "~/.clawdis/hooks",
331+
mappings: [
332+
{
333+
match: { path: "gmail" },
334+
action: "agent",
335+
wakeMode: "now",
336+
name: "Gmail",
337+
sessionKey: "hook:gmail:{{messages[0].id}}",
338+
messageTemplate:
339+
"From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
340+
},
341+
],
328342
}
329343
}
330344
```
@@ -337,9 +351,37 @@ Requests must include the hook token:
337351
Endpoints:
338352
- `POST /hooks/wake``{ text, mode?: "now"|"next-heartbeat" }`
339353
- `POST /hooks/agent``{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, thinking?, timeoutSeconds? }`
354+
- `POST /hooks/<name>` → resolved via `hooks.mappings`
340355

341356
`/hooks/agent` always posts a summary into the main session (and can optionally trigger an immediate heartbeat via `wakeMode: "now"`).
342357

358+
Mapping notes:
359+
- `match.path` matches the sub-path after `/hooks` (e.g. `/hooks/gmail``gmail`).
360+
- `match.source` matches a payload field (e.g. `{ source: "gmail" }`) so you can use a generic `/hooks/ingest` path.
361+
- Templates like `{{messages[0].subject}}` read from the payload.
362+
- `transform` can point to a JS/TS module that returns a hook action.
363+
364+
Gmail helper config (used by `clawdis hooks gmail setup` / `run`):
365+
366+
```json5
367+
{
368+
hooks: {
369+
gmail: {
370+
account: "[email protected]",
371+
topic: "projects/<project-id>/topics/gog-gmail-watch",
372+
subscription: "gog-gmail-watch-push",
373+
pushToken: "shared-push-token",
374+
hookUrl: "http://127.0.0.1:18789/hooks/gmail",
375+
includeBody: true,
376+
maxBytes: 20000,
377+
renewEveryMinutes: 720,
378+
serve: { bind: "127.0.0.1", port: 8788, path: "/gmail-pubsub" },
379+
tailscale: { mode: "funnel", path: "/gmail-pubsub" },
380+
}
381+
}
382+
}
383+
```
384+
343385
### `canvasHost` (LAN/tailnet Canvas file server + live reload)
344386

345387
The Gateway serves a directory of HTML/CSS/JS over HTTP so iOS/Android nodes can simply `canvas.navigate` to it.

docs/gmail-pubsub.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
summary: "Gmail Pub/Sub push wired into Clawdis webhooks via gogcli"
3+
read_when:
4+
- Wiring Gmail inbox triggers to Clawdis
5+
- Setting up Pub/Sub push for agent wake
6+
---
7+
8+
# Gmail Pub/Sub -> Clawdis
9+
10+
Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> Clawdis webhook.
11+
12+
## Prereqs
13+
14+
- `gcloud` installed and logged in.
15+
- `gog` (gogcli) installed and authorized for the Gmail account.
16+
- Clawdis hooks enabled (see `docs/webhook.md`).
17+
- `tailscale` logged in if you want a public HTTPS endpoint via Funnel.
18+
19+
Example hook config (enable Gmail preset mapping):
20+
21+
```json5
22+
{
23+
hooks: {
24+
enabled: true,
25+
token: "CLAWDIS_HOOK_TOKEN",
26+
path: "/hooks",
27+
presets: ["gmail"]
28+
}
29+
}
30+
```
31+
32+
To customize payload handling, add `hooks.mappings` or a JS/TS transform module
33+
under `hooks.transformsDir` (see `docs/webhook.md`).
34+
35+
## Wizard (recommended)
36+
37+
Use the Clawdis helper to wire everything together (installs deps on macOS via brew):
38+
39+
```bash
40+
clawdis hooks gmail setup \
41+
42+
```
43+
44+
Defaults:
45+
- Uses Tailscale Funnel for the public push endpoint.
46+
- Writes `hooks.gmail` config for `clawdis hooks gmail run`.
47+
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
48+
49+
Want a custom endpoint? Use `--push-endpoint <url>` or `--tailscale off`.
50+
51+
Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`
52+
via Homebrew; on Linux install them manually first.
53+
54+
Run the daemon (starts `gog gmail watch serve` + auto-renew):
55+
56+
```bash
57+
clawdis hooks gmail run
58+
```
59+
60+
## One-time setup
61+
62+
1) Select the GCP project **that owns the OAuth client** used by `gog`.
63+
64+
```bash
65+
gcloud auth login
66+
gcloud config set project <project-id>
67+
```
68+
69+
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
70+
71+
2) Enable APIs:
72+
73+
```bash
74+
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
75+
```
76+
77+
3) Create a topic:
78+
79+
```bash
80+
gcloud pubsub topics create gog-gmail-watch
81+
```
82+
83+
4) Allow Gmail push to publish:
84+
85+
```bash
86+
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
87+
--member=serviceAccount:[email protected] \
88+
--role=roles/pubsub.publisher
89+
```
90+
91+
## Start the watch
92+
93+
```bash
94+
gog gmail watch start \
95+
--account [email protected] \
96+
--label INBOX \
97+
--topic projects/<project-id>/topics/gog-gmail-watch
98+
```
99+
100+
Save the `history_id` from the output (for debugging).
101+
102+
## Run the push handler
103+
104+
Local example (shared token auth):
105+
106+
```bash
107+
gog gmail watch serve \
108+
--account [email protected] \
109+
--bind 127.0.0.1 \
110+
--port 8788 \
111+
--path /gmail-pubsub \
112+
--token <shared> \
113+
--hook-url http://127.0.0.1:18789/hooks/gmail \
114+
--hook-token CLAWDIS_HOOK_TOKEN \
115+
--include-body \
116+
--max-bytes 20000
117+
```
118+
119+
Notes:
120+
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
121+
- `--hook-url` points to Clawdis `/hooks/gmail` (mapped; isolated run + summary to main).
122+
- `--include-body` and `--max-bytes` control the body snippet sent to Clawdis.
123+
124+
Recommended: `clawdis hooks gmail run` wraps the same flow and auto-renews the watch.
125+
126+
## Expose the handler (dev)
127+
128+
For local testing, tunnel the handler and use the public URL in the push subscription:
129+
130+
```bash
131+
cloudflared tunnel --url http://127.0.0.1:8788 --no-autoupdate
132+
```
133+
134+
Use the generated URL as the push endpoint:
135+
136+
```bash
137+
gcloud pubsub subscriptions create gog-gmail-watch-push \
138+
--topic gog-gmail-watch \
139+
--push-endpoint "https://<public-url>/gmail-pubsub?token=<shared>"
140+
```
141+
142+
Production: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:
143+
144+
```bash
145+
gog gmail watch serve --verify-oidc --oidc-email <svc@...>
146+
```
147+
148+
## Test
149+
150+
Send a message to the watched inbox:
151+
152+
```bash
153+
gog gmail send \
154+
--account [email protected] \
155+
156+
--subject "watch test" \
157+
--body "ping"
158+
```
159+
160+
Check watch state and history:
161+
162+
```bash
163+
gog gmail watch status --account [email protected]
164+
gog gmail history --account [email protected] --since <historyId>
165+
```
166+
167+
## Troubleshooting
168+
169+
- `Invalid topicName`: project mismatch (topic not in the OAuth client project).
170+
- `User not authorized`: missing `roles/pubsub.publisher` on the topic.
171+
- Empty messages: Gmail push only provides `historyId`; fetch via `gog gmail history`.
172+
173+
## Cleanup
174+
175+
```bash
176+
gog gmail watch stop --account [email protected]
177+
gcloud pubsub subscriptions delete gog-gmail-watch-push
178+
gcloud pubsub topics delete gog-gmail-watch
179+
```

docs/webhook.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ Effect:
8080
- Always posts a summary into the **main** session
8181
- If `wakeMode=now`, triggers an immediate heartbeat
8282

83+
### `POST /hooks/<name>` (mapped)
84+
85+
Custom hook names are resolved via `hooks.mappings` (see configuration). A mapping can
86+
turn arbitrary payloads into `wake` or `agent` actions, with optional templates or
87+
code transforms.
88+
89+
Mapping options (summary):
90+
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
91+
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
92+
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
93+
- Use `match.source` to keep a generic ingest endpoint (payload-driven routing).
94+
- TS transforms require a TS loader (e.g. `tsx`) or precompiled `.js` at runtime.
95+
- `clawdis hooks gmail setup` writes `hooks.gmail` config for `clawdis hooks gmail run`.
96+
8397
## Responses
8498

8599
- `200` for `/hooks/wake`
@@ -104,6 +118,13 @@ curl -X POST http://127.0.0.1:18789/hooks/agent \
104118
-d '{"message":"Summarize inbox","name":"Email","wakeMode":"next-heartbeat"}'
105119
```
106120

121+
```bash
122+
curl -X POST http://127.0.0.1:18789/hooks/gmail \
123+
-H 'Authorization: Bearer SECRET' \
124+
-H 'Content-Type: application/json' \
125+
-d '{"source":"gmail","messages":[{"from":"Ada","subject":"Hello","snippet":"Hi"}]}'
126+
```
127+
107128
## Security
108129

109130
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.

0 commit comments

Comments
 (0)