# Wiki Notify for Google Chat (wiki-notify-gchat)

Turn wiki.adom.inc notification emails into a **live board in Google Chat** -
and keep your Gmail inbox clean.

```
wiki notification email ──poll──> per-item state ──render──> ONE card in YOUR Chat space
        │                         (open/closed)              newest first, row per item
        └──sweep──> trash / archive / label
```

The board is a single card styled like the **wiki digest posts**: one
divider-ruled row per issue. The left column is the wiki page's **hero
image** (clickable, opens the page - resolved to the public raster blob via
`adom-wiki page stats`, since Chat's image proxy cannot render the SVG
`/hero/` endpoint). The right column leads with the colored **OPEN/CLOSED
status** in the digest's `[new]`/`[updated]` slot, then the **hyperlinked
item ref** (`page #N`, opens the issue), the description, then three
explicitly labeled roles so nothing is ambiguous:
`Page author: <name>` (who owns the wiki page - not the issue),
`Issue opened by <name> on <date> <time>` (who posted the issue,
authoritative from the wiki DB), and
`Latest: <opened|commented|closed|reopened|mentioned you> by <name> on
<date> <time>` (whose activity triggered the most recent update). Then
**your involvement** - "Drew owns this fix" (assigned), "Drew was
mentioned", "Drew commented on this", "Drew watches this page" - from the
mail's "You're receiving this because..." footer, shown only when it adds
a fact the labeled lines don't already state (so no "opened this" next to
an Issue-opened-by line that names you). Set `fullName` in the config
(e.g. "Drew Owens") so the tool can match you against wiki authorship; it
is otherwise derived from the notification's To header. New activity bubbles to the top.
(Chat cards have no native table widget: the columns widget caps at 2 and
grid tiles are equal-width and unreadable, so the table is this 2-column
layout; set `"style": "blocks"` for the compact per-item layout with View
buttons.) Open items stay on the board until resolved; when an item
is closed it shows CLOSED - so you get the satisfaction of seeing it done -
then drops off at **6am the next day** to keep the board fresh.

![The board card in Google Chat](docs/board.png)

## How it works (and why a webhook + delete swap)

Google Chat incoming webhooks can only *create* messages, and only Chat apps
can post cards. So on every change, the tool POSTs a fresh card through your
webhook and then deletes the previous card via `adom-google` - you created
the space, so you are a space manager and may delete the webhook's messages.
Net effect: always exactly one board card, always the newest message in the
space. No change, no repost.

All Gmail + Chat auth is delegated to
[adom-google](https://wiki.adom.inc/adom/adom-google) (safe mode is enough).
The only secret this tool holds is **your webhook URL**, in your private
config (`0600`, outside any repo - never commit it).

## Install

```bash
adom-wiki pkg install adom/wiki-notify-gchat
```

## Setup (each team member does this once, ~3 minutes)

**Prerequisite:** an authorized [adom-google](https://wiki.adom.inc/adom/adom-google)
(`adom-google status` must show a refresh token; if not, run `adom-google auth`).

**Step 1 - create your own Chat space + webhook** (the one manual bit, in the
Google Chat UI):

1. Create a space for yourself, e.g. **Wiki Notifications**. It must be a
   space *you* create - the tool deletes its old board card as space manager.
2. Click the space name > **Apps & integrations** > **Webhooks** >
   **Add webhook**, name it (e.g. `wiki-notify`), and copy the URL.

**Step 2 - configure from the CLI** (no file editing needed):

```bash
wiki-notify-gchat setup                                  # writes the starter config
wiki-notify-gchat config set webhook '<paste the URL>'   # YOUR secret - stays in the 0600 config
wiki-notify-gchat config set timezone America/Chicago    # IANA zone; the 6am purge follows it
wiki-notify-gchat config set meName Drew                 # first name, for the involvement line
wiki-notify-gchat config set fullName 'Drew Owens'       # wiki author name, detects pages you own
wiki-notify-gchat config set action trash                # or archive | label | keep
wiki-notify-gchat config show                            # review everything (token masked)
```

The webhook URL is a secret: it lives only in that private config file. If
you ever copy it anywhere else, keep it out of git (`.gitignore` it).

**Step 3 - verify, seed, go live:**

```bash
wiki-notify-gchat health           # checks auth, config, webhook shape, cron
wiki-notify-gchat run --dry-run    # preview: what would be ingested + swept (sends nothing)
wiki-notify-gchat backfill         # seed the board from the last 30 days (no sweeping)
wiki-notify-gchat run              # ingest + sweep + post the board for real
wiki-notify-gchat install-cron     # keep it live every 2 minutes
```

Every command prints `OK:`/`ERROR:` plus `Hint:` lines telling you (or your
agent) the exact next step, and accepts `--json`.

![Dry-run preview and status](docs/dry-run.png)

## What happens to the emails

`"action"` in the config decides:

| action | effect |
|---|---|
| `trash` (default) | out of the inbox, recoverable from Trash for 30 days |
| `archive` | out of the inbox, stays in All Mail forever |
| `label` | out of the inbox, tagged with `labelName` (auto-created), e.g. `wiki-notify` |
| `keep` | left untouched (board only) |

A `requireInBody` guard (default `wiki.adom.inc`) means only genuine wiki
notifications are ever touched - anything else the sender mails stays in your
inbox. A failed board post never sweeps the email; it retries next pass.

## Board lifecycle

- **New issue / reopened** - row appears (or returns) as OPEN, jumps to the top.
- **Comment / mention** - row bumps to the top, and the **comment text itself
  renders under the row** - commenter name, timestamp, then the comment -
  one after another as a thread (up to the last 8; older ones collapse into
  a `+N earlier` note). Once a day at `purgeHour` - the same moment closed
  items are cleared - every item's thread is trimmed to just its most recent
  comment so the board never bloats.
- **Closed** - row shows CLOSED (gray) and sticks around so you can enjoy it,
  then is removed at `purgeHour` (default 6am, your `timezone`) the next day.
- The board caps at `maxRows` (default 40); older items collapse into a
  `+N older item(s)` line.

## Config reference (`~/.config/wiki-notify-gchat/config.json`)

| Field | Default | Meaning |
|---|---|---|
| `webhook` | - | YOUR incoming-webhook URL (secret) |
| `query` | `from:no-reply@adom.inc in:inbox` | Gmail search for candidate emails |
| `requireInBody` | `wiki.adom.inc` | body guard; non-matching mail is never touched |
| `action` | `trash` | `trash` / `archive` / `label` / `keep` |
| `labelName` | `wiki-notify` | label used when `action` = `label` |
| `pollSeconds` | `120` | poll + cron interval |
| `purgeHour` | `6` | hour (0-23) closed items drop off, day after closing |
| `timezone` | container local | IANA zone for dates + the purge |
| `maxRows` | `40` | card row cap |
| `boardTitle` | `Wiki Notifications` | card header |
| `style` | `table` | `table` (item ref + status left, details right, divider-ruled rows) or `blocks` (per-item blocks with View buttons) |
| `meName` | `You` | your first name, used in the involvement line ("Drew opened this") |
| `fullName` | auto (To header) | your wiki author name (e.g. "Drew Owens"), used to detect pages you authored |

## Staying alive and staying current (zero maintenance)

- **Cold boots self-heal.** `install-cron` wires three recovery triggers -
  a cron `@reboot` entry, a `~/.bashrc` snippet, and a Claude Code
  `UserPromptSubmit` hook - all calling `wiki-notify-gchat ensure`
  (throttled, 15 min). `ensure` restarts the cron service via passwordless
  sudo; if cron cannot start at all it launches a detached watch loop
  instead (and retires it once cron is back). Nobody ever runs sudo by hand.
- **Updates are automatic.** [adom-hook](https://wiki.adom.inc/adom/hook)
  (part of every adom/core install) runs `adom-wiki pkg update` on a
  throttled schedule, which pulls new wiki-notify-gchat versions and re-runs
  the install script. Nothing to do.
- Overlapping passes are impossible: `run` takes a file lock, so cron, the
  watch fallback, and `ensure` never double-post the board.

## Commands

```
setup | config [set <key> <value>] | health | run [--dry-run]
backfill [--days N] | board [--dry-run] | status | watch
install-cron | ensure [--force] | uninstall-cron | install
```

All commands accept `--json` for a `{status, data, hints}` envelope.
`board --dry-run` prints the exact card JSON without posting.

## Files

| Path | What |
|---|---|
| `~/.config/wiki-notify-gchat/config.json` | config incl. webhook (0600, private) |
| `~/.local/state/wiki-notify-gchat/state.json` | item state, processed ids, board message id |
| `~/.local/state/wiki-notify-gchat/activity.log` | one line per ingest/board/purge |
| `~/.local/state/wiki-notify-gchat/cron.log` | cron pass output |

## For future devs

[docs/DESIGN.md](docs/DESIGN.md) records why things are built this way: the
Chat API constraints (webhooks are create-only; only apps can send cards;
the delete-repost swap and the deleted-message-tombstone trap), the card
layouts we tried and killed (pipe tables, grids), the Gmail search quirks,
the two-source involvement line, the cron/cold-boot lessons, and why a
central service container was deliberately rejected. Read it before
redesigning anything.

## Troubleshooting

- `health` says not authorized: run `adom-google auth`.
- Board stopped updating after a container cold boot: it self-heals on the
  next terminal open or Claude prompt (see above); to force it immediately,
  `wiki-notify-gchat ensure --force`.
- Board card duplicated (old one not deleted): you must be a **space manager**
  of the space - use a space you created. The stray card is one manual delete
  away; the tool logs a WARN when this happens.
- Wrong purge time: set `"timezone"` to your IANA zone.

## Non-Adom orgs

Everything rides on adom-google; see
[its page](https://wiki.adom.inc/adom/adom-google) for standing up Google
OAuth outside Adom (self-hosted gateway or managed container via
support@adom.inc). Adjust `query`/`requireInBody` to your own wiki's
notification sender.
