agentic-notify 0.0.0-stage → 0.2.0

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.
@@ -0,0 +1,401 @@
1
+ // package.json
2
+ var package_default = {
3
+ name: "agentic-notify",
4
+ version: "0.2.0",
5
+ description: "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
6
+ keywords: [
7
+ "notifications",
8
+ "inbox",
9
+ "webhook",
10
+ "agents",
11
+ "mcp",
12
+ "cli",
13
+ "cloudflare-workers",
14
+ "typescript"
15
+ ],
16
+ license: "MIT",
17
+ author: "Pooria Arab",
18
+ repository: {
19
+ type: "git",
20
+ url: "git+https://github.com/pooriaarab/agentic-notify.git"
21
+ },
22
+ type: "module",
23
+ private: false,
24
+ engines: {
25
+ node: ">=20"
26
+ },
27
+ files: [
28
+ "dist"
29
+ ],
30
+ publishConfig: {
31
+ access: "public"
32
+ },
33
+ mcpName: "io.github.pooriaarab/agentic-notify",
34
+ main: "./dist/index.js",
35
+ types: "./dist/index.d.ts",
36
+ exports: {
37
+ ".": {
38
+ types: "./dist/index.d.ts",
39
+ import: "./dist/index.js"
40
+ },
41
+ "./testing": {
42
+ types: "./dist/testing.d.ts",
43
+ import: "./dist/testing.js"
44
+ },
45
+ "./sdk": {
46
+ types: "./dist/sdk.d.ts",
47
+ import: "./dist/sdk.js"
48
+ },
49
+ "./worker": {
50
+ types: "./dist/worker.d.ts",
51
+ import: "./dist/worker.js"
52
+ }
53
+ },
54
+ bin: {
55
+ "agentic-notify": "./dist/cli.js",
56
+ "agentic-notify-mcp": "./dist/mcp-bin.js"
57
+ },
58
+ scripts: {
59
+ build: "tsup",
60
+ typecheck: "tsc --noEmit",
61
+ test: "vitest run",
62
+ clean: "rm -rf dist",
63
+ prepack: "npm run build",
64
+ "ci:local": "npm run build && npm run typecheck && npm run check:worker && npm test",
65
+ "deploy:demo": "wrangler deploy --config wrangler.demo.jsonc",
66
+ "check:worker": "esbuild src/worker.ts --bundle --platform=neutral --format=esm --log-level=warning --outfile=node_modules/.cache/worker-check.js"
67
+ },
68
+ dependencies: {
69
+ "@modelcontextprotocol/sdk": "^1.12.0",
70
+ "agentic-identity": "^0.4.0",
71
+ hono: "^4.13.14",
72
+ zod: "^3.25.0"
73
+ },
74
+ devDependencies: {
75
+ "@types/node": "^22.0.0",
76
+ esbuild: "^0.28.2",
77
+ miniflare: "^4.20260730.0",
78
+ tsup: "^8.0.0",
79
+ typescript: "^5.6.0",
80
+ vitest: "^3.0.0",
81
+ wrangler: "^4.155.0"
82
+ },
83
+ peerDependencies: {
84
+ vitest: ">=3"
85
+ },
86
+ peerDependenciesMeta: {
87
+ vitest: {
88
+ optional: true
89
+ }
90
+ }
91
+ };
92
+
93
+ // src/version.ts
94
+ if (typeof package_default.version !== "string" || package_default.version.length === 0) {
95
+ throw new Error("package.json carries no version string");
96
+ }
97
+ var VERSION = package_default.version;
98
+
99
+ // src/core/types.ts
100
+ import { z } from "zod";
101
+ var CHANNELS = ["inbox", "webhook", "email"];
102
+ var id = z.string().min(1).max(200);
103
+ var recipientSchema = z.object({ kind: z.enum(["human", "agent"]), id }).strict();
104
+ var typeNameSchema = z.string().regex(/^[a-z][a-z0-9_.-]{0,63}$/);
105
+ var resourceRefSchema = z.object({
106
+ kind: z.string().min(1).max(100),
107
+ id,
108
+ url: z.string().max(2e3).refine((u) => /^https?:\/\//i.test(u) && URL.canParse(u), "url must be http or https").optional()
109
+ }).strict();
110
+ var actorSchema = z.object({ kind: z.enum(["human", "agent"]), id }).strict();
111
+ var notificationSchema = z.object({
112
+ id,
113
+ recipient: recipientSchema,
114
+ type: typeNameSchema,
115
+ resource: resourceRefSchema,
116
+ actor: actorSchema,
117
+ body: z.string().max(2e3),
118
+ createdAt: z.number().int().nonnegative(),
119
+ readAt: z.number().int().nonnegative().nullable()
120
+ }).strict();
121
+ var sendInputSchema = z.object({
122
+ recipient: recipientSchema,
123
+ type: typeNameSchema,
124
+ resource: resourceRefSchema,
125
+ actor: actorSchema,
126
+ body: z.string().max(2e3)
127
+ }).strict();
128
+ var preferencesSchema = z.object({
129
+ channels: z.record(typeNameSchema, z.array(z.enum(CHANNELS)).max(CHANNELS.length).refine((list) => new Set(list).size === list.length, "channels must not repeat")),
130
+ muted: z.boolean(),
131
+ mutedTypes: z.array(typeNameSchema).max(200)
132
+ }).strict();
133
+ var DEFAULT_CHANNELS = ["inbox"];
134
+ function defaultPreferences() {
135
+ return { channels: {}, muted: false, mutedTypes: [] };
136
+ }
137
+ function recipientKey(recipient) {
138
+ return `${recipient.kind}:${recipient.id}`;
139
+ }
140
+
141
+ // src/core/gdpr.ts
142
+ var NOTES = "notifications";
143
+ var PREFS = "preferences";
144
+ var DELETE_BATCH = 100;
145
+ async function deleteAll(store, ids) {
146
+ for (let i = 0; i < ids.length; i += DELETE_BATCH) {
147
+ const batch = ids.slice(i, i + DELETE_BATCH);
148
+ await store.transaction(async (tx) => {
149
+ for (const id2 of batch) await tx.delete(NOTES, id2);
150
+ });
151
+ }
152
+ }
153
+ async function* notificationDocs(store, recipient, ordered = true) {
154
+ let cursor;
155
+ do {
156
+ const page = await store.query(NOTES, {
157
+ where: { recipientKey: recipientKey(recipient) },
158
+ // Erase needs no order. Paging by id keeps it working when a doc has a bad createdAt.
159
+ ...ordered ? { orderBy: { field: "createdAt", direction: "asc" } } : {},
160
+ limit: 200,
161
+ cursor
162
+ });
163
+ yield* page.items;
164
+ cursor = page.nextCursor ?? void 0;
165
+ } while (cursor);
166
+ }
167
+ async function exportRecipient(store, recipient) {
168
+ const notifications = [];
169
+ for await (const item of notificationDocs(store, recipient, false)) {
170
+ const { recipientKey: _drop, ...rest } = item.data;
171
+ notifications.push(notificationSchema.parse(rest));
172
+ }
173
+ notifications.sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1));
174
+ const prefs = await store.get(PREFS, recipientKey(recipient));
175
+ return { recipient, preferences: prefs ? preferencesSchema.parse(prefs) : null, notifications };
176
+ }
177
+ async function eraseRecipient(store, recipient) {
178
+ let removed = 0;
179
+ for (let pass = 0; ; pass++) {
180
+ if (pass >= 20) throw new Error("erase did not finish: deletes had no effect");
181
+ const ids = [];
182
+ for await (const item of notificationDocs(store, recipient, false)) ids.push(item.id);
183
+ if (ids.length === 0) break;
184
+ await deleteAll(store, ids);
185
+ removed += ids.length;
186
+ }
187
+ await store.delete(PREFS, recipientKey(recipient));
188
+ return removed;
189
+ }
190
+ async function sweepBefore(store, cutoff) {
191
+ if (!Number.isFinite(cutoff)) throw new RangeError("cutoff must be a finite number");
192
+ const limit = 200;
193
+ let removed = 0;
194
+ const gone = /* @__PURE__ */ new Set();
195
+ for (; ; ) {
196
+ const page = await store.query(NOTES, { orderBy: { field: "createdAt", direction: "asc" }, limit });
197
+ const old = page.items.filter((i) => typeof i.data.createdAt === "number" && i.data.createdAt < cutoff);
198
+ if (old.some((i) => gone.has(i.id))) throw new Error("retention sweep did not finish: deletes had no effect");
199
+ await deleteAll(store, old.map((i) => i.id));
200
+ for (const item of old) gone.add(item.id);
201
+ removed += old.length;
202
+ if (old.length > 0) continue;
203
+ if (page.items.length < limit || page.items.some((i) => typeof i.data.createdAt === "number")) return removed;
204
+ throw new Error("retention sweep blocked: a full page of notifications has no usable createdAt");
205
+ }
206
+ }
207
+
208
+ // src/core/service.ts
209
+ import { z as z2 } from "zod";
210
+ var NotFoundError = class extends Error {
211
+ code = "NOT_FOUND";
212
+ constructor(message = "notification not found") {
213
+ super(message);
214
+ this.name = "NotFoundError";
215
+ }
216
+ };
217
+ function toDoc(n) {
218
+ return { ...n, recipientKey: recipientKey(n.recipient) };
219
+ }
220
+ function fromDoc(doc) {
221
+ const { recipientKey: _drop, ...rest } = doc;
222
+ return notificationSchema.parse(rest);
223
+ }
224
+ var DEFAULT_LIMIT = 50;
225
+ var MAX_LIMIT = 200;
226
+ function clampLimit(limit) {
227
+ if (limit === void 0) return DEFAULT_LIMIT;
228
+ if (!Number.isInteger(limit) || limit < 1) throw new RangeError("limit must be an integer of 1 or more");
229
+ return Math.min(limit, MAX_LIMIT);
230
+ }
231
+ function checkCursor(cursor) {
232
+ try {
233
+ const bytes = Uint8Array.from(atob(cursor.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
234
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
235
+ if (Array.isArray(parsed) && parsed.length === 2 && Number.isFinite(parsed[0]) && typeof parsed[1] === "string") return;
236
+ } catch {
237
+ }
238
+ throw new RangeError("bad cursor");
239
+ }
240
+ function createNotifier(options) {
241
+ const { store } = options;
242
+ const now = options.now ?? (() => Date.now());
243
+ const newId = options.newId ?? (() => crypto.randomUUID());
244
+ if (options.retentionMs !== void 0 && !(Number.isInteger(options.retentionMs) && options.retentionMs > 0)) {
245
+ throw new RangeError("retentionMs must be a positive integer");
246
+ }
247
+ async function getPreferences(recipient) {
248
+ const r = recipientSchema.parse(recipient);
249
+ const doc = await store.get(PREFS, recipientKey(r));
250
+ return doc ? preferencesSchema.parse(doc) : defaultPreferences();
251
+ }
252
+ async function setPreferences(recipient, prefs) {
253
+ const r = recipientSchema.parse(recipient);
254
+ const valid = preferencesSchema.parse(prefs);
255
+ await store.put(PREFS, recipientKey(r), valid);
256
+ return valid;
257
+ }
258
+ async function send(input) {
259
+ const valid = sendInputSchema.parse(input);
260
+ const prefs = await getPreferences(valid.recipient);
261
+ if (prefs.muted || prefs.mutedTypes.includes(valid.type)) {
262
+ return { notification: null, stored: false, channels: [], muted: true };
263
+ }
264
+ const own = Object.hasOwn(prefs.channels, valid.type) ? prefs.channels[valid.type] : void 0;
265
+ const channels = [...own ?? DEFAULT_CHANNELS];
266
+ const notification = notificationSchema.parse({
267
+ ...valid,
268
+ id: newId(),
269
+ createdAt: now(),
270
+ readAt: null
271
+ });
272
+ if (!channels.includes("inbox")) return { notification, stored: false, channels, muted: false };
273
+ await store.transaction(async (tx) => {
274
+ if (await tx.get(NOTES, notification.id)) throw new Error("notification id collision");
275
+ await tx.put(NOTES, notification.id, toDoc(notification));
276
+ });
277
+ return { notification, stored: true, channels, muted: false };
278
+ }
279
+ async function list(recipient, opts = {}) {
280
+ const r = recipientSchema.parse(recipient);
281
+ const where = { recipientKey: recipientKey(r) };
282
+ if (opts.unreadOnly) where.readAt = null;
283
+ if (opts.cursor !== void 0) checkCursor(opts.cursor);
284
+ const limit = clampLimit(opts.limit);
285
+ const page = await store.query(NOTES, {
286
+ where,
287
+ orderBy: { field: "createdAt", direction: "desc" },
288
+ limit,
289
+ cursor: opts.cursor
290
+ });
291
+ return { items: page.items.map((i) => fromDoc(i.data)), nextCursor: page.nextCursor ?? null };
292
+ }
293
+ function check(doc, recipient) {
294
+ if (!doc || doc.recipientKey !== recipientKey(recipient)) throw new NotFoundError();
295
+ return doc;
296
+ }
297
+ async function get(recipient, id2) {
298
+ const r = recipientSchema.parse(recipient);
299
+ return fromDoc(check(await store.get(NOTES, id2), r));
300
+ }
301
+ async function markReadOnce(recipient, id2) {
302
+ const r = recipientSchema.parse(recipient);
303
+ return store.transaction(async (tx) => {
304
+ const n = fromDoc(check(await tx.get(NOTES, id2), r));
305
+ if (n.readAt !== null) return { notification: n, won: false };
306
+ const next = notificationSchema.parse({ ...n, readAt: now() });
307
+ await tx.put(NOTES, id2, toDoc(next));
308
+ return { notification: next, won: true };
309
+ });
310
+ }
311
+ async function markRead(recipient, id2) {
312
+ return (await markReadOnce(recipient, id2)).notification;
313
+ }
314
+ async function markAllRead(recipient) {
315
+ let count = 0;
316
+ const tried = /* @__PURE__ */ new Set();
317
+ for (; ; ) {
318
+ const page = await list(recipient, { unreadOnly: true, limit: 100 });
319
+ const fresh = page.items.filter((n) => !tried.has(n.id));
320
+ if (fresh.length === 0) return count;
321
+ for (const n of fresh) {
322
+ tried.add(n.id);
323
+ if ((await markReadOnce(recipient, n.id)).won) count++;
324
+ }
325
+ }
326
+ }
327
+ async function unreadCount(recipient) {
328
+ let count = 0;
329
+ let cursor;
330
+ do {
331
+ const page = await list(recipient, { unreadOnly: true, limit: 200, cursor });
332
+ count += page.items.length;
333
+ cursor = page.nextCursor ?? void 0;
334
+ } while (cursor);
335
+ return count;
336
+ }
337
+ async function digest(recipient, since = 0) {
338
+ since = z2.number().int().nonnegative().parse(since);
339
+ const groups = /* @__PURE__ */ new Map();
340
+ let total = 0;
341
+ let cursor;
342
+ do {
343
+ const page = await list(recipient, { unreadOnly: true, limit: 200, cursor });
344
+ for (const n of page.items) {
345
+ if (n.createdAt < since) continue;
346
+ total++;
347
+ const g = groups.get(n.type);
348
+ if (!g) groups.set(n.type, { count: 1, latest: n });
349
+ else g.count++;
350
+ }
351
+ cursor = page.nextCursor ?? void 0;
352
+ } while (cursor);
353
+ return {
354
+ total,
355
+ groups: [...groups].map(([type, g]) => ({ type, ...g })).sort((a, b) => b.latest.createdAt - a.latest.createdAt)
356
+ };
357
+ }
358
+ async function exportData(recipient) {
359
+ return exportRecipient(store, recipientSchema.parse(recipient));
360
+ }
361
+ async function erase(recipient) {
362
+ return { notifications: await eraseRecipient(store, recipientSchema.parse(recipient)) };
363
+ }
364
+ async function sweep() {
365
+ if (options.retentionMs === void 0) return 0;
366
+ return sweepBefore(store, now() - options.retentionMs);
367
+ }
368
+ function parseType(type) {
369
+ return typeNameSchema.parse(type);
370
+ }
371
+ return {
372
+ send,
373
+ list,
374
+ get,
375
+ markRead,
376
+ markAllRead,
377
+ unreadCount,
378
+ digest,
379
+ getPreferences,
380
+ setPreferences,
381
+ parseType,
382
+ exportData,
383
+ erase,
384
+ sweep
385
+ };
386
+ }
387
+
388
+ export {
389
+ VERSION,
390
+ CHANNELS,
391
+ recipientSchema,
392
+ typeNameSchema,
393
+ resourceRefSchema,
394
+ preferencesSchema,
395
+ DEFAULT_CHANNELS,
396
+ defaultPreferences,
397
+ recipientKey,
398
+ NOTES,
399
+ NotFoundError,
400
+ createNotifier
401
+ };
@@ -0,0 +1,64 @@
1
+ // src/sdk.ts
2
+ var ApiError = class extends Error {
3
+ constructor(status, code) {
4
+ super(`request failed: ${status} ${code}`);
5
+ this.status = status;
6
+ this.code = code;
7
+ this.name = "ApiError";
8
+ }
9
+ status;
10
+ code;
11
+ };
12
+ function createClient(options) {
13
+ if (!options.token) throw new Error("token is required");
14
+ const doFetch = options.fetch ?? fetch;
15
+ const base = options.baseUrl.replace(/\/+$/, "");
16
+ async function call(method, path, body) {
17
+ const res = await doFetch(`${base}/api/v1${path}`, {
18
+ method,
19
+ headers: {
20
+ authorization: `Bearer ${options.token}`,
21
+ ...body === void 0 ? {} : { "content-type": "application/json" }
22
+ },
23
+ body: body === void 0 ? void 0 : JSON.stringify(body)
24
+ });
25
+ const text = await res.text();
26
+ let parsed;
27
+ let valid = true;
28
+ try {
29
+ parsed = JSON.parse(text);
30
+ } catch {
31
+ valid = false;
32
+ }
33
+ if (!res.ok) {
34
+ const code = valid ? parsed?.error : void 0;
35
+ throw new ApiError(res.status, typeof code === "string" ? code : "error");
36
+ }
37
+ if (!valid) throw new ApiError(res.status, "bad_response");
38
+ return parsed;
39
+ }
40
+ const qs = (params) => {
41
+ const u = new URLSearchParams();
42
+ for (const [k, v] of Object.entries(params)) if (v !== void 0 && v !== false) u.set(k, String(v === true ? "1" : v));
43
+ const s = u.toString();
44
+ return s ? `?${s}` : "";
45
+ };
46
+ return {
47
+ list: (o = {}) => call("GET", `/notifications${qs({ unread: o.unreadOnly, limit: o.limit, cursor: o.cursor })}`),
48
+ get: (id) => call("GET", `/notifications/${encodeURIComponent(id)}`),
49
+ markRead: (id) => call("POST", `/notifications/${encodeURIComponent(id)}/read`),
50
+ markAllRead: () => call("POST", "/notifications/read-all"),
51
+ unreadCount: () => call("GET", "/unread-count"),
52
+ digest: (since) => call("GET", `/digest${qs({ since })}`),
53
+ getPreferences: () => call("GET", "/preferences"),
54
+ setPreferences: (prefs) => call("PUT", "/preferences", prefs),
55
+ send: (request) => call("POST", "/notifications", request),
56
+ exportData: () => call("GET", "/me/export"),
57
+ erase: () => call("DELETE", "/me")
58
+ };
59
+ }
60
+
61
+ export {
62
+ ApiError,
63
+ createClient
64
+ };
@@ -0,0 +1,31 @@
1
+ // src/delivery/email.ts
2
+ var TestEmailAdapter = class {
3
+ sent = [];
4
+ failNext = 0;
5
+ async send(message) {
6
+ if (this.failNext > 0) {
7
+ this.failNext--;
8
+ throw new Error("test adapter forced failure");
9
+ }
10
+ this.sent.push({ ...message });
11
+ }
12
+ };
13
+ var CONTROL = /[\x00-\x1f\x7f]/;
14
+ var CONTROL_G = /[\x00-\x1f\x7f]+/g;
15
+ function createEmailChannel(options) {
16
+ return {
17
+ async deliver(n) {
18
+ const to = await options.resolveAddress(n.recipient);
19
+ if (!to) return "skipped";
20
+ if (CONTROL.test(to)) throw new Error("address holds control characters");
21
+ const subject = `${n.type.replace(/[_.-]+/g, " ")}: ${n.resource.kind} ${n.resource.id}`.replace(CONTROL_G, " ");
22
+ await options.port.send({ to, subject, text: n.body });
23
+ return "sent";
24
+ }
25
+ };
26
+ }
27
+
28
+ export {
29
+ TestEmailAdapter,
30
+ createEmailChannel
31
+ };
@@ -0,0 +1,86 @@
1
+ import {
2
+ PACKAGE_NAME
3
+ } from "./chunk-3N77SYRA.js";
4
+ import {
5
+ ApiError
6
+ } from "./chunk-FJPEUDTG.js";
7
+ import {
8
+ VERSION,
9
+ preferencesSchema,
10
+ recipientSchema,
11
+ resourceRefSchema,
12
+ typeNameSchema
13
+ } from "./chunk-6QCYA3X7.js";
14
+
15
+ // src/mcp.ts
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import { z } from "zod";
19
+ async function wrap(fn) {
20
+ try {
21
+ return { content: [{ type: "text", text: JSON.stringify(await fn()) }] };
22
+ } catch (error) {
23
+ if (error instanceof ApiError) {
24
+ return { content: [{ type: "text", text: JSON.stringify({ error: error.code, status: error.status }) }], isError: true };
25
+ }
26
+ throw error;
27
+ }
28
+ }
29
+ function createMcpServer(client) {
30
+ const server = new McpServer({ name: PACKAGE_NAME, version: VERSION });
31
+ const need = (fn) => () => wrap(async () => {
32
+ if (!client) throw new ApiError(0, "not_configured");
33
+ return fn(client);
34
+ });
35
+ const read = { readOnlyHint: true };
36
+ const write = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
37
+ server.registerTool(
38
+ "ping",
39
+ { description: "Check that the server runs. Returns the package version.", inputSchema: {}, annotations: read },
40
+ async () => ({ content: [{ type: "text", text: JSON.stringify({ ok: true, version: VERSION }) }] })
41
+ );
42
+ server.registerTool("list_notifications", {
43
+ description: "List your notifications, newest first.",
44
+ inputSchema: { unreadOnly: z.boolean().optional(), limit: z.number().int().min(1).max(200).optional(), cursor: z.string().optional() },
45
+ annotations: read
46
+ }, (a) => need((c) => c.list(a))());
47
+ server.registerTool(
48
+ "read_notification",
49
+ { description: "Get one notification by id.", inputSchema: { id: z.string() }, annotations: read },
50
+ ({ id }) => need((c) => c.get(id))()
51
+ );
52
+ server.registerTool(
53
+ "mark_read",
54
+ { description: "Mark one notification as read.", inputSchema: { id: z.string() }, annotations: write },
55
+ ({ id }) => need((c) => c.markRead(id))()
56
+ );
57
+ server.registerTool(
58
+ "mark_all_read",
59
+ { description: "Mark all your notifications as read.", inputSchema: {}, annotations: write },
60
+ need((c) => c.markAllRead())
61
+ );
62
+ server.registerTool(
63
+ "get_preferences",
64
+ { description: "Read your notification preferences.", inputSchema: {}, annotations: read },
65
+ need((c) => c.getPreferences())
66
+ );
67
+ server.registerTool("set_preferences", {
68
+ description: "Replace your notification preferences: channels per type, mute, muted types.",
69
+ inputSchema: preferencesSchema.shape,
70
+ annotations: write
71
+ }, (a) => need((c) => c.setPreferences(a))());
72
+ server.registerTool("send_notification", {
73
+ description: "Send a notification. You must hold the send right. You are the actor.",
74
+ inputSchema: { recipient: recipientSchema, type: typeNameSchema, resource: resourceRefSchema, body: z.string().max(2e3) },
75
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
76
+ }, (a) => need((c) => c.send(a))());
77
+ return server;
78
+ }
79
+ async function startMcp(client) {
80
+ await createMcpServer(client).connect(new StdioServerTransport());
81
+ }
82
+
83
+ export {
84
+ createMcpServer,
85
+ startMcp
86
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ interface CliIo {
3
+ out(line: string): void;
4
+ err(line: string): void;
5
+ env: Record<string, string | undefined>;
6
+ fetch?: typeof fetch;
7
+ }
8
+ declare function runCli(argv: string[], io?: CliIo): Promise<number>;
9
+
10
+ export { type CliIo, runCli };