Skip to content
Merged
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
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
# webhook-objects

A monorepo for working with webhook objects from a Gather user perspective.
> Note: These examples are built with AI, and are designed to be "food for thought", not recommendations that are fit for production use.

See the individual packages for more info:
A collection of quick examples for using Webhook Objects within [Gather 2.0](https://gather.town).

- [`@webhook-objects/client`](./packages/client)
- [`@webhook-objects/now-playing-inbox`](./packages/now-playing-inbox)
- [`@webhook-objects/gh-prs-inbox`](./packages/gh-prs-inbox)
- [`@webhook-objects/claude-status`](./packages/claude-status)
- [`@webhook-objects/low-battery-switch`](./packages/low-battery-switch)
## Getting Started

To get started, you can explore our individual packages:

* [`@webhook-objects/now-playing-inbox`](./packages/now-playing-inbox)
* [`@webhook-objects/gh-prs-inbox`](./packages/gh-prs-inbox)
* [`@webhook-objects/claude-status`](./packages/claude-status)
* [`@webhook-objects/low-battery-switch`](./packages/low-battery-switch)

## Support

We're committed to helping you get the most out of webhook-objects. That said, the examples provided here are just that - examples. We recommend using these as a jumping off point, not a recipe for production-quality solutions.

Further - While PRs are welcome, they're reviewed on a best-effort basis. If you have specific questions, please reach out to our support team directly via [gather.town/contact-us](https://gather.town/contact-us).

## License

This project is dual licensed under MIT and Apache 2.0.
5 changes: 3 additions & 2 deletions packages/claude-status/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# @webhook-objects/claude-status

PoC. Mirrors a Claude Code session's live status into a `status` webhook object,
driven by Claude's own hooks (no polling).
Mirrors a Claude Code session's live status into a `status` webhook object, driven by Claude Hooks.

## Usage

> Note: This example has a unique installation flow, due to challenges with `PATH` when running from within Claude. As a result, we don't use `tsx` to run this.

```sh
pnpm --filter @webhook-objects/claude-status build

Expand Down
157 changes: 1 addition & 156 deletions packages/client/README.md
Original file line number Diff line number Diff line change
@@ -1,156 +1 @@
# @webhook-objects/client

A typed client for dispatching [Standard Webhooks](https://www.standardwebhooks.com/)-signed events to Gather webhook objects, plus the full set of TypeScript types describing every event, capability, preset, and response.

The client signs each request (HMAC over `id`, `timestamp`, and body), `POST`s it to your object's webhook URL, and returns a strongly-typed response body.

## Installation

```bash
npm install @webhook-objects/client
# or
pnpm add @webhook-objects/client
```

### Runtime & `fetch`

- **Browser / Node 18+**: nothing extra needed — the global `fetch` is used by default.
- **Older Node / explicit `undici`**: install [`undici`](https://github.com/nodejs/undici) (an optional peer dependency) and import from `@webhook-objects/client/node`, which defaults `fetchImpl` to undici's `fetch` and falls back to the global `fetch` when undici isn't present.

```bash
pnpm add undici # only needed for the /node entry point's undici-backed default
```

## Entry points

| Import | Use case | Default `fetch` |
| --------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------ |
| `@webhook-objects/client` | Auto-resolves to the browser build (and node build under the `node` condition) | global `fetch` |
| `@webhook-objects/client/browser` | Explicit browser build | global `fetch` |
| `@webhook-objects/client/node` | Node build | `undici` (lazy import), falling back to global `fetch` |
| `@webhook-objects/client/objects` | Types only — no client | n/a |

## Usage

### Send a capability event

```ts
import { Client } from "@webhook-objects/client/node";

const client = new Client({
url: "https://api.gather.town/api/v2/hooks/spaces/<spaceId>/objects/<objectId>",
secret: "whsec_...",
});

const res = await client.send({
type: "counter.set",
timestamp: new Date().toISOString(),
data: { count: 1 },
});

if (res.status === "dispatched" || res.status === "space_idle") {
// applied
}
```

The `event` argument is a discriminated union: the `type` (`"<capability>.<method>"`) determines the exact shape required for `data`. Unknown properties and mismatched payloads are rejected at compile time.

### Probe object metadata (`webhook.ping`)

```ts
const meta = await client.requestMetadata();

meta.status; // "pong"
meta.objectId; // string
meta.spaceId; // string
meta.preset; // "counter" | "switch" | "inbox" | "status" | null
meta.capabilities; // declared capability state for the preset
```

### Browser

```ts
import { Client } from "@webhook-objects/client/browser";

const client = new Client({ url, secret });
```

## API

### `new Client(options)`

| Option | Type | Description |
| -------------- | ---------------- | ---------------------------------------------------------------- |
| `url` | `URL \| string` | The object's webhook receiver URL. |
| `secret` | `string` | Standard Webhooks signing secret (`whsec_...`). |
| `signOptions?` | `WebhookOptions` | Passed through to `standardwebhooks`. |
| `fetchImpl?` | `FetchImpl` | Override the `fetch` used. Defaults per entry point. |
| `idImpl?` | `IdImpl` | Override webhook id generation. Defaults to `msg_${Date.now()}`. |

> The `/node` `Client` accepts the same options; it only changes the default `fetchImpl`.

### `client.send(event, init?)`

Signs and `POST`s a capability event. Returns `Promise<WebhookEventResponseBody>` (`{ status: "dispatched" }` or `{ status: "space_idle" }`). The optional `init` is a `RequestInit` minus `body`, `method`, and `window` (those are controlled by the client) — use it for custom headers, `signal`, etc.

### `client.requestMetadata()`

Sends a `webhook.ping` and returns `Promise<PingResponseBody>` describing the object's preset and current capability state.

### Errors

`send` / `requestMetadata` reject with an `Error` when:

- the response status is not `2xx` — `cause` is `{ response }`.
- the body isn't valid JSON — `cause` is `{ error, response }`.

## Capabilities, methods & presets

Events are addressed as `"<capability>.<method>"`. The capabilities and their method payloads:

| Capability | Method | `data` |
| ---------- | ----------- | ---------------------------------------------------------------- |
| `info` | `set` | `{ name?, description? }` |
| `counter` | `set` | `{ count: number }` |
| `counter` | `increment` | `{ by?: number }` |
| `counter` | `reset` | `{}` |
| `switch` | `set_state` | `{ on: boolean }` |
| `switch` | `toggle` | `{}` |
| `status` | `set` | `{ state: "off" \| "on" \| "question" \| "alert" \| "working" }` |
| `status` | `reset` | `{}` |
| `activity` | `add` | `{ id, text, url? }` |
| `activity` | `remove` | `{ id }` |
| `activity` | `clear` | `{}` |

Presets compose a fixed set of capabilities (every preset also includes `info`):

| Preset | Capabilities |
| --------- | ----------------------------- |
| `counter` | `info`, `counter` |
| `switch` | `info`, `switch` |
| `inbox` | `info`, `activity`, `counter` |
| `status` | `info`, `status`, `activity` |

## Types

All event, capability, preset, and response types are exported from the package root and from `@webhook-objects/client/objects`. Notable ones:

- `WebhookEvent` — the union of all sendable capability events.
- `PresetWebhookEvent<P>` — events accepted by an object of preset `P`.
- `WebhookEventResponseBody`, `PingResponseBody` — success bodies.
- `WebhookHttpResponse`, `WebhookErrorResponse`, `WebhookErrorCode` — the full receiver response surface.

## Development

```bash
pnpm test # vitest + coverage (node + chromium projects)
pnpm build # vite build (JS) + tsc (.d.ts into dist/types)
```

Tests run in two projects: `node` (all specs) and `chromium` (browser-safe specs,
via `@vitest/browser` + Playwright). Target one with `vitest run --project node`
or `vitest run --project chromium`.

## License

Apache-2.0 OR MIT
TODO: Remove in favor of 1P SDK.
4 changes: 1 addition & 3 deletions packages/gh-prs-inbox/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# @webhook-objects/gh-prs-inbox

PoC. Polls GitHub for PRs awaiting your review and mirrors them into an `inbox`
webhook object — one activity entry per PR, plus a counter of how many are
waiting.
Uses the GitHub CLI to poll for PRs awaiting your review, mirroring them to an `inbox` webhook object.

Requires the [`gh`](https://cli.github.com) CLI, authenticated (`gh auth login`).

Expand Down
4 changes: 1 addition & 3 deletions packages/low-battery-switch/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# @webhook-objects/low-battery-switch

PoC. Polls macOS power state and mirrors "running low" into a `switch` webhook
object — on when you're on battery at/below a threshold, off otherwise. A shared
"might drop off the call" light for the space.
Updates a `switch` webhook object based on the current macOS power state. Turned on when your battery is running low.

macOS only — reads power via the built-in `pmset` (no extra deps).

Expand Down
5 changes: 2 additions & 3 deletions packages/now-playing-inbox/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# @webhook-objects/now-playing-inbox

macOS-only PoC. Watches what's playing (Spotify, then Music) and posts each new
track to an `inbox` webhook object's activity feed.
Watches your system for playing music (via Spotify, Applie Music), posting any new tracks to an `inbox` webhook object.

<img src="./docs/preview.png" alt="An inbox webhook object showing recently played songs" width="312" />
macOS only — Uses Apple Script to parse music application state.

## Usage

Expand Down
Binary file removed packages/now-playing-inbox/docs/preview.png
Binary file not shown.
Loading