agentic-notify 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,16 +5,15 @@
5
5
 
6
6
  Notifications for humans and agents: in-app inbox, email and webhook delivery, with a CLI, API, MCP server and SDK.
7
7
 
8
- **Status: 0.3.0.** Breaking for 0.1.0 code and databases, see the CHANGELOG upgrade steps. Works today: the notification model, preferences and mute, digest, a store port from agentic-identity with its memory and D1 adapters, signed webhook and email delivery through a dispatcher, an HTTP API in a Worker, an SDK, a CLI, MCP tools, per-recipient export and erase, and a retention sweep. Not done: a real email provider adapter, webhook endpoint management, and a web UI.
8
+ **Status: 0.4.1.** Breaking for Worker auth, see "Migrating from `NOTIFY_TOKENS`" below. Breaking for 0.1.0 code and databases, see the CHANGELOG upgrade steps. Works today: the notification model, preferences and mute, digest, a store port from agentic-identity with its memory and D1 adapters, signed webhook and email delivery through a dispatcher, an HTTP API in a Worker, an SDK, a CLI, MCP tools, per-recipient export and erase, and a retention sweep. Not done: a real email provider adapter, webhook endpoint management, and a web UI.
9
9
 
10
10
  ## Live demo
11
11
 
12
- A demo Worker runs at https://agentic-notify-demo.hello-fd1.workers.dev. `GET /health` answers 200. The `/api/v1` routes need a bearer token, so without one they answer 401. The demo keeps its data in the EU and sweeps notifications after 7 days.
12
+ A demo Worker runs at https://agentic-notify-demo.hello-fd1.workers.dev. `GET /health` answers 200. The `/api/v1` routes need a key, so without one they answer 401. The demo keeps its data in the EU and sweeps notifications after 7 days.
13
13
 
14
- Redeploy your own copy with `wrangler.demo.jsonc` (create the D1 database with `--location weur`, put its id in the file, set the `NOTIFY_TOKENS` secret):
14
+ Redeploy your own copy with `wrangler.demo.jsonc` (create the D1 database with `--location weur`, put its id in the file, then add principals and keys, see Identity):
15
15
 
16
16
  ```sh
17
- npx wrangler secret put NOTIFY_TOKENS --config wrangler.demo.jsonc
18
17
  WRANGLER_SEND_METRICS=false npm run deploy:demo
19
18
  ```
20
19
 
@@ -42,16 +41,16 @@ await notifier.send({
42
41
  const { items } = await notifier.list({ kind: "agent", id: "helper" });
43
42
  ```
44
43
 
45
- Worker (D1 binding `DB`, secret `NOTIFY_TOKENS`):
44
+ Worker (D1 binding `DB`):
46
45
 
47
46
  ```sh
48
47
  # Create the database first: npx wrangler d1 create agentic-notify --location weur
49
- # NOTIFY_TOKENS is JSON: { "<token of 32+ bytes>": { "kind": "human", "id": "ana", "canSend": true } }
50
- npx wrangler secret put NOTIFY_TOKENS
51
48
  npx wrangler deploy
52
49
  ```
53
50
 
54
- Without a D1 binding or `NOTIFY_TOKENS`, every `/api/v1` call answers 503. Nobody gets in by default.
51
+ The Worker reads keys and principals from the same D1 database, with `agentic-identity`. Issue a key for each caller with `agentic-identity` (for example `issueCredential`, or its Worker), into this database. See Identity below.
52
+
53
+ Without a D1 binding, every `/api/v1` call answers 503. A request without a valid key answers 401, on unknown paths too. Nobody gets in by default.
55
54
 
56
55
  CLI, SDK and MCP talk to that Worker:
57
56
 
@@ -64,7 +63,9 @@ npx agentic-notify mcp # tools: ping, list_notifications, read_notification
64
63
  # export_data, erase_data, send_notification
65
64
  ```
66
65
 
67
- The MCP server needs `NOTIFY_URL` and `NOTIFY_TOKEN`. Without them only `ping` works. `erase_data` needs `confirm: true`.
66
+ `npx agentic-notify mcp --help` (or `-h`) prints the usage and exits 0. `npx agentic-notify mcp --version` (or `-V` or `-v`) prints the version and exits 0. Neither starts the server.
67
+
68
+ The MCP server needs `NOTIFY_URL` and `NOTIFY_TOKEN`. Without them only `ping` works. `erase_data` needs `confirm: true` and a key with `notify:admin`. `send_notification` needs `notify-send:write`. A tool that lacks its scope reports 403.
68
69
 
69
70
  ```ts
70
71
  import { createClient } from "agentic-notify/sdk";
@@ -74,7 +75,16 @@ console.log(await client.unreadCount());
74
75
 
75
76
  ## HTTP API
76
77
 
77
- All routes sit under `/api/v1` and need `Authorization: Bearer <token>`. `GET /health` is open.
78
+ All routes sit under `/api/v1` and need `Authorization: Bearer <key>`. `GET /health` is open. A missing or bad key answers 401. A good key without the scope of the route answers 403. Each route needs one scope:
79
+
80
+ | Scope | Routes |
81
+ | --- | --- |
82
+ | `notify:read` | `GET /notifications`, `GET /notifications/:id`, `GET /unread-count`, `GET /digest`, `GET /preferences`, `GET /me/export` |
83
+ | `notify:write` | `POST /notifications/:id/read`, `POST /notifications/read-all`, `PUT /preferences` |
84
+ | `notify-send:write` | `POST /notifications` |
85
+ | `notify:admin` | `DELETE /me` |
86
+
87
+ A scope with a higher action covers the lower ones, so `notify:admin` covers `notify:write` and `notify:read`. `*:admin` covers all. `notify-send` is a separate resource, so a key that can only send cannot read.
78
88
 
79
89
  | Route | Use |
80
90
  | --- | --- |
@@ -87,7 +97,22 @@ All routes sit under `/api/v1` and need `Authorization: Bearer <token>`. `GET /h
87
97
 
88
98
  ## Identity
89
99
 
90
- Auth is a port: `PrincipalResolver` turns a request into `{ kind: "human" | "agent", id }` or null. `createStaticTokenResolver` is a small example. To use another identity system, call `createWorker({ resolver })` with your own resolver. A caller reads and changes only its own notifications.
100
+ The Worker authenticates with `agentic-identity`. `requirePrincipal` from `agentic-identity/http` reads the credential, and the `Auth` port checks that it is not expired or revoked. The default `Auth` takes a bearer key, and also a `session` cookie that `agentic-identity` sets. A write with a cookie is refused, because no origin is allowed here. Pass your own `Auth` to accept bearer keys only. The default `Auth` is `createStoreAuth` over the same D1 store. Each route checks one scope (see HTTP API). A delegated agent holds only the scopes that its person also holds.
101
+
102
+ A person, and a `user_token` of that person, use the inbox of the person. An agent (`agent`, `agent_of_human` or `agent_of_agent`) uses the inbox of its own id. A caller reads and changes only its own notifications.
103
+
104
+ To use another identity system, call `createWorker({ auth: (env, store) => myAuth })`, where `auth` builds your own `Auth` from the Worker env and the store. Import `createWorker` from `agentic-notify/worker`.
105
+
106
+ ### Migrating from `NOTIFY_TOKENS`
107
+
108
+ Before 0.4.0, the Worker read a static `NOTIFY_TOKENS` secret: a map of token to `{ kind, id, canSend }`. The tokens had no scopes. The Worker no longer reads it. Steps:
109
+
110
+ 1. Delete the `NOTIFY_TOKENS` secret (`npx wrangler secret delete NOTIFY_TOKENS`).
111
+ 2. Make each caller a principal in the D1 database, and issue it a key with `agentic-identity`. The old `{ "kind": "human" }` becomes kind `human`, and `{ "kind": "agent" }` becomes kind `agent`.
112
+ 3. Give each principal scopes. An old token could read, change and erase its own inbox. To keep that, use `notify:admin`. For less, use `notify:write` (it covers `notify:read`) or `notify:read`. For the old `canSend: true`, add `notify-send:write`. It is a separate resource, so it does not cover `notify:read`: a caller that sends and reads needs both.
113
+ 4. Give the new key to the CLI, SDK and MCP server as `NOTIFY_TOKEN`.
114
+
115
+ Library users: `createWorker` takes `auth` in place of `resolver`. `authorizeSend` still works as an extra check after the scope. Its first argument is now a `PrincipalContext` (`principalId` in place of `id`, no `canSend`). Its default changed: it was "only `canSend: true`", and now every caller with `notify-send:write` may send unless you pass your own.
91
116
 
92
117
  ## Privacy
93
118
 
@@ -97,6 +122,24 @@ Auth is a port: `PrincipalResolver` turns a request into `{ kind: "human" | "age
97
122
  - Logs hold ids, channel names and error names only. They never hold bodies or addresses.
98
123
  - Erase removes a recipient's own notifications and preferences. Notifications that name the person only as actor stay with their recipients.
99
124
 
125
+ ## Testing
126
+
127
+ `agentic-notify/testing` helps you test a store adapter and your own code.
128
+
129
+ `runStoreContract(make)` runs the store contract suite. Call it at the top level of a vitest test
130
+ file, and install `vitest` 3 or later. The factory makes a new store for each call. A store made
131
+ without `shared` must start empty. Stores made with the same `shared` value must see the same data.
132
+ So the factory passes `options` to the store:
133
+
134
+ ```ts
135
+ import { createMemoryStore } from "agentic-notify";
136
+ import { runStoreContract } from "agentic-notify/testing";
137
+
138
+ runStoreContract((options) => createMemoryStore(options));
139
+ ```
140
+
141
+ `TestEmailAdapter` records each message in memory and can fail a set number of sends.
142
+
100
143
  ## Limits
101
144
 
102
145
  - The default Worker serves the inbox. To fan out to webhook and email, compose `createApp` with `createDispatcher` in your own Worker.
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "agentic-notify",
4
- version: "0.3.0",
4
+ version: "0.4.1",
5
5
  description: "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
6
6
  keywords: [
7
7
  "notifications",
@@ -66,10 +66,10 @@ var package_default = {
66
66
  "check:worker": "esbuild src/worker.ts --bundle --platform=neutral --format=esm --log-level=warning --outfile=node_modules/.cache/worker-check.js"
67
67
  },
68
68
  dependencies: {
69
- "@modelcontextprotocol/sdk": "^1.12.0",
70
- "agentic-identity": "^0.4.0",
69
+ "@modelcontextprotocol/sdk": "^1.23.0",
70
+ "agentic-identity": "^0.7.0",
71
71
  hono: "^4.13.14",
72
- zod: "^3.25.0"
72
+ zod: "^4.6.7"
73
73
  },
74
74
  devDependencies: {
75
75
  "@types/node": "^22.0.0",
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import "./chunk-KJKM56FT.js";
10
10
  import "./chunk-ARL7FN6Y.js";
11
11
  import {
12
12
  VERSION
13
- } from "./chunk-O574VONP.js";
13
+ } from "./chunk-O2W4ELN4.js";
14
14
 
15
15
  // src/cli.ts
16
16
  import { realpathSync } from "fs";
@@ -43,7 +43,7 @@ function helpText() {
43
43
  " mcp Start the MCP server on stdio.",
44
44
  "",
45
45
  "Environment: NOTIFY_URL (server base url), NOTIFY_TOKEN (bearer token).",
46
- "Flags: --help, -h; --version, -V."
46
+ "Flags: --help, -h; --version, -V. mcp also takes -v for --version."
47
47
  ].join("\n");
48
48
  }
49
49
  function flags(args, known) {
@@ -84,6 +84,14 @@ async function runCli(argv, io = defaultIo()) {
84
84
  io.out(VERSION);
85
85
  return 0;
86
86
  }
87
+ if (cmd === "mcp" && args.some((a) => a === "--help" || a === "-h")) {
88
+ io.out(helpText());
89
+ return 0;
90
+ }
91
+ if (cmd === "mcp" && args.some((a) => a === "--version" || a === "-V" || a === "-v")) {
92
+ io.out(VERSION);
93
+ return 0;
94
+ }
87
95
  try {
88
96
  if (cmd === "mcp") {
89
97
  const { startMcp } = await import("./mcp.js");
@@ -1,7 +1,7 @@
1
- import { R as RecipientExport } from './gdpr-CUq5MUwo.js';
1
+ import { R as RecipientExport } from './gdpr-C3YxbZp-.js';
2
2
  import { Store } from 'agentic-identity';
3
- import { S as SendInput, N as Notification, C as Channel, R as Recipient, D as Digest, P as Preferences } from './types-C842YAMe.js';
4
- import { D as DeliveryChannel, L as Logger } from './types-DONKNIOo.js';
3
+ import { S as SendInput, N as Notification, C as Channel, R as Recipient, D as Digest, P as Preferences } from './types-BAG4ZSoy.js';
4
+ import { D as DeliveryChannel, L as Logger } from './types-DhgcIImw.js';
5
5
 
6
6
  interface NotifierOptions {
7
7
  store: Store;
@@ -74,4 +74,4 @@ declare function createDispatcher(options: DispatcherOptions): {
74
74
  notify(input: SendInput): Promise<DispatchResult>;
75
75
  };
76
76
 
77
- export { type DispatchResult as D, type ListOptions as L, NotFoundError as N, type SendResult as S, type DispatcherOptions as a, type ListResult as b, type Notifier as c, type NotifierOptions as d, createDispatcher as e, createNotifier as f };
77
+ export { type DispatchResult as D, type ListOptions as L, type Notifier as N, type SendResult as S, type DispatcherOptions as a, type ListResult as b, createDispatcher as c, NotFoundError as d, type NotifierOptions as e, createNotifier as f };
@@ -1,4 +1,4 @@
1
- import { R as Recipient, P as Preferences, N as Notification } from './types-C842YAMe.js';
1
+ import { R as Recipient, P as Preferences, N as Notification } from './types-BAG4ZSoy.js';
2
2
 
3
3
  interface RecipientExport {
4
4
  recipient: Recipient;
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- export { D as DispatchResult, a as DispatcherOptions, L as ListOptions, b as ListResult, N as NotFoundError, c as Notifier, d as NotifierOptions, S as SendResult, e as createDispatcher, f as createNotifier } from './dispatch-LBnkLOQg.js';
1
+ export { D as DispatchResult, a as DispatcherOptions, L as ListOptions, b as ListResult, d as NotFoundError, N as Notifier, e as NotifierOptions, S as SendResult, c as createDispatcher, f as createNotifier } from './dispatch-CH4Ju-_1.js';
2
2
  export { Doc, Page, Query, Scalar, Store, StoreOps, createMemoryStore } from 'agentic-identity';
3
- import { R as Recipient } from './types-C842YAMe.js';
4
- export { A as Actor, b as CHANNELS, C as Channel, c as DEFAULT_CHANNELS, D as Digest, d as DigestGroup, N as Notification, P as Preferences, a as ResourceRef, S as SendInput, e as defaultPreferences, r as recipientKey } from './types-C842YAMe.js';
5
- import { D as DeliveryChannel } from './types-DONKNIOo.js';
6
- export { a as DeliveryError, L as Logger } from './types-DONKNIOo.js';
7
- export { E as EmailChannelOptions, a as EmailMessage, b as EmailPort, c as createEmailChannel } from './testing-B5slvYDV.js';
3
+ import { R as Recipient } from './types-BAG4ZSoy.js';
4
+ export { A as Actor, b as CHANNELS, C as Channel, c as DEFAULT_CHANNELS, D as Digest, d as DigestGroup, N as Notification, P as Preferences, a as ResourceRef, S as SendInput, e as defaultPreferences, r as recipientKey } from './types-BAG4ZSoy.js';
5
+ import { D as DeliveryChannel } from './types-DhgcIImw.js';
6
+ export { a as DeliveryError, L as Logger } from './types-DhgcIImw.js';
7
+ export { E as EmailChannelOptions, a as EmailMessage, b as EmailPort, c as createEmailChannel } from './testing-DEBqTQFK.js';
8
8
  export { D1Like, D1_SCHEMA, createD1Store } from 'agentic-identity/cloudflare';
9
- export { R as RecipientExport } from './gdpr-CUq5MUwo.js';
9
+ export { R as RecipientExport } from './gdpr-C3YxbZp-.js';
10
10
  import 'zod';
11
11
  import 'agentic-identity/testing';
12
12
 
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  } from "./chunk-ARL7FN6Y.js";
24
24
  import {
25
25
  VERSION
26
- } from "./chunk-O574VONP.js";
26
+ } from "./chunk-O2W4ELN4.js";
27
27
  export {
28
28
  CHANNELS,
29
29
  D1_SCHEMA,
package/dist/mcp-bin.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  VERSION
4
- } from "./chunk-O574VONP.js";
4
+ } from "./chunk-O2W4ELN4.js";
5
5
 
6
6
  // src/mcp-bin.ts
7
7
  var HELP = `agentic-notify-mcp ${VERSION}
8
- Usage: agentic-notify-mcp [--help | --version]
8
+ Usage: agentic-notify-mcp [--help | -h | --version | -V]
9
9
 
10
10
  Runs the MCP server on stdio. An MCP client starts it.
11
11
  `;
package/dist/mcp.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { Client } from './sdk.js';
3
- import './types-C842YAMe.js';
3
+ import './types-BAG4ZSoy.js';
4
4
  import 'zod';
5
- import './gdpr-CUq5MUwo.js';
5
+ import './gdpr-C3YxbZp-.js';
6
6
 
7
7
  /** Without a client, only `ping` works and the other tools report that setup is missing. */
8
8
  declare function createMcpServer(client?: Client): McpServer;
package/dist/mcp.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-ARL7FN6Y.js";
14
14
  import {
15
15
  VERSION
16
- } from "./chunk-O574VONP.js";
16
+ } from "./chunk-O2W4ELN4.js";
17
17
 
18
18
  // src/mcp.ts
19
19
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/dist/sdk.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { N as Notification, D as Digest, P as Preferences, R as Recipient, a as ResourceRef } from './types-C842YAMe.js';
2
- import { R as RecipientExport } from './gdpr-CUq5MUwo.js';
1
+ import { N as Notification, D as Digest, P as Preferences, R as Recipient, a as ResourceRef } from './types-BAG4ZSoy.js';
2
+ import { R as RecipientExport } from './gdpr-C3YxbZp-.js';
3
3
  import 'zod';
4
4
 
5
5
  declare class ApiError extends Error {
@@ -43,40 +43,40 @@ declare function createClient(options: ClientOptions): {
43
43
  cursor?: string;
44
44
  }) => Promise<NotificationPage>;
45
45
  get: (id: string) => Promise<{
46
- type: string;
47
46
  id: string;
48
47
  recipient: {
49
- id: string;
50
48
  kind: "human" | "agent";
49
+ id: string;
51
50
  };
51
+ type: string;
52
52
  resource: {
53
- id: string;
54
53
  kind: string;
54
+ id: string;
55
55
  url?: string | undefined;
56
56
  };
57
57
  actor: {
58
- id: string;
59
58
  kind: "human" | "agent";
59
+ id: string;
60
60
  };
61
61
  body: string;
62
62
  createdAt: number;
63
63
  readAt: number | null;
64
64
  }>;
65
65
  markRead: (id: string) => Promise<{
66
- type: string;
67
66
  id: string;
68
67
  recipient: {
69
- id: string;
70
68
  kind: "human" | "agent";
69
+ id: string;
71
70
  };
71
+ type: string;
72
72
  resource: {
73
- id: string;
74
73
  kind: string;
74
+ id: string;
75
75
  url?: string | undefined;
76
76
  };
77
77
  actor: {
78
- id: string;
79
78
  kind: "human" | "agent";
79
+ id: string;
80
80
  };
81
81
  body: string;
82
82
  createdAt: number;
@@ -1,6 +1,6 @@
1
1
  import 'agentic-identity/testing';
2
- import { R as Recipient } from './types-C842YAMe.js';
3
- import { D as DeliveryChannel } from './types-DONKNIOo.js';
2
+ import { R as Recipient } from './types-BAG4ZSoy.js';
3
+ import { D as DeliveryChannel } from './types-DhgcIImw.js';
4
4
 
5
5
  interface EmailMessage {
6
6
  to: string;
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { runStoreContract } from 'agentic-identity/testing';
2
- export { T as TestEmailAdapter } from './testing-B5slvYDV.js';
3
- import './types-C842YAMe.js';
2
+ export { T as TestEmailAdapter } from './testing-DEBqTQFK.js';
3
+ import './types-BAG4ZSoy.js';
4
4
  import 'zod';
5
- import './types-DONKNIOo.js';
5
+ import './types-DhgcIImw.js';
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const CHANNELS: readonly ["inbox", "webhook", "email"];
4
+ type Channel = (typeof CHANNELS)[number];
5
+ declare const recipientSchema: z.ZodObject<{
6
+ kind: z.ZodEnum<{
7
+ human: "human";
8
+ agent: "agent";
9
+ }>;
10
+ id: z.ZodString;
11
+ }, z.core.$strict>;
12
+ type Recipient = z.infer<typeof recipientSchema>;
13
+ declare const resourceRefSchema: z.ZodObject<{
14
+ kind: z.ZodString;
15
+ id: z.ZodString;
16
+ url: z.ZodOptional<z.ZodString>;
17
+ }, z.core.$strict>;
18
+ type ResourceRef = z.infer<typeof resourceRefSchema>;
19
+ declare const actorSchema: z.ZodObject<{
20
+ kind: z.ZodEnum<{
21
+ human: "human";
22
+ agent: "agent";
23
+ }>;
24
+ id: z.ZodString;
25
+ }, z.core.$strict>;
26
+ type Actor = z.infer<typeof actorSchema>;
27
+ declare const notificationSchema: z.ZodObject<{
28
+ id: z.ZodString;
29
+ recipient: z.ZodObject<{
30
+ kind: z.ZodEnum<{
31
+ human: "human";
32
+ agent: "agent";
33
+ }>;
34
+ id: z.ZodString;
35
+ }, z.core.$strict>;
36
+ type: z.ZodString;
37
+ resource: z.ZodObject<{
38
+ kind: z.ZodString;
39
+ id: z.ZodString;
40
+ url: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strict>;
42
+ actor: z.ZodObject<{
43
+ kind: z.ZodEnum<{
44
+ human: "human";
45
+ agent: "agent";
46
+ }>;
47
+ id: z.ZodString;
48
+ }, z.core.$strict>;
49
+ body: z.ZodString;
50
+ createdAt: z.ZodNumber;
51
+ readAt: z.ZodNullable<z.ZodNumber>;
52
+ }, z.core.$strict>;
53
+ type Notification = z.infer<typeof notificationSchema>;
54
+ declare const sendInputSchema: z.ZodObject<{
55
+ recipient: z.ZodObject<{
56
+ kind: z.ZodEnum<{
57
+ human: "human";
58
+ agent: "agent";
59
+ }>;
60
+ id: z.ZodString;
61
+ }, z.core.$strict>;
62
+ type: z.ZodString;
63
+ resource: z.ZodObject<{
64
+ kind: z.ZodString;
65
+ id: z.ZodString;
66
+ url: z.ZodOptional<z.ZodString>;
67
+ }, z.core.$strict>;
68
+ actor: z.ZodObject<{
69
+ kind: z.ZodEnum<{
70
+ human: "human";
71
+ agent: "agent";
72
+ }>;
73
+ id: z.ZodString;
74
+ }, z.core.$strict>;
75
+ body: z.ZodString;
76
+ }, z.core.$strict>;
77
+ type SendInput = z.infer<typeof sendInputSchema>;
78
+ declare const preferencesSchema: z.ZodObject<{
79
+ channels: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodEnum<{
80
+ inbox: "inbox";
81
+ webhook: "webhook";
82
+ email: "email";
83
+ }>>>;
84
+ muted: z.ZodBoolean;
85
+ mutedTypes: z.ZodArray<z.ZodString>;
86
+ }, z.core.$strict>;
87
+ type Preferences = z.infer<typeof preferencesSchema>;
88
+ declare const DEFAULT_CHANNELS: readonly Channel[];
89
+ declare function defaultPreferences(): Preferences;
90
+ declare function recipientKey(recipient: Recipient): string;
91
+ interface DigestGroup {
92
+ type: string;
93
+ count: number;
94
+ latest: Notification;
95
+ }
96
+ interface Digest {
97
+ total: number;
98
+ groups: DigestGroup[];
99
+ }
100
+
101
+ export { type Actor as A, type Channel as C, type Digest as D, type Notification as N, type Preferences as P, type Recipient as R, type SendInput as S, type ResourceRef as a, CHANNELS as b, DEFAULT_CHANNELS as c, type DigestGroup as d, defaultPreferences as e, recipientKey as r };
@@ -1,4 +1,4 @@
1
- import { N as Notification } from './types-C842YAMe.js';
1
+ import { N as Notification } from './types-BAG4ZSoy.js';
2
2
 
3
3
  /** A delivery channel other than the inbox. */
4
4
  interface DeliveryChannel {
package/dist/worker.d.ts CHANGED
@@ -1,56 +1,39 @@
1
- import { z } from 'zod';
2
- import { c as Notifier, e as createDispatcher } from './dispatch-LBnkLOQg.js';
3
- import { R as Recipient } from './types-C842YAMe.js';
1
+ import { Auth, PrincipalContext, Store } from 'agentic-identity';
2
+ import { N as Notifier, c as createDispatcher } from './dispatch-CH4Ju-_1.js';
3
+ import { R as Recipient } from './types-BAG4ZSoy.js';
4
4
  import { D1Like } from 'agentic-identity/cloudflare';
5
- import './gdpr-CUq5MUwo.js';
6
- import 'agentic-identity';
7
- import './types-DONKNIOo.js';
8
-
9
- /** Who is calling. `kind` has no default: a missing kind is an error. */
10
- declare const principalSchema: z.ZodObject<{
11
- kind: z.ZodEnum<["human", "agent"]>;
12
- id: z.ZodString;
13
- /** Allowed to send notifications to others. */
14
- canSend: z.ZodOptional<z.ZodBoolean>;
15
- }, "strict", z.ZodTypeAny, {
16
- kind: "human" | "agent";
17
- id: string;
18
- canSend?: boolean | undefined;
19
- }, {
20
- kind: "human" | "agent";
21
- id: string;
22
- canSend?: boolean | undefined;
23
- }>;
24
- type Principal = z.infer<typeof principalSchema>;
25
- /** Port: turn a request into a principal, or null for "not signed in". Plug in any identity system. */
26
- type PrincipalResolver = (request: Request) => Promise<Principal | null> | Principal | null;
5
+ import './gdpr-C3YxbZp-.js';
6
+ import './types-DhgcIImw.js';
7
+ import 'zod';
27
8
 
28
9
  interface AppOptions {
29
10
  notifier: Notifier;
30
11
  /** When set, sends also go to webhook and email channels. */
31
12
  dispatcher?: ReturnType<typeof createDispatcher>;
32
- resolvePrincipal: PrincipalResolver;
33
- /** May this principal send to this recipient? Default: only principals with `canSend: true`. */
34
- authorizeSend?: (principal: Principal, recipient: Recipient) => Promise<boolean> | boolean;
13
+ /** Reads the credential on a request. Each route then checks one scope. */
14
+ auth: Auth;
15
+ /** Extra check on a send, after the `notify-send:write` scope. Default: allow. */
16
+ authorizeSend?: (principal: PrincipalContext, recipient: Recipient) => Promise<boolean> | boolean;
35
17
  }
36
18
 
37
19
  interface WorkerEnv {
38
20
  NOTIFY_NAME?: string;
39
21
  /** D1 binding. */
40
22
  DB?: D1Like;
41
- /** Secret. JSON map of bearer token (32 bytes or more) to principal. */
42
- NOTIFY_TOKENS?: string;
43
23
  /** Data region of the store: "eu", "us" or "unknown". Default "eu". */
44
24
  NOTIFY_REGION?: string;
45
25
  /** Retention window in days. Unset keeps notifications. */
46
26
  NOTIFY_RETENTION_DAYS?: string;
47
27
  }
48
28
  interface WorkerConfig {
49
- /** Build the principal resolver from the environment. Null means not configured. */
50
- resolver(env: WorkerEnv): Promise<PrincipalResolver | null>;
29
+ /**
30
+ * Build the `Auth` port. Default: `createStoreAuth` over the same store, so keys that
31
+ * agentic-identity issues into the database work here. Each route checks a scope.
32
+ */
33
+ auth?(env: WorkerEnv, store: Store): Auth;
51
34
  authorizeSend?: AppOptions["authorizeSend"];
52
35
  }
53
- /** Compose the Worker. Bring your own identity system through `resolver`. */
36
+ /** Compose the Worker. Bring your own identity system through `auth`. */
54
37
  declare function createWorker(config: WorkerConfig): {
55
38
  fetch(request: Request, env: WorkerEnv): Promise<Response>;
56
39
  /** Delete notifications past the retention window. Wire to a cron trigger. */
package/dist/worker.js CHANGED
@@ -9,76 +9,81 @@ import {
9
9
  } from "./chunk-ARL7FN6Y.js";
10
10
  import {
11
11
  VERSION
12
- } from "./chunk-O574VONP.js";
12
+ } from "./chunk-O2W4ELN4.js";
13
13
 
14
14
  // src/api.ts
15
15
  import { Hono } from "hono";
16
16
  import { bodyLimit } from "hono/body-limit";
17
- import { ZodError, z as z2 } from "zod";
17
+ import { ZodError, z } from "zod";
18
+ import { requirePrincipal } from "agentic-identity/http";
18
19
 
19
20
  // src/auth.ts
20
- import { z } from "zod";
21
- var principalSchema = z.object({
22
- kind: z.enum(["human", "agent"]),
23
- id: z.string().min(1).max(200),
24
- /** Allowed to send notifications to others. */
25
- canSend: z.boolean().optional()
26
- }).strict();
27
- var MIN_TOKEN_BYTES = 32;
28
- var enc = new TextEncoder();
29
- async function digest(token) {
30
- const d = await crypto.subtle.digest("SHA-256", enc.encode(token));
31
- return [...new Uint8Array(d)].map((b) => b.toString(16).padStart(2, "0")).join("");
32
- }
33
- async function createStaticTokenResolver(tokens) {
34
- const byDigest = /* @__PURE__ */ new Map();
35
- for (const [token, principal] of Object.entries(tokens)) {
36
- if (enc.encode(token).length < MIN_TOKEN_BYTES) throw new Error(`tokens must hold at least ${MIN_TOKEN_BYTES} bytes`);
37
- byDigest.set(await digest(token), principalSchema.parse(principal));
21
+ var SCOPES = {
22
+ /** List, read, count, digest, read preferences, export. */
23
+ read: "notify:read",
24
+ /** Mark read, change preferences. */
25
+ write: "notify:write",
26
+ /** Erase all data held about the caller. */
27
+ erase: "notify:admin",
28
+ /** Notify someone else. */
29
+ send: "notify-send:write"
30
+ };
31
+ function recipientOf(context) {
32
+ switch (context.kind) {
33
+ case "human":
34
+ return { kind: "human", id: context.principalId };
35
+ case "user_token": {
36
+ const root = context.chain[0];
37
+ if (!root || root.kind !== "human") throw new RangeError("a user token needs a person at the root of its chain");
38
+ return { kind: "human", id: root.id };
39
+ }
40
+ case "agent":
41
+ case "agent_of_human":
42
+ case "agent_of_agent":
43
+ return { kind: "agent", id: context.principalId };
44
+ default:
45
+ throw new RangeError("unknown principal kind");
38
46
  }
39
- if (byDigest.size === 0) throw new Error("no tokens configured");
40
- return async (request) => {
41
- const m = /^Bearer ([^\s]+)$/.exec(request.headers.get("authorization") ?? "");
42
- return m ? byDigest.get(await digest(m[1])) ?? null : null;
43
- };
44
47
  }
45
48
 
46
49
  // src/api.ts
47
- var sendBody = z2.object({ recipient: recipientSchema, type: typeNameSchema, resource: resourceRefSchema, body: z2.string().max(2e3) }).strict();
48
- var num = z2.coerce.number().int().nonnegative();
50
+ function invalidInput(path, message) {
51
+ return z.string().refine(() => false, { message, path }).safeParse("").error;
52
+ }
53
+ var sendBody = z.object({ recipient: recipientSchema, type: typeNameSchema, resource: resourceRefSchema, body: z.string().max(2e3) }).strict();
54
+ var num = z.coerce.number().int().nonnegative();
49
55
  function createApp(options) {
50
56
  const { notifier } = options;
51
- const authorizeSend = options.authorizeSend ?? ((p) => p.canSend === true);
57
+ const authorizeSend = options.authorizeSend ?? (() => true);
52
58
  const app = new Hono();
53
59
  app.get("/health", (c) => c.json({ ok: true }));
54
60
  const api = new Hono();
55
61
  api.use("*", bodyLimit({ maxSize: 64 * 1024, onError: (c) => c.json({ error: "too_large" }, 413) }));
56
- api.use("*", async (c, next) => {
57
- const raw = await options.resolvePrincipal(c.req.raw);
58
- const parsed = principalSchema.safeParse(raw);
59
- if (!parsed.success) return c.json({ error: "unauthorized" }, 401);
60
- c.set("principal", parsed.data);
61
- c.set("me", { kind: parsed.data.kind, id: parsed.data.id });
62
+ const guard = (scope) => async (c, next) => {
63
+ const who = await requirePrincipal(c.req.raw, options.auth, { scope });
64
+ if (who instanceof Response) return who;
65
+ c.set("principal", who);
66
+ c.set("me", recipientOf(who));
62
67
  await next();
63
- });
68
+ };
64
69
  async function json2(c) {
65
70
  try {
66
71
  return await c.req.json();
67
72
  } catch {
68
- throw new ZodError([{ code: "custom", path: [], message: "body must be JSON" }]);
73
+ throw invalidInput([], "body must be JSON");
69
74
  }
70
75
  }
71
- api.get("/notifications", async (c) => {
76
+ api.get("/notifications", guard(SCOPES.read), async (c) => {
72
77
  const q = c.req.query();
73
- const limit = q.limit === void 0 ? void 0 : z2.coerce.number().int().min(1).parse(q.limit);
78
+ const limit = q.limit === void 0 ? void 0 : z.coerce.number().int().min(1).parse(q.limit);
74
79
  try {
75
80
  return c.json(await notifier.list(c.var.me, { unreadOnly: q.unread === "1" || q.unread === "true", limit, cursor: q.cursor }));
76
81
  } catch (error) {
77
- if (error instanceof RangeError) throw new ZodError([{ code: "custom", path: ["cursor"], message: "bad cursor" }]);
82
+ if (error instanceof RangeError) throw invalidInput(["cursor"], "bad cursor");
78
83
  throw error;
79
84
  }
80
85
  });
81
- api.post("/notifications", async (c) => {
86
+ api.post("/notifications", guard(SCOPES.send), async (c) => {
82
87
  const input = sendBody.parse(await json2(c));
83
88
  if (!await authorizeSend(c.var.principal, input.recipient)) return c.json({ error: "forbidden" }, 403);
84
89
  const full = { ...input, actor: c.var.me };
@@ -87,18 +92,22 @@ function createApp(options) {
87
92
  const deliveries = sent.channels.filter((channel) => channel !== "inbox").map((channel) => ({ channel, ok: false, skipped: "no_channel" }));
88
93
  return c.json({ ...sent, deliveries }, 201);
89
94
  });
90
- api.post("/notifications/read-all", async (c) => c.json({ marked: await notifier.markAllRead(c.var.me) }));
91
- api.get("/notifications/:id", async (c) => c.json(await notifier.get(c.var.me, c.req.param("id"))));
92
- api.post("/notifications/:id/read", async (c) => c.json(await notifier.markRead(c.var.me, c.req.param("id"))));
93
- api.get("/unread-count", async (c) => c.json({ count: await notifier.unreadCount(c.var.me) }));
94
- api.get("/digest", async (c) => {
95
+ api.post("/notifications/read-all", guard(SCOPES.write), async (c) => c.json({ marked: await notifier.markAllRead(c.var.me) }));
96
+ api.get("/notifications/:id", guard(SCOPES.read), async (c) => c.json(await notifier.get(c.var.me, c.req.param("id"))));
97
+ api.post("/notifications/:id/read", guard(SCOPES.write), async (c) => c.json(await notifier.markRead(c.var.me, c.req.param("id"))));
98
+ api.get("/unread-count", guard(SCOPES.read), async (c) => c.json({ count: await notifier.unreadCount(c.var.me) }));
99
+ api.get("/digest", guard(SCOPES.read), async (c) => {
95
100
  const since = c.req.query("since");
96
101
  return c.json(await notifier.digest(c.var.me, since === void 0 ? 0 : num.parse(since)));
97
102
  });
98
- api.get("/preferences", async (c) => c.json(await notifier.getPreferences(c.var.me)));
99
- api.put("/preferences", async (c) => c.json(await notifier.setPreferences(c.var.me, preferencesSchema.parse(await json2(c)))));
100
- api.get("/me/export", async (c) => c.json(await notifier.exportData(c.var.me)));
101
- api.delete("/me", async (c) => c.json(await notifier.erase(c.var.me)));
103
+ api.get("/preferences", guard(SCOPES.read), async (c) => c.json(await notifier.getPreferences(c.var.me)));
104
+ api.put("/preferences", guard(SCOPES.write), async (c) => c.json(await notifier.setPreferences(c.var.me, preferencesSchema.parse(await json2(c)))));
105
+ api.get("/me/export", guard(SCOPES.read), async (c) => c.json(await notifier.exportData(c.var.me)));
106
+ api.delete("/me", guard(SCOPES.erase), async (c) => c.json(await notifier.erase(c.var.me)));
107
+ api.all("*", async (c) => {
108
+ const who = await requirePrincipal(c.req.raw, options.auth);
109
+ return who instanceof Response ? who : c.json({ error: "not_found" }, 404);
110
+ });
102
111
  app.route("/api/v1", api);
103
112
  app.notFound((c) => c.json({ error: "not_found" }, 404));
104
113
  app.onError((error, c) => {
@@ -113,7 +122,7 @@ function createApp(options) {
113
122
  }
114
123
 
115
124
  // src/worker.ts
116
- import { parseRegion } from "agentic-identity";
125
+ import { createStoreAuth, parseRegion } from "agentic-identity";
117
126
  import { createD1Store, D1_SCHEMA } from "agentic-identity/cloudflare";
118
127
  function json(value, status = 200) {
119
128
  return new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
@@ -132,16 +141,17 @@ function createWorker(config) {
132
141
  const days = env.NOTIFY_RETENTION_DAYS === void 0 ? void 0 : Number(env.NOTIFY_RETENTION_DAYS);
133
142
  const store = createD1Store(env.DB, { region: parseRegion(env.NOTIFY_REGION ?? "eu") });
134
143
  await store.query(NOTES, { limit: 1 });
135
- return createNotifier({
144
+ const notifier = createNotifier({
136
145
  store,
137
146
  retentionMs: days === void 0 ? void 0 : days * 864e5
138
147
  });
148
+ return { notifier, store };
139
149
  }
140
150
  async function build(env) {
141
- const resolvePrincipal = await config.resolver(env);
142
- const notifier = resolvePrincipal ? await buildNotifier(env) : null;
143
- if (!resolvePrincipal || !notifier) return null;
144
- return createApp({ notifier, resolvePrincipal, authorizeSend: config.authorizeSend });
151
+ const built = await buildNotifier(env);
152
+ if (!built) return null;
153
+ const auth = config.auth ? config.auth(env, built.store) : createStoreAuth(built.store);
154
+ return createApp({ notifier: built.notifier, auth, authorizeSend: config.authorizeSend });
145
155
  }
146
156
  return {
147
157
  async fetch(request, env) {
@@ -164,16 +174,11 @@ function createWorker(config) {
164
174
  },
165
175
  /** Delete notifications past the retention window. Wire to a cron trigger. */
166
176
  async scheduled(_event, env) {
167
- await (await buildNotifier(env))?.sweep();
177
+ await (await buildNotifier(env))?.notifier.sweep();
168
178
  }
169
179
  };
170
180
  }
171
- var worker_default = createWorker({
172
- async resolver(env) {
173
- if (!env.NOTIFY_TOKENS) return null;
174
- return createStaticTokenResolver(JSON.parse(env.NOTIFY_TOKENS));
175
- }
176
- });
181
+ var worker_default = createWorker({});
177
182
  export {
178
183
  createWorker,
179
184
  worker_default as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-notify",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
5
5
  "keywords": [
6
6
  "notifications",
@@ -65,10 +65,10 @@
65
65
  "check:worker": "esbuild src/worker.ts --bundle --platform=neutral --format=esm --log-level=warning --outfile=node_modules/.cache/worker-check.js"
66
66
  },
67
67
  "dependencies": {
68
- "@modelcontextprotocol/sdk": "^1.12.0",
69
- "agentic-identity": "^0.4.0",
68
+ "@modelcontextprotocol/sdk": "^1.23.0",
69
+ "agentic-identity": "^0.7.0",
70
70
  "hono": "^4.13.14",
71
- "zod": "^3.25.0"
71
+ "zod": "^4.6.7"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@types/node": "^22.0.0",
@@ -1,217 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- declare const CHANNELS: readonly ["inbox", "webhook", "email"];
4
- type Channel = (typeof CHANNELS)[number];
5
- declare const recipientSchema: z.ZodObject<{
6
- kind: z.ZodEnum<["human", "agent"]>;
7
- id: z.ZodString;
8
- }, "strict", z.ZodTypeAny, {
9
- id: string;
10
- kind: "human" | "agent";
11
- }, {
12
- id: string;
13
- kind: "human" | "agent";
14
- }>;
15
- type Recipient = z.infer<typeof recipientSchema>;
16
- declare const resourceRefSchema: z.ZodObject<{
17
- kind: z.ZodString;
18
- id: z.ZodString;
19
- url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
20
- }, "strict", z.ZodTypeAny, {
21
- id: string;
22
- kind: string;
23
- url?: string | undefined;
24
- }, {
25
- id: string;
26
- kind: string;
27
- url?: string | undefined;
28
- }>;
29
- type ResourceRef = z.infer<typeof resourceRefSchema>;
30
- declare const actorSchema: z.ZodObject<{
31
- kind: z.ZodEnum<["human", "agent"]>;
32
- id: z.ZodString;
33
- }, "strict", z.ZodTypeAny, {
34
- id: string;
35
- kind: "human" | "agent";
36
- }, {
37
- id: string;
38
- kind: "human" | "agent";
39
- }>;
40
- type Actor = z.infer<typeof actorSchema>;
41
- declare const notificationSchema: z.ZodObject<{
42
- id: z.ZodString;
43
- recipient: z.ZodObject<{
44
- kind: z.ZodEnum<["human", "agent"]>;
45
- id: z.ZodString;
46
- }, "strict", z.ZodTypeAny, {
47
- id: string;
48
- kind: "human" | "agent";
49
- }, {
50
- id: string;
51
- kind: "human" | "agent";
52
- }>;
53
- type: z.ZodString;
54
- resource: z.ZodObject<{
55
- kind: z.ZodString;
56
- id: z.ZodString;
57
- url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
58
- }, "strict", z.ZodTypeAny, {
59
- id: string;
60
- kind: string;
61
- url?: string | undefined;
62
- }, {
63
- id: string;
64
- kind: string;
65
- url?: string | undefined;
66
- }>;
67
- actor: z.ZodObject<{
68
- kind: z.ZodEnum<["human", "agent"]>;
69
- id: z.ZodString;
70
- }, "strict", z.ZodTypeAny, {
71
- id: string;
72
- kind: "human" | "agent";
73
- }, {
74
- id: string;
75
- kind: "human" | "agent";
76
- }>;
77
- body: z.ZodString;
78
- createdAt: z.ZodNumber;
79
- readAt: z.ZodNullable<z.ZodNumber>;
80
- }, "strict", z.ZodTypeAny, {
81
- type: string;
82
- id: string;
83
- recipient: {
84
- id: string;
85
- kind: "human" | "agent";
86
- };
87
- resource: {
88
- id: string;
89
- kind: string;
90
- url?: string | undefined;
91
- };
92
- actor: {
93
- id: string;
94
- kind: "human" | "agent";
95
- };
96
- body: string;
97
- createdAt: number;
98
- readAt: number | null;
99
- }, {
100
- type: string;
101
- id: string;
102
- recipient: {
103
- id: string;
104
- kind: "human" | "agent";
105
- };
106
- resource: {
107
- id: string;
108
- kind: string;
109
- url?: string | undefined;
110
- };
111
- actor: {
112
- id: string;
113
- kind: "human" | "agent";
114
- };
115
- body: string;
116
- createdAt: number;
117
- readAt: number | null;
118
- }>;
119
- type Notification = z.infer<typeof notificationSchema>;
120
- declare const sendInputSchema: z.ZodObject<{
121
- recipient: z.ZodObject<{
122
- kind: z.ZodEnum<["human", "agent"]>;
123
- id: z.ZodString;
124
- }, "strict", z.ZodTypeAny, {
125
- id: string;
126
- kind: "human" | "agent";
127
- }, {
128
- id: string;
129
- kind: "human" | "agent";
130
- }>;
131
- type: z.ZodString;
132
- resource: z.ZodObject<{
133
- kind: z.ZodString;
134
- id: z.ZodString;
135
- url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
136
- }, "strict", z.ZodTypeAny, {
137
- id: string;
138
- kind: string;
139
- url?: string | undefined;
140
- }, {
141
- id: string;
142
- kind: string;
143
- url?: string | undefined;
144
- }>;
145
- actor: z.ZodObject<{
146
- kind: z.ZodEnum<["human", "agent"]>;
147
- id: z.ZodString;
148
- }, "strict", z.ZodTypeAny, {
149
- id: string;
150
- kind: "human" | "agent";
151
- }, {
152
- id: string;
153
- kind: "human" | "agent";
154
- }>;
155
- body: z.ZodString;
156
- }, "strict", z.ZodTypeAny, {
157
- type: string;
158
- recipient: {
159
- id: string;
160
- kind: "human" | "agent";
161
- };
162
- resource: {
163
- id: string;
164
- kind: string;
165
- url?: string | undefined;
166
- };
167
- actor: {
168
- id: string;
169
- kind: "human" | "agent";
170
- };
171
- body: string;
172
- }, {
173
- type: string;
174
- recipient: {
175
- id: string;
176
- kind: "human" | "agent";
177
- };
178
- resource: {
179
- id: string;
180
- kind: string;
181
- url?: string | undefined;
182
- };
183
- actor: {
184
- id: string;
185
- kind: "human" | "agent";
186
- };
187
- body: string;
188
- }>;
189
- type SendInput = z.infer<typeof sendInputSchema>;
190
- declare const preferencesSchema: z.ZodObject<{
191
- channels: z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodArray<z.ZodEnum<["inbox", "webhook", "email"]>, "many">, ("inbox" | "webhook" | "email")[], ("inbox" | "webhook" | "email")[]>>;
192
- muted: z.ZodBoolean;
193
- mutedTypes: z.ZodArray<z.ZodString, "many">;
194
- }, "strict", z.ZodTypeAny, {
195
- channels: Record<string, ("inbox" | "webhook" | "email")[]>;
196
- muted: boolean;
197
- mutedTypes: string[];
198
- }, {
199
- channels: Record<string, ("inbox" | "webhook" | "email")[]>;
200
- muted: boolean;
201
- mutedTypes: string[];
202
- }>;
203
- type Preferences = z.infer<typeof preferencesSchema>;
204
- declare const DEFAULT_CHANNELS: readonly Channel[];
205
- declare function defaultPreferences(): Preferences;
206
- declare function recipientKey(recipient: Recipient): string;
207
- interface DigestGroup {
208
- type: string;
209
- count: number;
210
- latest: Notification;
211
- }
212
- interface Digest {
213
- total: number;
214
- groups: DigestGroup[];
215
- }
216
-
217
- export { type Actor as A, type Channel as C, type Digest as D, type Notification as N, type Preferences as P, type Recipient as R, type SendInput as S, type ResourceRef as a, CHANNELS as b, DEFAULT_CHANNELS as c, type DigestGroup as d, defaultPreferences as e, recipientKey as r };