@asyncify-hq/agent 0.1.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.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @asyncify-hq/agent
2
+
3
+ Build the brain; Asyncify handles the channels. This SDK receives
4
+ normalized conversation events from Asyncify (any channel — in-app today,
5
+ more coming), lets your code reply, and batches **signals** — conversation
6
+ metadata, workflow triggers, resolution — into the single HTTP response.
7
+
8
+ Zero dependencies. Works with plain Node `http`, Express, Fastify, or any
9
+ framework that exposes the raw request.
10
+
11
+ ## Quickstart
12
+
13
+ 1. Register an agent (dashboard → Agents, or the API) with the URL your
14
+ handler will listen on. Copy the signing secret — it is shown once.
15
+
16
+ 2. Write the brain:
17
+
18
+ ```ts
19
+ import http from 'node:http';
20
+ import { defineAgent, createHandler } from '@asyncify-hq/agent';
21
+
22
+ const support = defineAgent({
23
+ async onMessage(ctx) {
24
+ // ctx.history is pre-shaped for LLM SDKs: [{ role, content }, ...]
25
+ if (ctx.message.text.toLowerCase().includes('order')) {
26
+ ctx.metadata.set('topic', 'orders');
27
+ // fire a real notification workflow, mid-conversation
28
+ ctx.trigger('order-replacement', { payload: { order: '#1042' } });
29
+ return 'So sorry! A replacement is on the way — confirmation email incoming.';
30
+ }
31
+ if (ctx.message.text.toLowerCase().includes('thanks')) {
32
+ ctx.resolve('customer satisfied');
33
+ return 'Anytime!';
34
+ }
35
+ return `You said: ${ctx.message.text}`;
36
+ },
37
+ });
38
+
39
+ http
40
+ .createServer(createHandler(support, { signingSecret: process.env.ASYNCIFY_AGENT_SECRET! }))
41
+ .listen(4100);
42
+ ```
43
+
44
+ 3. Send a message from the `<AgentChat />` widget (`@asyncify-hq/react`)
45
+ or the API — your handler answers, Asyncify delivers.
46
+
47
+ ## Adding an LLM
48
+
49
+ `ctx.history` maps directly onto chat-completion messages:
50
+
51
+ ```ts
52
+ const { text } = await generateText({
53
+ model: openai('gpt-4o-mini'),
54
+ system: 'You are a support agent for Acme.',
55
+ messages: [...ctx.history, { role: 'user', content: ctx.message.text }],
56
+ });
57
+ return text;
58
+ ```
59
+
60
+ ## Security
61
+
62
+ Every request is HMAC-SHA256 signed (`x-asyncify-signature` over
63
+ `timestamp.body`, replay-protected by `x-asyncify-timestamp`). The handler
64
+ rejects anything unsigned or stale; `verifySignature` is exported if you
65
+ need to verify manually.
package/dist/index.cjs ADDED
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createHandler: () => createHandler,
24
+ defineAgent: () => defineAgent,
25
+ handleEvent: () => handleEvent,
26
+ verifySignature: () => verifySignature
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ var import_node_crypto = require("crypto");
30
+ function defineAgent(definition) {
31
+ if (typeof definition.onMessage !== "function") {
32
+ throw new Error("defineAgent requires an onMessage handler");
33
+ }
34
+ return definition;
35
+ }
36
+ function verifySignature(secret, timestamp, signature, rawBody, toleranceSec = 300) {
37
+ if (!timestamp || !signature) return false;
38
+ const ts = Number.parseInt(timestamp, 10);
39
+ if (!Number.isFinite(ts) || Math.abs(Date.now() / 1e3 - ts) > toleranceSec) return false;
40
+ const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
41
+ let provided;
42
+ try {
43
+ provided = Buffer.from(signature, "hex");
44
+ } catch {
45
+ return false;
46
+ }
47
+ return provided.length === expected.length && (0, import_node_crypto.timingSafeEqual)(provided, expected);
48
+ }
49
+ async function handleEvent(agent, event) {
50
+ const signals = [];
51
+ let reply;
52
+ const metadata = { ...event.conversation.metadata };
53
+ const ctx = {
54
+ message: event.message,
55
+ conversation: event.conversation,
56
+ subscriber: event.subscriber,
57
+ history: event.history,
58
+ reply(text) {
59
+ reply = text;
60
+ },
61
+ metadata: {
62
+ set(key, value) {
63
+ metadata[key] = value;
64
+ signals.push({ type: "metadata.set", key, value });
65
+ },
66
+ get(key) {
67
+ return metadata[key];
68
+ }
69
+ },
70
+ trigger(workflowKey, options) {
71
+ signals.push({
72
+ type: "trigger",
73
+ workflowKey,
74
+ payload: options?.payload,
75
+ priority: options?.priority
76
+ });
77
+ },
78
+ resolve(summary) {
79
+ signals.push({ type: "resolve", summary });
80
+ }
81
+ };
82
+ const returned = await agent.onMessage(ctx);
83
+ if (typeof returned === "string") reply = returned;
84
+ return { reply, signals };
85
+ }
86
+ function createHandler(agent, options) {
87
+ if (!options.signingSecret) throw new Error("createHandler requires signingSecret");
88
+ return (req, res) => {
89
+ const chunks = [];
90
+ req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
91
+ req.on("error", () => respond(res, 400, { error: "read error" }));
92
+ req.on("end", () => {
93
+ void (async () => {
94
+ const rawBody = Buffer.concat(chunks).toString("utf8");
95
+ const header = (name) => {
96
+ const v = req.headers[name];
97
+ return Array.isArray(v) ? v[0] : v;
98
+ };
99
+ const ok = verifySignature(
100
+ options.signingSecret,
101
+ header("x-asyncify-timestamp"),
102
+ header("x-asyncify-signature"),
103
+ rawBody,
104
+ options.toleranceSec
105
+ );
106
+ if (!ok) return respond(res, 401, { error: "invalid signature" });
107
+ let event;
108
+ try {
109
+ event = JSON.parse(rawBody);
110
+ } catch {
111
+ return respond(res, 400, { error: "invalid JSON" });
112
+ }
113
+ if (event.type !== "message") return respond(res, 200, { signals: [] });
114
+ try {
115
+ respond(res, 200, await handleEvent(agent, event));
116
+ } catch (err) {
117
+ respond(res, 500, { error: err.message });
118
+ }
119
+ })();
120
+ });
121
+ };
122
+ }
123
+ function respond(res, status, body) {
124
+ res.statusCode = status;
125
+ res.setHeader("content-type", "application/json");
126
+ res.end(JSON.stringify(body));
127
+ }
128
+ // Annotate the CommonJS export names for ESM import in node:
129
+ 0 && (module.exports = {
130
+ createHandler,
131
+ defineAgent,
132
+ handleEvent,
133
+ verifySignature
134
+ });
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @asyncify-hq/agent — build the brain, Asyncify handles the channels.
3
+ *
4
+ * import { defineAgent, createHandler } from '@asyncify-hq/agent';
5
+ *
6
+ * const support = defineAgent({
7
+ * async onMessage(ctx) {
8
+ * if (ctx.message.text.includes('order')) {
9
+ * ctx.metadata.set('topic', 'orders');
10
+ * ctx.trigger('order-shipped', { payload: { order: '#1042' } });
11
+ * return 'A replacement is on the way — confirmation email incoming.';
12
+ * }
13
+ * return `You said: ${ctx.message.text}`;
14
+ * },
15
+ * });
16
+ *
17
+ * http.createServer(createHandler(support, {
18
+ * signingSecret: process.env.ASYNCIFY_AGENT_SECRET!,
19
+ * })).listen(4100);
20
+ *
21
+ * Asyncify POSTs each normalized conversation turn (any channel) to this
22
+ * handler; the reply and any signals (metadata / trigger / resolve) are
23
+ * batched into the single HTTP response. Zero dependencies.
24
+ */
25
+ interface AgentEventMessage {
26
+ id: string;
27
+ text: string;
28
+ createdAt: string;
29
+ }
30
+ interface AgentEventConversation {
31
+ id: string;
32
+ channel: string;
33
+ status: 'active' | 'resolved';
34
+ metadata: Record<string, unknown>;
35
+ messageCount: number;
36
+ }
37
+ interface AgentEventSubscriber {
38
+ subscriberId: string;
39
+ email: string | null;
40
+ phone: string | null;
41
+ }
42
+ /** Prior turns, pre-shaped for LLM SDKs (drop straight into messages[]). */
43
+ interface HistoryEntry {
44
+ role: 'user' | 'assistant';
45
+ content: string;
46
+ }
47
+ interface AgentEvent {
48
+ type: 'message';
49
+ agent: {
50
+ identifier: string;
51
+ name: string;
52
+ };
53
+ conversation: AgentEventConversation;
54
+ subscriber: AgentEventSubscriber;
55
+ message: AgentEventMessage;
56
+ history: HistoryEntry[];
57
+ }
58
+ type Priority = 'p0' | 'p1' | 'p2';
59
+ type Signal = {
60
+ type: 'metadata.set';
61
+ key: string;
62
+ value: unknown;
63
+ } | {
64
+ type: 'trigger';
65
+ workflowKey: string;
66
+ payload?: Record<string, unknown>;
67
+ priority?: Priority;
68
+ } | {
69
+ type: 'resolve';
70
+ summary?: string;
71
+ };
72
+ interface BridgeResponse {
73
+ reply?: string;
74
+ signals: Signal[];
75
+ }
76
+ interface AgentContext {
77
+ /** The inbound turn being handled. */
78
+ message: AgentEventMessage;
79
+ conversation: AgentEventConversation;
80
+ subscriber: AgentEventSubscriber;
81
+ /** Prior user/assistant turns, oldest first. */
82
+ history: HistoryEntry[];
83
+ /** Send a reply to the user (last call wins; returning a string does the same). */
84
+ reply(text: string): void;
85
+ metadata: {
86
+ /** Persist a key on the conversation (survives across turns, 64KB total). */
87
+ set(key: string, value: unknown): void;
88
+ get(key: string): unknown;
89
+ };
90
+ /** Fire a normal Asyncify notification workflow, mid-conversation. */
91
+ trigger(workflowKey: string, options?: {
92
+ payload?: Record<string, unknown>;
93
+ priority?: Priority;
94
+ }): void;
95
+ /** Mark the conversation resolved (a new message reopens it). */
96
+ resolve(summary?: string): void;
97
+ }
98
+ type MessageHandler = (ctx: AgentContext) => string | void | undefined | Promise<string | void | undefined>;
99
+ interface AgentDefinition {
100
+ onMessage: MessageHandler;
101
+ /** Reserved for platform-dispatched resolve events (not sent yet). */
102
+ onResolve?: (ctx: AgentContext) => void | Promise<void>;
103
+ }
104
+ declare function defineAgent(definition: AgentDefinition): AgentDefinition;
105
+ declare function verifySignature(secret: string, timestamp: string | undefined, signature: string | undefined, rawBody: string, toleranceSec?: number): boolean;
106
+ declare function handleEvent(agent: AgentDefinition, event: AgentEvent): Promise<BridgeResponse>;
107
+ /** Structural subset of http.IncomingMessage / ServerResponse. */
108
+ interface RequestLike {
109
+ headers: Record<string, string | string[] | undefined>;
110
+ on(event: 'data', cb: (chunk: unknown) => void): unknown;
111
+ on(event: 'end', cb: () => void): unknown;
112
+ on(event: 'error', cb: (err: Error) => void): unknown;
113
+ }
114
+ interface ResponseLike {
115
+ statusCode: number;
116
+ setHeader(name: string, value: string): unknown;
117
+ end(body?: string): unknown;
118
+ }
119
+ interface HandlerOptions {
120
+ /** The signing secret shown once when the agent was created (ags_...). */
121
+ signingSecret: string;
122
+ /** Clock-skew tolerance for the signed timestamp, seconds. */
123
+ toleranceSec?: number;
124
+ }
125
+ declare function createHandler(agent: AgentDefinition, options: HandlerOptions): (req: RequestLike, res: ResponseLike) => void;
126
+
127
+ export { type AgentContext, type AgentDefinition, type AgentEvent, type AgentEventConversation, type AgentEventMessage, type AgentEventSubscriber, type BridgeResponse, type HandlerOptions, type HistoryEntry, type MessageHandler, type Priority, type RequestLike, type ResponseLike, type Signal, createHandler, defineAgent, handleEvent, verifySignature };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @asyncify-hq/agent — build the brain, Asyncify handles the channels.
3
+ *
4
+ * import { defineAgent, createHandler } from '@asyncify-hq/agent';
5
+ *
6
+ * const support = defineAgent({
7
+ * async onMessage(ctx) {
8
+ * if (ctx.message.text.includes('order')) {
9
+ * ctx.metadata.set('topic', 'orders');
10
+ * ctx.trigger('order-shipped', { payload: { order: '#1042' } });
11
+ * return 'A replacement is on the way — confirmation email incoming.';
12
+ * }
13
+ * return `You said: ${ctx.message.text}`;
14
+ * },
15
+ * });
16
+ *
17
+ * http.createServer(createHandler(support, {
18
+ * signingSecret: process.env.ASYNCIFY_AGENT_SECRET!,
19
+ * })).listen(4100);
20
+ *
21
+ * Asyncify POSTs each normalized conversation turn (any channel) to this
22
+ * handler; the reply and any signals (metadata / trigger / resolve) are
23
+ * batched into the single HTTP response. Zero dependencies.
24
+ */
25
+ interface AgentEventMessage {
26
+ id: string;
27
+ text: string;
28
+ createdAt: string;
29
+ }
30
+ interface AgentEventConversation {
31
+ id: string;
32
+ channel: string;
33
+ status: 'active' | 'resolved';
34
+ metadata: Record<string, unknown>;
35
+ messageCount: number;
36
+ }
37
+ interface AgentEventSubscriber {
38
+ subscriberId: string;
39
+ email: string | null;
40
+ phone: string | null;
41
+ }
42
+ /** Prior turns, pre-shaped for LLM SDKs (drop straight into messages[]). */
43
+ interface HistoryEntry {
44
+ role: 'user' | 'assistant';
45
+ content: string;
46
+ }
47
+ interface AgentEvent {
48
+ type: 'message';
49
+ agent: {
50
+ identifier: string;
51
+ name: string;
52
+ };
53
+ conversation: AgentEventConversation;
54
+ subscriber: AgentEventSubscriber;
55
+ message: AgentEventMessage;
56
+ history: HistoryEntry[];
57
+ }
58
+ type Priority = 'p0' | 'p1' | 'p2';
59
+ type Signal = {
60
+ type: 'metadata.set';
61
+ key: string;
62
+ value: unknown;
63
+ } | {
64
+ type: 'trigger';
65
+ workflowKey: string;
66
+ payload?: Record<string, unknown>;
67
+ priority?: Priority;
68
+ } | {
69
+ type: 'resolve';
70
+ summary?: string;
71
+ };
72
+ interface BridgeResponse {
73
+ reply?: string;
74
+ signals: Signal[];
75
+ }
76
+ interface AgentContext {
77
+ /** The inbound turn being handled. */
78
+ message: AgentEventMessage;
79
+ conversation: AgentEventConversation;
80
+ subscriber: AgentEventSubscriber;
81
+ /** Prior user/assistant turns, oldest first. */
82
+ history: HistoryEntry[];
83
+ /** Send a reply to the user (last call wins; returning a string does the same). */
84
+ reply(text: string): void;
85
+ metadata: {
86
+ /** Persist a key on the conversation (survives across turns, 64KB total). */
87
+ set(key: string, value: unknown): void;
88
+ get(key: string): unknown;
89
+ };
90
+ /** Fire a normal Asyncify notification workflow, mid-conversation. */
91
+ trigger(workflowKey: string, options?: {
92
+ payload?: Record<string, unknown>;
93
+ priority?: Priority;
94
+ }): void;
95
+ /** Mark the conversation resolved (a new message reopens it). */
96
+ resolve(summary?: string): void;
97
+ }
98
+ type MessageHandler = (ctx: AgentContext) => string | void | undefined | Promise<string | void | undefined>;
99
+ interface AgentDefinition {
100
+ onMessage: MessageHandler;
101
+ /** Reserved for platform-dispatched resolve events (not sent yet). */
102
+ onResolve?: (ctx: AgentContext) => void | Promise<void>;
103
+ }
104
+ declare function defineAgent(definition: AgentDefinition): AgentDefinition;
105
+ declare function verifySignature(secret: string, timestamp: string | undefined, signature: string | undefined, rawBody: string, toleranceSec?: number): boolean;
106
+ declare function handleEvent(agent: AgentDefinition, event: AgentEvent): Promise<BridgeResponse>;
107
+ /** Structural subset of http.IncomingMessage / ServerResponse. */
108
+ interface RequestLike {
109
+ headers: Record<string, string | string[] | undefined>;
110
+ on(event: 'data', cb: (chunk: unknown) => void): unknown;
111
+ on(event: 'end', cb: () => void): unknown;
112
+ on(event: 'error', cb: (err: Error) => void): unknown;
113
+ }
114
+ interface ResponseLike {
115
+ statusCode: number;
116
+ setHeader(name: string, value: string): unknown;
117
+ end(body?: string): unknown;
118
+ }
119
+ interface HandlerOptions {
120
+ /** The signing secret shown once when the agent was created (ags_...). */
121
+ signingSecret: string;
122
+ /** Clock-skew tolerance for the signed timestamp, seconds. */
123
+ toleranceSec?: number;
124
+ }
125
+ declare function createHandler(agent: AgentDefinition, options: HandlerOptions): (req: RequestLike, res: ResponseLike) => void;
126
+
127
+ export { type AgentContext, type AgentDefinition, type AgentEvent, type AgentEventConversation, type AgentEventMessage, type AgentEventSubscriber, type BridgeResponse, type HandlerOptions, type HistoryEntry, type MessageHandler, type Priority, type RequestLike, type ResponseLike, type Signal, createHandler, defineAgent, handleEvent, verifySignature };
package/dist/index.js ADDED
@@ -0,0 +1,106 @@
1
+ // src/index.ts
2
+ import { createHmac, timingSafeEqual } from "crypto";
3
+ function defineAgent(definition) {
4
+ if (typeof definition.onMessage !== "function") {
5
+ throw new Error("defineAgent requires an onMessage handler");
6
+ }
7
+ return definition;
8
+ }
9
+ function verifySignature(secret, timestamp, signature, rawBody, toleranceSec = 300) {
10
+ if (!timestamp || !signature) return false;
11
+ const ts = Number.parseInt(timestamp, 10);
12
+ if (!Number.isFinite(ts) || Math.abs(Date.now() / 1e3 - ts) > toleranceSec) return false;
13
+ const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
14
+ let provided;
15
+ try {
16
+ provided = Buffer.from(signature, "hex");
17
+ } catch {
18
+ return false;
19
+ }
20
+ return provided.length === expected.length && timingSafeEqual(provided, expected);
21
+ }
22
+ async function handleEvent(agent, event) {
23
+ const signals = [];
24
+ let reply;
25
+ const metadata = { ...event.conversation.metadata };
26
+ const ctx = {
27
+ message: event.message,
28
+ conversation: event.conversation,
29
+ subscriber: event.subscriber,
30
+ history: event.history,
31
+ reply(text) {
32
+ reply = text;
33
+ },
34
+ metadata: {
35
+ set(key, value) {
36
+ metadata[key] = value;
37
+ signals.push({ type: "metadata.set", key, value });
38
+ },
39
+ get(key) {
40
+ return metadata[key];
41
+ }
42
+ },
43
+ trigger(workflowKey, options) {
44
+ signals.push({
45
+ type: "trigger",
46
+ workflowKey,
47
+ payload: options?.payload,
48
+ priority: options?.priority
49
+ });
50
+ },
51
+ resolve(summary) {
52
+ signals.push({ type: "resolve", summary });
53
+ }
54
+ };
55
+ const returned = await agent.onMessage(ctx);
56
+ if (typeof returned === "string") reply = returned;
57
+ return { reply, signals };
58
+ }
59
+ function createHandler(agent, options) {
60
+ if (!options.signingSecret) throw new Error("createHandler requires signingSecret");
61
+ return (req, res) => {
62
+ const chunks = [];
63
+ req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
64
+ req.on("error", () => respond(res, 400, { error: "read error" }));
65
+ req.on("end", () => {
66
+ void (async () => {
67
+ const rawBody = Buffer.concat(chunks).toString("utf8");
68
+ const header = (name) => {
69
+ const v = req.headers[name];
70
+ return Array.isArray(v) ? v[0] : v;
71
+ };
72
+ const ok = verifySignature(
73
+ options.signingSecret,
74
+ header("x-asyncify-timestamp"),
75
+ header("x-asyncify-signature"),
76
+ rawBody,
77
+ options.toleranceSec
78
+ );
79
+ if (!ok) return respond(res, 401, { error: "invalid signature" });
80
+ let event;
81
+ try {
82
+ event = JSON.parse(rawBody);
83
+ } catch {
84
+ return respond(res, 400, { error: "invalid JSON" });
85
+ }
86
+ if (event.type !== "message") return respond(res, 200, { signals: [] });
87
+ try {
88
+ respond(res, 200, await handleEvent(agent, event));
89
+ } catch (err) {
90
+ respond(res, 500, { error: err.message });
91
+ }
92
+ })();
93
+ });
94
+ };
95
+ }
96
+ function respond(res, status, body) {
97
+ res.statusCode = status;
98
+ res.setHeader("content-type", "application/json");
99
+ res.end(JSON.stringify(body));
100
+ }
101
+ export {
102
+ createHandler,
103
+ defineAgent,
104
+ handleEvent,
105
+ verifySignature
106
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@asyncify-hq/agent",
3
+ "version": "0.1.0",
4
+ "description": "Agent SDK for Asyncify — receive normalized conversation events from any channel, reply, and fire notification workflows mid-conversation",
5
+ "keywords": ["agents", "ai-agents", "conversations", "chat", "notifications", "asyncify"],
6
+ "homepage": "https://asyncify.org",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/shubam14dec/Scalable-Notification-System.git",
10
+ "directory": "packages/agent"
11
+ },
12
+ "license": "MIT",
13
+ "author": "Shubam Patil",
14
+ "type": "module",
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.cjs"
23
+ }
24
+ },
25
+ "files": ["dist"],
26
+ "sideEffects": false,
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "tsup": "^8.3.5",
37
+ "typescript": "^5.7.2"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }