@agiflowai/hooks-adapter 0.0.21 → 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/dist/index.cjs CHANGED
@@ -1,4 +1,5 @@
1
- //#region rolldown:runtime
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -6,16 +7,12 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
7
  var __getProtoOf = Object.getPrototypeOf;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) {
13
- __defProp(to, key, {
14
- get: ((k) => from[k]).bind(null, key),
15
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
- });
17
- }
18
- }
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
19
16
  }
20
17
  return to;
21
18
  };
@@ -23,18 +20,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
20
  value: mod,
24
21
  enumerable: true
25
22
  }) : target, mod));
26
-
27
23
  //#endregion
28
- let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
24
+ let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
29
25
  let node_fs_promises = require("node:fs/promises");
30
- node_fs_promises = __toESM(node_fs_promises);
26
+ node_fs_promises = __toESM(node_fs_promises, 1);
31
27
  let node_path = require("node:path");
32
- node_path = __toESM(node_path);
28
+ node_path = __toESM(node_path, 1);
33
29
  let node_os = require("node:os");
34
- node_os = __toESM(node_os);
30
+ node_os = __toESM(node_os, 1);
35
31
  let node_crypto = require("node:crypto");
36
- node_crypto = __toESM(node_crypto);
37
-
32
+ node_crypto = __toESM(node_crypto, 1);
38
33
  //#region src/constants/hookTypes.ts
39
34
  /**
40
35
  * Hook Types Constants
@@ -83,7 +78,6 @@ const TASK_COMPLETED = ClaudeCodeHookTypes.TASK_COMPLETED;
83
78
  const BEFORE_TOOL_USE = GeminiCliHookTypes.BEFORE_TOOL_USE;
84
79
  /** Hook event fired after a tool is executed in Gemini CLI */
85
80
  const AFTER_TOOL_USE = GeminiCliHookTypes.AFTER_TOOL_USE;
86
-
87
81
  //#endregion
88
82
  //#region src/constants/decisions.ts
89
83
  /**
@@ -94,7 +88,6 @@ const DECISION_ALLOW = "allow";
94
88
  const DECISION_DENY = "deny";
95
89
  const DECISION_SKIP = "skip";
96
90
  const DECISION_ASK = "ask";
97
-
98
91
  //#endregion
99
92
  //#region src/adapters/BaseAdapter.ts
100
93
  /**
@@ -164,7 +157,7 @@ var BaseAdapter = class {
164
157
  return new Promise((resolve, reject) => {
165
158
  const chunks = [];
166
159
  process.stdin.on("data", (chunk) => {
167
- chunks.push(chunk);
160
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
168
161
  });
169
162
  process.stdin.on("end", () => {
170
163
  resolve(Buffer.concat(chunks).toString("utf8"));
@@ -190,7 +183,6 @@ var BaseAdapter = class {
190
183
  process.exit(0);
191
184
  }
192
185
  };
193
-
194
186
  //#endregion
195
187
  //#region src/adapters/ClaudeCodeAdapter.ts
196
188
  /**
@@ -232,10 +224,10 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
232
224
  * @returns ClaudeCodeHookInput
233
225
  */
234
226
  parseInput(stdin) {
235
- __agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Parsing input", { stdin });
227
+ _agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Parsing input", { stdin });
236
228
  const input = JSON.parse(stdin);
237
229
  this.hookEventName = input.hook_event_name;
238
- __agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Parsed input", {
230
+ _agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Parsed input", {
239
231
  hookEventName: this.hookEventName,
240
232
  input
241
233
  });
@@ -249,13 +241,13 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
249
241
  * @returns JSON string for Claude Code
250
242
  */
251
243
  formatOutput(response) {
252
- __agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Formatting output", {
244
+ _agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Formatting output", {
253
245
  hookEventName: this.hookEventName,
254
246
  response
255
247
  });
256
248
  if (response.decision === "skip") {
257
249
  const emptyOutput = JSON.stringify({}, null, 2);
258
- __agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Skip decision, returning empty output");
250
+ _agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Skip decision, returning empty output");
259
251
  return emptyOutput;
260
252
  }
261
253
  if (BLOCKABLE_EVENTS.has(this.hookEventName)) return this.formatBlockableOutput(response);
@@ -274,7 +266,7 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
274
266
  if (response.updatedInput) output.hookSpecificOutput.updatedInput = response.updatedInput;
275
267
  if (response.decision === "allow" && response.message) output.hookSpecificOutput.additionalContext = response.message;
276
268
  const formattedOutput = JSON.stringify(output, null, 2);
277
- __agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Formatted PreToolUse output", { output: formattedOutput });
269
+ _agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Formatted PreToolUse output", { output: formattedOutput });
278
270
  return formattedOutput;
279
271
  }
280
272
  /**
@@ -288,7 +280,7 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
288
280
  }
289
281
  if (response.decision === "allow" && response.message) output.hookSpecificOutput.additionalContext = response.message;
290
282
  const formattedOutput = JSON.stringify(output, null, 2);
291
- __agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Formatted PostToolUse output", { output: formattedOutput });
283
+ _agiflowai_aicode_utils.log.debug("ClaudeCodeAdapter: Formatted PostToolUse output", { output: formattedOutput });
292
284
  return formattedOutput;
293
285
  }
294
286
  /**
@@ -302,11 +294,10 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
302
294
  output.reason = response.message;
303
295
  }
304
296
  const formattedOutput = JSON.stringify(output, null, 2);
305
- __agiflowai_aicode_utils.log.debug(`ClaudeCodeAdapter: Formatted ${this.hookEventName} output`, { output: formattedOutput });
297
+ _agiflowai_aicode_utils.log.debug(`ClaudeCodeAdapter: Formatted ${this.hookEventName} output`, { output: formattedOutput });
306
298
  return formattedOutput;
307
299
  }
308
300
  };
309
-
310
301
  //#endregion
311
302
  //#region src/adapters/GeminiCliAdapter.ts
312
303
  /**
@@ -337,9 +328,9 @@ var GeminiCliAdapter = class extends BaseAdapter {
337
328
  * @returns Full Gemini CLI hook input
338
329
  */
339
330
  parseInput(stdin) {
340
- __agiflowai_aicode_utils.log.debug("GeminiCliAdapter.parseInput - Raw input:", stdin);
331
+ _agiflowai_aicode_utils.log.debug("GeminiCliAdapter.parseInput - Raw input:", stdin);
341
332
  const input = JSON.parse(stdin);
342
- __agiflowai_aicode_utils.log.debug("GeminiCliAdapter.parseInput - Parsed input:", JSON.stringify(input, null, 2));
333
+ _agiflowai_aicode_utils.log.debug("GeminiCliAdapter.parseInput - Parsed input:", JSON.stringify(input, null, 2));
343
334
  return input;
344
335
  }
345
336
  /**
@@ -349,25 +340,24 @@ var GeminiCliAdapter = class extends BaseAdapter {
349
340
  * @returns JSON string for Gemini CLI
350
341
  */
351
342
  formatOutput(response) {
352
- __agiflowai_aicode_utils.log.debug("GeminiCliAdapter.formatOutput - Normalized response:", JSON.stringify(response, null, 2));
353
- if (response.decision === DECISION_SKIP) {
354
- const output$1 = JSON.stringify({ decision: "ALLOW" }, null, 2);
355
- __agiflowai_aicode_utils.log.debug("GeminiCliAdapter.formatOutput - Output (skip):", output$1);
356
- return output$1;
343
+ _agiflowai_aicode_utils.log.debug("GeminiCliAdapter.formatOutput - Normalized response:", JSON.stringify(response, null, 2));
344
+ if (response.decision === "skip") {
345
+ const output = JSON.stringify({ decision: "ALLOW" }, null, 2);
346
+ _agiflowai_aicode_utils.log.debug("GeminiCliAdapter.formatOutput - Output (skip):", output);
347
+ return output;
357
348
  }
358
349
  const output = { decision: {
359
- [DECISION_ALLOW]: "ALLOW",
360
- [DECISION_DENY]: "DENY",
361
- [DECISION_ASK]: "ASK_USER"
350
+ ["allow"]: "ALLOW",
351
+ ["deny"]: "DENY",
352
+ ["ask"]: "ASK_USER"
362
353
  }[response.decision] || "ALLOW" };
363
354
  if (response.message) output.message = response.message;
364
355
  if (response.updatedInput) output.updatedInput = response.updatedInput;
365
356
  const outputStr = JSON.stringify(output, null, 2);
366
- __agiflowai_aicode_utils.log.debug("GeminiCliAdapter.formatOutput - Output:", outputStr);
357
+ _agiflowai_aicode_utils.log.debug("GeminiCliAdapter.formatOutput - Output:", outputStr);
367
358
  return outputStr;
368
359
  }
369
360
  };
370
-
371
361
  //#endregion
372
362
  //#region src/services/ExecutionLogService.ts
373
363
  /**
@@ -597,7 +587,7 @@ var ExecutionLogService = class ExecutionLogService {
597
587
  break;
598
588
  }
599
589
  }
600
- if (!lastExecution || !lastExecution.fileChecksum) return true;
590
+ if (!lastExecution?.fileChecksum) return true;
601
591
  const currentMetadata = await this.getFileMetadata(filePath);
602
592
  if (!currentMetadata) return true;
603
593
  return currentMetadata.checksum !== lastExecution.fileChecksum;
@@ -687,7 +677,6 @@ var ExecutionLogService = class ExecutionLogService {
687
677
  }
688
678
  }
689
679
  };
690
-
691
680
  //#endregion
692
681
  //#region src/utils/guards.ts
693
682
  /**
@@ -731,7 +720,6 @@ function isHookContext(value) {
731
720
  if (!("sessionId" in value) || typeof value.sessionId !== "string") return false;
732
721
  return true;
733
722
  }
734
-
735
723
  //#endregion
736
724
  //#region src/utils/parseHookType.ts
737
725
  /**
@@ -757,7 +745,6 @@ function parseHookType(hookType) {
757
745
  hookMethod
758
746
  };
759
747
  }
760
-
761
748
  //#endregion
762
749
  exports.AFTER_TOOL_USE = AFTER_TOOL_USE;
763
750
  exports.BEFORE_TOOL_USE = BEFORE_TOOL_USE;
@@ -778,4 +765,4 @@ exports.TASK_COMPLETED = TASK_COMPLETED;
778
765
  exports.USER_PROMPT_SUBMIT = USER_PROMPT_SUBMIT;
779
766
  exports.isHookContext = isHookContext;
780
767
  exports.isHookResponse = isHookResponse;
781
- exports.parseHookType = parseHookType;
768
+ exports.parseHookType = parseHookType;
package/dist/index.d.cts CHANGED
@@ -19,24 +19,17 @@
19
19
  * Grouped hook type identifiers for Claude Code hook events
20
20
  */
21
21
  declare const ClaudeCodeHookTypes: {
22
- /** Fires before a tool is executed, allowing interception or modification */
23
- readonly PRE_TOOL_USE: "PreToolUse";
24
- /** Fires after a tool has executed, allowing post-processing or blocking */
25
- readonly POST_TOOL_USE: "PostToolUse";
26
- /** Fires when a session is about to stop */
27
- readonly STOP: "Stop";
28
- /** Fires when the user submits a new prompt */
29
- readonly USER_PROMPT_SUBMIT: "UserPromptSubmit";
30
- /** Fires when an agentic task completes */
22
+ /** Fires before a tool is executed, allowing interception or modification */readonly PRE_TOOL_USE: "PreToolUse"; /** Fires after a tool has executed, allowing post-processing or blocking */
23
+ readonly POST_TOOL_USE: "PostToolUse"; /** Fires when a session is about to stop */
24
+ readonly STOP: "Stop"; /** Fires when the user submits a new prompt */
25
+ readonly USER_PROMPT_SUBMIT: "UserPromptSubmit"; /** Fires when an agentic task completes */
31
26
  readonly TASK_COMPLETED: "TaskCompleted";
32
27
  };
33
28
  /**
34
29
  * Grouped hook type identifiers for Gemini CLI hook events
35
30
  */
36
31
  declare const GeminiCliHookTypes: {
37
- /** Fires before a tool is executed in Gemini CLI */
38
- readonly BEFORE_TOOL_USE: "BeforeTool";
39
- /** Fires after a tool is executed in Gemini CLI */
32
+ /** Fires before a tool is executed in Gemini CLI */readonly BEFORE_TOOL_USE: "BeforeTool"; /** Fires after a tool is executed in Gemini CLI */
40
33
  readonly AFTER_TOOL_USE: "AfterTool";
41
34
  };
42
35
  /** Hook event fired before a tool is executed in Claude Code */
package/dist/index.d.mts CHANGED
@@ -19,24 +19,17 @@
19
19
  * Grouped hook type identifiers for Claude Code hook events
20
20
  */
21
21
  declare const ClaudeCodeHookTypes: {
22
- /** Fires before a tool is executed, allowing interception or modification */
23
- readonly PRE_TOOL_USE: "PreToolUse";
24
- /** Fires after a tool has executed, allowing post-processing or blocking */
25
- readonly POST_TOOL_USE: "PostToolUse";
26
- /** Fires when a session is about to stop */
27
- readonly STOP: "Stop";
28
- /** Fires when the user submits a new prompt */
29
- readonly USER_PROMPT_SUBMIT: "UserPromptSubmit";
30
- /** Fires when an agentic task completes */
22
+ /** Fires before a tool is executed, allowing interception or modification */readonly PRE_TOOL_USE: "PreToolUse"; /** Fires after a tool has executed, allowing post-processing or blocking */
23
+ readonly POST_TOOL_USE: "PostToolUse"; /** Fires when a session is about to stop */
24
+ readonly STOP: "Stop"; /** Fires when the user submits a new prompt */
25
+ readonly USER_PROMPT_SUBMIT: "UserPromptSubmit"; /** Fires when an agentic task completes */
31
26
  readonly TASK_COMPLETED: "TaskCompleted";
32
27
  };
33
28
  /**
34
29
  * Grouped hook type identifiers for Gemini CLI hook events
35
30
  */
36
31
  declare const GeminiCliHookTypes: {
37
- /** Fires before a tool is executed in Gemini CLI */
38
- readonly BEFORE_TOOL_USE: "BeforeTool";
39
- /** Fires after a tool is executed in Gemini CLI */
32
+ /** Fires before a tool is executed in Gemini CLI */readonly BEFORE_TOOL_USE: "BeforeTool"; /** Fires after a tool is executed in Gemini CLI */
40
33
  readonly AFTER_TOOL_USE: "AfterTool";
41
34
  };
42
35
  /** Hook event fired before a tool is executed in Claude Code */
package/dist/index.mjs CHANGED
@@ -3,7 +3,6 @@ import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import * as os from "node:os";
5
5
  import * as crypto from "node:crypto";
6
-
7
6
  //#region src/constants/hookTypes.ts
8
7
  /**
9
8
  * Hook Types Constants
@@ -52,7 +51,6 @@ const TASK_COMPLETED = ClaudeCodeHookTypes.TASK_COMPLETED;
52
51
  const BEFORE_TOOL_USE = GeminiCliHookTypes.BEFORE_TOOL_USE;
53
52
  /** Hook event fired after a tool is executed in Gemini CLI */
54
53
  const AFTER_TOOL_USE = GeminiCliHookTypes.AFTER_TOOL_USE;
55
-
56
54
  //#endregion
57
55
  //#region src/constants/decisions.ts
58
56
  /**
@@ -63,7 +61,6 @@ const DECISION_ALLOW = "allow";
63
61
  const DECISION_DENY = "deny";
64
62
  const DECISION_SKIP = "skip";
65
63
  const DECISION_ASK = "ask";
66
-
67
64
  //#endregion
68
65
  //#region src/adapters/BaseAdapter.ts
69
66
  /**
@@ -133,7 +130,7 @@ var BaseAdapter = class {
133
130
  return new Promise((resolve, reject) => {
134
131
  const chunks = [];
135
132
  process.stdin.on("data", (chunk) => {
136
- chunks.push(chunk);
133
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
137
134
  });
138
135
  process.stdin.on("end", () => {
139
136
  resolve(Buffer.concat(chunks).toString("utf8"));
@@ -159,7 +156,6 @@ var BaseAdapter = class {
159
156
  process.exit(0);
160
157
  }
161
158
  };
162
-
163
159
  //#endregion
164
160
  //#region src/adapters/ClaudeCodeAdapter.ts
165
161
  /**
@@ -275,7 +271,6 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
275
271
  return formattedOutput;
276
272
  }
277
273
  };
278
-
279
274
  //#endregion
280
275
  //#region src/adapters/GeminiCliAdapter.ts
281
276
  /**
@@ -319,15 +314,15 @@ var GeminiCliAdapter = class extends BaseAdapter {
319
314
  */
320
315
  formatOutput(response) {
321
316
  log.debug("GeminiCliAdapter.formatOutput - Normalized response:", JSON.stringify(response, null, 2));
322
- if (response.decision === DECISION_SKIP) {
323
- const output$1 = JSON.stringify({ decision: "ALLOW" }, null, 2);
324
- log.debug("GeminiCliAdapter.formatOutput - Output (skip):", output$1);
325
- return output$1;
317
+ if (response.decision === "skip") {
318
+ const output = JSON.stringify({ decision: "ALLOW" }, null, 2);
319
+ log.debug("GeminiCliAdapter.formatOutput - Output (skip):", output);
320
+ return output;
326
321
  }
327
322
  const output = { decision: {
328
- [DECISION_ALLOW]: "ALLOW",
329
- [DECISION_DENY]: "DENY",
330
- [DECISION_ASK]: "ASK_USER"
323
+ ["allow"]: "ALLOW",
324
+ ["deny"]: "DENY",
325
+ ["ask"]: "ASK_USER"
331
326
  }[response.decision] || "ALLOW" };
332
327
  if (response.message) output.message = response.message;
333
328
  if (response.updatedInput) output.updatedInput = response.updatedInput;
@@ -336,7 +331,6 @@ var GeminiCliAdapter = class extends BaseAdapter {
336
331
  return outputStr;
337
332
  }
338
333
  };
339
-
340
334
  //#endregion
341
335
  //#region src/services/ExecutionLogService.ts
342
336
  /**
@@ -566,7 +560,7 @@ var ExecutionLogService = class ExecutionLogService {
566
560
  break;
567
561
  }
568
562
  }
569
- if (!lastExecution || !lastExecution.fileChecksum) return true;
563
+ if (!lastExecution?.fileChecksum) return true;
570
564
  const currentMetadata = await this.getFileMetadata(filePath);
571
565
  if (!currentMetadata) return true;
572
566
  return currentMetadata.checksum !== lastExecution.fileChecksum;
@@ -656,7 +650,6 @@ var ExecutionLogService = class ExecutionLogService {
656
650
  }
657
651
  }
658
652
  };
659
-
660
653
  //#endregion
661
654
  //#region src/utils/guards.ts
662
655
  /**
@@ -700,7 +693,6 @@ function isHookContext(value) {
700
693
  if (!("sessionId" in value) || typeof value.sessionId !== "string") return false;
701
694
  return true;
702
695
  }
703
-
704
696
  //#endregion
705
697
  //#region src/utils/parseHookType.ts
706
698
  /**
@@ -726,6 +718,5 @@ function parseHookType(hookType) {
726
718
  hookMethod
727
719
  };
728
720
  }
729
-
730
721
  //#endregion
731
- export { AFTER_TOOL_USE, BEFORE_TOOL_USE, BaseAdapter, ClaudeCodeAdapter, ClaudeCodeHookTypes, DECISION_ALLOW, DECISION_ASK, DECISION_DENY, DECISION_SKIP, ExecutionLogService, GeminiCliAdapter, GeminiCliHookTypes, POST_TOOL_USE, PRE_TOOL_USE, STOP, TASK_COMPLETED, USER_PROMPT_SUBMIT, isHookContext, isHookResponse, parseHookType };
722
+ export { AFTER_TOOL_USE, BEFORE_TOOL_USE, BaseAdapter, ClaudeCodeAdapter, ClaudeCodeHookTypes, DECISION_ALLOW, DECISION_ASK, DECISION_DENY, DECISION_SKIP, ExecutionLogService, GeminiCliAdapter, GeminiCliHookTypes, POST_TOOL_USE, PRE_TOOL_USE, STOP, TASK_COMPLETED, USER_PROMPT_SUBMIT, isHookContext, isHookResponse, parseHookType };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agiflowai/hooks-adapter",
3
3
  "description": "Hook adapters for normalizing AI agent hook formats (Claude Code, Gemini, etc.)",
4
- "version": "0.0.21",
4
+ "version": "0.1.0",
5
5
  "license": "AGPL-3.0",
6
6
  "author": "AgiflowIO",
7
7
  "repository": {
@@ -25,16 +25,16 @@
25
25
  "README.md"
26
26
  ],
27
27
  "dependencies": {
28
- "@agiflowai/coding-agent-bridge": "1.0.23",
29
- "@agiflowai/aicode-utils": "1.0.20"
28
+ "@agiflowai/aicode-utils": "1.1.0",
29
+ "@agiflowai/coding-agent-bridge": "1.1.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^22.0.0",
33
- "@vitest/coverage-v8": "^3.0.0",
34
- "chance": "^1.1.13",
35
- "tsdown": "^0.16.4",
32
+ "@types/node": "25.6.0",
33
+ "@vitest/coverage-v8": "4.1.4",
34
+ "chance": "1.1.13",
35
+ "tsdown": "0.21.8",
36
36
  "typescript": "5.9.3",
37
- "vitest": "4.0.15"
37
+ "vitest": "4.1.4"
38
38
  },
39
39
  "type": "module",
40
40
  "exports": {