create-message-kit 1.1.8 → 1.1.9-beta.1
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 +32 -11
- package/package.json +1 -1
- package/templates/agent/src/{handler/ens.ts → handler.ts} +6 -7
- package/templates/agent/src/index.ts +51 -27
- package/templates/agent/src/skills.ts +1 -20
- package/templates/ens-agent-pro/.env.example +2 -0
- package/templates/ens-agent-pro/src/index.ts +90 -0
- package/templates/ens-agent-pro/src/prompt.ts +19 -0
- package/templates/ens-agent-pro/src/skills.ts +233 -0
- package/templates/gpt/.env.example +2 -0
- package/templates/gpt/src/index.ts +38 -0
- package/templates/gpt/src/prompt.ts +20 -0
- package/templates/group/src/handler/payment.ts +3 -3
- package/templates/group/src/index.ts +1 -7
- package/templates/group/src/prompt.ts +7 -24
- package/templates/group/src/skills.ts +6 -6
- package/templates/agent/src/prompt.ts +0 -69
- package/templates/gm/.env.example +0 -1
- package/templates/gm/src/index.ts +0 -5
package/index.js
CHANGED
@@ -79,19 +79,25 @@ async function addPackagejson(destDir, name, pkgManager) {
|
|
79
79
|
dependencies: {
|
80
80
|
"@xmtp/message-kit": "latest",
|
81
81
|
},
|
82
|
+
devDependencies: {
|
83
|
+
"@types/node": "latest",
|
84
|
+
typescript: "latest",
|
85
|
+
},
|
82
86
|
engines: {
|
83
87
|
node: ">=20",
|
84
88
|
},
|
85
89
|
};
|
86
90
|
|
87
|
-
if (pkgManager.
|
88
|
-
packageTemplate.packageManager = `${pkgManager}`;
|
91
|
+
if (pkgManager.includes("yarn")) {
|
92
|
+
//packageTemplate.packageManager = `${pkgManager}`;
|
89
93
|
// Add .yarnrc.yml to disable PnP mode
|
90
|
-
fs.writeFileSync(
|
91
|
-
resolve(destDir, ".yarnrc.yml"),
|
92
|
-
"nodeLinker: node-modules\n",
|
93
|
-
);
|
94
94
|
}
|
95
|
+
|
96
|
+
fs.writeFileSync(
|
97
|
+
resolve(destDir, ".yarnrc.yml"),
|
98
|
+
"nodeLinker: node-modules\n",
|
99
|
+
);
|
100
|
+
|
95
101
|
fs.writeJsonSync(resolve(destDir, "package.json"), packageTemplate, {
|
96
102
|
spaces: 2,
|
97
103
|
});
|
@@ -99,9 +105,10 @@ async function addPackagejson(destDir, name, pkgManager) {
|
|
99
105
|
|
100
106
|
async function gatherProjectInfo() {
|
101
107
|
const templateOptions = [
|
102
|
-
{ value: "
|
108
|
+
{ value: "gpt", label: "GPT" },
|
103
109
|
{ value: "agent", label: "Agent" },
|
104
110
|
{ value: "group", label: "Group" },
|
111
|
+
{ value: "ens-agent-pro", label: "Ens Agent Pro" },
|
105
112
|
];
|
106
113
|
|
107
114
|
const templateType = await select({
|
@@ -201,11 +208,27 @@ yarn-error.log*
|
|
201
208
|
|
202
209
|
fs.writeFileSync(resolve(destDir, ".gitignore"), gitignoreContent.trim());
|
203
210
|
}
|
204
|
-
|
205
211
|
async function detectPackageManager() {
|
206
212
|
try {
|
207
|
-
|
213
|
+
// Check if running through bun create
|
214
|
+
if (process.env.BUN_CREATE === "true" || process.env._?.includes("bun")) {
|
215
|
+
return "bun";
|
216
|
+
}
|
217
|
+
|
208
218
|
const userAgent = process.env.npm_config_user_agent;
|
219
|
+
|
220
|
+
// Check if running through npm init
|
221
|
+
if (userAgent?.startsWith("npm")) {
|
222
|
+
return "npm";
|
223
|
+
}
|
224
|
+
|
225
|
+
// Check for Bun in process.argv
|
226
|
+
if (process.argv.some((arg) => arg.includes("bun"))) {
|
227
|
+
return "bun";
|
228
|
+
}
|
229
|
+
|
230
|
+
// Fallback to detect for other cases
|
231
|
+
const pkgManager = await detect();
|
209
232
|
let version = "";
|
210
233
|
|
211
234
|
if (userAgent && pkgManager === "yarn") {
|
@@ -217,11 +240,9 @@ async function detectPackageManager() {
|
|
217
240
|
|
218
241
|
return pkgManager + version;
|
219
242
|
} catch (error) {
|
220
|
-
// Fallback to npm if detection fails
|
221
243
|
return "npm";
|
222
244
|
}
|
223
245
|
}
|
224
|
-
|
225
246
|
function kebabcase(str) {
|
226
247
|
return str
|
227
248
|
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
package/package.json
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
import { HandlerContext, SkillResponse } from "@xmtp/message-kit";
|
2
2
|
import { getUserInfo, clearInfoCache, isOnXMTP } from "@xmtp/message-kit";
|
3
|
-
import { isAddress } from "viem";
|
4
3
|
import { clearMemory } from "@xmtp/message-kit";
|
5
4
|
|
6
5
|
export const frameUrl = "https://ens.steer.fun/";
|
7
6
|
export const ensUrl = "https://app.ens.domains/";
|
8
|
-
export const
|
7
|
+
export const txpayUrl = "https://txpay.vercel.app";
|
9
8
|
|
10
9
|
export async function handleEns(
|
11
10
|
context: HandlerContext,
|
@@ -56,6 +55,7 @@ export async function handleEns(
|
|
56
55
|
}
|
57
56
|
// Generate URL for the ens
|
58
57
|
let url_ens = ensUrl + domain;
|
58
|
+
context.send(`${url_ens}`);
|
59
59
|
return { code: 200, message: `${url_ens}` };
|
60
60
|
} else if (skill == "info") {
|
61
61
|
const { domain } = params;
|
@@ -128,13 +128,12 @@ export async function handleEns(
|
|
128
128
|
};
|
129
129
|
}
|
130
130
|
const data = await getUserInfo(address);
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
console.log(txUrl);
|
131
|
+
|
132
|
+
let sendUrl = `${txpayUrl}/?&amount=1&token=USDC&receiver=${address}`;
|
133
|
+
|
135
134
|
return {
|
136
135
|
code: 200,
|
137
|
-
message:
|
136
|
+
message: sendUrl,
|
138
137
|
};
|
139
138
|
} else if (skill == "cool") {
|
140
139
|
const { domain } = params;
|
@@ -1,31 +1,55 @@
|
|
1
1
|
import { run, HandlerContext } from "@xmtp/message-kit";
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
import {
|
2
|
+
import { agentRun } from "@xmtp/message-kit";
|
3
|
+
import { skills } from "./skills.js";
|
4
|
+
import { defaultPromptTemplate } from "@xmtp/message-kit";
|
5
|
+
|
6
|
+
export async function agent_prompt(senderAddress: string) {
|
7
|
+
let fineTuning = `
|
8
|
+
## Example responses:
|
9
|
+
|
10
|
+
1. Check if the user does not have a ENS domain
|
11
|
+
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
|
12
|
+
|
13
|
+
2. If the user has a ENS domain
|
14
|
+
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}
|
15
|
+
|
16
|
+
3. Check if the ENS domain is available
|
17
|
+
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}
|
18
|
+
|
19
|
+
4. If the ENS domain is available,
|
20
|
+
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!
|
21
|
+
|
22
|
+
5. If the ENS domain is already registered, let me suggest 5 cool alternatives
|
23
|
+
Looks like {ENS_DOMAIN} is already registered!\n What about these cool alternatives?\n/cool {ENS_DOMAIN}
|
24
|
+
|
25
|
+
6. If the user wants to register a ENS domain, use the command "/register [domain]"
|
26
|
+
Looks like {ENS_DOMAIN} is available! Let me help you register it\n/register {ENS_DOMAIN}
|
27
|
+
|
28
|
+
7. If the user wants to directly to tip to the ENS domain owner, use directly the command "/tip [address]", this will return a url but a button to send the tip
|
29
|
+
Here is the url to send the tip:\n/tip 0x...
|
30
|
+
|
31
|
+
8. If the user wants to get information about the ENS domain, use the command "/info [domain]"
|
32
|
+
Hello! I'll help you get info about {ENS_DOMAIN}.\n Give me a moment.\n/info {ENS_DOMAIN}
|
33
|
+
|
34
|
+
9. If the user wants to renew their domain, use the command "/renew [domain]"
|
35
|
+
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}
|
36
|
+
|
37
|
+
10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
|
38
|
+
Here are some cool suggestions for your domain.\n/cool {ENS_DOMAIN}
|
39
|
+
|
40
|
+
## Most common bugs
|
41
|
+
|
42
|
+
1. Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
|
43
|
+
But you forgot to add the command at the end of the message.
|
44
|
+
You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
|
45
|
+
`;
|
46
|
+
|
47
|
+
return defaultPromptTemplate(fineTuning, senderAddress, skills, "@ens");
|
48
|
+
}
|
5
49
|
|
6
50
|
run(async (context: HandlerContext) => {
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
},
|
12
|
-
} = context;
|
13
|
-
|
14
|
-
try {
|
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
|
-
}
|
21
|
-
const { reply } = await textGeneration(
|
22
|
-
sender.address,
|
23
|
-
userPrompt,
|
24
|
-
await agent_prompt(userInfo),
|
25
|
-
);
|
26
|
-
await processMultilineResponse(sender.address, reply, context);
|
27
|
-
} catch (error) {
|
28
|
-
console.error("Error during OpenAI call:", error);
|
29
|
-
await context.send("An error occurred while processing your request.");
|
30
|
-
}
|
51
|
+
agentRun(context, async (address: string) => {
|
52
|
+
const result = (await agent_prompt(address)) ?? "No response available";
|
53
|
+
return result;
|
54
|
+
});
|
31
55
|
});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { handleEns } from "./handler
|
1
|
+
import { handleEns } from "./handler.js";
|
2
2
|
import type { SkillGroup } from "@xmtp/message-kit";
|
3
3
|
|
4
4
|
export const skills: SkillGroup[] = [
|
@@ -9,7 +9,6 @@ export const skills: SkillGroup[] = [
|
|
9
9
|
skills: [
|
10
10
|
{
|
11
11
|
skill: "/register [domain]",
|
12
|
-
triggers: ["/register"],
|
13
12
|
handler: handleEns,
|
14
13
|
description:
|
15
14
|
"Register a new ENS domain. Returns a URL to complete the registration process.",
|
@@ -20,21 +19,8 @@ export const skills: SkillGroup[] = [
|
|
20
19
|
},
|
21
20
|
},
|
22
21
|
},
|
23
|
-
{
|
24
|
-
skill: "/exists",
|
25
|
-
examples: ["/exists"],
|
26
|
-
handler: handleEns,
|
27
|
-
triggers: ["/exists"],
|
28
|
-
description: "Check if an address is onboarded.",
|
29
|
-
params: {
|
30
|
-
address: {
|
31
|
-
type: "address",
|
32
|
-
},
|
33
|
-
},
|
34
|
-
},
|
35
22
|
{
|
36
23
|
skill: "/info [domain]",
|
37
|
-
triggers: ["/info"],
|
38
24
|
handler: handleEns,
|
39
25
|
description:
|
40
26
|
"Get detailed information about an ENS domain including owner, expiry date, and resolver.",
|
@@ -47,7 +33,6 @@ export const skills: SkillGroup[] = [
|
|
47
33
|
},
|
48
34
|
{
|
49
35
|
skill: "/renew [domain]",
|
50
|
-
triggers: ["/renew"],
|
51
36
|
handler: handleEns,
|
52
37
|
description:
|
53
38
|
"Extend the registration period of your ENS domain. Returns a URL to complete the renewal.",
|
@@ -60,7 +45,6 @@ export const skills: SkillGroup[] = [
|
|
60
45
|
},
|
61
46
|
{
|
62
47
|
skill: "/check [domain]",
|
63
|
-
triggers: ["/check"],
|
64
48
|
handler: handleEns,
|
65
49
|
examples: ["/check vitalik.eth", "/check fabri.base.eth"],
|
66
50
|
description: "Check if a domain is available.",
|
@@ -72,7 +56,6 @@ export const skills: SkillGroup[] = [
|
|
72
56
|
},
|
73
57
|
{
|
74
58
|
skill: "/cool [domain]",
|
75
|
-
triggers: ["/cool"],
|
76
59
|
examples: ["/cool vitalik.eth"],
|
77
60
|
handler: handleEns,
|
78
61
|
description: "Get cool alternatives for a .eth domain.",
|
@@ -84,7 +67,6 @@ export const skills: SkillGroup[] = [
|
|
84
67
|
},
|
85
68
|
{
|
86
69
|
skill: "/reset",
|
87
|
-
triggers: ["/reset"],
|
88
70
|
examples: ["/reset"],
|
89
71
|
handler: handleEns,
|
90
72
|
description: "Reset the conversation.",
|
@@ -93,7 +75,6 @@ export const skills: SkillGroup[] = [
|
|
93
75
|
{
|
94
76
|
skill: "/tip [address]",
|
95
77
|
description: "Show a URL for tipping a domain owner.",
|
96
|
-
triggers: ["/tip"],
|
97
78
|
handler: handleEns,
|
98
79
|
examples: ["/tip 0x1234567890123456789012345678901234567890"],
|
99
80
|
params: {
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import { run, HandlerContext } from "@xmtp/message-kit";
|
2
|
+
import { ChatOpenAI } from "@langchain/openai";
|
3
|
+
import { createOpenAIFunctionsAgent, AgentExecutor } from "langchain/agents";
|
4
|
+
import { tools } from "./skills.js";
|
5
|
+
import { systemPrompt } from "./prompt.js";
|
6
|
+
import { ChatPromptTemplate } from "@langchain/core/prompts";
|
7
|
+
|
8
|
+
// Initialize OpenAI chat model
|
9
|
+
const model = new ChatOpenAI({
|
10
|
+
temperature: 0.7,
|
11
|
+
modelName: "gpt-4o",
|
12
|
+
openAIApiKey: process.env.OPEN_AI_API_KEY,
|
13
|
+
});
|
14
|
+
|
15
|
+
// Create the prompt template with required variables
|
16
|
+
const prompt = ChatPromptTemplate.fromMessages([
|
17
|
+
["system", systemPrompt],
|
18
|
+
]).partial({
|
19
|
+
tools: tools.map((tool) => `${tool.name}: ${tool.description}`).join("\n"),
|
20
|
+
tool_names: tools.map((tool) => tool.name).join(", "),
|
21
|
+
});
|
22
|
+
|
23
|
+
// Initialize chat history storage
|
24
|
+
const chatHistory = new Map<string, { role: string; content: string }[]>();
|
25
|
+
|
26
|
+
// Initialize the agent and executor
|
27
|
+
const initializeAgent = async () => {
|
28
|
+
const agent = await createOpenAIFunctionsAgent({
|
29
|
+
llm: model,
|
30
|
+
tools: tools as any,
|
31
|
+
prompt: await prompt,
|
32
|
+
});
|
33
|
+
|
34
|
+
return new AgentExecutor({
|
35
|
+
agent,
|
36
|
+
tools: tools as any,
|
37
|
+
returnIntermediateSteps: false,
|
38
|
+
verbose: true, // Set to true for checking the agent's thought process
|
39
|
+
});
|
40
|
+
};
|
41
|
+
|
42
|
+
// Create executor instance
|
43
|
+
const executor = await initializeAgent();
|
44
|
+
|
45
|
+
run(
|
46
|
+
async (context: HandlerContext) => {
|
47
|
+
const {
|
48
|
+
message: {
|
49
|
+
content: { text },
|
50
|
+
sender,
|
51
|
+
},
|
52
|
+
} = context;
|
53
|
+
|
54
|
+
console.log("Received message:", text);
|
55
|
+
|
56
|
+
// Get or initialize chat history for this sender
|
57
|
+
if (!chatHistory.has(sender.address)) {
|
58
|
+
chatHistory.set(sender.address, []);
|
59
|
+
}
|
60
|
+
const userHistory: any = chatHistory.get(sender.address)!;
|
61
|
+
|
62
|
+
// Add user message to history
|
63
|
+
userHistory.push({ role: "user", content: context.message.content.text });
|
64
|
+
|
65
|
+
try {
|
66
|
+
// Execute agent with user's message and chat history
|
67
|
+
const result = await executor.invoke({
|
68
|
+
input: text,
|
69
|
+
chat_history: userHistory,
|
70
|
+
});
|
71
|
+
|
72
|
+
console.log("Agent response:", result.output);
|
73
|
+
const output = result.output.replace(/\*/g, "");
|
74
|
+
|
75
|
+
// Add assistant's response to history
|
76
|
+
userHistory.push({ role: "assistant", content: output });
|
77
|
+
|
78
|
+
await context.send(output);
|
79
|
+
} catch (error) {
|
80
|
+
console.error("Error:", error);
|
81
|
+
// Add error message to history
|
82
|
+
userHistory.push({
|
83
|
+
role: "assistant",
|
84
|
+
content: "An error occurred while processing your request.",
|
85
|
+
});
|
86
|
+
await context.send("An error occurred while processing your request.");
|
87
|
+
}
|
88
|
+
},
|
89
|
+
{ skills: [] },
|
90
|
+
);
|
@@ -0,0 +1,19 @@
|
|
1
|
+
export const systemPrompt = `You are a helpful AI assistant that can use various tools to help users.
|
2
|
+
|
3
|
+
You have access to the following tools:
|
4
|
+
{tools}
|
5
|
+
|
6
|
+
Tool Names: {tool_names}
|
7
|
+
|
8
|
+
Instructions:
|
9
|
+
1. DO NOT respond in markdown. ALWAYS respond in plain text.
|
10
|
+
2. Use tools when appropriate
|
11
|
+
3. Be friendly and helpful
|
12
|
+
4. Ask for clarification if you don't understand the user's request
|
13
|
+
5. For tipping, always confirm the amount with the user once
|
14
|
+
|
15
|
+
Previous conversation history:
|
16
|
+
{chat_history}
|
17
|
+
|
18
|
+
User Input: {input}
|
19
|
+
{agent_scratchpad}`;
|
@@ -0,0 +1,233 @@
|
|
1
|
+
import { DynamicStructuredTool } from "langchain/tools";
|
2
|
+
import { getUserInfo } from "@xmtp/message-kit";
|
3
|
+
import { clearMemory } from "@xmtp/message-kit";
|
4
|
+
import { isAddress } from "viem";
|
5
|
+
import { z } from "zod";
|
6
|
+
|
7
|
+
const frameUrl = "https://ens.steer.fun/";
|
8
|
+
const ensUrl = "https://app.ens.domains/";
|
9
|
+
const txpayUrl = "https://txpay.vercel.app";
|
10
|
+
const converseUrl = "https://converse.xyz/profile/";
|
11
|
+
|
12
|
+
// Add interface for Converse API response
|
13
|
+
interface ConverseProfile {
|
14
|
+
address: string;
|
15
|
+
avatar?: string;
|
16
|
+
formattedName?: string;
|
17
|
+
name?: string;
|
18
|
+
onXmtp: boolean;
|
19
|
+
}
|
20
|
+
|
21
|
+
// Add function to check XMTP status
|
22
|
+
async function checkXMTPStatus(address: string): Promise<boolean> {
|
23
|
+
try {
|
24
|
+
const response = await fetch(converseUrl + address, {
|
25
|
+
method: "POST",
|
26
|
+
headers: {
|
27
|
+
"Content-Type": "application/json",
|
28
|
+
Accept: "application/json",
|
29
|
+
},
|
30
|
+
body: JSON.stringify({ address }),
|
31
|
+
});
|
32
|
+
|
33
|
+
if (!response.ok) {
|
34
|
+
console.error(`Failed to check XMTP status: ${response.status}`);
|
35
|
+
return false;
|
36
|
+
}
|
37
|
+
|
38
|
+
const data = (await response.json()) as ConverseProfile;
|
39
|
+
return data.onXmtp;
|
40
|
+
} catch (error) {
|
41
|
+
console.error("Error checking XMTP status:", error);
|
42
|
+
return false;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
const generateCoolAlternatives = (domain: string) => {
|
47
|
+
const suffixes = ["lfg", "cool", "degen", "moon", "base", "gm"];
|
48
|
+
const alternatives = [];
|
49
|
+
for (let i = 0; i < 5; i++) {
|
50
|
+
const randomPosition = Math.random() < 0.5;
|
51
|
+
const baseDomain = domain.replace(/\.eth$/, ""); // Remove any existing .eth suffix
|
52
|
+
alternatives.push(
|
53
|
+
randomPosition
|
54
|
+
? `${suffixes[i]}${baseDomain}.eth`
|
55
|
+
: `${baseDomain}${suffixes[i]}.eth`,
|
56
|
+
);
|
57
|
+
}
|
58
|
+
|
59
|
+
return alternatives
|
60
|
+
.map(
|
61
|
+
(alternative: string, index: number) => `${index + 1}. ${alternative} ✨`,
|
62
|
+
)
|
63
|
+
.join("\n");
|
64
|
+
};
|
65
|
+
|
66
|
+
// Export tools array with all tools
|
67
|
+
export const tools = [
|
68
|
+
new DynamicStructuredTool({
|
69
|
+
name: "reset_ens_conversation",
|
70
|
+
description: "Reset the ENS conversation and clear memory",
|
71
|
+
schema: z.object({}),
|
72
|
+
func: async () => {
|
73
|
+
clearMemory();
|
74
|
+
return "Conversation reset successfully.";
|
75
|
+
},
|
76
|
+
}),
|
77
|
+
|
78
|
+
new DynamicStructuredTool({
|
79
|
+
name: "renew_ens_domain",
|
80
|
+
description:
|
81
|
+
"Generate renewal URL for an ENS domain. Only works if sender owns the domain",
|
82
|
+
schema: z.object({
|
83
|
+
domain: z.string().describe("The ENS domain to renew"),
|
84
|
+
}),
|
85
|
+
func: async ({ domain }) => {
|
86
|
+
const data = await getUserInfo(domain);
|
87
|
+
if (!data?.address) {
|
88
|
+
return "Domain not found or not registered.";
|
89
|
+
}
|
90
|
+
return `${frameUrl}frames/manage?name=${domain}`;
|
91
|
+
},
|
92
|
+
}),
|
93
|
+
|
94
|
+
new DynamicStructuredTool({
|
95
|
+
name: "register_ens_domain",
|
96
|
+
description: "Get URL to register a new ENS domain",
|
97
|
+
schema: z.object({
|
98
|
+
domain: z.string().describe("The ENS domain to register"),
|
99
|
+
}),
|
100
|
+
func: async ({ domain }) => {
|
101
|
+
if (!domain) return "Please provide a domain name";
|
102
|
+
return `${ensUrl}${domain}`;
|
103
|
+
},
|
104
|
+
}),
|
105
|
+
|
106
|
+
new DynamicStructuredTool({
|
107
|
+
name: "get_ens_info",
|
108
|
+
description:
|
109
|
+
"Get detailed information about an ENS domain including owner, avatar, description, etc",
|
110
|
+
schema: z.object({
|
111
|
+
domain: z.string().describe("The ENS domain to get information about"),
|
112
|
+
}),
|
113
|
+
func: async ({ domain }) => {
|
114
|
+
const data = await getUserInfo(domain);
|
115
|
+
if (!data?.ensDomain) {
|
116
|
+
return "Domain not found.";
|
117
|
+
}
|
118
|
+
|
119
|
+
const formattedData = {
|
120
|
+
Address: data?.address,
|
121
|
+
"Avatar URL": data?.ensInfo?.avatar,
|
122
|
+
Description: data?.ensInfo?.description,
|
123
|
+
ENS: data?.ensDomain,
|
124
|
+
"Primary ENS": data?.ensInfo?.ens_primary,
|
125
|
+
GitHub: data?.ensInfo?.github,
|
126
|
+
Resolver: data?.ensInfo?.resolverAddress,
|
127
|
+
Twitter: data?.ensInfo?.twitter,
|
128
|
+
URL: `${ensUrl}${domain}`,
|
129
|
+
};
|
130
|
+
|
131
|
+
let message = "Domain information:\n\n";
|
132
|
+
for (const [key, value] of Object.entries(formattedData)) {
|
133
|
+
if (value) {
|
134
|
+
message += `${key}: ${value}\n`;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
message +=
|
138
|
+
"\nWould you like to tip the domain owner for getting there first? 🤣";
|
139
|
+
|
140
|
+
// Check XMTP status if we have an address
|
141
|
+
if (data.address) {
|
142
|
+
const isOnXMTP = await checkXMTPStatus(data.address);
|
143
|
+
if (isOnXMTP) {
|
144
|
+
message += `\n\nAh, this domain is on XMTP! You can message it directly: https://converse.xyz/dm/${domain}`;
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
return message;
|
149
|
+
},
|
150
|
+
}),
|
151
|
+
|
152
|
+
new DynamicStructuredTool({
|
153
|
+
name: "check_ens_availability",
|
154
|
+
description: "Check if an ENS domain is available for registration",
|
155
|
+
schema: z.object({
|
156
|
+
domain: z
|
157
|
+
.string()
|
158
|
+
.transform((str) => str.replace(/^["'](.+)["']$/, "$1")) // Remove quotes if present
|
159
|
+
.transform((str) => str.toLowerCase())
|
160
|
+
.describe("The ENS domain to check availability for"),
|
161
|
+
}),
|
162
|
+
func: async ({ domain }) => {
|
163
|
+
if (!domain) return "Please provide a domain name to check.";
|
164
|
+
|
165
|
+
if (domain.includes(".") && !domain.endsWith(".eth")) {
|
166
|
+
return "Invalid ENS domain. Only .eth domains are supported.";
|
167
|
+
}
|
168
|
+
|
169
|
+
if (!domain.includes(".")) {
|
170
|
+
domain = `${domain}.eth`;
|
171
|
+
}
|
172
|
+
|
173
|
+
const data = await getUserInfo(domain);
|
174
|
+
if (!data?.address) {
|
175
|
+
return `Looks like ${domain} is available! Here you can register it: ${ensUrl}${domain} or would you like to see some cool alternatives?`;
|
176
|
+
} else {
|
177
|
+
const alternatives = generateCoolAlternatives(domain);
|
178
|
+
return `Looks like ${domain} is already registered!\n\nHere are some cool alternatives:\n${alternatives}`;
|
179
|
+
}
|
180
|
+
},
|
181
|
+
}),
|
182
|
+
|
183
|
+
new DynamicStructuredTool({
|
184
|
+
name: "get_ens_alternatives",
|
185
|
+
description: "Generate cool alternative names for an ENS domain",
|
186
|
+
schema: z.object({
|
187
|
+
domain: z
|
188
|
+
.string()
|
189
|
+
.describe("The ENS domain to generate alternatives for"),
|
190
|
+
}),
|
191
|
+
func: async ({ domain }) => {
|
192
|
+
if (!domain) return "Please provide a domain name.";
|
193
|
+
return `What about these cool alternatives?\n\n${generateCoolAlternatives(domain)}`;
|
194
|
+
},
|
195
|
+
}),
|
196
|
+
|
197
|
+
new DynamicStructuredTool({
|
198
|
+
name: "get_ens_tip_url",
|
199
|
+
description:
|
200
|
+
"Generate a URL to tip an ENS domain owner in USDC. Works with both ENS domains and Ethereum addresses.",
|
201
|
+
schema: z.object({
|
202
|
+
addressOrDomain: z
|
203
|
+
.string()
|
204
|
+
.describe("The ENS domain or Ethereum address to tip"),
|
205
|
+
amount: z
|
206
|
+
.number()
|
207
|
+
.optional()
|
208
|
+
.default(1)
|
209
|
+
.describe("The amount of USDC to tip"),
|
210
|
+
}),
|
211
|
+
func: async ({ addressOrDomain, amount }) => {
|
212
|
+
if (!addressOrDomain) {
|
213
|
+
return "Please provide an address or ENS domain to tip.";
|
214
|
+
}
|
215
|
+
|
216
|
+
let address: string | undefined;
|
217
|
+
|
218
|
+
if (isAddress(addressOrDomain)) {
|
219
|
+
address = addressOrDomain;
|
220
|
+
} else {
|
221
|
+
const data = await getUserInfo(addressOrDomain);
|
222
|
+
address = data?.address;
|
223
|
+
}
|
224
|
+
|
225
|
+
if (!address) {
|
226
|
+
return "Could not resolve address for tipping. Please provide a valid ENS domain or Ethereum address.";
|
227
|
+
}
|
228
|
+
|
229
|
+
let sendUrl = `${txpayUrl}/?&amount=${amount}&token=USDC&receiver=${address}`;
|
230
|
+
return sendUrl;
|
231
|
+
},
|
232
|
+
}),
|
233
|
+
];
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import {
|
2
|
+
run,
|
3
|
+
HandlerContext,
|
4
|
+
textGeneration,
|
5
|
+
processMultilineResponse,
|
6
|
+
} from "@xmtp/message-kit";
|
7
|
+
import { agent_prompt } from "./prompt.js";
|
8
|
+
|
9
|
+
if (!process.env.OPEN_AI_API_KEY) {
|
10
|
+
console.error("OPEN_AI_API_KEY is not set");
|
11
|
+
}
|
12
|
+
|
13
|
+
run(async (context: HandlerContext) => {
|
14
|
+
if (!process.env.OPEN_AI_API_KEY) {
|
15
|
+
context.send("gm");
|
16
|
+
return;
|
17
|
+
}
|
18
|
+
|
19
|
+
const {
|
20
|
+
message: {
|
21
|
+
content: { text, params },
|
22
|
+
sender,
|
23
|
+
},
|
24
|
+
} = context;
|
25
|
+
|
26
|
+
try {
|
27
|
+
let userPrompt = params?.prompt ?? text;
|
28
|
+
const { reply } = await textGeneration(
|
29
|
+
sender.address,
|
30
|
+
userPrompt,
|
31
|
+
await agent_prompt(sender.address),
|
32
|
+
);
|
33
|
+
await processMultilineResponse(sender.address, reply, context);
|
34
|
+
} catch (error) {
|
35
|
+
console.error("Error during OpenAI call:", error);
|
36
|
+
await context.send("An error occurred while processing your request.");
|
37
|
+
}
|
38
|
+
});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { defaultPromptTemplate } from "@xmtp/message-kit";
|
2
|
+
|
3
|
+
export async function agent_prompt(senderAddress: string) {
|
4
|
+
let fineTunedPrompt = `
|
5
|
+
|
6
|
+
### Context
|
7
|
+
|
8
|
+
You are a helpful agent that lives inside a web3 messaging group that helps interpret user requests and execute commands.
|
9
|
+
The message was sent by @${senderAddress}
|
10
|
+
|
11
|
+
|
12
|
+
Important:
|
13
|
+
- If a user asks jokes, make jokes about web3 devs\n
|
14
|
+
- 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.
|
15
|
+
- Populate the command with the correct or random values. Always return commands with real values only, using usernames with @ and excluding addresses.\n
|
16
|
+
- 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
|
17
|
+
- If the user is grateful, respond asking for a tip in a playful manner.
|
18
|
+
`;
|
19
|
+
return defaultPromptTemplate(fineTunedPrompt, senderAddress, [], "@bot");
|
20
|
+
}
|
@@ -6,10 +6,10 @@ export async function handler(context: HandlerContext) {
|
|
6
6
|
content: { skill, params },
|
7
7
|
},
|
8
8
|
} = context;
|
9
|
-
const
|
9
|
+
const txpayUrl = "https://txpay.vercel.app";
|
10
10
|
|
11
11
|
if (skill === "pay") {
|
12
|
-
const { amount: amountSend, token: tokenSend, username } = params;
|
12
|
+
const { amount: amountSend, token: tokenSend, username } = params;
|
13
13
|
console.log("username", username);
|
14
14
|
let senderInfo = await getUserInfo(username);
|
15
15
|
if (!amountSend || !tokenSend || !senderInfo) {
|
@@ -23,7 +23,7 @@ export async function handler(context: HandlerContext) {
|
|
23
23
|
};
|
24
24
|
}
|
25
25
|
|
26
|
-
let sendUrl = `${
|
26
|
+
let sendUrl = `${txpayUrl}/?&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
|
27
27
|
await context.send(`${sendUrl}`);
|
28
28
|
}
|
29
29
|
}
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import { run, HandlerContext } from "@xmtp/message-kit";
|
2
2
|
import { textGeneration, processMultilineResponse } from "@xmtp/message-kit";
|
3
3
|
import { agent_prompt } from "./prompt.js";
|
4
|
-
import { getUserInfo } from "@xmtp/message-kit";
|
5
4
|
|
6
5
|
run(async (context: HandlerContext) => {
|
7
6
|
const {
|
@@ -13,15 +12,10 @@ run(async (context: HandlerContext) => {
|
|
13
12
|
|
14
13
|
try {
|
15
14
|
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
|
-
}
|
21
15
|
const { reply } = await textGeneration(
|
22
16
|
sender.address,
|
23
17
|
userPrompt,
|
24
|
-
await agent_prompt(
|
18
|
+
await agent_prompt(sender.address),
|
25
19
|
);
|
26
20
|
await processMultilineResponse(sender.address, reply, context);
|
27
21
|
} catch (error) {
|
@@ -1,18 +1,7 @@
|
|
1
1
|
import { skills } from "./skills.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";
|
9
|
-
|
10
|
-
export async function agent_prompt(userInfo: UserInfo) {
|
11
|
-
let systemPrompt =
|
12
|
-
PROMPT_RULES +
|
13
|
-
PROMPT_USER_CONTENT(userInfo) +
|
14
|
-
PROMPT_SKILLS_AND_EXAMPLES(skills, "@bot");
|
2
|
+
import { defaultPromptTemplate } from "@xmtp/message-kit";
|
15
3
|
|
4
|
+
export async function agent_prompt(senderAddress: string) {
|
16
5
|
let fineTunedPrompt = `
|
17
6
|
## Example response
|
18
7
|
|
@@ -20,7 +9,7 @@ export async function agent_prompt(userInfo: UserInfo) {
|
|
20
9
|
Hey! Sure let's do that.\n/game wordle
|
21
10
|
|
22
11
|
2. When user wants to pay a specific token:
|
23
|
-
I'll help you pay 1 USDC to 0x123...\n/pay 1
|
12
|
+
I'll help you pay 1 USDC to 0x123...\n/pay 1 [token] 0x123456789...
|
24
13
|
*This will return a url to pay
|
25
14
|
|
26
15
|
3. If the user wants to pay a eth domain:
|
@@ -30,16 +19,10 @@ export async function agent_prompt(userInfo: UserInfo) {
|
|
30
19
|
4. If the user wants to pay a username:
|
31
20
|
I'll help you pay 1 USDC to @fabri\nBe aware that this only works on mobile with a installed wallet on Base network\n/pay 1 @fabri
|
32
21
|
*This will return a url to pay
|
22
|
+
|
23
|
+
5. If the user wants to play a game suggest direcly a game like wordle:
|
24
|
+
Let's play wordle!\n/game wordle
|
33
25
|
`;
|
34
26
|
|
35
|
-
|
36
|
-
// Replace the variables in the system prompt
|
37
|
-
systemPrompt = PROMPT_REPLACE_VARIABLES(
|
38
|
-
systemPrompt,
|
39
|
-
userInfo?.address ?? "",
|
40
|
-
userInfo,
|
41
|
-
"@bot",
|
42
|
-
);
|
43
|
-
console.log(systemPrompt);
|
44
|
-
return systemPrompt;
|
27
|
+
return defaultPromptTemplate(fineTunedPrompt, senderAddress, skills, "@bot");
|
45
28
|
}
|
@@ -12,7 +12,6 @@ export const skills: SkillGroup[] = [
|
|
12
12
|
skills: [
|
13
13
|
{
|
14
14
|
skill: "/tip [usernames] [amount] [token]",
|
15
|
-
triggers: ["/tip"],
|
16
15
|
examples: ["/tip @vitalik 10 usdc"],
|
17
16
|
description: "Tip users in a specified token.",
|
18
17
|
handler: tipping,
|
@@ -26,12 +25,16 @@ export const skills: SkillGroup[] = [
|
|
26
25
|
default: 10,
|
27
26
|
type: "number",
|
28
27
|
},
|
28
|
+
token: {
|
29
|
+
default: "usdc",
|
30
|
+
type: "string",
|
31
|
+
values: ["eth", "dai", "usdc", "degen"],
|
32
|
+
},
|
29
33
|
},
|
30
34
|
},
|
31
35
|
{
|
32
36
|
skill: "/pay [amount] [token] [username]",
|
33
|
-
|
34
|
-
examples: ["/pay 10 vitalik.eth"],
|
37
|
+
examples: ["/pay 10 usdc vitalik.eth", "/pay 1 @alix"],
|
35
38
|
description:
|
36
39
|
"Send a specified amount of a cryptocurrency to a destination address.",
|
37
40
|
handler: payment,
|
@@ -53,7 +56,6 @@ export const skills: SkillGroup[] = [
|
|
53
56
|
},
|
54
57
|
{
|
55
58
|
skill: "/game [game]",
|
56
|
-
triggers: ["/game", "🔎", "🔍"],
|
57
59
|
handler: games,
|
58
60
|
description: "Play a game.",
|
59
61
|
examples: ["/game wordle", "/game slot", "/game help"],
|
@@ -67,7 +69,6 @@ export const skills: SkillGroup[] = [
|
|
67
69
|
},
|
68
70
|
{
|
69
71
|
skill: "/help",
|
70
|
-
triggers: ["/help"],
|
71
72
|
examples: ["/help"],
|
72
73
|
handler: help,
|
73
74
|
description: "Get help with the bot.",
|
@@ -78,7 +79,6 @@ export const skills: SkillGroup[] = [
|
|
78
79
|
adminOnly: true,
|
79
80
|
examples: ["/id"],
|
80
81
|
handler: help,
|
81
|
-
triggers: ["/id"],
|
82
82
|
description: "Get the group ID.",
|
83
83
|
params: {},
|
84
84
|
},
|
@@ -1,69 +0,0 @@
|
|
1
|
-
import { skills } from "./skills.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";
|
9
|
-
|
10
|
-
export async function agent_prompt(userInfo: UserInfo) {
|
11
|
-
let systemPrompt =
|
12
|
-
PROMPT_RULES +
|
13
|
-
PROMPT_USER_CONTENT(userInfo) +
|
14
|
-
PROMPT_SKILLS_AND_EXAMPLES(skills, "@ens");
|
15
|
-
|
16
|
-
let fineTunning = `
|
17
|
-
|
18
|
-
## Example responses:
|
19
|
-
|
20
|
-
1. Check if the user does not have a ENS domain
|
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
|
22
|
-
|
23
|
-
2. If the user has a ENS domain
|
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}
|
25
|
-
|
26
|
-
3. Check if the ENS domain is available
|
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}
|
28
|
-
|
29
|
-
4. If the ENS domain is available,
|
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!
|
31
|
-
|
32
|
-
5. If the ENS domain is already registered, let me suggest 5 cool alternatives
|
33
|
-
Looks like {ENS_DOMAIN} is already registered!\n What about these cool alternatives?\n/cool {ENS_DOMAIN}
|
34
|
-
|
35
|
-
6. If the user wants to register a ENS domain, use the command "/register [domain]"
|
36
|
-
Looks like {ENS_DOMAIN} is available! Let me help you register it\n/register {ENS_DOMAIN}
|
37
|
-
|
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
|
39
|
-
Here is the url to send the tip:\n/tip {ENS_DOMAIN}
|
40
|
-
|
41
|
-
8. If the user wants to get information about the ENS domain, use the command "/info [domain]"
|
42
|
-
Hello! I'll help you get info about {ENS_DOMAIN}.\n Give me a moment.\n/info {ENS_DOMAIN}
|
43
|
-
|
44
|
-
9. If the user wants to renew their domain, use the command "/renew [domain]"
|
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}
|
46
|
-
|
47
|
-
10. If the user wants cool suggestions about a domain, use the command "/cool [domain]"
|
48
|
-
Here are some cool suggestions for your domain.\n/cool {ENS_DOMAIN}
|
49
|
-
|
50
|
-
## Most common bugs
|
51
|
-
|
52
|
-
1. Some times you will say something like: "Looks like vitalik.eth is registered! What about these cool alternatives?"
|
53
|
-
But you forgot to add the command at the end of the message.
|
54
|
-
You should have said something like: "Looks like vitalik.eth is registered! What about these cool alternatives?\n/cool vitalik.eth
|
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
|
-
);
|
67
|
-
// console.log(systemPrompt);
|
68
|
-
return systemPrompt;
|
69
|
-
}
|
@@ -1 +0,0 @@
|
|
1
|
-
KEY= # the private key of the agent wallet
|