create-message-kit 1.0.15 → 1.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. package/index.js +12 -3
  2. package/package.json +2 -2
  3. package/templates/agent/.env.example +2 -0
  4. package/{examples/one-to-one → templates/agent}/package.json +2 -4
  5. package/templates/agent/src/handler/ens.ts +211 -0
  6. package/templates/agent/src/index.ts +14 -0
  7. package/{examples/group → templates/agent}/src/lib/openai.ts +42 -4
  8. package/templates/agent/src/lib/resolver.ts +126 -0
  9. package/templates/agent/src/prompt.ts +81 -0
  10. package/templates/agent/src/skills.ts +93 -0
  11. package/templates/gm/.env.example +1 -0
  12. package/{examples → templates}/gm/package.json +1 -0
  13. package/templates/group/.env.example +3 -0
  14. package/{examples → templates}/group/package.json +1 -0
  15. package/{examples → templates}/group/src/handler/agent.ts +18 -10
  16. package/{examples → templates}/group/src/handler/game.ts +2 -2
  17. package/{examples → templates}/group/src/handler/loyalty.ts +3 -11
  18. package/{examples → templates}/group/src/handler/splitpayment.ts +18 -11
  19. package/{examples → templates}/group/src/handler/tipping.ts +10 -6
  20. package/{examples → templates}/group/src/handler/transaction.ts +11 -40
  21. package/templates/group/src/index.ts +57 -0
  22. package/templates/group/src/lib/openai.ts +137 -0
  23. package/templates/group/src/lib/resolver.ts +126 -0
  24. package/{examples/group/src/commands.ts → templates/group/src/skills.ts} +24 -26
  25. package/examples/gm/.env.example +0 -1
  26. package/examples/group/.env.example +0 -3
  27. package/examples/group/src/index.ts +0 -68
  28. package/examples/one-to-one/.env.example +0 -2
  29. package/examples/one-to-one/src/index.ts +0 -72
  30. package/examples/one-to-one/src/lib/cron.ts +0 -34
  31. package/examples/one-to-one/src/lib/redis.ts +0 -15
  32. /package/{examples → templates}/gm/src/index.ts +0 -0
  33. /package/{examples → templates}/group/src/lib/stack.ts +0 -0
package/index.js CHANGED
@@ -19,7 +19,16 @@ program
19
19
  .name("byob")
20
20
  .description("CLI to initialize projects")
21
21
  .action(async () => {
22
- intro(pc.red(`Welcome to MessageKit CLI v${version}!`));
22
+ log.message(`\x1b[38;2;250;105;119m\
23
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
24
+
25
+ ███╗ ███╗███████╗███████╗███████╗ █████╗ ██████╗ ███████╗██╗ ██╗██╗████████╗
26
+ ████╗ ████║██╔════╝██╔════╝██╔════╝██╔══██╗██╔════╝ ██╔════╝██║ ██╔╝██║╚══██╔══╝
27
+ ██╔████╔██║█████╗ ███████╗███████╗███████║██║ ███╗█████╗ █████╔╝ ██║ ██║
28
+ ██║╚██╔╝██║██╔══╝ ╚════██║╚════██║██╔══██║██║ ██║██╔══╝ ██╔═██╗ ██║ ██║
29
+ ██║ ╚═╝ ██║███████╗███████║███████║██║ ██║╚██████╔╝███████╗██║ ██╗██║ ██║
30
+ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
31
+ Powered by XMTP \x1b[0m`);
23
32
 
24
33
  const { templateType, displayName, destDir } = await gatherProjectInfo();
25
34
 
@@ -57,7 +66,7 @@ program.parse(process.argv);
57
66
  async function gatherProjectInfo() {
58
67
  const templateOptions = [
59
68
  { value: "gm", label: "GM" },
60
- { value: "one-to-one", label: "One-to-One" },
69
+ { value: "agent", label: "Agent" },
61
70
  { value: "group", label: "Group" },
62
71
  ];
63
72
 
@@ -70,7 +79,7 @@ async function gatherProjectInfo() {
70
79
  process.exit(0);
71
80
  }
72
81
 
73
- const templateDir = resolve(__dirname, `./examples/${templateType}`);
82
+ const templateDir = resolve(__dirname, `./templates/${templateType}`);
74
83
 
75
84
  // Ensure the template directory exists
76
85
  if (!fs.existsSync(templateDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-message-kit",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -8,7 +8,7 @@
8
8
  "bin": "index.js",
9
9
  "files": [
10
10
  "index.js",
11
- "examples/**/*"
11
+ "templates/**/*"
12
12
  ],
13
13
  "scripts": {
14
14
  "clean": "rm -rf .turbo && rm -rf node_modules",
@@ -0,0 +1,2 @@
1
+ KEY= # the private key of the wallet
2
+ OPEN_AI_API_KEY= # sk-proj-...
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "one-to-one",
2
+ "name": "agent",
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {
@@ -8,13 +8,11 @@
8
8
  "start": "node dist/index.js"
9
9
  },
10
10
  "dependencies": {
11
- "@redis/client": "^1.5.16",
12
11
  "@xmtp/message-kit": "workspace:*",
13
- "node-cron": "^3.0.3"
12
+ "openai": "^4.65.0"
14
13
  },
15
14
  "devDependencies": {
16
15
  "@types/node": "^20.14.2",
17
- "@types/node-cron": "^3.0.11",
18
16
  "nodemon": "^3.1.3",
19
17
  "typescript": "^5.4.5"
20
18
  },
@@ -0,0 +1,211 @@
1
+ import { HandlerContext } from "@xmtp/message-kit";
2
+ import { getUserInfo, clearInfoCache, isOnXMTP } from "../lib/resolver.js";
3
+ import { textGeneration } from "../lib/openai.js";
4
+ import { processResponseWithSkill } from "../lib/openai.js";
5
+ import { isAddress } from "viem";
6
+ import { ens_agent_prompt } from "../prompt.js";
7
+ import { frameUrl, ensUrl, baseTxUrl } from "../index.js";
8
+ import { clearChatHistories } from "../lib/openai.js";
9
+
10
+ export async function handleEns(context: HandlerContext) {
11
+ const {
12
+ message: {
13
+ content: { command, params, sender },
14
+ },
15
+ } = context;
16
+ if (command == "reset") {
17
+ clear();
18
+ return { code: 200, message: "Conversation reset." };
19
+ } else if (command == "renew") {
20
+ // Destructure and validate parameters for the ens command
21
+ const { domain } = params;
22
+ // Check if the user holds the domain
23
+ if (!domain) {
24
+ return {
25
+ code: 400,
26
+ message: "Missing required parameters. Please provide domain.",
27
+ };
28
+ }
29
+
30
+ const data = await getUserInfo(domain);
31
+
32
+ if (!data || data?.address !== sender?.address) {
33
+ return {
34
+ code: 403,
35
+ message:
36
+ "Looks like this domain is not registered to you. Only the owner can renew it.",
37
+ };
38
+ }
39
+
40
+ // Generate URL for the ens
41
+ let url_ens = frameUrl + "frames/manage?name=" + domain;
42
+ return { code: 200, message: `${url_ens}` };
43
+ } else if (command == "register") {
44
+ // Destructure and validate parameters for the ens command
45
+ const { domain } = params;
46
+
47
+ if (!domain) {
48
+ return {
49
+ code: 400,
50
+ message: "Missing required parameters. Please provide domain.",
51
+ };
52
+ }
53
+ // Generate URL for the ens
54
+ let url_ens = ensUrl + domain;
55
+ return { code: 200, message: `${url_ens}` };
56
+ } else if (command == "info") {
57
+ const { domain } = params;
58
+
59
+ const data = await getUserInfo(domain);
60
+ if (!data) {
61
+ return {
62
+ code: 404,
63
+ message: "Domain not found.",
64
+ };
65
+ }
66
+
67
+ const formattedData = {
68
+ Address: data?.address,
69
+ "Avatar URL": data?.ensInfo?.avatar,
70
+ Description: data?.ensInfo?.description,
71
+ ENS: data?.ensDomain,
72
+ "Primary ENS": data?.ensInfo?.ens_primary,
73
+ GitHub: data?.ensInfo?.github,
74
+ Resolver: data?.ensInfo?.resolverAddress,
75
+ Twitter: data?.ensInfo?.twitter,
76
+ URL: `${ensUrl}${domain}`,
77
+ };
78
+
79
+ let message = "Domain information:\n\n";
80
+ for (const [key, value] of Object.entries(formattedData)) {
81
+ if (value) {
82
+ message += `${key}: ${value}\n`;
83
+ }
84
+ }
85
+ message += `\n\nWould you like to tip the domain owner for getting there first 🤣?`;
86
+ message = message.trim();
87
+ if (
88
+ await isOnXMTP(
89
+ context.v2client,
90
+ data?.ensInfo?.ens,
91
+ data?.ensInfo?.address,
92
+ )
93
+ ) {
94
+ await context.send(
95
+ `Ah, this domains is in XMTP, you can message it directly: https://converse.xyz/dm/${domain}`,
96
+ );
97
+ }
98
+ return { code: 200, message };
99
+ } else if (command == "check") {
100
+ const { domain } = params;
101
+
102
+ if (!domain) {
103
+ return {
104
+ code: 400,
105
+ message: "Please provide a domain name to check.",
106
+ };
107
+ }
108
+
109
+ const data = await getUserInfo(domain);
110
+ if (!data?.address) {
111
+ let message = `Looks like ${domain} is available! Do you want to register it? ${ensUrl}${domain} or would you like to see some cool alternatives?`;
112
+ return {
113
+ code: 200,
114
+ message,
115
+ };
116
+ } else {
117
+ let message = `Looks like ${domain} is already registered!`;
118
+ await context.skill("/cool " + domain);
119
+ return {
120
+ code: 404,
121
+ message,
122
+ };
123
+ }
124
+ } else if (command == "tip") {
125
+ const { address } = params;
126
+ if (!address) {
127
+ return {
128
+ code: 400,
129
+ message: "Please provide an address to tip.",
130
+ };
131
+ }
132
+ const data = await getUserInfo(address);
133
+ let txUrl = `${baseTxUrl}/transaction/?transaction_type=send&buttonName=Tip%20${data?.ensDomain ?? ""}&amount=1&token=USDC&receiver=${
134
+ isAddress(address) ? address : data?.address
135
+ }`;
136
+ console.log(txUrl);
137
+ return {
138
+ code: 200,
139
+ message: txUrl,
140
+ };
141
+ } else if (command == "cool") {
142
+ const { domain } = params;
143
+ //What about these cool alternatives?\
144
+ return {
145
+ code: 200,
146
+ message: `${generateCoolAlternatives(domain)}`,
147
+ };
148
+ }
149
+ }
150
+
151
+ export async function ensAgent(context: HandlerContext) {
152
+ if (!process?.env?.OPEN_AI_API_KEY) {
153
+ console.warn("No OPEN_AI_API_KEY found in .env");
154
+ return;
155
+ }
156
+
157
+ const {
158
+ message: {
159
+ content: { content, params },
160
+ sender,
161
+ },
162
+ group,
163
+ } = context;
164
+
165
+ try {
166
+ let userPrompt = params?.prompt ?? content;
167
+ const userInfo = await getUserInfo(sender.address);
168
+ if (!userInfo) {
169
+ console.log("User info not found");
170
+ return;
171
+ }
172
+ const { ensDomain, converseUsername } = userInfo;
173
+
174
+ const { reply } = await textGeneration(
175
+ sender.address,
176
+ userPrompt,
177
+ await ens_agent_prompt(sender.address, ensDomain, converseUsername),
178
+ group !== undefined,
179
+ );
180
+ await processResponseWithSkill(sender.address, reply, context);
181
+ } catch (error) {
182
+ console.error("Error during OpenAI call:", error);
183
+ await context.send("An error occurred while processing your request.");
184
+ }
185
+ }
186
+
187
+ export const generateCoolAlternatives = (domain: string) => {
188
+ const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
189
+ const alternatives = [];
190
+ for (let i = 0; i < 5; i++) {
191
+ const randomPosition = Math.random() < 0.5;
192
+ const baseDomain = domain.replace(/\.eth$/, ""); // Remove any existing .eth suffix
193
+ alternatives.push(
194
+ randomPosition
195
+ ? `${suffixes[i]}${baseDomain}.eth`
196
+ : `${baseDomain}${suffixes[i]}.eth`,
197
+ );
198
+ }
199
+
200
+ const cool_alternativesFormat = alternatives
201
+ .map(
202
+ (alternative: string, index: number) => `${index + 1}. ${alternative} ✨`,
203
+ )
204
+ .join("\n");
205
+ return cool_alternativesFormat;
206
+ };
207
+
208
+ export async function clear() {
209
+ clearChatHistories();
210
+ clearInfoCache();
211
+ }
@@ -0,0 +1,14 @@
1
+ import { run, HandlerContext } from "@xmtp/message-kit";
2
+ import { ensAgent } from "./handler/ens.js";
3
+
4
+ export const frameUrl = "https://ens.steer.fun/";
5
+ export const ensUrl = "https://app.ens.domains/";
6
+ export const baseTxUrl = "https://base-tx-frame.vercel.app";
7
+
8
+ run(async (context: HandlerContext) => {
9
+ const { group, message } = context;
10
+ /*All the commands are handled through the commands file*/
11
+ /* If its just text, it will be handled by the ensAgent*/
12
+ /* If its a group message, it will be handled by the groupAgent*/
13
+ await ensAgent(context);
14
+ });
@@ -1,5 +1,4 @@
1
1
  import dotenv from "dotenv";
2
- import { response } from "express";
3
2
  dotenv.config();
4
3
 
5
4
  import OpenAI from "openai";
@@ -7,12 +6,17 @@ const openai = new OpenAI({
7
6
  apiKey: process.env.OPEN_AI_API_KEY,
8
7
  });
9
8
 
9
+ export type ChatHistoryEntry = { role: string; content: string };
10
+ export type ChatHistories = Record<string, ChatHistoryEntry[]>;
11
+
12
+ let chatHistories: ChatHistories = {};
10
13
  export async function textGeneration(
14
+ address: string,
11
15
  userPrompt: string,
12
16
  systemPrompt: string,
13
- chatHistory?: any[],
17
+ isGroup: boolean = false,
14
18
  ) {
15
- let messages = chatHistory ? [...chatHistory] : []; // Start with existing chat history
19
+ let messages = chatHistories[address] || [];
16
20
  if (messages.length === 0) {
17
21
  messages.push({
18
22
  role: "system",
@@ -34,7 +38,7 @@ export async function textGeneration(
34
38
  content: reply || "No response from OpenAI.",
35
39
  });
36
40
  const cleanedReply = responseParser(reply as string);
37
-
41
+ if (!isGroup) chatHistories[address] = messages;
38
42
  return { reply: cleanedReply, history: messages };
39
43
  } catch (error) {
40
44
  console.error("Failed to fetch from OpenAI:", error);
@@ -79,6 +83,35 @@ export async function vision(imageData: Uint8Array, systemPrompt: string) {
79
83
  }
80
84
  }
81
85
 
86
+ export async function processResponseWithSkill(
87
+ address: string,
88
+ reply: string,
89
+ context: any,
90
+ ) {
91
+ let messages = reply
92
+ .split("\n")
93
+ .map((message: string) => responseParser(message))
94
+ .filter((message): message is string => message.length > 0);
95
+
96
+ console.log(messages);
97
+ for (const message of messages) {
98
+ if (message.startsWith("/")) {
99
+ const response = await context.skill(message);
100
+ if (response && response.message) {
101
+ let msg = responseParser(response.message);
102
+
103
+ chatHistories[address].push({
104
+ role: "system",
105
+ content: msg,
106
+ });
107
+
108
+ await context.send(response.message);
109
+ }
110
+ } else {
111
+ await context.send(message);
112
+ }
113
+ }
114
+ }
82
115
  export function responseParser(message: string) {
83
116
  let trimmedMessage = message;
84
117
  // Remove bold and underline markdown
@@ -95,5 +128,10 @@ export function responseParser(message: string) {
95
128
  trimmedMessage = trimmedMessage?.replace(/^\s+|\s+$/g, "");
96
129
  // Remove any remaining leading or trailing whitespace
97
130
  trimmedMessage = trimmedMessage.trim();
131
+
98
132
  return trimmedMessage;
99
133
  }
134
+
135
+ export const clearChatHistories = () => {
136
+ chatHistories = {};
137
+ };
@@ -0,0 +1,126 @@
1
+ import { Client } from "@xmtp/xmtp-js";
2
+ import { isAddress } from "viem";
3
+
4
+ export const converseEndpointURL =
5
+ "https://converse-website-git-endpoit-ephemerahq.vercel.app";
6
+ //export const converseEndpointURL = "http://localhost:3000";
7
+
8
+ export type InfoCache = Map<string, UserInfo>;
9
+ export type ConverseProfile = {
10
+ address: string | null;
11
+ onXmtp: boolean;
12
+ avatar: string | null;
13
+ formattedName: string | null;
14
+ name: string | null;
15
+ };
16
+ export type UserInfo = {
17
+ ensDomain?: string | undefined;
18
+ address?: string | undefined;
19
+ converseUsername?: string | undefined;
20
+ ensInfo?: EnsData | undefined;
21
+ avatar?: string | undefined;
22
+ };
23
+ export interface EnsData {
24
+ address?: string;
25
+ avatar?: string;
26
+ avatar_small?: string;
27
+ converse?: string;
28
+ avatar_url?: string;
29
+ contentHash?: string;
30
+ description?: string;
31
+ ens?: string;
32
+ ens_primary?: string;
33
+ github?: string;
34
+ resolverAddress?: string;
35
+ twitter?: string;
36
+ url?: string;
37
+ wallets?: {
38
+ eth?: string;
39
+ };
40
+ }
41
+
42
+ let infoCache: InfoCache = new Map();
43
+
44
+ export const clearInfoCache = () => {
45
+ infoCache.clear();
46
+ };
47
+ export const getUserInfo = async (
48
+ key: string,
49
+ clientAddress?: string,
50
+ ): Promise<UserInfo | null> => {
51
+ let data: UserInfo = infoCache.get(key) || {
52
+ ensDomain: undefined,
53
+ address: undefined,
54
+ converseUsername: undefined,
55
+ ensInfo: undefined,
56
+ };
57
+ //console.log("Getting user info", key, clientAddress);
58
+ if (isAddress(clientAddress || "")) {
59
+ data.address = clientAddress;
60
+ } else if (isAddress(key || "")) {
61
+ data.address = key;
62
+ } else if (key.includes(".eth")) {
63
+ data.ensDomain = key;
64
+ } else if (key == "@user" || key == "@me" || key == "@bot") {
65
+ data.address = clientAddress;
66
+ data.ensDomain = key.replace("@", "") + ".eth";
67
+ data.converseUsername = key.replace("@", "");
68
+ } else if (key == "@alix") {
69
+ data.address = "0x3a044b218BaE80E5b9E16609443A192129A67BeA";
70
+ data.converseUsername = "alix";
71
+ } else if (key == "@bo") {
72
+ data.address = "0xbc3246461ab5e1682baE48fa95172CDf0689201a";
73
+ data.converseUsername = "bo";
74
+ } else {
75
+ data.converseUsername = key;
76
+ }
77
+
78
+ let keyToUse = data.address || data.ensDomain || data.converseUsername;
79
+ let cacheData = keyToUse && infoCache.get(keyToUse);
80
+ if (cacheData) {
81
+ //console.log("Getting user info", keyToUse, cacheData);
82
+ return cacheData;
83
+ } else {
84
+ //console.log("Getting user info", keyToUse, data);
85
+ }
86
+
87
+ if (keyToUse?.includes(".eth")) {
88
+ const response = await fetch(`https://ensdata.net/${keyToUse}`);
89
+ const ensData: EnsData = (await response.json()) as EnsData;
90
+ //console.log("Ens data", ensData);
91
+ if (ensData) {
92
+ data.ensInfo = ensData;
93
+ data.ensDomain = ensData?.ens;
94
+ data.address = ensData?.address;
95
+ }
96
+ } else if (keyToUse) {
97
+ keyToUse = keyToUse.replace("@", "");
98
+ const response = await fetch(`${converseEndpointURL}/profile/${keyToUse}`, {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ Accept: "application/json",
103
+ },
104
+ body: JSON.stringify({
105
+ peer: keyToUse,
106
+ }),
107
+ });
108
+ const converseData = (await response.json()) as ConverseProfile;
109
+ if (process.env.MSG_LOG)
110
+ console.log("Converse data", keyToUse, converseData);
111
+ data.converseUsername =
112
+ converseData?.formattedName || converseData?.name || undefined;
113
+ data.address = converseData?.address || undefined;
114
+ data.avatar = converseData?.avatar || undefined;
115
+ }
116
+ if (data.address) infoCache.set(data.address, data);
117
+ return data;
118
+ };
119
+ export const isOnXMTP = async (
120
+ client: Client,
121
+ domain: string | undefined,
122
+ address: string | undefined,
123
+ ) => {
124
+ if (domain == "fabri.eth") return false;
125
+ if (address) return (await client.canMessage([address])).length > 0;
126
+ };
@@ -0,0 +1,81 @@
1
+ export async function ens_agent_prompt(
2
+ address: string,
3
+ domain?: string,
4
+ name?: string,
5
+ converseUsername?: string,
6
+ txUrl?: string,
7
+ ) {
8
+ const systemPrompt = `You are a helpful and playful agent that lives inside a web3 messaging app called Converse.
9
+ - You can respond with multiple messages if needed. Each message should be separated by a newline character.
10
+ - You can trigger commands by only sending the command in a newline message.
11
+ - Never announce actions without using a command separated by a newline character.
12
+ - Only provide answers based on verified information.
13
+ - Dont answer in markdown format, just answer in plaintext.
14
+ - Do not make guesses or assumptions
15
+ - CHECK that you are not missing a command
16
+
17
+ User context:
18
+ - Users address is: ${address}
19
+ ${domain != undefined ? `- User ENS domain is: ${domain}` : ""}
20
+ ${name != undefined ? `- Converse username is: ${name}` : ""}
21
+
22
+ ## Task
23
+ - Start by fetch their domain from or Convese username
24
+ - Call the user by their name or domain, in case they have one
25
+ - Ask for a name (if they don't have one) so you can suggest domains.
26
+
27
+ Commands:
28
+ - "/info [domain]": To get information about a domain use this command.
29
+ - "/check [domain]": To check to see if a domain is available use this command.
30
+ - "/register [domain]": To register a domain use this command. This will return a url pointing to the registration page.
31
+ - "/renew [domain]": To trigger renewal of a domain use this command. This will return a url with a button to renew the domain.
32
+ - "/tip [address]": To tip a domain or address use this command. This will return a url with a button to send the tip
33
+ - "/cool [domain]": To get cool alternatives for a .eth domain use this command.
34
+
35
+ Examples:
36
+ - /check ${domain}
37
+ - /info nick.eth
38
+ - /register vitalik.eth
39
+ - /renew fabri.base.eth
40
+ - /tip 0xf0EA7663233F99D0c12370671abBb6Cca980a490
41
+ - /cool vitalik.eth
42
+
43
+ ## Example response:
44
+
45
+ 1. Check if the user does not have a ENS domain
46
+ Hey ${name}! it looks like you don't have a ENS domain yet! \n\Let me start by checking your Converse username ${converseUsername}.eth\n/check ${converseUsername}.eth
47
+
48
+ 2. If the user has a ENS domain
49
+ Hello ${domain} ! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${domain}. Give me a moment.\n/check ${domain}
50
+
51
+ 3. Check if the ENS domain is available
52
+ Hello! I'll help you get your domain.\n Let's start by checking your ENS domain ${domain}. Give me a moment.\n/check ${domain}
53
+
54
+ 4. If the ENS domain is available,
55
+ Looks like ${domain} is available! Would you like to register it?\n/register ${domain}\n or I can suggest some cool alternatives? Le me know!
56
+
57
+ 5. If the ENS domain is already registered, let me suggest 5 cool alternatives
58
+ Looks like ${domain} is already registered!\n What about these cool alternatives?\n/cool ${domain}
59
+
60
+ 6. If the user wants to register a ENS domain, use the command "/register [domain]"
61
+ Looks like ${domain} is available! Let me help you register it\n/register ${domain}
62
+
63
+ 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
64
+ Here is the url to send the tip:\n/tip ${domain}
65
+
66
+ 8. If the user wants to get information about the ENS domain, use the command "/info [domain]"
67
+ Hello! I'll help you get info about ${domain}.\n Give me a moment.\n/info ${domain}
68
+
69
+ 9. If the user wants to renew their domain, use the command "/renew [domain]"
70
+ Hello! I'll help you get your ENS domain.\n Let's start by checking your ENS domain ${domain}. Give me a moment.\n/renew ${domain}
71
+
72
+ 10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
73
+ Here are some cool suggestions for your domain.\n/cool ${domain}
74
+
75
+ ## Most common bug
76
+ Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
77
+ But you forgot to add the command at the end of the message.
78
+ You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
79
+ `;
80
+ return systemPrompt;
81
+ }
@@ -0,0 +1,93 @@
1
+ import type { SkillGroup } from "@xmtp/message-kit";
2
+ import { handleEns } from "./handler/ens.js";
3
+
4
+ export const skills: SkillGroup[] = [
5
+ {
6
+ name: "Ens Domain Bot",
7
+ description: "Register ENS domains.",
8
+ skills: [
9
+ {
10
+ command: "/register [domain]",
11
+ triggers: ["/register", "@ensbot"],
12
+ handler: handleEns,
13
+ description:
14
+ "Register a new ENS domain. Returns a URL to complete the registration process.",
15
+ example: "/register vitalik.eth",
16
+ params: {
17
+ domain: {
18
+ type: "string",
19
+ },
20
+ },
21
+ },
22
+ {
23
+ command: "/info [domain]",
24
+ triggers: ["/info", "@ensbot"],
25
+ handler: handleEns,
26
+ description:
27
+ "Get detailed information about an ENS domain including owner, expiry date, and resolver.",
28
+ example: "/info nick.eth",
29
+ params: {
30
+ domain: {
31
+ type: "string",
32
+ },
33
+ },
34
+ },
35
+ {
36
+ command: "/renew [domain]",
37
+ triggers: ["/renew", "@ensbot"],
38
+ handler: handleEns,
39
+ description:
40
+ "Extend the registration period of your ENS domain. Returns a URL to complete the renewal.",
41
+ example: "/renew fabri.base.eth",
42
+ params: {
43
+ domain: {
44
+ type: "string",
45
+ },
46
+ },
47
+ },
48
+ {
49
+ command: "/check [domain] [cool_alternatives]",
50
+ triggers: ["/check"],
51
+ handler: handleEns,
52
+ description: "Check if a domain is available.",
53
+ params: {
54
+ domain: {
55
+ type: "string",
56
+ },
57
+ cool_alternatives: {
58
+ type: "quoted",
59
+ },
60
+ },
61
+ },
62
+ {
63
+ command: "/cool [domain]",
64
+ triggers: ["/cool"],
65
+ handler: handleEns,
66
+ description: "Get cool alternatives for a .eth domain.",
67
+ params: {
68
+ domain: {
69
+ type: "string",
70
+ },
71
+ },
72
+ },
73
+ {
74
+ command: "/reset",
75
+ triggers: ["/reset"],
76
+ handler: handleEns,
77
+ description: "Reset the conversation.",
78
+ params: {},
79
+ },
80
+ {
81
+ command: "/tip [address]",
82
+ description: "Show a URL for tipping a domain owner.",
83
+ triggers: ["/tip"],
84
+ handler: handleEns,
85
+ params: {
86
+ address: {
87
+ type: "string",
88
+ },
89
+ },
90
+ },
91
+ ],
92
+ },
93
+ ];
@@ -0,0 +1 @@
1
+ KEY= # the private key of the bot wallet