create-message-kit 1.2.20 → 1.2.22

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/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