@syengup/friday-channel-next 0.0.26 → 0.0.28

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.
@@ -29,9 +29,7 @@ function realHome() {
29
29
  const USER_HOME = realHome();
30
30
  const PLUGIN_DIR = process.argv[2] || join(USER_HOME, ".openclaw", "extensions", "friday-channel-next");
31
31
  const OPENCLAW_CONFIG = join(USER_HOME, ".openclaw", "openclaw.json");
32
- const REPO_URL = process.env.FRIDAY_NEXT_REPO || "https://github.com/SyengUp/openclaw-fridaynext-channel.git";
33
32
 
34
- const G = (s) => `\x1b[32m${s}\x1b[0m`;
35
33
  const Y = (s) => `\x1b[33m${s}\x1b[0m`;
36
34
  const R = (s) => `\x1b[31m${s}\x1b[0m`;
37
35
  function log(msg) {
@@ -137,37 +135,20 @@ function copyFromNpmPackage() {
137
135
  });
138
136
  }
139
137
 
140
- if (existsSync(PLUGIN_DIR)) {
138
+ if (isRunningFromNpmPackage()) {
139
+ copyFromNpmPackage();
140
+ } else if (existsSync(PLUGIN_DIR)) {
141
141
  log(`Plugin directory found: ${PLUGIN_DIR}`);
142
- if (existsSync(join(PLUGIN_DIR, ".git"))) {
143
- try {
144
- log("Pulling latest changes...");
145
- execSync("git fetch origin && git checkout -f origin/main", { cwd: PLUGIN_DIR, stdio: "pipe" });
146
- log("Updated to latest version.");
147
- } catch {
148
- warn("Could not update from git — continuing with existing source.");
149
- }
150
- } else if (isRunningFromNpmPackage()) {
151
- copyFromNpmPackage();
152
- } else {
153
- // No .git, running locally — re-execute via npx to get the latest
154
- log("Updating via npx...");
155
- try {
156
- execSync("npx -y @syengup/friday-channel-next", { stdio: "inherit" });
157
- process.exit(0);
158
- } catch {
159
- warn("Could not update via npx — continuing with existing source.");
160
- }
142
+ log("Updating via npx...");
143
+ try {
144
+ execSync("npx -y @syengup/friday-channel-next", { stdio: "inherit" });
145
+ process.exit(0);
146
+ } catch {
147
+ warn("Could not update via npx — continuing with existing source.");
161
148
  }
162
- } else if (isRunningFromNpmPackage()) {
163
- copyFromNpmPackage();
164
149
  } else {
165
- if (!has("git")) {
166
- err("git is required for installation from GitHub. Install git first or use npx @openclaw/friday-channel-next.");
167
- process.exit(1);
168
- }
169
- log(`Cloning plugin to ${PLUGIN_DIR} ...`);
170
- execSync(`git clone "${REPO_URL}" "${PLUGIN_DIR}"`, { stdio: "inherit" });
150
+ err("This plugin is installed via npx. Run: npx -y @syengup/friday-channel-next");
151
+ process.exit(1);
171
152
  }
172
153
 
173
154
  process.chdir(PLUGIN_DIR);
package/cli.js ADDED
@@ -0,0 +1,460 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "node:child_process";
3
+ import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { homedir, networkInterfaces } from "node:os";
5
+ import { dirname, join, relative, resolve, sep } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ const sudoUser = process.env.SUDO_USER;
12
+
13
+ function realHome() {
14
+ if (!sudoUser) return homedir();
15
+ // Under sudo, homedir() may return /root. Check if HOME was preserved.
16
+ const current = homedir();
17
+ if (current !== "/root" && current !== "/var/root" && existsSync(current)) return current;
18
+ // Resolve the real user's home
19
+ try {
20
+ const h = execSync(`sh -c 'echo ~${sudoUser}'`, { encoding: "utf8" }).trim();
21
+ if (h && !h.startsWith("~") && existsSync(h)) return h;
22
+ } catch {}
23
+ for (const g of [`/home/${sudoUser}`, `/Users/${sudoUser}`]) {
24
+ if (existsSync(g)) return g;
25
+ }
26
+ return current;
27
+ }
28
+
29
+ const USER_HOME = realHome();
30
+ const PLUGIN_DIR = process.argv[2] || join(USER_HOME, ".openclaw", "extensions", "friday-channel-next");
31
+ const OPENCLAW_CONFIG = join(USER_HOME, ".openclaw", "openclaw.json");
32
+
33
+ const Y = (s) => `\x1b[33m${s}\x1b[0m`;
34
+ const R = (s) => `\x1b[31m${s}\x1b[0m`;
35
+ function log(msg) {
36
+ console.log(` ${msg}`);
37
+ }
38
+ function warn(msg) {
39
+ console.log(` ${Y("!")} ${msg}`);
40
+ }
41
+ function err(msg) {
42
+ console.error(` ${R("X")} ${msg}`);
43
+ }
44
+
45
+ function has(cmd) {
46
+ try {
47
+ execSync(`${cmd} --version`, { stdio: "ignore" });
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ let openclawCmd = "openclaw";
55
+
56
+ function hasOpenclaw() {
57
+ if (has("openclaw")) return true;
58
+ if (!sudoUser) return false;
59
+ // Under sudo, openclaw isn't in root's PATH — run via the real user.
60
+ try {
61
+ execSync(`sudo -u "${sudoUser}" openclaw --version`, { stdio: "ignore" });
62
+ openclawCmd = `sudo -u "${sudoUser}" openclaw`;
63
+ return true;
64
+ } catch {}
65
+ return false;
66
+ }
67
+
68
+ // Running from an npm/npx package when we have the full source (index.ts + package.json)
69
+ // and are NOT already inside the target plugin dir.
70
+ function isRunningFromNpmPackage() {
71
+ return (
72
+ resolve(__dirname) !== resolve(PLUGIN_DIR) &&
73
+ existsSync(join(__dirname, "package.json")) &&
74
+ existsSync(join(__dirname, "index.ts"))
75
+ );
76
+ }
77
+
78
+ // --------------- prerequisites ---------------
79
+
80
+ if (sudoUser) {
81
+ warn("Running under sudo is unnecessary and may cause issues.");
82
+ warn("If possible, run without sudo: npx -y @syengup/friday-channel-next");
83
+ }
84
+
85
+ const missing = [];
86
+ if (!has("node")) missing.push("node");
87
+ if (!hasOpenclaw()) missing.push("openclaw");
88
+ if (missing.length) {
89
+ missing.forEach((c) => err(`${c} is required but not found. Install it first.`));
90
+ if (sudoUser && missing.includes("openclaw")) {
91
+ err("Could not find openclaw even via the real user's PATH.");
92
+ err(`Check that openclaw is installed under ${USER_HOME}.`);
93
+ }
94
+ process.exit(1);
95
+ }
96
+
97
+ const PKG = has("pnpm") ? "pnpm" : has("npm") ? "npm" : null;
98
+ if (!PKG) {
99
+ err("pnpm or npm is required but not found. Install one first.");
100
+ process.exit(1);
101
+ }
102
+
103
+ // Auto-detect best registry (measure latency; fall back to npmmirror if slow/unreachable)
104
+ let registryFlag = "";
105
+ try {
106
+ const start = Date.now();
107
+ execSync('curl -s -o /dev/null --connect-timeout 2 --max-time 4 https://registry.npmjs.org/', { stdio: "pipe", timeout: 6000 });
108
+ if (Date.now() - start > 1500) {
109
+ warn("Default registry slow, using https://registry.npmmirror.com");
110
+ registryFlag = "--registry=https://registry.npmmirror.com";
111
+ }
112
+ } catch {
113
+ warn("Default registry unreachable, using https://registry.npmmirror.com");
114
+ registryFlag = "--registry=https://registry.npmmirror.com";
115
+ }
116
+
117
+ if (!existsSync(OPENCLAW_CONFIG)) {
118
+ err(`OpenClaw config not found at ${OPENCLAW_CONFIG}`);
119
+ err("Make sure OpenClaw is installed and has been run at least once.");
120
+ process.exit(1);
121
+ }
122
+
123
+ // --------------- acquire source ---------------
124
+
125
+ function copyFromNpmPackage() {
126
+ log(`Copying plugin from npm package to ${PLUGIN_DIR} ...`);
127
+ cpSync(__dirname, PLUGIN_DIR, {
128
+ recursive: true,
129
+ filter: (src) => {
130
+ const rel = relative(__dirname, src);
131
+ if (rel === "") return true; // root dir
132
+ const top = rel.split(sep)[0];
133
+ return ![".git", "node_modules", "dist", "attachments", ".claude"].includes(top);
134
+ },
135
+ });
136
+ }
137
+
138
+ if (isRunningFromNpmPackage()) {
139
+ copyFromNpmPackage();
140
+ } else if (existsSync(PLUGIN_DIR)) {
141
+ log(`Plugin directory found: ${PLUGIN_DIR}`);
142
+ log("Updating via npx...");
143
+ try {
144
+ execSync("npx -y @syengup/friday-channel-next", { stdio: "inherit" });
145
+ process.exit(0);
146
+ } catch {
147
+ warn("Could not update via npx — continuing with existing source.");
148
+ }
149
+ } else {
150
+ err("This plugin is installed via npx. Run: npx -y @syengup/friday-channel-next");
151
+ process.exit(1);
152
+ }
153
+
154
+ process.chdir(PLUGIN_DIR);
155
+
156
+ // --------------- install + build ---------------
157
+
158
+ log("Installing dependencies...");
159
+ try {
160
+ execSync(`${PKG} install ${registryFlag}`, { stdio: "inherit" });
161
+ } catch {
162
+ err("Dependency installation failed.");
163
+ err("Check your network connection and try again.");
164
+ if (sudoUser) err("If running under sudo, ensure the real user can access the package manager.");
165
+ process.exit(1);
166
+ }
167
+
168
+ log("Building TypeScript...");
169
+ try {
170
+ execSync(`${PKG} run build`, { stdio: "inherit" });
171
+ } catch {
172
+ err("TypeScript build failed.");
173
+ err("Check the compilation errors above and make sure the package is not corrupted.");
174
+ process.exit(1);
175
+ }
176
+
177
+ // --------------- configure OpenClaw ---------------
178
+
179
+ log("Configuring OpenClaw...");
180
+
181
+ let config;
182
+ try {
183
+ config = JSON.parse(readFileSync(OPENCLAW_CONFIG, "utf8"));
184
+ } catch {
185
+ err(`Failed to read ${OPENCLAW_CONFIG}.`);
186
+ err("The file may be missing or corrupted. Verify OpenClaw is installed correctly.");
187
+ process.exit(1);
188
+ }
189
+
190
+ if (!config.plugins) config.plugins = {};
191
+ if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
192
+ if (!config.plugins.allow.includes("friday-next")) {
193
+ config.plugins.allow.push("friday-next");
194
+ console.log(" + Added friday-next to plugins.allow");
195
+ }
196
+
197
+ if (!config.plugins.entries) config.plugins.entries = {};
198
+ if (!config.plugins.entries["friday-next"]) {
199
+ config.plugins.entries["friday-next"] = { enabled: true };
200
+ console.log(" + Added friday-next to plugins.entries (enabled)");
201
+ } else if (!config.plugins.entries["friday-next"].enabled) {
202
+ config.plugins.entries["friday-next"].enabled = true;
203
+ console.log(" + Enabled friday-next in plugins.entries");
204
+ }
205
+
206
+ if (!config.plugins.allow.includes("canvas")) {
207
+ config.plugins.allow.push("canvas");
208
+ console.log(" + Added canvas to plugins.allow");
209
+ }
210
+ if (!config.plugins.entries["canvas"]) {
211
+ config.plugins.entries["canvas"] = { enabled: true };
212
+ console.log(" + Added canvas to plugins.entries (enabled)");
213
+ } else if (!config.plugins.entries["canvas"].enabled) {
214
+ config.plugins.entries["canvas"].enabled = true;
215
+ console.log(" + Enabled canvas in plugins.entries");
216
+ }
217
+
218
+ if (!config.channels) config.channels = {};
219
+ if (!config.channels["friday-next"]) {
220
+ config.channels["friday-next"] = { enabled: true, transport: "http+sse" };
221
+ console.log(" + Added friday-next channel config (auth defaults to gateway token)");
222
+ } else {
223
+ if (!config.channels["friday-next"].enabled) {
224
+ config.channels["friday-next"].enabled = true;
225
+ console.log(" + Enabled friday-next channel");
226
+ }
227
+ if (!config.channels["friday-next"].transport) {
228
+ config.channels["friday-next"].transport = "http+sse";
229
+ console.log(" + Set friday-next transport to http+sse");
230
+ }
231
+ }
232
+
233
+ if (!config.gateway) config.gateway = {};
234
+ if (config.gateway.bind !== "lan") {
235
+ config.gateway.bind = "lan";
236
+ console.log(" + Set gateway.bind to lan");
237
+ }
238
+ if (!config.gateway.nodes) config.gateway.nodes = {};
239
+ if (!Array.isArray(config.gateway.nodes.allowCommands)) config.gateway.nodes.allowCommands = [];
240
+ for (const cmd of [
241
+ "canvas.navigate",
242
+ "canvas.present",
243
+ "canvas.hide",
244
+ "canvas.eval",
245
+ "canvas.snapshot",
246
+ "canvas.a2ui.push",
247
+ "canvas.a2ui.reset",
248
+ "canvas.a2ui.pushJSONL",
249
+ ]) {
250
+ if (!config.gateway.nodes.allowCommands.includes(cmd)) {
251
+ config.gateway.nodes.allowCommands.push(cmd);
252
+ console.log(" + Added " + cmd + " to gateway.nodes.allowCommands");
253
+ }
254
+ }
255
+
256
+ // Ensure canvas and nodes are in the main agent's tools.alsoAllow (not deny)
257
+ if (!config.agents) config.agents = {};
258
+ if (!Array.isArray(config.agents.list)) config.agents.list = [];
259
+ let mainAgent = config.agents.list.find((a) => a.id === "main");
260
+ if (!mainAgent) {
261
+ mainAgent = { id: "main" };
262
+ config.agents.list.push(mainAgent);
263
+ }
264
+ if (!mainAgent.tools) mainAgent.tools = {};
265
+ if (!Array.isArray(mainAgent.tools.alsoAllow)) mainAgent.tools.alsoAllow = [];
266
+ for (const tool of ["canvas", "nodes"]) {
267
+ if (!mainAgent.tools.alsoAllow.includes(tool)) {
268
+ mainAgent.tools.alsoAllow.push(tool);
269
+ console.log(" + Added " + tool + " to agent 'main' tools.alsoAllow");
270
+ }
271
+ }
272
+ if (Array.isArray(mainAgent.tools.deny)) {
273
+ for (const tool of ["canvas", "nodes"]) {
274
+ const idx = mainAgent.tools.deny.indexOf(tool);
275
+ if (idx !== -1) {
276
+ mainAgent.tools.deny.splice(idx, 1);
277
+ console.log(" - Removed " + tool + " from agent 'main' tools.deny");
278
+ }
279
+ }
280
+ }
281
+
282
+ try {
283
+ writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2) + "\n", "utf8");
284
+ } catch {
285
+ err(`Failed to write ${OPENCLAW_CONFIG}.`);
286
+ err("Check disk space and file permissions.");
287
+ process.exit(1);
288
+ }
289
+ console.log(" Config updated.");
290
+
291
+ if (sudoUser) {
292
+ try {
293
+ execSync(`chown -R "${sudoUser}" "${PLUGIN_DIR}" "${OPENCLAW_CONFIG}"`, { stdio: "ignore" });
294
+ log("Fixed file ownership back to " + sudoUser);
295
+ } catch {
296
+ warn("Could not fix file ownership — files in " + PLUGIN_DIR + " may be owned by root.");
297
+ }
298
+ }
299
+
300
+ // --------------- restart gateway ---------------
301
+
302
+ log("Restarting OpenClaw gateway...");
303
+ try {
304
+ const out = execSync(`${openclawCmd} gateway restart`, { encoding: "utf8", stdio: "pipe" });
305
+ if (out.trim()) console.log(out.trim());
306
+ } catch (e) {
307
+ if (e.stdout?.trim()) console.log(e.stdout.trim());
308
+ if (e.stderr?.trim()) console.error(e.stderr.trim());
309
+ warn("Gateway restart failed. The plugin files are installed but the gateway was not restarted.");
310
+ warn("Check 'openclaw gateway status' and restart manually: openclaw gateway restart");
311
+ }
312
+
313
+ // --------------- verify ---------------
314
+
315
+ function getLanIp() {
316
+ const nets = networkInterfaces();
317
+ for (const name of Object.keys(nets)) {
318
+ for (const net of nets[name]) {
319
+ if (net.family === "IPv4" && !net.internal) return net.address;
320
+ }
321
+ }
322
+ return "127.0.0.1";
323
+ }
324
+
325
+ const gatewayPort = config.gateway?.port || 18789;
326
+ const gatewayToken = config.gateway?.auth?.token || "(not set)";
327
+ const bindMode = config.gateway?.bind || "localhost";
328
+
329
+ const gatewayUrl = bindMode === "lan"
330
+ ? `http://${getLanIp()}:${gatewayPort}`
331
+ : `http://127.0.0.1:${gatewayPort}`;
332
+
333
+ async function verifyGateway(url, token, retries = 6) {
334
+ const http = await import("node:http");
335
+ const { hostname, port } = new URL(url);
336
+ for (let i = 1; i <= retries; i++) {
337
+ await new Promise((r) => setTimeout(r, 1000));
338
+ try {
339
+ const res = await new Promise((resolve, reject) => {
340
+ const req = http.request(
341
+ { hostname, port, path: "/friday-next/status", method: "GET",
342
+ headers: { authorization: `Bearer ${token}` }, timeout: 5000 },
343
+ (res) => { let body = ""; res.on("data", (c) => body += c); res.on("end", () => resolve({ status: res.statusCode, body })); },
344
+ );
345
+ req.on("error", reject);
346
+ req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
347
+ req.end();
348
+ });
349
+ if (res.status === 200) {
350
+ try {
351
+ const data = JSON.parse(res.body);
352
+ if (data.ok) {
353
+ log("Gateway verified OK (friday-next " + data.version + ", " + data.connections + " connections).");
354
+ return true;
355
+ }
356
+ warn("Plugin responded but ok=false — " + JSON.stringify(data));
357
+ return false;
358
+ } catch {
359
+ // body is not JSON (e.g. HTML control panel) — plugin route not registered yet
360
+ if (i < 3) {
361
+ warn(`Plugin routes not registered yet, retrying (${i}/${retries})...`);
362
+ } else if (i < retries) {
363
+ warn(`Gateway is up but plugin routes missing — may need config reload, retrying (${i}/${retries})...`);
364
+ } else {
365
+ warn("Gateway is running but plugin routes were not loaded. Check plugin config in openclaw.json.");
366
+ }
367
+ continue;
368
+ }
369
+ }
370
+ if (res.status === 401) {
371
+ warn("Auth token mismatch — check gateway.auth.token in openclaw.json.");
372
+ return false;
373
+ }
374
+ if (res.status === 404) {
375
+ warn("Route /friday-next/status not found — plugin may not be loaded.");
376
+ return false;
377
+ }
378
+ if (i < retries) warn(`Gateway responded ${res.status}, retrying (${i}/${retries})...`);
379
+ } catch {
380
+ // Connection refused / timeout — gateway not running yet
381
+ if (i < retries) warn(`Gateway not reachable, retrying (${i}/${retries})...`);
382
+ }
383
+ }
384
+ warn("Gateway verification timed out — check 'openclaw gateway status' manually.");
385
+ return false;
386
+ }
387
+
388
+ log("Verifying gateway...");
389
+ const verified = await verifyGateway(gatewayUrl, gatewayToken);
390
+
391
+ // --------------- show connection info ---------------
392
+
393
+ const BOLD_YELLOW = (s) => `\x1b[1;33m${s}\x1b[0m`;
394
+
395
+ log("--------------------------------------------------");
396
+ if (verified) {
397
+ log("Installation complete! Friday Next channel is now active.");
398
+ } else {
399
+ warn("Installation complete, but gateway verification failed.");
400
+ warn("Check 'openclaw gateway status' and restart the gateway if needed.");
401
+ warn("Also ensure OpenClaw is updated to 2026.5.7 or above: openclaw update");
402
+ warn("同时请确认 OpenClaw 已升级至 2026.5.7 或以上版本:openclaw update");
403
+ }
404
+ log("");
405
+
406
+ // --------------- QR code ---------------
407
+
408
+ const qrPayload = JSON.stringify({ url: gatewayUrl, token: gatewayToken });
409
+ let qrShown = false;
410
+
411
+ try {
412
+ const { createRequire } = await import("node:module");
413
+ const qrcode = createRequire(import.meta.url)("qrcode-terminal");
414
+ log(BOLD_YELLOW("Scan below to auto-fill URL & Token in FridayNext app:"));
415
+ log(BOLD_YELLOW("扫描下方二维码自动填入 URL 和 Token:"));
416
+ log("");
417
+ qrcode.generate(qrPayload, { small: true });
418
+ log("");
419
+ log("If QR scan doesn't work, enter manually:");
420
+ log("若二维码无法使用,请手动输入:");
421
+ qrShown = true;
422
+ } catch {
423
+ // qrcode-terminal not available, fall through to manual-only
424
+ }
425
+
426
+ // --------------- manual input ---------------
427
+
428
+ if (!qrShown) {
429
+ log(BOLD_YELLOW("Input the URL and Token below into your FridayNext app to connect."));
430
+ log(BOLD_YELLOW("请将下方 URL 和 Token 输入至 FridayNext App 完成连接。"));
431
+ }
432
+ log("");
433
+ log("Gateway URL: " + BOLD_YELLOW(gatewayUrl));
434
+ log("Bearer Token: " + BOLD_YELLOW(gatewayToken));
435
+ log("");
436
+ function classifyIp(ip) {
437
+ const p = ip.split(".").map(Number);
438
+ if (p[0] === 100 && p[1] >= 64 && p[1] <= 127) return "tailscale";
439
+ if (p[0] === 127) return "loopback";
440
+ if (p[0] === 10) return "private";
441
+ if (p[0] === 172 && p[1] >= 16 && p[1] <= 31) return "private";
442
+ if (p[0] === 192 && p[1] === 168) return "private";
443
+ if (p[0] === 169 && p[1] === 254) return "private";
444
+ return "public";
445
+ }
446
+ const ip = new URL(gatewayUrl).hostname;
447
+ const ipType = classifyIp(ip);
448
+ if (ipType === "tailscale") {
449
+ log("This is a Tailscale network URL (" + ip + ").");
450
+ log("Accessible from your Tailnet devices.");
451
+ } else if (ipType === "private") {
452
+ log("This is a LOCAL network URL (" + ip + ", bind=" + bindMode + ").");
453
+ log("If you need public access, configure HTTPS, Tailscale, or a reverse proxy.");
454
+ } else if (ipType === "loopback") {
455
+ log("This is a LOOPBACK URL (" + ip + ").");
456
+ log("Only accessible from this machine.");
457
+ } else {
458
+ log("This URL appears to be publicly accessible (" + ip + ").");
459
+ }
460
+ log("--------------------------------------------------");
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@syengup/friday-channel-next",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "OpenClaw Friday Next Apple channel plugin",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "index.ts",
8
8
  "src/",
9
- "install.js",
9
+ "cli",
10
+ "cli.js",
10
11
  "tsconfig.json",
11
12
  "openclaw.plugin.json"
12
13
  ],
@@ -19,8 +20,7 @@
19
20
  "test:msg-live": "node scripts/message-roundtrip-live.mjs"
20
21
  },
21
22
  "bin": {
22
- "friday-channel-next": "./install.js",
23
- "install-friday-next": "./install.js"
23
+ "friday-channel-next": "./cli"
24
24
  },
25
25
  "main": "./dist/index.js",
26
26
  "types": "./dist/index.d.ts",