@lessie/mcp-server 0.1.1 → 0.1.3

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/dist/auth.js CHANGED
@@ -13,13 +13,24 @@
13
13
  * 客户端信息和令牌持久化到 ~/.lessie/oauth.json,进程重启后复用,
14
14
  * 避免每次都打开浏览器。
15
15
  */
16
+ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
16
17
  import { createServer } from "node:http";
17
18
  import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
18
19
  import { join } from "node:path";
19
20
  import { homedir } from "node:os";
21
+ import { REMOTE_MCP_URL } from "./config.js";
20
22
  export const DEFAULT_CALLBACK_PORT = 19836;
21
23
  export const PORT_SCAN_RANGE = 10;
22
- const STORAGE_DIR = join(homedir(), ".lessie");
24
+ function getStorageDir() {
25
+ try {
26
+ const host = new URL(REMOTE_MCP_URL).hostname;
27
+ return join(homedir(), ".lessie", host);
28
+ }
29
+ catch {
30
+ return join(homedir(), ".lessie");
31
+ }
32
+ }
33
+ const STORAGE_DIR = getStorageDir();
23
34
  const STORAGE_FILE = join(STORAGE_DIR, "oauth.json");
24
35
  const CALLBACK_TIMEOUT_MS = 120_000;
25
36
  function tryListenHttp(port) {
@@ -56,6 +67,10 @@ export class LessieAuthProvider {
56
67
  _httpServer = null;
57
68
  _callbackTimer = null;
58
69
  _callbackPort;
70
+ _authCompletion = null;
71
+ _authFetchFn;
72
+ _onAuthorizationComplete = null;
73
+ _onAuthorizationFailure = null;
59
74
  /** 等待用户访问的授权 URL;授权完成后清除 */
60
75
  pendingAuthUrl = null;
61
76
  /** 回调服务器开始监听的时间戳,用于计算等待时长 */
@@ -68,6 +83,24 @@ export class LessieAuthProvider {
68
83
  get callbackPort() {
69
84
  return this._callbackPort;
70
85
  }
86
+ /** 注册 completion 成功回调(授权码已换 token) */
87
+ setOnAuthorizationComplete(cb) {
88
+ this._onAuthorizationComplete = cb;
89
+ }
90
+ /** 注册 completion 失败回调(超时、拒绝授权、回调服务错误等) */
91
+ setOnAuthorizationFailure(cb) {
92
+ this._onAuthorizationFailure = cb;
93
+ }
94
+ /** 设置 auth() 的可选 fetch 实现(用于调试日志等) */
95
+ setAuthFetchFn(fetchFn) {
96
+ this._authFetchFn = fetchFn;
97
+ }
98
+ /** 等待 provider 内部 completion 结束(成功或失败) */
99
+ async waitForAuthorizationCompletion() {
100
+ if (!this._authCompletion)
101
+ return;
102
+ await this._authCompletion.catch(() => { });
103
+ }
71
104
  /**
72
105
  * 扫描端口范围,创建 HTTP 服务器占住可用端口。
73
106
  * redirectToAuthorization 直接复用该服务器,无需释放重听。
@@ -131,13 +164,26 @@ export class LessieAuthProvider {
131
164
  }
132
165
  /**
133
166
  * SDK 在需要用户授权时调用此方法。
134
- * prepareCallbackServer() 已监听的 HTTP 服务器上挂载回调处理逻辑,等待浏览器回调。
167
+ * 在监听中的 HTTP 服务器上挂载回调处理逻辑,进入 pending 授权状态。
135
168
  *
136
- * 必须先调用 prepareCallbackServer(),否则抛出异常。
169
+ * 为兼容 SDK 在普通工具调用路径中触发的重授权,这里会在必要时自动启动回调服务器,
170
+ * 并启动 provider 内部 completion 任务(等待 code -> 换 token)。
137
171
  */
138
172
  async redirectToAuthorization(url) {
173
+ // 并发 auth() 调用可能重复进入 redirect。若已有 pending 授权,复用现有流程,
174
+ // 避免覆盖 _callbackPromise 导致原 completion 永久等待。
175
+ if (this._authCompletion) {
176
+ if (this.pendingAuthUrl && this._callbackPromise) {
177
+ return;
178
+ }
179
+ await this.waitForAuthorizationCompletion();
180
+ }
139
181
  if (!this._httpServer?.listening) {
140
- throw new Error("No listening server — prepareCallbackServer() must be called before redirectToAuthorization()");
182
+ await this.prepareCallbackServer();
183
+ }
184
+ const server = this._httpServer;
185
+ if (!server?.listening) {
186
+ throw new Error("Callback server is not listening");
141
187
  }
142
188
  this._expectedState = url.searchParams.get("state") || "";
143
189
  this.waitingSince = null;
@@ -147,13 +193,15 @@ export class LessieAuthProvider {
147
193
  resolveCode = resolve;
148
194
  rejectCode = reject;
149
195
  });
150
- this._httpServer.removeAllListeners("request");
151
- this._httpServer.removeAllListeners("error");
152
- this._httpServer.on("error", (err) => {
196
+ server.removeAllListeners("request");
197
+ server.removeAllListeners("error");
198
+ server.on("error", (err) => {
153
199
  this._cleanupServer();
154
- rejectCode(new Error(`Callback server error: ${err.message}`));
200
+ this.pendingAuthUrl = null;
201
+ const authError = new Error(`Callback server error: ${err.message}`);
202
+ rejectCode(authError);
155
203
  });
156
- this._httpServer.on("request", (req, res) => {
204
+ server.on("request", (req, res) => {
157
205
  const reqUrl = new URL(req.url, `http://127.0.0.1:${this._callbackPort}`);
158
206
  if (reqUrl.pathname !== "/callback") {
159
207
  res.writeHead(404);
@@ -180,21 +228,26 @@ export class LessieAuthProvider {
180
228
  }
181
229
  else {
182
230
  const error = reqUrl.searchParams.get("error") ?? "unknown_error";
183
- rejectCode(new Error(`Authorization denied: ${error}`));
231
+ const authError = new Error(`Authorization denied: ${error}`);
232
+ rejectCode(authError);
184
233
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
185
234
  res.end("<!DOCTYPE html><html><body>" +
186
235
  "<h1>Authorization Failed</h1>" +
187
236
  `<p>${escapeHtml(error)}</p>` +
188
237
  "</body></html>");
189
238
  this._cleanupServer();
239
+ this.pendingAuthUrl = null;
190
240
  }
191
241
  });
192
242
  this.waitingSince = Date.now();
193
243
  this._callbackTimer = setTimeout(() => {
194
244
  this._cleanupServer();
195
- rejectCode(new Error("Authorization timed out — no callback received within 2 minutes"));
245
+ this.pendingAuthUrl = null;
246
+ const timeoutError = new Error("Authorization timed out — no callback received within 2 minutes");
247
+ rejectCode(timeoutError);
196
248
  }, CALLBACK_TIMEOUT_MS);
197
249
  this.pendingAuthUrl = url.toString();
250
+ this._ensureAuthorizationCompletion();
198
251
  }
199
252
  _cleanupServer() {
200
253
  if (this._callbackTimer) {
@@ -208,6 +261,59 @@ export class LessieAuthProvider {
208
261
  setTimeout(() => server.close(), 500);
209
262
  }
210
263
  }
264
+ /**
265
+ * provider 内部 completion 状态机:
266
+ * - 等待浏览器回调 code
267
+ * - 用 code 交换 token
268
+ * - 通过 Complete/Error 事件通知外层
269
+ */
270
+ _ensureAuthorizationCompletion() {
271
+ if (this._authCompletion)
272
+ return;
273
+ const serverUrl = REMOTE_MCP_URL;
274
+ if (!serverUrl) {
275
+ const err = new Error("LESSIE_REMOTE_MCP_URL is not configured");
276
+ this.pendingAuthUrl = null;
277
+ this._notifyAuthorizationFailure(err);
278
+ return;
279
+ }
280
+ this._authCompletion = (async () => {
281
+ try {
282
+ const code = await this.waitForCallback();
283
+ await auth(this, { serverUrl, authorizationCode: code, fetchFn: this._authFetchFn });
284
+ this.pendingAuthUrl = null;
285
+ this._notifyAuthorizationComplete();
286
+ }
287
+ catch (e) {
288
+ this.pendingAuthUrl = null;
289
+ const err = e instanceof Error ? e : new Error(String(e));
290
+ this._notifyAuthorizationFailure(err);
291
+ throw err;
292
+ }
293
+ finally {
294
+ this._callbackPromise = null;
295
+ this._authCompletion = null;
296
+ }
297
+ })();
298
+ // completion 主要由外层 wait/event 消费,这里兜底避免未处理拒绝告警
299
+ void this._authCompletion.catch(() => { });
300
+ }
301
+ _notifyAuthorizationComplete() {
302
+ try {
303
+ this._onAuthorizationComplete?.();
304
+ }
305
+ catch (err) {
306
+ console.warn("[lessie] onAuthorizationComplete callback failed:", err);
307
+ }
308
+ }
309
+ _notifyAuthorizationFailure(error) {
310
+ try {
311
+ this._onAuthorizationFailure?.(error);
312
+ }
313
+ catch (err) {
314
+ console.warn("[lessie] onAuthorizationFailure callback failed:", err);
315
+ }
316
+ }
211
317
  /**
212
318
  * 等待用户在浏览器中完成授权后回调,返回授权码。
213
319
  * 必须在 redirectToAuthorization 之后调用。
package/dist/config.js CHANGED
@@ -16,7 +16,7 @@ function readPkgVersion() {
16
16
  }
17
17
  export const pkg = { version: readPkgVersion() };
18
18
  /** 远程 MCP Server 地址 */
19
- export const REMOTE_MCP_URL = process.env.LESSIE_REMOTE_MCP_URL || "https://www.lessie.ai/mcp-server/mcp";
19
+ export const REMOTE_MCP_URL = process.env.LESSIE_REMOTE_MCP_URL || "https://app.lessie.ai/mcp-server/mcp";
20
20
  /** 读取 SKILL.md 作为 MCP instructions,跳过 YAML front-matter */
21
21
  export function loadInstructions() {
22
22
  try {
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import "./process-handlers.js";
9
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
11
  import { REMOTE_MCP_URL, pkg, loadInstructions } from "./config.js";
12
- import { connectToRemote, listRemoteTools, isRemoteConnected, setOnAuthComplete, setOnAuthError } from "./remote.js";
12
+ import { connectToRemote, listRemoteTools, setOnAuthComplete, setOnAuthError } from "./remote.js";
13
13
  import { registerTools } from "./tools.js";
14
14
  console.error("[lessie] Modules loaded");
15
15
  const server = new McpServer({ name: "lessie-mcp", version: pkg.version }, {
@@ -45,9 +45,9 @@ setOnAuthError(({ code, message }) => {
45
45
  });
46
46
  console.error("[lessie] Connecting to remote...");
47
47
  try {
48
- await connectToRemote();
49
- console.error("[lessie] connectToRemote() completed, connected:", isRemoteConnected());
50
- if (isRemoteConnected()) {
48
+ const connected = await connectToRemote();
49
+ console.error("[lessie] connectToRemote() completed, connected:", connected);
50
+ if (connected) {
51
51
  const remoteTools = await listRemoteTools();
52
52
  server.sendLoggingMessage({
53
53
  level: "info",
package/dist/remote.js CHANGED
@@ -34,8 +34,8 @@ const debugFetch = async (input, init) => {
34
34
  const fetchFn = DEBUG ? debugFetch : undefined;
35
35
  let client = null;
36
36
  let cachedTools = [];
37
- let reconnecting = null;
38
- let authCompletion = null;
37
+ // 授权码换 token 完成后,remote 侧需要做一次连接同步(重连 + 刷新工具)。
38
+ let postAuthSync = null;
39
39
  let onAuthComplete = null;
40
40
  let onAuthError = null;
41
41
  let lastAuthError = null;
@@ -50,25 +50,36 @@ export function setOnAuthError(cb) {
50
50
  export function isRemoteConnected() {
51
51
  return client !== null;
52
52
  }
53
- /** 若授权流程正在进行中,等待其完成(成功或失败) */
53
+ /** 等待授权 provider 完成,并等待 remote 侧连接同步完成。 */
54
54
  export async function waitForAuthCompletion() {
55
- if (authCompletion)
56
- await authCompletion;
55
+ await authProvider.waitForAuthorizationCompletion();
56
+ if (postAuthSync)
57
+ await postAuthSync;
58
+ }
59
+ /** 获取当前已连接客户端的工具列表并刷新缓存。 */
60
+ async function listConnectedTools() {
61
+ if (!client)
62
+ throw new Error("Remote client is not connected");
63
+ const { tools } = await client.listTools();
64
+ cachedTools = tools;
65
+ return tools;
57
66
  }
58
67
  /** 启动时静默连接:有缓存令牌才尝试,无令牌或连接失败则跳过 */
59
68
  export async function connectToRemote() {
60
69
  if (!REMOTE_MCP_URL)
61
- return;
70
+ return false;
62
71
  if (!(await authProvider.tokens()))
63
- return;
72
+ return false;
64
73
  const url = new URL(REMOTE_MCP_URL);
65
74
  try {
66
75
  client = await tryConnect(url);
67
76
  const { tools } = await client.listTools();
68
77
  cachedTools = tools;
78
+ return true;
69
79
  }
70
80
  catch {
71
81
  client = null;
82
+ return false;
72
83
  }
73
84
  }
74
85
  /**
@@ -81,15 +92,12 @@ export async function connectToRemote() {
81
92
  * error — 授权流程出错(附带错误码和引导信息)
82
93
  */
83
94
  export async function initiateAuth() {
84
- if (client) {
85
- const tools = await listRemoteTools();
86
- return { status: "connected", toolCount: tools.length };
87
- }
88
95
  if (!REMOTE_MCP_URL) {
89
96
  return { status: "error", errorCode: "not_configured", message: "LESSIE_REMOTE_MCP_URL is not configured" };
90
97
  }
98
+ const serverUrl = REMOTE_MCP_URL;
91
99
  // 授权流程进行中:告诉 Agent 当前等待状态
92
- if (authProvider.pendingAuthUrl && authCompletion) {
100
+ if (authProvider.pendingAuthUrl) {
93
101
  const elapsed = authProvider.waitingSince
94
102
  ? Date.now() - authProvider.waitingSince
95
103
  : 0;
@@ -99,62 +107,93 @@ export async function initiateAuth() {
99
107
  elapsedMs: elapsed,
100
108
  };
101
109
  }
110
+ // 若上一次授权流程刚完成,先等待 remote 侧连接同步,避免与当前 authorize 并发竞争。
111
+ await waitForAuthCompletion();
102
112
  // 上次异步错误(超时、授权拒绝等),记录后清除
103
113
  const prevError = lastAuthError;
104
114
  lastAuthError = null;
105
- const serverUrl = REMOTE_MCP_URL;
106
115
  try {
107
- await authProvider.prepareCallbackServer();
108
116
  const result = await auth(authProvider, { serverUrl, fetchFn });
109
- if (result === "AUTHORIZED") {
110
- const url = new URL(serverUrl);
111
- client = await tryConnect(url);
112
- const { tools } = await client.listTools();
113
- cachedTools = tools;
114
- return { status: "connected", toolCount: tools.length };
117
+ if (result === "REDIRECT") {
118
+ const authUrl = authProvider.pendingAuthUrl;
119
+ if (!authUrl) {
120
+ return { status: "error", errorCode: "no_auth_url", message: "OAuth flow initiated but no authorization URL was generated" };
121
+ }
122
+ return {
123
+ status: "auth_url",
124
+ authUrl,
125
+ ...(prevError ? { previousError: prevError.message } : {}),
126
+ };
115
127
  }
116
- const authUrl = authProvider.pendingAuthUrl;
117
- if (!authUrl) {
118
- return { status: "error", errorCode: "no_auth_url", message: "OAuth flow initiated but no authorization URL was generated" };
128
+ if (result !== "AUTHORIZED") {
129
+ return { status: "error", errorCode: "unexpected_auth_result", message: `Unexpected auth result: ${result}` };
119
130
  }
120
- ensureAuthCompletion();
121
- return {
122
- status: "auth_url",
123
- authUrl,
124
- ...(prevError ? { previousError: prevError.message } : {}),
125
- };
131
+ // 只有在 auth() 已确认 AUTHORIZED 后,才认为当前会话可用。
132
+ if (client) {
133
+ try {
134
+ const tools = await listConnectedTools();
135
+ return { status: "connected", toolCount: tools.length };
136
+ }
137
+ catch {
138
+ // 连接对象已失效:清理后重建连接。
139
+ const old = client;
140
+ client = null;
141
+ await old?.close().catch(() => { });
142
+ }
143
+ }
144
+ const nextClient = await tryConnect(new URL(serverUrl));
145
+ client = nextClient;
146
+ const tools = await listConnectedTools();
147
+ return { status: "connected", toolCount: tools.length };
126
148
  }
127
149
  catch (err) {
128
150
  const error = err instanceof Error ? err : new Error(String(err));
129
151
  return { status: "error", errorCode: categorizeAuthError(error), message: error.message };
130
152
  }
131
153
  }
132
- /** 启动后台任务:等待用户浏览器回调 交换令牌 连接远程服务器 */
133
- function ensureAuthCompletion() {
134
- if (authCompletion)
154
+ // provider 负责 OAuth completion;remote 只监听完成/失败事件并同步连接状态。
155
+ authProvider.setAuthFetchFn(fetchFn);
156
+ authProvider.setOnAuthorizationComplete(() => {
157
+ syncRemoteAfterAuthorization();
158
+ });
159
+ authProvider.setOnAuthorizationFailure((error) => {
160
+ const code = categorizeAuthError(error);
161
+ lastAuthError = { code, message: error.message };
162
+ onAuthError?.({ code, message: error.message });
163
+ });
164
+ function syncRemoteAfterAuthorization() {
165
+ if (postAuthSync)
135
166
  return;
136
- const serverUrl = REMOTE_MCP_URL;
137
- const url = new URL(serverUrl);
138
- authCompletion = (async () => {
167
+ if (!REMOTE_MCP_URL)
168
+ return;
169
+ const url = new URL(REMOTE_MCP_URL);
170
+ postAuthSync = (async () => {
139
171
  try {
140
- const code = await authProvider.waitForCallback();
141
- await auth(authProvider, { serverUrl, authorizationCode: code, fetchFn });
142
- client = await tryConnect(url);
143
- const { tools } = await client.listTools();
144
- cachedTools = tools;
145
- authProvider.pendingAuthUrl = null;
172
+ const old = client;
173
+ let next = null;
174
+ try {
175
+ next = await tryConnect(url);
176
+ const { tools } = await next.listTools();
177
+ client = next;
178
+ next = null;
179
+ cachedTools = tools;
180
+ }
181
+ finally {
182
+ // 新连接初始化失败时,关闭半初始化连接,避免泄漏。
183
+ await next?.close().catch(() => { });
184
+ }
185
+ await old?.close().catch(() => { });
146
186
  onAuthComplete?.(cachedTools.length);
147
187
  }
148
188
  catch (e) {
149
- authProvider.pendingAuthUrl = null;
150
189
  const error = e instanceof Error ? e : new Error(String(e));
151
190
  const code = categorizeAuthError(error);
152
191
  lastAuthError = { code, message: error.message };
153
192
  onAuthError?.({ code, message: error.message });
154
- console.error("[lessie] Authorization completion failed:", e);
193
+ console.error("[lessie] Post-authorization sync failed:", e);
155
194
  }
156
195
  finally {
157
- authCompletion = null;
196
+ postAuthSync = null;
158
197
  }
159
198
  })();
160
199
  }
@@ -191,8 +230,7 @@ export async function listRemoteTools() {
191
230
  }
192
231
  /** 转发工具调用到远程 MCP Server;失败时引导用户重新授权 */
193
232
  export async function callRemoteTool(name, args) {
194
- if (authCompletion)
195
- await authCompletion;
233
+ await waitForAuthCompletion();
196
234
  if (!client) {
197
235
  return {
198
236
  content: [
@@ -208,32 +246,65 @@ export async function callRemoteTool(name, args) {
208
246
  return (await client.callTool({ name, arguments: args }));
209
247
  }
210
248
  catch (err) {
211
- if (!isAuthError(err)) {
249
+ if (authProvider.pendingAuthUrl) {
212
250
  return {
213
251
  content: [
214
252
  {
215
253
  type: "text",
216
- text: `工具 ${name} 调用失败: ${err instanceof Error ? err.message : String(err)}`,
254
+ text: buildReauthPrompt(name),
217
255
  },
218
256
  ],
219
257
  isError: true,
220
258
  };
221
259
  }
222
- await reconnect();
223
- if (!client) {
260
+ if (!isAuthError(err)) {
224
261
  return {
225
262
  content: [
226
263
  {
227
264
  type: "text",
228
- text: `工具 ${name} 调用失败(认证过期),请使用 authorize 工具重新登录。`,
265
+ text: `工具 ${name} 调用失败: ${err instanceof Error ? err.message : String(err)}`,
229
266
  },
230
267
  ],
231
268
  isError: true,
232
269
  };
233
270
  }
234
- return (await client.callTool({ name, arguments: args }));
271
+ return {
272
+ content: [
273
+ {
274
+ type: "text",
275
+ text: buildReauthPrompt(name),
276
+ },
277
+ ],
278
+ isError: true,
279
+ };
235
280
  }
236
281
  }
282
+ function buildReauthPrompt(toolName) {
283
+ const authUrl = authProvider.pendingAuthUrl;
284
+ if (!authUrl) {
285
+ return [
286
+ `工具 ${toolName} 需要重新授权。`,
287
+ "",
288
+ "请按以下步骤操作:",
289
+ "1. 调用 authorize 工具,生成新的授权链接",
290
+ "2. 在浏览器中完成授权流程",
291
+ "3. 授权完成后,重新调用当前工具",
292
+ ].join("\n");
293
+ }
294
+ return [
295
+ `工具 ${toolName} 需要重新授权。`,
296
+ "",
297
+ "请按以下步骤操作:",
298
+ "1. 在浏览器中打开并完成以下授权链接:",
299
+ "",
300
+ authUrl,
301
+ "",
302
+ "2. 完成授权后,调用 authorize 工具确认连接状态",
303
+ "3. 重新调用当前工具",
304
+ "",
305
+ "如果链接失效,请再次调用 authorize 工具生成新链接。",
306
+ ].join("\n");
307
+ }
237
308
  function isAuthError(err) {
238
309
  const obj = err;
239
310
  const status = obj?.status ?? obj?.statusCode;
@@ -245,23 +316,3 @@ function isAuthError(err) {
245
316
  }
246
317
  return false;
247
318
  }
248
- /**
249
- * 重连,关闭旧连接;并发调用共享同一个 Promise 避免竞争。
250
- * 静默尝试连接,若令牌已失效则 client 保持 null。
251
- */
252
- async function reconnect() {
253
- if (reconnecting)
254
- return reconnecting;
255
- reconnecting = (async () => {
256
- try {
257
- const old = client;
258
- client = null;
259
- await old?.close().catch(() => { });
260
- await connectToRemote();
261
- }
262
- finally {
263
- reconnecting = null;
264
- }
265
- })();
266
- return reconnecting;
267
- }
package/dist/tools.js CHANGED
@@ -51,7 +51,7 @@ function formatAuthResult(result) {
51
51
  return {
52
52
  content: [{
53
53
  type: "text",
54
- text: `已连接到远程 MCP 服务器,共发现 ${result.toolCount} 个远程工具,无需重新授权。\n\n可通过 use_lessie 工具调用远程功能:不传 tool 参数可列出所有工具,传入 tool 和 arguments 可调用指定工具。`,
54
+ text: `已连接到远程 MCP 服务器,共发现 ${result.toolCount} 个远程工具。\n\n可通过 use_lessie 工具调用远程功能:不传 tool 参数可列出所有工具,传入 tool 和 arguments 可调用指定工具。若后续调用提示未授权,请重新执行 authorize。`,
55
55
  }],
56
56
  };
57
57
  case "auth_url": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessie/mcp-server",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {