create-message-kit 1.1.7-beta.2 → 1.1.7-beta.20

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
@@ -32,8 +32,8 @@ Powered by XMTP`;
32
32
 
33
33
  const { templateType, displayName, destDir } = await gatherProjectInfo();
34
34
 
35
- // Replace dot files
36
- //replaceDotfiles(destDir);
35
+ // Add package.json
36
+ addPackagejson(destDir, displayName);
37
37
 
38
38
  // Create .gitignore
39
39
  createGitignore(destDir);
@@ -44,9 +44,6 @@ Powered by XMTP`;
44
44
  // Create tsconfig.json file
45
45
  createTsconfig(destDir);
46
46
 
47
- // Replace package.json properties
48
- updatePackageJson(destDir, displayName);
49
-
50
47
  // Wrap up
51
48
  log.success(`Project launched in ${pc.red(destDir)}!`);
52
49
 
@@ -63,6 +60,31 @@ Powered by XMTP`;
63
60
 
64
61
  program.parse(process.argv);
65
62
 
63
+ async function addPackagejson(destDir, name) {
64
+ // Create package.json based on the template
65
+ const packageTemplate = {
66
+ name: name,
67
+ private: true,
68
+ type: "module",
69
+ scripts: {
70
+ build: "tsc",
71
+ dev: "tsc -w & sleep 1 && node --watch dist/index.js",
72
+ start: "node dist/index.js",
73
+ postinstall: "tsc",
74
+ },
75
+ dependencies: {
76
+ "@xmtp/message-kit": "workspace:*",
77
+ },
78
+ engines: {
79
+ node: ">=20",
80
+ },
81
+ };
82
+
83
+ fs.writeJsonSync(resolve(destDir, "package.json"), packageTemplate, {
84
+ spaces: 2,
85
+ });
86
+ }
87
+
66
88
  async function gatherProjectInfo() {
67
89
  const templateOptions = [
68
90
  { value: "gm", label: "GM" },
@@ -151,22 +173,6 @@ function createTsconfig(destDir) {
151
173
  spaces: 2,
152
174
  });
153
175
  }
154
- function replaceDotfiles(destDir) {
155
- for (const file of fs.readdirSync(destDir)) {
156
- if (!file.startsWith("_")) continue;
157
- fs.renameSync(
158
- resolve(destDir, file),
159
- resolve(destDir, `.${file.slice(1)}`),
160
- );
161
- }
162
- }
163
-
164
- function updatePackageJson(destDir, name) {
165
- const pkgJson = fs.readJsonSync(resolve(destDir, "package.json"));
166
- pkgJson.name = name;
167
- updateDependenciesToLatest(pkgJson);
168
- fs.writeJsonSync(resolve(destDir, "package.json"), pkgJson, { spaces: 2 });
169
- }
170
176
 
171
177
  function logNextSteps(name) {
172
178
  log.message("Next steps:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-message-kit",
3
- "version": "1.1.7-beta.2",
3
+ "version": "1.1.7-beta.20",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -1,7 +1,7 @@
1
1
  import { HandlerContext, SkillResponse } from "@xmtp/message-kit";
2
- import { getUserInfo, clearInfoCache, isOnXMTP } from "../lib/resolver.js";
2
+ import { getUserInfo, clearInfoCache, isOnXMTP } from "@xmtp/message-kit";
3
3
  import { isAddress } from "viem";
4
- import { clearMemory } from "../lib/gpt.js";
4
+ import { clearMemory } from "@xmtp/message-kit";
5
5
 
6
6
  export const frameUrl = "https://ens.steer.fun/";
7
7
  export const ensUrl = "https://app.ens.domains/";
@@ -9,18 +9,19 @@ export const baseTxUrl = "https://base-tx-frame.vercel.app";
9
9
 
10
10
  export async function handleEns(
11
11
  context: HandlerContext,
12
- ): Promise<SkillResponse> {
12
+ ): Promise<SkillResponse | undefined> {
13
13
  const {
14
14
  message: {
15
- content: { command, params, sender },
15
+ sender,
16
+ content: { skill, params },
16
17
  },
17
- skill,
18
18
  } = context;
19
- if (command == "reset") {
19
+
20
+ if (skill == "reset") {
20
21
  clearMemory();
21
22
  return { code: 200, message: "Conversation reset." };
22
- } else if (command == "renew") {
23
- // Destructure and validate parameters for the ens command
23
+ } else if (skill == "renew") {
24
+ // Destructure and validate parameters for the ens
24
25
  const { domain } = params;
25
26
  // Check if the user holds the domain
26
27
  if (!domain) {
@@ -43,8 +44,8 @@ export async function handleEns(
43
44
  // Generate URL for the ens
44
45
  let url_ens = frameUrl + "frames/manage?name=" + domain;
45
46
  return { code: 200, message: `${url_ens}` };
46
- } else if (command == "register") {
47
- // Destructure and validate parameters for the ens command
47
+ } else if (skill == "register") {
48
+ // Destructure and validate parameters for the ens
48
49
  const { domain } = params;
49
50
 
50
51
  if (!domain) {
@@ -56,7 +57,7 @@ export async function handleEns(
56
57
  // Generate URL for the ens
57
58
  let url_ens = ensUrl + domain;
58
59
  return { code: 200, message: `${url_ens}` };
59
- } else if (command == "info") {
60
+ } else if (skill == "info") {
60
61
  const { domain } = params;
61
62
 
62
63
  const data = await getUserInfo(domain);
@@ -87,19 +88,13 @@ export async function handleEns(
87
88
  }
88
89
  message += `\n\nWould you like to tip the domain owner for getting there first 🤣?`;
89
90
  message = message.trim();
90
- if (
91
- await isOnXMTP(
92
- context.v2client,
93
- data?.ensInfo?.ens,
94
- data?.ensInfo?.address,
95
- )
96
- ) {
91
+ if (await isOnXMTP(context.client, context.v2client, sender?.address)) {
97
92
  await context.send(
98
93
  `Ah, this domains is in XMTP, you can message it directly: https://converse.xyz/dm/${domain}`,
99
94
  );
100
95
  }
101
96
  return { code: 200, message };
102
- } else if (command == "check") {
97
+ } else if (skill == "check") {
103
98
  const { domain } = params;
104
99
 
105
100
  if (!domain) {
@@ -118,13 +113,13 @@ export async function handleEns(
118
113
  };
119
114
  } else {
120
115
  let message = `Looks like ${domain} is already registered!`;
121
- await skill("/cool " + domain);
116
+ await context.executeSkill("/cool " + domain);
122
117
  return {
123
118
  code: 404,
124
119
  message,
125
120
  };
126
121
  }
127
- } else if (command == "tip") {
122
+ } else if (skill == "tip") {
128
123
  const { address } = params;
129
124
  if (!address) {
130
125
  return {
@@ -141,7 +136,7 @@ export async function handleEns(
141
136
  code: 200,
142
137
  message: txUrl,
143
138
  };
144
- } else if (command == "cool") {
139
+ } else if (skill == "cool") {
145
140
  const { domain } = params;
146
141
  //What about these cool alternatives?\
147
142
  return {
@@ -149,7 +144,7 @@ export async function handleEns(
149
144
  message: `${generateCoolAlternatives(domain)}`,
150
145
  };
151
146
  } else {
152
- return { code: 400, message: "Command not found." };
147
+ return { code: 400, message: "Skill not found." };
153
148
  }
154
149
  }
155
150
 
@@ -1,26 +1,18 @@
1
1
  import { run, HandlerContext } from "@xmtp/message-kit";
2
- import { textGeneration, processMultilineResponse } from "./lib/gpt.js";
2
+ import { textGeneration, processMultilineResponse } from "@xmtp/message-kit";
3
3
  import { agent_prompt } from "./prompt.js";
4
- import { getUserInfo } from "./lib/resolver.js";
4
+ import { getUserInfo } from "@xmtp/message-kit";
5
5
 
6
6
  run(async (context: HandlerContext) => {
7
- /*All the skills are handled through the skills file*/
8
- /* If its just text, it will be handled by the ensAgent*/
9
- /* If its a group message, it will be handled by the groupAgent*/
10
- if (!process?.env?.OPEN_AI_API_KEY) {
11
- console.warn("No OPEN_AI_API_KEY found in .env");
12
- return;
13
- }
14
-
15
7
  const {
16
8
  message: {
17
- content: { content, params },
9
+ content: { text, params },
18
10
  sender,
19
11
  },
20
12
  } = context;
21
13
 
22
14
  try {
23
- let userPrompt = params?.prompt ?? content;
15
+ let userPrompt = params?.prompt ?? text;
24
16
  const userInfo = await getUserInfo(sender.address);
25
17
  if (!userInfo) {
26
18
  console.log("User info not found");
@@ -1,52 +1,51 @@
1
1
  import { skills } from "./skills.js";
2
- import { UserInfo, PROMPT_USER_CONTENT } from "./lib/resolver.js";
3
- import { PROMPT_RULES, PROMPT_SKILLS_AND_EXAMPLES } from "./lib/gpt.js";
2
+ import {
3
+ UserInfo,
4
+ PROMPT_USER_CONTENT,
5
+ PROMPT_RULES,
6
+ PROMPT_SKILLS_AND_EXAMPLES,
7
+ PROMPT_REPLACE_VARIABLES,
8
+ } from "@xmtp/message-kit";
4
9
 
5
10
  export async function agent_prompt(userInfo: UserInfo) {
6
- let { address, ensDomain, converseUsername, preferredName } = userInfo;
11
+ let systemPrompt =
12
+ PROMPT_RULES +
13
+ PROMPT_USER_CONTENT(userInfo) +
14
+ PROMPT_SKILLS_AND_EXAMPLES(skills, "@ens");
7
15
 
8
- //Update the name of the agent with predefined prompt
9
- let systemPrompt = PROMPT_RULES.replace("{NAME}", skills?.[0]?.tag ?? "@ens");
10
-
11
- //Add user context to the prompt
12
- systemPrompt += PROMPT_USER_CONTENT(userInfo);
13
-
14
- //Add skills and examples to the prompt
15
- systemPrompt += PROMPT_SKILLS_AND_EXAMPLES(skills, "@ens");
16
-
17
- systemPrompt += `
16
+ let fineTunning = `
18
17
 
19
18
  ## Example responses:
20
19
 
21
20
  1. Check if the user does not have a ENS domain
22
- Hey ${preferredName}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username with the .eth suffix\n/check ${converseUsername}.eth
21
+ Hey {PREFERRED_NAME}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username with the .eth suffix\n/check {CONVERSE_USERNAME}.eth
23
22
 
24
23
  2. If the user has a ENS domain
25
- Hello ${preferredName} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${ensDomain}. Give me a moment.\n/check ${ensDomain}
24
+ Hello {PREFERRED_NAME} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/check {ENS_DOMAIN}
26
25
 
27
26
  3. Check if the ENS domain is available
28
- Hello! I'll help you get your domain.\n Let's start by checking your ENS domain ${ensDomain}. Give me a moment.\n/check ${ensDomain}
27
+ Hello! I'll help you get your domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/check {ENS_DOMAIN}
29
28
 
30
29
  4. If the ENS domain is available,
31
- Looks like ${ensDomain} is available! Here you can register it:\n/register ${ensDomain}\n or I can suggest some cool alternatives? Le me know!
30
+ Looks like {ENS_DOMAIN} is available! Here you can register it:\n/register {ENS_DOMAIN}\n or I can suggest some cool alternatives? Le me know!
32
31
 
33
32
  5. If the ENS domain is already registered, let me suggest 5 cool alternatives
34
- Looks like ${ensDomain} is already registered!\n What about these cool alternatives?\n/cool ${ensDomain}
33
+ Looks like {ENS_DOMAIN} is already registered!\n What about these cool alternatives?\n/cool {ENS_DOMAIN}
35
34
 
36
35
  6. If the user wants to register a ENS domain, use the command "/register [domain]"
37
- Looks like ${ensDomain} is available! Let me help you register it\n/register ${ensDomain}
36
+ Looks like {ENS_DOMAIN} is available! Let me help you register it\n/register {ENS_DOMAIN}
38
37
 
39
38
  7. If the user wants to directly to tip to the ENS domain owner, use directly the command "/tip [domain]", this will return a url but a button to send the tip
40
- Here is the url to send the tip:\n/tip ${ensDomain}
39
+ Here is the url to send the tip:\n/tip {ENS_DOMAIN}
41
40
 
42
41
  8. If the user wants to get information about the ENS domain, use the command "/info [domain]"
43
- Hello! I'll help you get info about ${ensDomain}.\n Give me a moment.\n/info ${ensDomain}
42
+ Hello! I'll help you get info about {ENS_DOMAIN}.\n Give me a moment.\n/info {ENS_DOMAIN}
44
43
 
45
44
  9. If the user wants to renew their domain, use the command "/renew [domain]"
46
- Hello! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${ensDomain}. Give me a moment.\n/renew ${ensDomain}
45
+ Hello! I'll help you get your ENS domain.\n Let's start by checking your ENS domain {ENS_DOMAIN}. Give me a moment.\n/renew {ENS_DOMAIN}
47
46
 
48
47
  10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
49
- Here are some cool suggestions for your domain.\n/cool ${ensDomain}
48
+ Here are some cool suggestions for your domain.\n/cool {ENS_DOMAIN}
50
49
 
51
50
  ## Most common bugs
52
51
 
@@ -54,5 +53,16 @@ export async function agent_prompt(userInfo: UserInfo) {
54
53
  But you forgot to add the command at the end of the message.
55
54
  You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
56
55
  `;
56
+
57
+ // Add the fine tuning to the system prompt
58
+ systemPrompt += fineTunning;
59
+
60
+ // Replace the variables in the system prompt
61
+ systemPrompt = PROMPT_REPLACE_VARIABLES(
62
+ systemPrompt,
63
+ userInfo?.address ?? "",
64
+ userInfo,
65
+ "@ens",
66
+ );
57
67
  return systemPrompt;
58
68
  }
@@ -8,7 +8,7 @@ export const skills: SkillGroup[] = [
8
8
  description: "Register ENS domains.",
9
9
  skills: [
10
10
  {
11
- command: "/register [domain]",
11
+ skill: "/register [domain]",
12
12
  triggers: ["/register"],
13
13
  handler: handleEns,
14
14
  description:
@@ -21,8 +21,7 @@ export const skills: SkillGroup[] = [
21
21
  },
22
22
  },
23
23
  {
24
- command: "/exists",
25
- adminOnly: true,
24
+ skill: "/exists",
26
25
  examples: ["/exists"],
27
26
  handler: handleEns,
28
27
  triggers: ["/exists"],
@@ -34,7 +33,7 @@ export const skills: SkillGroup[] = [
34
33
  },
35
34
  },
36
35
  {
37
- command: "/info [domain]",
36
+ skill: "/info [domain]",
38
37
  triggers: ["/info"],
39
38
  handler: handleEns,
40
39
  description:
@@ -47,7 +46,7 @@ export const skills: SkillGroup[] = [
47
46
  },
48
47
  },
49
48
  {
50
- command: "/renew [domain]",
49
+ skill: "/renew [domain]",
51
50
  triggers: ["/renew"],
52
51
  handler: handleEns,
53
52
  description:
@@ -60,7 +59,7 @@ export const skills: SkillGroup[] = [
60
59
  },
61
60
  },
62
61
  {
63
- command: "/check [domain]",
62
+ skill: "/check [domain]",
64
63
  triggers: ["/check"],
65
64
  handler: handleEns,
66
65
  examples: ["/check vitalik.eth", "/check fabri.base.eth"],
@@ -72,7 +71,7 @@ export const skills: SkillGroup[] = [
72
71
  },
73
72
  },
74
73
  {
75
- command: "/cool [domain]",
74
+ skill: "/cool [domain]",
76
75
  triggers: ["/cool"],
77
76
  examples: ["/cool vitalik.eth"],
78
77
  handler: handleEns,
@@ -84,7 +83,7 @@ export const skills: SkillGroup[] = [
84
83
  },
85
84
  },
86
85
  {
87
- command: "/reset",
86
+ skill: "/reset",
88
87
  triggers: ["/reset"],
89
88
  examples: ["/reset"],
90
89
  handler: handleEns,
@@ -92,7 +91,7 @@ export const skills: SkillGroup[] = [
92
91
  params: {},
93
92
  },
94
93
  {
95
- command: "/tip [address]",
94
+ skill: "/tip [address]",
96
95
  description: "Show a URL for tipping a domain owner.",
97
96
  triggers: ["/tip"],
98
97
  handler: handleEns,
@@ -1,9 +1,14 @@
1
1
  import { run, HandlerContext } from "@xmtp/message-kit";
2
+ import { skills } from "./skills.js";
3
+ run(
4
+ async (context: HandlerContext) => {
5
+ // Get the message and the address from the sender
6
+ const { content, sender } = context.message;
2
7
 
3
- run(async (context: HandlerContext) => {
4
- // Get the message and the address from the sender
5
- const { content, sender } = context.message;
6
-
7
- // To reply, just call `reply` on the HandlerContext
8
- await context.send(`gm`);
9
- });
8
+ // To reply, just call `reply` on the HandlerContext
9
+ await context.send(`gm`);
10
+ },
11
+ {
12
+ skills: skills,
13
+ },
14
+ );
@@ -0,0 +1,29 @@
1
+ import type { SkillGroup } from "@xmtp/message-kit";
2
+
3
+ export const skills: SkillGroup[] = [
4
+ {
5
+ name: "Group bot",
6
+ tag: "@bot",
7
+ description: "Group bot for tipping and transactions.",
8
+ skills: [
9
+ {
10
+ skill: "/tip [usernames] [amount] [token]",
11
+ triggers: ["/tip"],
12
+ examples: ["/tip @vitalik 10 usdc"],
13
+ description: "Tip users in a specified token.",
14
+ handler: undefined,
15
+ params: {
16
+ username: {
17
+ default: "",
18
+ plural: true,
19
+ type: "username",
20
+ },
21
+ amount: {
22
+ default: 10,
23
+ type: "number",
24
+ },
25
+ },
26
+ },
27
+ ],
28
+ },
29
+ ];
@@ -1,3 +1,2 @@
1
1
  KEY= # the private key of the bot wallet
2
- OPEN_AI_API_KEY= # openai api key
3
- STACK_API_KEY= # stack api key
2
+ OPEN_AI_API_KEY= # openai api key
@@ -1,68 +1,31 @@
1
- import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
2
- import { textGeneration } from "../lib/gpt.js";
3
-
4
- export async function handler(context: HandlerContext) {
5
- if (!process?.env?.OPEN_AI_API_KEY) {
6
- console.warn("No OPEN_AI_API_KEY found in .env");
7
- return;
8
- }
1
+ import { run, HandlerContext } from "@xmtp/message-kit";
2
+ import { textGeneration, processMultilineResponse } from "@xmtp/message-kit";
3
+ import { agent_prompt } from "../prompt.js";
4
+ import { getUserInfo } from "@xmtp/message-kit";
9
5
 
6
+ run(async (context: HandlerContext) => {
10
7
  const {
11
8
  message: {
9
+ content: { text, params },
12
10
  sender,
13
- content: { content, params },
14
11
  },
15
- skill,
16
12
  } = context;
17
13
 
18
- const systemPrompt = generateSystemPrompt(context);
19
14
  try {
20
- let userPrompt = params?.prompt ?? content;
21
-
15
+ let userPrompt = params?.prompt ?? text;
16
+ const userInfo = await getUserInfo(sender.address);
17
+ if (!userInfo) {
18
+ console.log("User info not found");
19
+ return;
20
+ }
22
21
  const { reply } = await textGeneration(
23
22
  sender.address,
24
23
  userPrompt,
25
- systemPrompt,
24
+ await agent_prompt(userInfo),
26
25
  );
27
- skill(reply);
26
+ await processMultilineResponse(sender.address, reply, context);
28
27
  } catch (error) {
29
28
  console.error("Error during OpenAI call:", error);
30
- await context.reply("An error occurred while processing your request.");
29
+ await context.send("An error occurred while processing your request.");
31
30
  }
32
- }
33
-
34
- function generateSystemPrompt(context: HandlerContext) {
35
- const {
36
- members,
37
- skills,
38
- message: { sender },
39
- } = context;
40
-
41
- const systemPrompt = `
42
- ### Context
43
-
44
- You are a helpful bot agent that lives inside a web3 messaging group that helps interpret user requests and execute commands.
45
- #### Users
46
- ${JSON.stringify(
47
- members?.map((member: AbstractedMember) => ({
48
- ...member,
49
- username: `@${member.accountAddresses[0]}`,
50
- })),
51
- )}\n
52
- #### Commands
53
- ${JSON.stringify(skills)}\n
54
- The message was sent by @${sender?.address}
55
-
56
- ### Examples
57
- prompt /agent tip alix and bo
58
- reply /tip @alix @bo 10
59
-
60
- Important:
61
- - If a user asks jokes, make jokes about web3 devs\n
62
- - If the user asks about performing an action and you can think of a command that would help, answer directly with the command and nothing else.
63
- - Populate the command with the correct or random values. Always return skills with real values only, using usernames with @ and excluding addresses.\n
64
- - If the user asks a question or makes a statement that does not clearly map to a command, respond with helpful information or a clarification question.\n
65
- - If the user is grateful, respond asking for a tip in a playful manner.
66
- `;
67
- return systemPrompt;
68
- }
31
+ });
@@ -1,14 +1,13 @@
1
1
  import { HandlerContext } from "@xmtp/message-kit";
2
2
 
3
- // Handler function to process game-related commands
3
+ // Handler function to process game-related
4
4
  export async function handler(context: HandlerContext) {
5
5
  const {
6
6
  message: {
7
- content: { command, params },
7
+ content: { skill, params, text },
8
8
  },
9
9
  } = context;
10
- if (!command) {
11
- const { content: text } = context?.message?.content;
10
+ if (!skill) {
12
11
  if (text === "🔎" || text === "🔍") {
13
12
  // Send the URL for the requested game
14
13
  context.reply("https://framedl.xyz/");
@@ -36,7 +35,7 @@ export async function handler(context: HandlerContext) {
36
35
  default:
37
36
  // Inform the user about unrecognized skills and provide available options
38
37
  context.send(
39
- "Command not recognized. Available games: wordle, slot, or help.",
38
+ "Skill not recognized. Available games: wordle, slot, or help.",
40
39
  );
41
40
  }
42
41
  }
@@ -3,22 +3,26 @@ import { HandlerContext } from "@xmtp/message-kit";
3
3
  export async function handler(context: HandlerContext) {
4
4
  const {
5
5
  skills,
6
- group,
7
6
  message: {
8
- content: { command },
7
+ content: { skill },
9
8
  },
9
+ group,
10
10
  } = context;
11
11
 
12
- if (command == "help") {
12
+ if (skill == "help") {
13
13
  const intro =
14
14
  "Available experiences:\n" +
15
15
  skills
16
16
  ?.flatMap((app) => app.skills)
17
- .map((skill) => `${skill.command} - ${skill.description}`)
17
+ .map((skill) => `${skill.skill} - ${skill.description}`)
18
18
  .join("\n") +
19
19
  "\nUse these skills to interact with specific apps.";
20
20
  context.send(intro);
21
- } else if (command == "id") {
22
- context.send(context.group?.id);
21
+ } else if (skill == "id") {
22
+ if (!group?.id) {
23
+ context.send("This skill only works in group chats.");
24
+ return;
25
+ }
26
+ context.send(group?.id);
23
27
  }
24
28
  }