create-message-kit 1.2.20 → 1.2.22

Sign up to get free protection for your applications and to get access to all the features.
package/index.js CHANGED
@@ -7,7 +7,7 @@ import { default as fs } from "fs-extra";
7
7
  import { isCancel } from "@clack/prompts";
8
8
  import { detect } from "detect-package-manager";
9
9
  import pc from "picocolors";
10
- const defVersion = "1.2.20";
10
+ const defVersion = "1.2.22";
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
12
 
13
13
  // Read package.json to get the version
@@ -140,6 +140,11 @@ async function gatherProjectInfo() {
140
140
  const name = kebabcase(displayName);
141
141
  const destDir = resolve(process.cwd(), name);
142
142
 
143
+ // Remove existing directory if it exists
144
+ if (fs.existsSync(destDir)) {
145
+ fs.removeSync(destDir);
146
+ }
147
+
143
148
  // Copy template files
144
149
  fs.copySync(templateDir, destDir);
145
150
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "create-message-kit",
3
- "version": "1.2.20",
3
+ "version": "1.2.22",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
8
8
  "bin": "index.js",
9
9
  "files": [
10
- "index.js",
10
+ "templates.json",
11
11
  "templates/**/*"
12
12
  ],
13
13
  "scripts": {
@@ -5,7 +5,6 @@ interface Participant {
5
5
  response: string;
6
6
  name: string;
7
7
  address: string;
8
- agent_address: string;
9
8
  }
10
9
  export interface TossData {
11
10
  group_id: string;
@@ -18,7 +17,6 @@ export interface TossData {
18
17
  end_time: string;
19
18
  description: string;
20
19
  participants: Participant[];
21
- toss_wallet_address: string;
22
20
  }
23
21
  export async function checkTossCorrect(
24
22
  context: XMTPContext,
@@ -64,8 +62,7 @@ export async function checkTossCorrect(
64
62
  return undefined;
65
63
  }
66
64
 
67
- const pool = tossData.amount * (tossData.participants?.length || 0);
68
- return { ...tossData, toss_id, pool };
65
+ return { ...tossData, toss_id };
69
66
  }
70
67
 
71
68
  export function extractTossId(message: string): string | null {
@@ -139,9 +139,13 @@ export async function handleTossCreation(context: XMTPContext) {
139
139
  if (params.description && params.options && !isNaN(Number(params.amount))) {
140
140
  const keys = await tossDBClient.keys("*");
141
141
  let tossId = keys.length + 1;
142
- const createdTossWallet = await walletService.createTempWallet(
143
- tossId.toString(),
142
+ const isCreated = await walletService.createWallet(
143
+ tossId + ":" + sender.address,
144
144
  );
145
+ if (!isCreated) {
146
+ await context.reply("Failed to create toss wallet");
147
+ return;
148
+ }
145
149
 
146
150
  let tossData: TossData = {
147
151
  toss_id: tossId.toString(),
@@ -158,20 +162,13 @@ export async function handleTossCreation(context: XMTPContext) {
158
162
  ? new Date(params.endTime).toLocaleString()
159
163
  : new Date(Date.now() + 24 * 60 * 60 * 1000).toLocaleString(),
160
164
  participants: [],
161
- toss_wallet_address: createdTossWallet?.address,
162
165
  };
163
- await tossDBClient.set(
164
- "toss:" + tossId.toString(),
165
- JSON.stringify(tossData),
166
- );
167
- console.log(tossData);
166
+ await tossDBClient.set("toss:" + tossId, JSON.stringify(tossData));
168
167
  if (tossId !== undefined) {
169
- let msg = generateTossMessage(tossData);
170
- console.log(msg);
171
- await context.send(msg);
168
+ await context.send(generateTossMessage(tossData));
172
169
  } else {
173
170
  await context.reply(
174
- `An error occurred while creating the toss. ${JSON.stringify(tossId)}`,
171
+ `An error occurred while creating the toss. ${tossId}`,
175
172
  );
176
173
  }
177
174
  }
@@ -183,7 +180,7 @@ export async function handleJoinToss(context: XMTPContext) {
183
180
  return;
184
181
  }
185
182
 
186
- const { toss_id, participants, amount } = tossData;
183
+ const { toss_id, participants, amount, admin_address } = tossData;
187
184
 
188
185
  const {
189
186
  message: {
@@ -192,7 +189,6 @@ export async function handleJoinToss(context: XMTPContext) {
192
189
  params: { response },
193
190
  },
194
191
  },
195
- group,
196
192
  walletService,
197
193
  } = context;
198
194
 
@@ -201,35 +197,16 @@ export async function handleJoinToss(context: XMTPContext) {
201
197
  await context.reply("You have already joined this toss.");
202
198
  return;
203
199
  }
204
-
205
- const tossWallet = await walletService.getTempWallet(toss_id);
206
- if (!tossWallet) {
207
- await context.reply("Toss wallet not found");
208
- return;
209
- }
200
+ //Create wallet for sender
201
+ await walletService.createWallet(sender.address);
210
202
  const balance = await walletService.checkBalance(sender.address);
211
-
212
- if (balance < amount) {
213
- await context.send("You need to fund your account. Check your DMs:");
214
- await walletService.requestFunds(context, amount);
215
- return;
216
- }
203
+ if (balance < amount) return walletService.requestFunds(amount);
217
204
 
218
205
  try {
219
- const senderWallet = await walletService.getUserWallet(sender.address);
220
- if (!senderWallet) {
221
- await context.reply("Sender wallet not found");
222
- return;
223
- }
224
- const transfer = await walletService.transfer(
225
- senderWallet,
226
- tossWallet,
227
- amount,
228
- );
229
- console.log("Transfer:", transfer.getTransactionHash());
206
+ let tempWalletID = toss_id + ":" + admin_address;
207
+ await walletService.transfer(sender.address, tempWalletID, amount);
230
208
  const participant = {
231
209
  address: sender.address,
232
- agent_address: senderWallet.address,
233
210
  response: response,
234
211
  name:
235
212
  (await context.getUserInfo(sender.address))?.preferredName ??
@@ -274,49 +251,30 @@ export async function handleEndToss(context: XMTPContext) {
274
251
  await context.reply("No participants for this toss.");
275
252
  return;
276
253
  } else if (admin_address.toLowerCase() !== sender.address.toLowerCase()) {
277
- await context.reply("Only the admin can end the toss.");
278
- return;
279
- } else if (
280
- !options
281
- .split(",")
282
- .map((o) => o.toLowerCase())
283
- .includes(option.toLowerCase())
284
- ) {
285
- await context.reply("Invalid option selected.");
254
+ await context.reply("Only the admin can cancel the toss.");
286
255
  return;
287
256
  }
288
- const { winners, losers } = await extractWinners(participants ?? [], option);
289
257
 
290
- if (winners.length === 0) {
291
- await context.reply("No winners for this toss.");
258
+ let tempWalletID = toss_id + ":" + admin_address;
259
+ const balance = await walletService.checkBalance(tempWalletID);
260
+ const fundsNeeded = tossData.amount * participants?.length;
261
+ if (balance < fundsNeeded) {
262
+ await context.reply(
263
+ `Toss wallet does not have enough funds ${fundsNeeded}, has ${balance}`,
264
+ );
292
265
  return;
293
266
  }
294
267
 
268
+ //Winners
269
+
270
+ const { winners, losers } = await extractWinners(participants, option);
271
+
295
272
  const prize =
296
273
  (tossData.amount * (participants?.length ?? 0)) / (winners.length ?? 1);
297
274
 
298
275
  try {
299
276
  for (const winner of winners) {
300
- const tossWallet = await walletService.getTempWallet(toss_id);
301
-
302
- if (!tossWallet) {
303
- await context.reply("Toss wallet not found");
304
- return;
305
- }
306
- const winnerWallet = await walletService.getUserWallet(
307
- winner.address,
308
- winner.address,
309
- );
310
- if (!winnerWallet) {
311
- await context.reply("Winner wallet not found");
312
- return;
313
- }
314
- const transfer = await walletService.transfer(
315
- tossWallet,
316
- winnerWallet,
317
- prize,
318
- );
319
- console.log("Transfer:", transfer.getTransactionHash());
277
+ await walletService.transfer(tempWalletID, winner.address, prize);
320
278
  await tossDBClient.set(
321
279
  `toss:${toss_id}`,
322
280
  JSON.stringify({ ...tossData, status: "closed" }),
@@ -357,31 +315,26 @@ export async function handleCancelToss(context: XMTPContext) {
357
315
  return;
358
316
  }
359
317
 
360
- for (const participant of participants ?? []) {
318
+ let tempWalletID = toss_id + ":" + admin_address;
319
+ const balance = await walletService.checkBalance(tempWalletID);
320
+ const fundsNeeded = tossData.amount * participants?.length;
321
+ if (balance < fundsNeeded) {
322
+ await context.reply(
323
+ `Toss wallet does not have enough funds ${fundsNeeded}, has ${balance}`,
324
+ );
325
+ return;
326
+ }
327
+ for (const participant of participants) {
361
328
  try {
362
- const tossWallet = await walletService.getTempWallet(toss_id);
363
-
364
- if (!tossWallet) {
365
- await context.reply("Toss wallet not found");
366
- return;
367
- }
368
-
369
- const participantWallet = await walletService.getUserWallet(
370
- participant.address,
329
+ await walletService.transfer(tempWalletID, participant.address, amount);
330
+ } catch (error) {
331
+ console.error(
332
+ `Failed to send prize to ${participant.address} agent wallet:`,
333
+ error,
371
334
  );
372
- if (!participantWallet) {
373
- await context.reply("Participant wallet not found");
374
- return;
375
- }
376
- const transfer = await walletService.transfer(
377
- tossWallet,
378
- participantWallet,
379
- amount,
335
+ await context.reply(
336
+ `Failed to send prize to ${participant.address} agent wallet`,
380
337
  );
381
- console.log("Transfer:", transfer.getTransactionHash());
382
- } catch (error) {
383
- console.error(`Failed to send prize to ${participant.address}:`, error);
384
- await context.reply(`Failed to send prize to ${participant.address}`);
385
338
  }
386
339
  }
387
340
 
@@ -424,20 +377,17 @@ export async function handleDM(context: XMTPContext) {
424
377
  if (skill === "help") {
425
378
  await context.send(DM_HELP_MESSAGE);
426
379
  } else if (skill === "create") {
427
- const walletExist = await walletService.getUserWallet(sender.address);
380
+ const walletExist = await walletService.getWallet(sender.address);
428
381
  if (walletExist) {
429
382
  await context.reply("You already have an agent wallet.");
430
383
  return;
431
384
  }
432
- const userWallet = await walletService.createUserWallet(sender.address);
433
- await context.reply(
434
- `Your agent wallet address is ${userWallet.address}\nBalance: $${await walletService.checkBalance(sender.address)}`,
435
- );
385
+ await walletService.createWallet(sender.address);
436
386
  } else if (skill === "balance") {
437
- const userWallet = await walletService.getUserWallet(sender.address);
387
+ const userWallet = await walletService.getWallet(sender.address);
438
388
 
439
389
  context.sendTo(
440
- `Your agent wallet address is ${userWallet?.address}\nBalance: $${await walletService.checkBalance(sender.address)}`,
390
+ `Your agent wallet for address is ${sender.address}\nBalance: $${await walletService.checkBalance(sender.address)}`,
441
391
  [sender.address],
442
392
  );
443
393
  } else if (skill === "fund") {
@@ -447,8 +397,7 @@ export async function handleDM(context: XMTPContext) {
447
397
  return;
448
398
  } else if (amount) {
449
399
  if (amount + balance <= 10) {
450
- await walletService.requestFunds(context, Number(amount));
451
- return;
400
+ return walletService.requestFunds(Number(amount));
452
401
  } else {
453
402
  await context.send("Wrong amount. Max 10 USDC.");
454
403
  return;
@@ -464,7 +413,7 @@ export async function handleDM(context: XMTPContext) {
464
413
  `Please specify the amount of USDC to prefund (1 to ${10 - balance}):`,
465
414
  options,
466
415
  );
467
- await walletService.requestFunds(context, Number(response));
416
+ return walletService.requestFunds(Number(response));
468
417
  } else if (skill === "withdraw") {
469
418
  const balance = await walletService.checkBalance(sender.address);
470
419
  if (balance === 0) {
@@ -478,6 +427,6 @@ export async function handleDM(context: XMTPContext) {
478
427
  `Please specify the amount of USDC to withdraw (1 to ${balance}):`,
479
428
  options,
480
429
  );
481
- await walletService.withdrawFunds(sender.address, Number(response));
430
+ await walletService.withdrawFunds(Number(response));
482
431
  }
483
432
  }
package/templates.json ADDED
@@ -0,0 +1,58 @@
1
+ [
2
+ {
3
+ "href": "/templates/ens",
4
+ "title": "ENS Agent",
5
+ "description": "A template for working with ENS domains.",
6
+ "icon": "🔗",
7
+ "author": "humanagent"
8
+ },
9
+ {
10
+ "href": "/templates/simple",
11
+ "title": "Simple Template",
12
+ "description": "A simple template without skills.",
13
+ "icon": "🤖",
14
+ "author": "humanagent"
15
+ },
16
+ {
17
+ "href": "/templates/coinbase-agent",
18
+ "title": "Coinbase Agent",
19
+ "description": "A template for a Coinbase features.",
20
+ "icon": "💰",
21
+ "author": "humanagent"
22
+ },
23
+ {
24
+ "href": "/templates/thegeneralstore",
25
+ "title": "The General Store",
26
+ "description": "All the goodies needed in a hackathon.",
27
+ "icon": "🏪",
28
+ "author": "humanagent"
29
+ },
30
+ {
31
+ "href": "/templates/faucet",
32
+ "title": "Faucet Agent",
33
+ "description": "A template for requesting testnet funds.",
34
+ "icon": "💧",
35
+ "author": "humanagent"
36
+ },
37
+ {
38
+ "href": "/templates/gated-group",
39
+ "title": "Gated Group",
40
+ "description": "A template for a gated group.",
41
+ "icon": "🔒",
42
+ "author": "humanagent"
43
+ },
44
+ {
45
+ "href": "/templates/gm",
46
+ "title": "GM Bot",
47
+ "description": "A template for a GM bot.",
48
+ "icon": "👑",
49
+ "author": "humanagent"
50
+ },
51
+ {
52
+ "href": "/templates/toss",
53
+ "title": "Toss",
54
+ "description": "A friendly game for groups.",
55
+ "icon": "🪙",
56
+ "author": "humanagent"
57
+ }
58
+ ]
@@ -1,290 +0,0 @@
1
- # MessageKit Skill Template
2
-
3
- ## Examples
4
-
5
- ### Check if a Domain is Available
6
-
7
-
8
- import { ensUrl } from "../index.js";
9
- import { XMTPContext } from "@xmtp/message-kit";
10
- import type { Skill } from "@xmtp/message-kit";
11
-
12
- // Define Skill
13
- export const checkDomain: Skill[] = [
14
- {
15
- skill: "check",
16
- handler: handler,
17
- examples: ["/check vitalik.eth", "/check fabri.base.eth"],
18
- description: "Check if a domain is available.",
19
- params: {
20
- domain: {
21
- type: "string",
22
- },
23
- },
24
- },
25
- ];
26
-
27
- // Handler Implementation
28
- export async function handler(context: XMTPContext) {
29
- const {
30
- message: {
31
- content: {
32
- params: { domain },
33
- },
34
- },
35
- } = context;
36
-
37
- const data = await context.getUserInfo(domain);
38
-
39
- if (!data?.address) {
40
- let message = `Looks like ${domain} is available! Here you can register it: ${ensUrl}${domain} or would you like to see some cool alternatives?`;
41
- return {
42
- code: 200,
43
- message,
44
- };
45
- } else {
46
- let message = `Looks like ${domain} is already registered!`;
47
- await context.executeSkill("/cool " + domain);
48
- return {
49
- code: 404,
50
- message,
51
- };
52
- }
53
- }
54
-
55
- ### Generate a payment request
56
-
57
-
58
- import { XMTPContext } from "@xmtp/message-kit";
59
- import type { Skill } from "@xmtp/message-kit";
60
-
61
- // Define Skill
62
- export const paymentRequest: Skill[] = [
63
- {
64
- skill: "pay",
65
- examples: [
66
- "/pay 10 vitalik.eth",
67
- "/pay 1 usdc to 0xC60E6Bb79322392761BFe3081E302aEB79B30B03",
68
- ],
69
- description:
70
- "Send a specified amount of a cryptocurrency to a destination address. \nWhen tipping, you can assume it's 1 USDC.",
71
- handler: handler,
72
- params: {
73
- amount: {
74
- default: 10,
75
- type: "number",
76
- },
77
- token: {
78
- default: "usdc",
79
- type: "string",
80
- values: ["eth", "dai", "usdc", "degen"], // Accepted tokens
81
- },
82
- username: {
83
- default: "",
84
- type: "username",
85
- },
86
- address: {
87
- default: "",
88
- type: "address",
89
- },
90
- },
91
- },
92
- ];
93
-
94
- // Handler Implementation
95
- export async function handler(context: XMTPContext) {
96
- const {
97
- message: {
98
- content: {
99
- params: { amount, token, username, address },
100
- },
101
- },
102
- } = context;
103
- let receiverAddress = address;
104
- if (username) {
105
- receiverAddress = (await context.getUserInfo(username))?.address;
106
- }
107
- if (address) {
108
- // Prioritize address over username
109
- receiverAddress = address;
110
- }
111
-
112
- await context.requestPayment(amount, token, receiverAddress);
113
- }
114
-
115
-
116
- ## Types
117
-
118
- import { XMTPContext } from "../plugins/xmtp.js";
119
- import { ClientOptions, GroupMember } from "@xmtp/node-sdk";
120
- import { ContentTypeId } from "@xmtp/content-type-primitives";
121
-
122
- export type MessageAbstracted = {
123
- id: string;
124
- sent: Date;
125
- content: {
126
- text?: string | undefined;
127
- reply?: string | undefined;
128
- previousMsg?: string | undefined;
129
- react?: string | undefined;
130
- content?: any | undefined;
131
- params?: any | undefined;
132
- reference?: string | undefined;
133
- skill?: string | undefined;
134
- };
135
- version: "v2" | "v3";
136
- sender: AbstractedMember;
137
- typeId: string;
138
- };
139
- export type GroupAbstracted = {
140
- id: string;
141
- sync: () => Promise<void>;
142
- addMembers: (addresses: string[]) => Promise<void>;
143
- addMembersByInboxId: (inboxIds: string[]) => Promise<void>;
144
- send: (content: string, contentType?: ContentTypeId) => Promise<string>;
145
- isAdmin: (inboxId: string) => boolean;
146
- isSuperAdmin: (inboxId: string) => boolean;
147
- admins: string[];
148
- superAdmins: string[];
149
- createdAt: Date;
150
- members: GroupMember[];
151
- };
152
- export type SkillResponse = {
153
- code: number;
154
- message: string;
155
- data?: any;
156
- };
157
-
158
- export type SkillHandler = (
159
- context: XMTPContext,
160
- ) => Promise<SkillResponse | void>;
161
-
162
- export type Handler = (context: XMTPContext) => Promise<void>;
163
-
164
- export type RunConfig = {
165
- // client options from XMTP client
166
- client?: ClientOptions;
167
- // private key to be used for the client, if not, default from env
168
- privateKey?: string;
169
- // if true, the init log message with messagekit logo and stuff will be hidden
170
- experimental?: boolean;
171
- // hide the init log message with messagekit logo and stuff
172
- hideInitLogMessage?: boolean;
173
- // if true, attachments will be enabled
174
- attachments?: boolean;
175
- // if true, member changes will be enabled, like adding members to the group
176
- memberChange?: boolean;
177
- // skills to be used
178
- agent?: Agent;
179
- // model to be used
180
- gptModel?: string;
181
- };
182
- export interface SkillParamConfig {
183
- default?: string | number | boolean;
184
- type:
185
- | "number"
186
- | "string"
187
- | "username"
188
- | "quoted"
189
- | "address"
190
- | "prompt"
191
- | "url";
192
- plural?: boolean;
193
- values?: string[]; // Accepted values for the parameter
194
- }
195
-
196
- export interface Frame {
197
- title: string;
198
- buttons: { content: string; action: string; target: string }[];
199
- image: string;
200
- }
201
- export interface Agent {
202
- name: string;
203
- description: string;
204
- tag: string;
205
- skills: Skill[];
206
- }
207
- export interface Skill {
208
- skill: string;
209
- handler?: SkillHandler | undefined;
210
- adminOnly?: boolean;
211
- description: string;
212
- examples: string[];
213
- params: Record<string, SkillParamConfig>;
214
- }
215
-
216
- export interface AbstractedMember {
217
- inboxId: string;
218
- address: string;
219
- accountAddresses: string[];
220
- installationIds?: string[];
221
- }
222
-
223
- export type MetadataValue = string | number | boolean;
224
- export type Metadata = Record<string, MetadataValue | MetadataValue[]>;
225
-
226
-
227
- ## Example final prompt
228
-
229
- Your are a helpful and playful ens agent called @bot that lives inside a messaging app called Converse.
230
-
231
-
232
- # Rules
233
- - You can respond with multiple messages if needed. Each message should be separated by a newline character.
234
- - You can trigger skills by only sending the command in a newline message.
235
- - Each command starts with a slash (/).
236
- - Never announce actions without using a command separated by a newline character.
237
- - Never use markdown in your responses.
238
- - Do not make guesses or assumptions
239
- - Only answer if the verified information is in the prompt.
240
- - Check that you are not missing a command
241
- - Focus only on helping users with operations detailed below.
242
- - Date: Fri, 06 Dec 2024 16:03:22 GMT
243
- - When mentioning any action related to available skills, you MUST trigger the corresponding command in a new line
244
- - If you suggest an action that has a command, you must trigger that command
245
-
246
-
247
- ## User context
248
- - Start by fetch their domain from or Converse username
249
- - Call the user by their name or domain, in case they have one
250
- - Ask for a name (if they don't have one) so you can suggest domains.
251
- - Message sent date: 2024-12-06T16:03:36.582Z
252
- - Users address is: 0x40f08f0f853d1c42c61815652b7ccd5a50f0be09
253
- - Users name is: ArizonaOregon
254
- - Converse username is: ArizonaOregon
255
-
256
- ## Commands
257
- /check [domain] - Check if a domain is available.
258
- /cool [domain] - Get cool alternatives for a .eth domain.
259
- /info [domain] - Get detailed information about an ENS domain including owner, expiry date, and resolver.
260
- /register [domain] - Register a new ENS domain. Returns a URL to complete the registration process.
261
- /renew [domain] - Extend the registration period of your ENS domain. Returns a URL to complete the renewal.
262
- /reset - Reset the conversation clearing memory and usernames cache.
263
- /pay [amount] [token] [username] [address] - Send a specified amount of a cryptocurrency to a destination address.
264
- When tipping, you can asume its 1 usdc.
265
-
266
- ## Examples
267
- /check vitalik.eth
268
- /check fabri.base.eth
269
- /cool vitalik.eth
270
- /info nick.eth
271
- /register vitalik.eth
272
- /renew fabri.base.eth
273
- /reset
274
- /pay 10 vitalik.eth
275
- /pay 1 usdc to 0xC60E6Bb79322392761BFe3081E302aEB79B30B03
276
-
277
- ## Scenarios
278
- 1. Missing commands in responses
279
- **Issue**: Sometimes responses are sent without the required command.
280
- **Example**:
281
- Incorrect:
282
- > "Looks like vitalik.eth is registered! What about these cool alternatives?"
283
- Correct:
284
- > "Looks like vitalik.eth is registered! What about these cool alternatives?
285
- > /cool vitalik.eth"
286
-
287
- Incorrect:
288
- > Here is a summary of your TODOs. I will now send it via email.
289
- Correct:
290
- > /todo
@@ -1,9 +0,0 @@
1
- KEY= # the private key of the agent wallet
2
- TEST_ENCRYPTION_KEY= # a different private key for encryption
3
- NOTION_API_KEY= # your Notion API key
4
- NOTION_PAGE_ID= # the ID of the Notion page
5
- NOTION_POAP_DB= # the ID of the Notion database
6
- OPEN_AI_API_KEY= # your OpenAI API key
7
- REDIS_CONNECTION_STRING= # your Redis connection string
8
- LEARN_WEB3_API_KEY= # your Learn Web3 API key
9
- FRAME_BASE_URL= # the URL of the Frame API
@@ -1,9 +0,0 @@
1
- compressionLevel: mixed
2
-
3
- enableGlobalCache: false
4
-
5
- enableTelemetry: false
6
-
7
- nodeLinker: node-modules
8
-
9
- yarnPath: .yarn/releases/yarn-4.5.1.cjs