@tuwaio/quasar-sdk 0.1.8 → 0.1.10

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.
Files changed (3) hide show
  1. package/README.md +82 -0
  2. package/dist/cli.cjs +467 -0
  3. package/package.json +5 -1
package/README.md CHANGED
@@ -22,6 +22,7 @@ It operates strictly on the server (Node.js, Next.js Server Actions, or Edge fun
22
22
  ## ✨ Key Features
23
23
 
24
24
  - **☁️ Cloud Sync**: Automatically persist pending and terminal transaction states to the Quasar Database for cross-device history.
25
+ - **⚡ Native Webhook Local Dev Relay**: Stream production webhooks directly to your local machine via CLI with zero tunnels (no ngrok/cloudflared required).
25
26
  - **🔐 Headless SIWX (CAIP-122) Auth Ready**: Seamlessly pairs with `@tuwaio/sdk/siwx/server` for strict cryptographic verification of user sessions before allowing database writes.
26
27
  - **⚡ Edge Ready**: Uses `ofetch` and lightweight cryptography to run seamlessly in Cloudflare Workers and Vercel Edge.
27
28
  - **📦 InMemory Sync**: Perfectly pairs with `@tuwaio/sdk/pulsar` (`createTxInMemoryStore`) to fetch history and hydrate local React states.
@@ -38,6 +39,87 @@ _Note: `ofetch` and `@tuwaio/pulsar-core` are required peer dependencies. The `@
38
39
 
39
40
  ---
40
41
 
42
+ ## ⚡ Quasar Webhook Local Dev Relay (Native CLI)
43
+
44
+ Testing Web3 webhooks locally against live cloud indexers usually requires spinning up temporary third-party tunnels (`ngrok`, `cloudflared`), pasting ephemeral URLs into dashboard forms, and restarting your workflow when tunnels expire.
45
+
46
+ The **Quasar SDK CLI** provides a zero-tunnel local development relay. When you configure a `localhost` webhook endpoint in the Quasar Dashboard, Quasar Cloud streams incoming events to your local machine via real-time Server-Sent Events (SSE).
47
+
48
+ ### 1. Zero-Config Local Development
49
+
50
+ Add your webhook signing secret from the Quasar Dashboard to `.env.local` or `.env`:
51
+
52
+ ```env
53
+ # Signing secret copied from the Quasar Dashboard webhook endpoint
54
+ QUASAR_WEBHOOK_SECRET=whsec_...
55
+
56
+ # Optional Quasar API base URL (defaults to https://api.tuwa.io)
57
+ NEXT_PUBLIC_QUASAR_BASE_URL=https://api.tuwa.io
58
+ ```
59
+
60
+ Run the relay listener in your terminal:
61
+
62
+ ```bash
63
+ npx @tuwaio/quasar-sdk listen
64
+ ```
65
+
66
+ The CLI automatically reads `QUASAR_WEBHOOK_SECRET` and starts forwarding webhook payloads to the default Cosmos Playground endpoint (`http://localhost:3000/api/webhooks/quasar`).
67
+
68
+ ### 2. Custom Forwarding & CLI Options
69
+
70
+ ```bash
71
+ # Forward to a custom port or path
72
+ npx @tuwaio/quasar-sdk listen --forward-to http://localhost:8080/api/webhooks
73
+
74
+ # Provide secret explicitly via flags
75
+ npx @tuwaio/quasar-sdk listen --secret whsec_... --forward-to http://localhost:3000/api/webhooks/quasar
76
+
77
+ # Specify custom .env file
78
+ npx @tuwaio/quasar-sdk listen --env-file .env.development
79
+ ```
80
+
81
+ | Flag | Shorthand | Description | Default |
82
+ | :------------- | :-------- | :-------------------------------------------- | :------------------------------------------ |
83
+ | `--secret` | `-s` | Webhook endpoint signing secret (`whsec_...`) | `QUASAR_WEBHOOK_SECRET` |
84
+ | `--forward-to` | `-f` | Local destination endpoint | `http://localhost:3000/api/webhooks/quasar` |
85
+ | `--api-url` | `-a` | Quasar Cloud API base URL | `https://api.tuwa.io` |
86
+ | `--env-file` | `-e` | Custom path to `.env` file | Auto-detected (`.env.local`, `.env`) |
87
+ | `--help` | `-h` | Display help instructions | — |
88
+ | `--version` | `-v` | Display CLI version | — |
89
+
90
+ ### 3. Local Webhook Route Handler (Next.js Example)
91
+
92
+ ```typescript
93
+ // app/api/webhooks/quasar/route.ts
94
+ import { NextResponse } from 'next/server';
95
+ import crypto from 'node:crypto';
96
+
97
+ export async function POST(req: Request) {
98
+ const secret = process.env.QUASAR_WEBHOOK_SECRET;
99
+ if (!secret) {
100
+ return NextResponse.json({ error: 'Server misconfigured' }, { status: 500 });
101
+ }
102
+
103
+ const rawBody = await req.text();
104
+ const signature = req.headers.get('x-quasar-signature');
105
+ const event = req.headers.get('x-quasar-event');
106
+
107
+ // Verify HMAC-SHA256 signature
108
+ const expectedSignature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
109
+
110
+ if (signature !== expectedSignature) {
111
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
112
+ }
113
+
114
+ const payload = JSON.parse(rawBody);
115
+ console.log(`[Webhook Received] ${event}:`, payload);
116
+
117
+ return NextResponse.json({ received: true });
118
+ }
119
+ ```
120
+
121
+ ---
122
+
41
123
  ## 🚀 Quick Start (Node.js / Edge)
42
124
 
43
125
  This is a basic example of how to interact with the Quasar Cloud directly from your secure backend environments (like Next.js API Routes, Server Actions, or NestJS).
package/dist/cli.cjs ADDED
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli.ts
32
+ var cli_exports = {};
33
+ __export(cli_exports, {
34
+ CLI_VERSION: () => CLI_VERSION,
35
+ DEFAULT_API_URL: () => DEFAULT_API_URL,
36
+ DEFAULT_FORWARD_URL: () => DEFAULT_FORWARD_URL,
37
+ handleSseMessage: () => handleSseMessage,
38
+ loadEnv: () => loadEnv,
39
+ main: () => main,
40
+ parseArgs: () => parseArgs,
41
+ parseEnvFile: () => parseEnvFile
42
+ });
43
+ module.exports = __toCommonJS(cli_exports);
44
+ var fs = __toESM(require("fs"), 1);
45
+ var path = __toESM(require("path"), 1);
46
+
47
+ // package.json
48
+ var package_default = {
49
+ name: "@tuwaio/quasar-sdk",
50
+ version: "0.1.10",
51
+ private: false,
52
+ author: "Oleksandr Tkach",
53
+ license: "Apache-2.0",
54
+ description: "Layer 5 (L5) of the TUWA Ecosystem. The Quasar Cloud SDK providing server-side Node.js & Edge utilities for transaction indexing, billing, and backend API integration.",
55
+ type: "module",
56
+ main: "./dist/index.cjs",
57
+ module: "./dist/index.js",
58
+ types: "./dist/index.d.ts",
59
+ exports: {
60
+ ".": {
61
+ types: "./dist/index.d.ts",
62
+ import: "./dist/index.js",
63
+ require: "./dist/index.cjs"
64
+ }
65
+ },
66
+ publishConfig: {
67
+ access: "public"
68
+ },
69
+ files: [
70
+ "dist",
71
+ "socket.json"
72
+ ],
73
+ bin: {
74
+ "@tuwaio/quasar-sdk": "./dist/cli.cjs",
75
+ "quasar-sdk": "./dist/cli.cjs"
76
+ },
77
+ scripts: {
78
+ start: "tsup src/index.ts --watch",
79
+ build: "tsup",
80
+ test: "vitest run"
81
+ },
82
+ keywords: [
83
+ "tuwa",
84
+ "web3",
85
+ "evm",
86
+ "ethereum",
87
+ "solana",
88
+ "wagmi",
89
+ "viem",
90
+ "@solana/kit",
91
+ "wallet-standard",
92
+ "wallet",
93
+ "wallet-connect",
94
+ "wallets",
95
+ "transaction",
96
+ "tracking",
97
+ "transaction-tracking",
98
+ "react",
99
+ "ui",
100
+ "ui-kit",
101
+ "design-system",
102
+ "hooks",
103
+ "utils",
104
+ "components",
105
+ "tailwind",
106
+ "tailwindcss",
107
+ "typescript",
108
+ "core",
109
+ "headless",
110
+ "zustand",
111
+ "immer",
112
+ "i18n",
113
+ "modal",
114
+ "toast",
115
+ "quasar",
116
+ "cloud",
117
+ "sdk",
118
+ "next-auth",
119
+ "cosmos",
120
+ "playground",
121
+ "cli"
122
+ ],
123
+ repository: {
124
+ type: "git",
125
+ url: "git+https://github.com/TuwaIO/sdk.git",
126
+ directory: "packages/quasar-sdk"
127
+ },
128
+ homepage: "https://github.com/TuwaIO/sdk",
129
+ bugs: {
130
+ url: "https://github.com/TuwaIO/sdk/issues"
131
+ },
132
+ contributors: [
133
+ {
134
+ name: "Oleksandr Tkach",
135
+ url: "https://github.com/Argeare5"
136
+ }
137
+ ],
138
+ peerDependencies: {
139
+ "@tuwaio/pulsar-core": ">=0.7",
140
+ ofetch: "^1.5.1",
141
+ "@tuwaio/siwx-react": ">=0.3",
142
+ "@tuwaio/siwx-core": ">=0.3",
143
+ "@tuwaio/siwx-server": ">=0.3"
144
+ },
145
+ peerDependenciesMeta: {
146
+ "@tuwaio/pulsar-core": {
147
+ optional: false
148
+ },
149
+ ofetch: {
150
+ optional: false
151
+ },
152
+ "@tuwaio/siwx-core": {
153
+ optional: true
154
+ },
155
+ "@tuwaio/siwx-server": {
156
+ optional: true
157
+ },
158
+ "@tuwaio/siwx-react": {
159
+ optional: true
160
+ }
161
+ },
162
+ devDependencies: {
163
+ "@tuwaio/pulsar-core": "^0.7.0",
164
+ ofetch: "^1.5.1",
165
+ tsup: "^8.5.1",
166
+ typescript: "6.0.3",
167
+ "@tuwaio/siwx-react": "^0.3.0",
168
+ "@tuwaio/siwx-core": "^0.3.0",
169
+ "@tuwaio/siwx-server": "^0.3.0"
170
+ }
171
+ };
172
+
173
+ // src/cli.ts
174
+ var DEFAULT_FORWARD_URL = "http://localhost:3000/api/webhooks/quasar";
175
+ var DEFAULT_API_URL = "https://api.tuwa.io";
176
+ var CLI_VERSION = package_default.version || "0.0.0";
177
+ function parseEnvFile(content) {
178
+ const env = {};
179
+ const lines = content.split("\n");
180
+ for (const line of lines) {
181
+ const trimmed = line.trim();
182
+ if (!trimmed || trimmed.startsWith("#")) continue;
183
+ const equalsIdx = trimmed.indexOf("=");
184
+ if (equalsIdx <= 0) continue;
185
+ const key = trimmed.slice(0, equalsIdx).trim();
186
+ let val = trimmed.slice(equalsIdx + 1).trim();
187
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
188
+ val = val.slice(1, -1);
189
+ }
190
+ env[key] = val;
191
+ }
192
+ return env;
193
+ }
194
+ function loadEnv(customPath) {
195
+ const cwd = process.cwd();
196
+ if (customPath) {
197
+ const fullPath = path.isAbsolute(customPath) ? customPath : path.join(cwd, customPath);
198
+ if (fs.existsSync(fullPath)) {
199
+ return parseEnvFile(fs.readFileSync(fullPath, "utf8"));
200
+ }
201
+ console.warn(`\x1B[33m[warn]\x1B[0m Specified env file not found: ${customPath}`);
202
+ return {};
203
+ }
204
+ const candidates = [".env.local", ".env.development", ".env"];
205
+ for (const candidate of candidates) {
206
+ const filePath = path.join(cwd, candidate);
207
+ if (fs.existsSync(filePath)) {
208
+ return parseEnvFile(fs.readFileSync(filePath, "utf8"));
209
+ }
210
+ }
211
+ return {};
212
+ }
213
+ function parseArgs(args) {
214
+ const options = {};
215
+ const cleanArgs = [...args];
216
+ if (cleanArgs.length > 0 && !cleanArgs[0].startsWith("-")) {
217
+ options.command = cleanArgs.shift();
218
+ }
219
+ for (let i = 0; i < cleanArgs.length; i++) {
220
+ const arg = cleanArgs[i];
221
+ if (arg === "--help" || arg === "-h") {
222
+ options.help = true;
223
+ } else if (arg === "--version" || arg === "-v") {
224
+ options.version = true;
225
+ } else if (arg === "--secret" || arg === "-s") {
226
+ options.secret = cleanArgs[++i];
227
+ } else if (arg === "--forward-to" || arg === "-f") {
228
+ options.forwardTo = cleanArgs[++i];
229
+ } else if (arg === "--api-url" || arg === "-a") {
230
+ options.apiUrl = cleanArgs[++i];
231
+ } else if (arg === "--env-file" || arg === "-e") {
232
+ options.envFile = cleanArgs[++i];
233
+ }
234
+ }
235
+ return options;
236
+ }
237
+ function printHelp() {
238
+ console.log(`
239
+ \x1B[1m\x1B[36m\u26A1 Quasar Webhook Local Dev Relay\x1B[0m
240
+ Stream production Quasar Cloud webhooks directly to your local dev machine.
241
+ Zero tunnels, zero third-party proxies, zero friction.
242
+
243
+ \x1B[1mUSAGE\x1B[0m
244
+ $ npx @tuwaio/quasar-sdk listen [options]
245
+
246
+ \x1B[1mCOMMANDS\x1B[0m
247
+ listen Connect to Quasar Cloud SSE and forward events locally
248
+
249
+ \x1B[1mOPTIONS\x1B[0m
250
+ -s, --secret <secret> Webhook signing secret (whsec_...) [or QUASAR_WEBHOOK_SECRET]
251
+ -f, --forward-to <url> Local endpoint to forward to (default: ${DEFAULT_FORWARD_URL})
252
+ -a, --api-url <url> Quasar Cloud API base URL (default: ${DEFAULT_API_URL})
253
+ -e, --env-file <path> Custom path to .env file
254
+ -v, --version Show version number
255
+ -h, --help Show help details
256
+
257
+ \x1B[1mZERO-CONFIG SETUP\x1B[0m
258
+ Add to your local .env or .env.local:
259
+ QUASAR_WEBHOOK_SECRET=whsec_...
260
+ NEXT_PUBLIC_QUASAR_BASE_URL=https://api.tuwa.io # (optional)
261
+
262
+ Then simply run:
263
+ $ npx @tuwaio/quasar-sdk listen
264
+ `);
265
+ }
266
+ async function runListen(options) {
267
+ const { secret, forwardTo, apiUrl } = options;
268
+ const cleanApiUrl = apiUrl.replace(/\/+$/, "");
269
+ const listenUrl = `${cleanApiUrl}/v1/engine/webhooks/listen?secret=${encodeURIComponent(secret)}`;
270
+ console.log(`
271
+ \x1B[36m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m
272
+ \x1B[36m\u2502\x1B[0m \x1B[1m\x1B[36m\u26A1 Quasar Webhook Local Dev Relay\x1B[0m \x1B[36m\u2502\x1B[0m
273
+ \x1B[36m\u2502\x1B[0m Forwarding to: \x1B[32m${forwardTo.padEnd(44)}\x1B[0m\x1B[36m\u2502\x1B[0m
274
+ \x1B[36m\u2502\x1B[0m Quasar Cloud: \x1B[34m${cleanApiUrl.padEnd(44)}\x1B[0m\x1B[36m\u2502\x1B[0m
275
+ \x1B[36m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m
276
+ `);
277
+ let reconnectDelay = 1e3;
278
+ let isShuttingDown = false;
279
+ let activeAbortController = null;
280
+ const cleanup = () => {
281
+ isShuttingDown = true;
282
+ if (activeAbortController) {
283
+ activeAbortController.abort();
284
+ }
285
+ console.log("\n\x1B[33m[relay]\x1B[0m Disconnected. Bye!");
286
+ process.exit(0);
287
+ };
288
+ process.on("SIGINT", cleanup);
289
+ process.on("SIGTERM", cleanup);
290
+ while (!isShuttingDown) {
291
+ try {
292
+ activeAbortController = new AbortController();
293
+ console.log(`\x1B[90mConnecting to Quasar Cloud SSE stream...\x1B[0m`);
294
+ const response = await fetch(listenUrl, {
295
+ method: "GET",
296
+ headers: {
297
+ Accept: "text/event-stream",
298
+ "Cache-Control": "no-cache"
299
+ },
300
+ signal: activeAbortController.signal
301
+ });
302
+ if (!response.ok) {
303
+ if (response.status === 401) {
304
+ console.error(
305
+ `\x1B[31m\u2716 Unauthorized (401):\x1B[0m Invalid webhook signing secret. Check your QUASAR_WEBHOOK_SECRET.`
306
+ );
307
+ process.exit(1);
308
+ }
309
+ if (response.status === 404) {
310
+ console.error(
311
+ `\x1B[31m\u2716 Endpoint Not Found (404):\x1B[0m No webhook endpoint found matching this signing secret.`
312
+ );
313
+ process.exit(1);
314
+ }
315
+ const errText = await response.text();
316
+ throw new Error(`HTTP ${response.status} ${response.statusText}: ${errText}`);
317
+ }
318
+ if (!response.body) {
319
+ throw new Error("No readable stream returned by server.");
320
+ }
321
+ console.log(`\x1B[32m\u2714 Connected!\x1B[0m Ready and listening for events...`);
322
+ reconnectDelay = 1e3;
323
+ const reader = response.body.getReader();
324
+ const decoder = new TextDecoder("utf-8");
325
+ let buffer = "";
326
+ while (!isShuttingDown) {
327
+ const { done, value } = await reader.read();
328
+ if (done) break;
329
+ buffer += decoder.decode(value, { stream: true });
330
+ const lines = buffer.split("\n");
331
+ buffer = lines.pop() || "";
332
+ let currentEvent = "message";
333
+ let currentData = "";
334
+ for (const line of lines) {
335
+ const trimmed = line.trim();
336
+ if (!trimmed) {
337
+ if (currentData) {
338
+ await handleSseMessage(currentEvent, currentData, forwardTo);
339
+ currentEvent = "message";
340
+ currentData = "";
341
+ }
342
+ continue;
343
+ }
344
+ if (trimmed.startsWith("event:")) {
345
+ currentEvent = trimmed.slice(6).trim();
346
+ } else if (trimmed.startsWith("data:")) {
347
+ const dataLine = trimmed.slice(5).trim();
348
+ currentData = currentData ? `${currentData}
349
+ ${dataLine}` : dataLine;
350
+ }
351
+ }
352
+ }
353
+ } catch (err) {
354
+ if (isShuttingDown) break;
355
+ const message = err instanceof Error ? err.message : String(err);
356
+ console.warn(`\x1B[33m[warn]\x1B[0m Connection lost (${message}). Reconnecting in ${reconnectDelay}ms...`);
357
+ await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
358
+ reconnectDelay = Math.min(reconnectDelay * 2, 15e3);
359
+ }
360
+ }
361
+ }
362
+ async function handleSseMessage(event, rawData, forwardTo) {
363
+ if (event === "ping" || rawData === ":keepalive" || rawData.startsWith(":")) {
364
+ return;
365
+ }
366
+ try {
367
+ const payload = JSON.parse(rawData);
368
+ if (event === "connected") {
369
+ return;
370
+ }
371
+ if (event === "payload" || event === "webhook" || event === "message") {
372
+ const dataObj = payload;
373
+ const deliveryId = dataObj.deliveryId || dataObj.id || "";
374
+ const eventType = dataObj.event || dataObj.eventType || dataObj.action || "webhook";
375
+ const forwardedData = dataObj.data !== void 0 ? dataObj.data : dataObj.payload !== void 0 ? dataObj.payload : dataObj;
376
+ const signature = dataObj.signature || "";
377
+ const startTime = Date.now();
378
+ const timeStr = (/* @__PURE__ */ new Date()).toLocaleTimeString();
379
+ try {
380
+ const res = await fetch(forwardTo, {
381
+ method: "POST",
382
+ headers: {
383
+ "Content-Type": "application/json",
384
+ "x-quasar-signature": signature,
385
+ "x-quasar-event": eventType,
386
+ "x-quasar-delivery-id": deliveryId
387
+ },
388
+ body: JSON.stringify(forwardedData)
389
+ });
390
+ const duration = Date.now() - startTime;
391
+ if (res.ok) {
392
+ console.log(
393
+ `\x1B[90m${timeStr}\x1B[0m \x1B[32m[${res.status} OK]\x1B[0m \x1B[1m${eventType}\x1B[0m \u2794 ${forwardTo} \x1B[90m(${duration}ms)\x1B[0m`
394
+ );
395
+ } else {
396
+ console.warn(
397
+ `\x1B[90m${timeStr}\x1B[0m \x1B[31m[${res.status} ${res.statusText}]\x1B[0m \x1B[1m${eventType}\x1B[0m \u2794 ${forwardTo} \x1B[90m(${duration}ms)\x1B[0m`
398
+ );
399
+ }
400
+ } catch (postErr) {
401
+ const errMsg = postErr instanceof Error ? postErr.message : String(postErr);
402
+ console.error(
403
+ `\x1B[90m${timeStr}\x1B[0m \x1B[31m[FAIL]\x1B[0m \x1B[1m${eventType}\x1B[0m \u2794 Could not reach ${forwardTo}: ${errMsg}`
404
+ );
405
+ console.error(` \x1B[33mTip: Make sure your local application is running on ${forwardTo}\x1B[0m`);
406
+ }
407
+ }
408
+ } catch {
409
+ }
410
+ }
411
+ async function main() {
412
+ const rawArgs = process.argv.slice(2);
413
+ const options = parseArgs(rawArgs);
414
+ if (options.help || rawArgs.length === 0 && !options.version) {
415
+ printHelp();
416
+ return;
417
+ }
418
+ if (options.version) {
419
+ console.log(`@tuwaio/quasar-sdk v${CLI_VERSION}`);
420
+ return;
421
+ }
422
+ if (options.command !== "listen") {
423
+ console.error(`\x1B[31m\u2716 Unknown command:\x1B[0m "${options.command}". Did you mean "listen"?`);
424
+ console.log(`Run \x1B[1mnpx @tuwaio/quasar-sdk --help\x1B[0m for available commands.`);
425
+ process.exit(1);
426
+ }
427
+ const env = loadEnv(options.envFile);
428
+ const secret = options.secret || process.env.QUASAR_WEBHOOK_SECRET || env.QUASAR_WEBHOOK_SECRET;
429
+ const forwardTo = options.forwardTo || process.env.QUASAR_WEBHOOK_FORWARD_TO || env.QUASAR_WEBHOOK_FORWARD_TO || DEFAULT_FORWARD_URL;
430
+ const apiUrl = options.apiUrl || process.env.NEXT_PUBLIC_QUASAR_BASE_URL || env.NEXT_PUBLIC_QUASAR_BASE_URL || process.env.QUASAR_BASE_URL || env.QUASAR_BASE_URL || DEFAULT_API_URL;
431
+ if (!secret) {
432
+ console.error(`
433
+ \x1B[31m\u2716 Missing Webhook Signing Secret\x1B[0m
434
+ The local relay requires your webhook endpoint signing secret (starting with whsec_).
435
+
436
+ \x1B[1mHow to fix:\x1B[0m
437
+ 1. Provide via command-line argument:
438
+ $ npx @tuwaio/quasar-sdk listen --secret whsec_...
439
+
440
+ 2. Or add to your local .env / .env.local file:
441
+ QUASAR_WEBHOOK_SECRET=whsec_...
442
+ `);
443
+ process.exit(1);
444
+ }
445
+ await runListen({
446
+ secret,
447
+ forwardTo,
448
+ apiUrl
449
+ });
450
+ }
451
+ if (!process.env.VITEST) {
452
+ void main().catch((err) => {
453
+ console.error("\x1B[31mFatal error:\x1B[0m", err);
454
+ process.exit(1);
455
+ });
456
+ }
457
+ // Annotate the CommonJS export names for ESM import in node:
458
+ 0 && (module.exports = {
459
+ CLI_VERSION,
460
+ DEFAULT_API_URL,
461
+ DEFAULT_FORWARD_URL,
462
+ handleSseMessage,
463
+ loadEnv,
464
+ main,
465
+ parseArgs,
466
+ parseEnvFile
467
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuwaio/quasar-sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
@@ -23,6 +23,10 @@
23
23
  "dist",
24
24
  "socket.json"
25
25
  ],
26
+ "bin": {
27
+ "@tuwaio/quasar-sdk": "./dist/cli.cjs",
28
+ "quasar-sdk": "./dist/cli.cjs"
29
+ },
26
30
  "keywords": [
27
31
  "tuwa",
28
32
  "web3",