create-hybrid 1.2.3 → 1.2.5
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/README.md +1 -1
- package/dist/index.js +314 -102
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
- package/templates/agent/README.md +0 -164
- package/templates/agent/package.json +0 -37
- package/templates/agent/src/agent.test.ts +0 -11
- package/templates/agent/src/agent.ts +0 -52
- package/templates/agent/tsconfig.json +0 -22
- package/templates/agent/vitest.config.ts +0 -8
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ This package creates a new Hybrid XMTP agent project with:
|
|
|
30
30
|
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
|
31
31
|
|
|
32
32
|
# XMTP Wallet and Encryption Keys
|
|
33
|
-
# Run 'npx hybrid
|
|
33
|
+
# Run 'npx hybrid keys' to generate these values
|
|
34
34
|
XMTP_WALLET_KEY=your_wallet_key_here
|
|
35
35
|
XMTP_ENCRYPTION_KEY=your_encryption_key_here
|
|
36
36
|
|
package/dist/index.js
CHANGED
|
@@ -1,127 +1,289 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import degit from "degit";
|
|
6
|
+
import { readFile, readdir, writeFile } from "fs/promises";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import prompts from "prompts";
|
|
9
|
+
var DEFAULT_REPO = "ian/hybrid";
|
|
10
|
+
var REPO = process.env.REPO || DEFAULT_REPO;
|
|
11
|
+
var EXAMPLES = [
|
|
12
|
+
{
|
|
13
|
+
name: "basic",
|
|
14
|
+
description: "Basic XMTP agent with message filtering and AI responses",
|
|
15
|
+
path: "basic",
|
|
16
|
+
available: true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "with-ponder",
|
|
20
|
+
description: "Agent with Ponder integration for indexing blockchain data",
|
|
21
|
+
path: "with-ponder",
|
|
22
|
+
available: false,
|
|
23
|
+
message: "Coming soon"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "with-foundry",
|
|
27
|
+
description: "Agent with Foundry integration for smart contract development",
|
|
28
|
+
path: "with-foundry",
|
|
29
|
+
available: false,
|
|
30
|
+
message: "Coming soon"
|
|
31
|
+
}
|
|
32
|
+
];
|
|
21
33
|
function replaceTemplateVariables(content, variables) {
|
|
22
34
|
return content.replace(
|
|
23
35
|
/\{\{(\w+)\}\}/g,
|
|
24
36
|
(match, key) => variables[key] || match
|
|
25
37
|
);
|
|
26
38
|
}
|
|
27
|
-
async function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await
|
|
39
|
+
async function updateTemplateFiles(projectDir, projectName) {
|
|
40
|
+
const variables = { projectName };
|
|
41
|
+
const filesToUpdate = [
|
|
42
|
+
join(projectDir, "package.json"),
|
|
43
|
+
join(projectDir, "README.md"),
|
|
44
|
+
join(projectDir, "src", "agent.ts")
|
|
45
|
+
];
|
|
46
|
+
for (const filePath of filesToUpdate) {
|
|
47
|
+
try {
|
|
48
|
+
let content = await readFile(filePath, "utf-8");
|
|
49
|
+
content = replaceTemplateVariables(content, variables);
|
|
50
|
+
if (filePath.endsWith("package.json")) {
|
|
51
|
+
try {
|
|
52
|
+
const packageJson = JSON.parse(content);
|
|
53
|
+
let updated = false;
|
|
54
|
+
if (packageJson.name === "agent" || packageJson.name === "hybrid-example-basic-agent") {
|
|
55
|
+
packageJson.name = projectName;
|
|
56
|
+
updated = true;
|
|
57
|
+
}
|
|
58
|
+
if (!packageJson.scripts) {
|
|
59
|
+
packageJson.scripts = {};
|
|
60
|
+
}
|
|
61
|
+
const requiredScripts = {
|
|
62
|
+
clean: "hybrid clean",
|
|
63
|
+
dev: "hybrid dev",
|
|
64
|
+
build: "hybrid build",
|
|
65
|
+
start: "hybrid start",
|
|
66
|
+
keys: "hybrid keys --write",
|
|
67
|
+
test: "vitest",
|
|
68
|
+
"test:watch": "vitest --watch",
|
|
69
|
+
"test:coverage": "vitest --coverage",
|
|
70
|
+
lint: "biome lint --write",
|
|
71
|
+
"lint:check": "biome lint",
|
|
72
|
+
format: "biome format --write",
|
|
73
|
+
"format:check": "biome format --check",
|
|
74
|
+
typecheck: "tsc --noEmit"
|
|
75
|
+
};
|
|
76
|
+
for (const [scriptName, scriptCommand] of Object.entries(
|
|
77
|
+
requiredScripts
|
|
78
|
+
)) {
|
|
79
|
+
packageJson.scripts[scriptName] = scriptCommand;
|
|
80
|
+
updated = true;
|
|
81
|
+
}
|
|
82
|
+
if (packageJson.dependencies) {
|
|
83
|
+
if (packageJson.dependencies.hybrid === "workspace:*") {
|
|
84
|
+
packageJson.dependencies.hybrid = "latest";
|
|
85
|
+
updated = true;
|
|
86
|
+
}
|
|
87
|
+
if (packageJson.dependencies["@openrouter/ai-sdk-provider"] === "catalog:ai") {
|
|
88
|
+
packageJson.dependencies["@openrouter/ai-sdk-provider"] = "^1.1.2";
|
|
89
|
+
updated = true;
|
|
90
|
+
}
|
|
91
|
+
if (packageJson.dependencies.zod === "catalog:stack") {
|
|
92
|
+
packageJson.dependencies.zod = "^3.23.8";
|
|
93
|
+
updated = true;
|
|
94
|
+
}
|
|
95
|
+
if (packageJson.dependencies["@hybrd/xmtp"]) {
|
|
96
|
+
packageJson.dependencies["@hybrd/xmtp"] = void 0;
|
|
97
|
+
updated = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (packageJson.devDependencies) {
|
|
101
|
+
const independentDevDeps = {
|
|
102
|
+
"@biomejs/biome": "^1.9.4",
|
|
103
|
+
"@types/node": "^22.0.0",
|
|
104
|
+
"@hybrd/cli": "latest",
|
|
105
|
+
tsx: "^4.20.5",
|
|
106
|
+
typescript: "^5.8.3",
|
|
107
|
+
vitest: "^3.2.4"
|
|
108
|
+
};
|
|
109
|
+
packageJson.devDependencies["@config/biome"] = void 0;
|
|
110
|
+
packageJson.devDependencies["@config/tsconfig"] = void 0;
|
|
111
|
+
for (const [depName, depVersion] of Object.entries(
|
|
112
|
+
independentDevDeps
|
|
113
|
+
)) {
|
|
114
|
+
packageJson.devDependencies[depName] = depVersion;
|
|
115
|
+
}
|
|
116
|
+
updated = true;
|
|
117
|
+
}
|
|
118
|
+
if (updated) {
|
|
119
|
+
content = `${JSON.stringify(packageJson, null, " ")}
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
} catch (parseError) {
|
|
123
|
+
console.log("\u26A0\uFE0F Could not parse package.json for name update");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (filePath.endsWith("README.md")) {
|
|
127
|
+
content = content.replace(/^# .*$/m, `# ${projectName}`);
|
|
128
|
+
}
|
|
129
|
+
await writeFile(filePath, content, "utf-8");
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.log(
|
|
132
|
+
`\u26A0\uFE0F Could not update ${filePath.split("/").pop()}: file not found or error occurred`
|
|
133
|
+
);
|
|
37
134
|
}
|
|
38
135
|
}
|
|
136
|
+
const envPath = join(projectDir, ".env");
|
|
137
|
+
try {
|
|
138
|
+
await readFile(envPath, "utf-8");
|
|
139
|
+
} catch {
|
|
140
|
+
const envContent = `# Required
|
|
141
|
+
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
|
142
|
+
XMTP_WALLET_KEY=your_wallet_key_here
|
|
143
|
+
XMTP_ENCRYPTION_KEY=your_encryption_key_here
|
|
144
|
+
|
|
145
|
+
# Optional
|
|
146
|
+
XMTP_ENV=dev
|
|
147
|
+
PORT=8454`;
|
|
148
|
+
await writeFile(envPath, envContent, "utf-8");
|
|
149
|
+
console.log("\u{1F4C4} Created .env template file");
|
|
150
|
+
}
|
|
151
|
+
const vitestConfigPath = join(projectDir, "vitest.config.ts");
|
|
152
|
+
try {
|
|
153
|
+
await readFile(vitestConfigPath, "utf-8");
|
|
154
|
+
} catch {
|
|
155
|
+
const vitestConfigContent = `import { defineConfig } from "vitest/config"
|
|
156
|
+
|
|
157
|
+
export default defineConfig({
|
|
158
|
+
test: {
|
|
159
|
+
environment: "node",
|
|
160
|
+
globals: true,
|
|
161
|
+
setupFiles: []
|
|
162
|
+
},
|
|
163
|
+
resolve: {
|
|
164
|
+
alias: {
|
|
165
|
+
"@": "./src"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
})`;
|
|
169
|
+
await writeFile(vitestConfigPath, vitestConfigContent, "utf-8");
|
|
170
|
+
console.log("\u{1F4C4} Created vitest.config.ts file");
|
|
171
|
+
}
|
|
172
|
+
const agentTestPath = join(projectDir, "src", "agent.test.ts");
|
|
173
|
+
try {
|
|
174
|
+
await readFile(agentTestPath, "utf-8");
|
|
175
|
+
} catch {
|
|
176
|
+
const agentTestContent = `import { describe, expect, it } from "vitest"
|
|
177
|
+
|
|
178
|
+
// Example test file - replace with actual tests for your agent
|
|
179
|
+
|
|
180
|
+
describe("Agent", () => {
|
|
181
|
+
it("should be defined", () => {
|
|
182
|
+
// This is a placeholder test
|
|
183
|
+
// Add real tests for your agent functionality
|
|
184
|
+
expect(true).toBe(true)
|
|
185
|
+
})
|
|
186
|
+
})`;
|
|
187
|
+
await writeFile(agentTestPath, agentTestContent, "utf-8");
|
|
188
|
+
console.log("\u{1F4C4} Created src/agent.test.ts file");
|
|
189
|
+
}
|
|
39
190
|
}
|
|
40
|
-
async function
|
|
191
|
+
async function checkDirectoryEmpty(dirPath) {
|
|
192
|
+
try {
|
|
193
|
+
const files = await readdir(dirPath);
|
|
194
|
+
const significantFiles = files.filter(
|
|
195
|
+
(file) => !file.startsWith(".") && file !== "node_modules" && file !== "package-lock.json" && file !== "yarn.lock" && file !== "pnpm-lock.yaml"
|
|
196
|
+
);
|
|
197
|
+
return significantFiles.length === 0;
|
|
198
|
+
} catch {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function createProject(projectName, exampleName) {
|
|
41
203
|
console.log("\u{1F680} Creating a new Hybrid project...");
|
|
42
|
-
|
|
43
|
-
let projectName = projectNameArg;
|
|
44
|
-
if (projectNameArg === "") {
|
|
204
|
+
if (!projectName || projectName.trim() === "") {
|
|
45
205
|
console.error("\u274C Project name is required");
|
|
46
206
|
process.exit(1);
|
|
47
207
|
}
|
|
48
|
-
if (!projectName || !projectName.trim()) {
|
|
49
|
-
while (!projectName || !projectName.trim()) {
|
|
50
|
-
projectName = await prompt("Enter project name: ");
|
|
51
|
-
if (!projectName || !projectName.trim()) {
|
|
52
|
-
console.log("\u274C Project name is required. Please enter a valid name.");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
208
|
const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
57
209
|
const currentDir = process.cwd();
|
|
58
210
|
const projectDir = projectName === "." ? currentDir : join(currentDir, sanitizedName);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
211
|
+
const isEmpty = await checkDirectoryEmpty(projectDir);
|
|
212
|
+
if (!isEmpty) {
|
|
213
|
+
console.error(
|
|
214
|
+
`\u274C Directory "${sanitizedName}" already exists and is not empty`
|
|
215
|
+
);
|
|
216
|
+
console.error(
|
|
217
|
+
"Please choose a different name or remove the existing directory"
|
|
218
|
+
);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
let selectedExample;
|
|
222
|
+
if (exampleName) {
|
|
223
|
+
const example = EXAMPLES.find((ex) => ex.name === exampleName);
|
|
224
|
+
if (!example) {
|
|
225
|
+
console.error(`\u274C Example "${exampleName}" not found`);
|
|
226
|
+
console.error(
|
|
227
|
+
`Available examples: ${EXAMPLES.map((ex) => ex.name).join(", ")}`
|
|
228
|
+
);
|
|
229
|
+
process.exit(1);
|
|
72
230
|
}
|
|
231
|
+
selectedExample = example;
|
|
232
|
+
console.log(`\u{1F4CB} Using example: ${selectedExample.name}`);
|
|
73
233
|
} else {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
(file) => !file.startsWith(".") && file !== "node_modules" && file !== "package-lock.json" && file !== "yarn.lock" && file !== "pnpm-lock.yaml"
|
|
234
|
+
if (!process.stdin.isTTY) {
|
|
235
|
+
console.error(
|
|
236
|
+
"\u274C Example is required in non-interactive mode. Use --example <name>"
|
|
78
237
|
);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
238
|
+
console.error(
|
|
239
|
+
`Available examples: ${EXAMPLES.map((ex) => ex.name).join(", ")}`
|
|
240
|
+
);
|
|
241
|
+
process.exit(1);
|
|
87
242
|
}
|
|
243
|
+
const { example } = await prompts({
|
|
244
|
+
type: "select",
|
|
245
|
+
name: "example",
|
|
246
|
+
message: "Which example would you like to use?",
|
|
247
|
+
choices: EXAMPLES.map((ex) => ({
|
|
248
|
+
title: ex.name,
|
|
249
|
+
description: ex.available ? ex.description : `${ex.description} (${ex.message || "Coming soon"})`,
|
|
250
|
+
value: ex,
|
|
251
|
+
disabled: !ex.available
|
|
252
|
+
})),
|
|
253
|
+
initial: 0
|
|
254
|
+
});
|
|
255
|
+
if (!example) {
|
|
256
|
+
console.log("\u274C No example selected. Exiting...");
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
if (!example.available) {
|
|
260
|
+
console.log(
|
|
261
|
+
`\u274C Example "${example.name}" is not yet available. ${example.message || "Coming soon"}`
|
|
262
|
+
);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
selectedExample = example;
|
|
88
266
|
}
|
|
89
|
-
console.log(
|
|
90
|
-
const templateDir = join(__dirname, "..", "templates", "agent");
|
|
91
|
-
try {
|
|
92
|
-
await stat(templateDir);
|
|
93
|
-
} catch {
|
|
94
|
-
console.error("\u274C Template directory not found");
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
267
|
+
console.log(`\u{1F4E6} Cloning ${selectedExample.name} example...`);
|
|
97
268
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
269
|
+
let degitSource;
|
|
270
|
+
if (REPO.includes("#")) {
|
|
271
|
+
const [repoWithBranch] = REPO.split("/examples/");
|
|
272
|
+
degitSource = `${repoWithBranch}/examples/${selectedExample.name}`;
|
|
273
|
+
} else {
|
|
274
|
+
degitSource = `${REPO}/examples/${selectedExample.name}`;
|
|
275
|
+
}
|
|
276
|
+
console.log(`\u{1F50D} Degit source: ${degitSource}`);
|
|
277
|
+
const emitter = degit(degitSource);
|
|
278
|
+
await emitter.clone(projectDir);
|
|
279
|
+
console.log(`\u2705 Template cloned to: ${sanitizedName}`);
|
|
101
280
|
} catch (error) {
|
|
102
|
-
console.error("\u274C Failed to
|
|
281
|
+
console.error("\u274C Failed to clone template:", error);
|
|
103
282
|
process.exit(1);
|
|
104
283
|
}
|
|
105
|
-
|
|
106
|
-
projectName: sanitizedName
|
|
107
|
-
};
|
|
284
|
+
console.log("\u{1F527} Updating template variables...");
|
|
108
285
|
try {
|
|
109
|
-
|
|
110
|
-
join(projectDir, "package.json"),
|
|
111
|
-
join(projectDir, "README.md"),
|
|
112
|
-
join(projectDir, "src", "agent.ts")
|
|
113
|
-
];
|
|
114
|
-
for (const filePath of filesToUpdate) {
|
|
115
|
-
try {
|
|
116
|
-
let content = await readFile(filePath, "utf-8");
|
|
117
|
-
content = replaceTemplateVariables(content, variables);
|
|
118
|
-
await writeFile(filePath, content, "utf-8");
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.log(
|
|
121
|
-
`\u26A0\uFE0F Could not update ${filePath.split("/").pop()}: file not found`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
286
|
+
await updateTemplateFiles(projectDir, sanitizedName);
|
|
125
287
|
console.log("\u2705 Template variables updated");
|
|
126
288
|
} catch (error) {
|
|
127
289
|
console.error("\u274C Failed to update template variables:", error);
|
|
@@ -130,19 +292,69 @@ async function initializeProject() {
|
|
|
130
292
|
console.log(`
|
|
131
293
|
\u{1F4C2} Project created in: ${projectDir}`);
|
|
132
294
|
console.log("\n\u{1F4CB} Next steps:");
|
|
133
|
-
|
|
295
|
+
if (projectName !== ".") {
|
|
296
|
+
console.log(`1. cd ${sanitizedName}`);
|
|
297
|
+
}
|
|
134
298
|
console.log(
|
|
135
|
-
"2. Install dependencies (npm install, yarn install, or pnpm install)
|
|
299
|
+
`${projectName !== "." ? "2" : "1"}. Install dependencies (npm install, yarn install, or pnpm install)`
|
|
300
|
+
);
|
|
301
|
+
console.log(
|
|
302
|
+
`${projectName !== "." ? "3" : "2"}. Get your OpenRouter API key from https://openrouter.ai/keys`
|
|
303
|
+
);
|
|
304
|
+
console.log(
|
|
305
|
+
`${projectName !== "." ? "4" : "3"}. Add your API key to the OPENROUTER_API_KEY in .env`
|
|
306
|
+
);
|
|
307
|
+
console.log(
|
|
308
|
+
`${projectName !== "." ? "5" : "4"}. Set XMTP_ENV in .env (dev or production)`
|
|
309
|
+
);
|
|
310
|
+
console.log(
|
|
311
|
+
`${projectName !== "." ? "6" : "5"}. Generate keys: npm run keys (or yarn/pnpm equivalent)`
|
|
312
|
+
);
|
|
313
|
+
console.log(
|
|
314
|
+
`${projectName !== "." ? "7" : "6"}. Start development: npm run dev (or yarn/pnpm equivalent)`
|
|
136
315
|
);
|
|
137
|
-
console.log("3. Get your OpenRouter API key from https://openrouter.ai/keys");
|
|
138
|
-
console.log("4. Add your API key to the OPENROUTER_API_KEY in .env");
|
|
139
|
-
console.log("5. Set XMTP_ENV in .env (dev or production)");
|
|
140
|
-
console.log("6. Generate keys: npm run keys (or yarn/pnpm equivalent)");
|
|
141
|
-
console.log("7. Start development: npm run dev (or yarn/pnpm equivalent)");
|
|
142
316
|
console.log(
|
|
143
317
|
"\n\u{1F4D6} For more information, see the README.md file in your project"
|
|
144
318
|
);
|
|
145
319
|
}
|
|
320
|
+
async function initializeProject() {
|
|
321
|
+
const program = new Command();
|
|
322
|
+
program.name("create-hybrid").description("Create a new Hybrid XMTP agent project").version("1.2.3").argument("[project-name]", "Name of the project").option(
|
|
323
|
+
"-e, --example <example>",
|
|
324
|
+
"Example to use (basic, with-ponder, with-foundry)"
|
|
325
|
+
).action(async (projectName, options) => {
|
|
326
|
+
let finalProjectName = projectName;
|
|
327
|
+
if (process.env.CI) {
|
|
328
|
+
console.log(
|
|
329
|
+
`\u{1F50D} Debug: projectName="${projectName}", options.example="${options?.example}"`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (!finalProjectName || finalProjectName.trim() === "") {
|
|
333
|
+
if (!process.stdin.isTTY) {
|
|
334
|
+
console.error("\u274C Project name is required");
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
const { name } = await prompts({
|
|
338
|
+
type: "text",
|
|
339
|
+
name: "name",
|
|
340
|
+
message: "What is your project name?",
|
|
341
|
+
validate: (value) => {
|
|
342
|
+
if (!value || !value.trim()) {
|
|
343
|
+
return "Project name is required";
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
if (!name) {
|
|
349
|
+
console.log("\u274C Project name is required. Exiting...");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
finalProjectName = name;
|
|
353
|
+
}
|
|
354
|
+
await createProject(finalProjectName, options?.example);
|
|
355
|
+
});
|
|
356
|
+
await program.parseAsync();
|
|
357
|
+
}
|
|
146
358
|
async function main() {
|
|
147
359
|
const nodeVersion = process.versions.node;
|
|
148
360
|
const [major] = nodeVersion.split(".").map(Number);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { readFile, readdir, writeFile, mkdir, copyFile, stat } from \"node:fs/promises\"\nimport { dirname, join } from \"node:path\"\nimport { createInterface } from \"node:readline\"\nimport { fileURLToPath } from \"node:url\"\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nfunction prompt(question: string): Promise<string> {\n\tconst rl = createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout\n\t})\n\n\treturn new Promise((resolve) => {\n\t\trl.question(question, (answer) => {\n\t\t\trl.close()\n\t\t\tresolve(answer)\n\t\t})\n\t})\n}\n\nfunction replaceTemplateVariables(\n\tcontent: string,\n\tvariables: Record<string, string>\n): string {\n\treturn content.replace(\n\t\t/\\{\\{(\\w+)\\}\\}/g,\n\t\t(match, key) => variables[key] || match\n\t)\n}\n\nasync function copyTemplate(sourceDir: string, targetDir: string): Promise<void> {\n\tconst entries = await readdir(sourceDir, { withFileTypes: true })\n\t\n\tfor (const entry of entries) {\n\t\tconst sourcePath = join(sourceDir, entry.name)\n\t\tconst targetPath = join(targetDir, entry.name)\n\t\t\n\t\tif (entry.isDirectory()) {\n\t\t\tawait mkdir(targetPath, { recursive: true })\n\t\t\tawait copyTemplate(sourcePath, targetPath)\n\t\t} else {\n\t\t\tawait copyFile(sourcePath, targetPath)\n\t\t}\n\t}\n}\n\nexport async function initializeProject() {\n\tconsole.log(\"🚀 Creating a new Hybrid project...\")\n\n\tconst projectNameArg = process.argv[2]\n\tlet projectName = projectNameArg\n\n\tif (projectNameArg === \"\") {\n\t\tconsole.error(\"❌ Project name is required\")\n\t\tprocess.exit(1)\n\t}\n\n\tif (!projectName || !projectName.trim()) {\n\t\twhile (!projectName || !projectName.trim()) {\n\t\t\tprojectName = await prompt(\"Enter project name: \")\n\t\t\tif (!projectName || !projectName.trim()) {\n\t\t\t\tconsole.log(\"❌ Project name is required. Please enter a valid name.\")\n\t\t\t}\n\t\t}\n\t}\n\n\tconst sanitizedName = projectName\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9-]/g, \"-\")\n\t\t.replace(/-+/g, \"-\")\n\t\t.replace(/^-|-$/g, \"\")\n\n\tconst currentDir = process.cwd()\n\tconst projectDir =\n\t\tprojectName === \".\" ? currentDir : join(currentDir, sanitizedName)\n\n\tif (projectName !== \".\") {\n\t\ttry {\n\t\t\tconst existingFiles = await readdir(projectDir)\n\t\t\tif (existingFiles.length > 0) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`❌ Directory \"${sanitizedName}\" already exists and is not empty`\n\t\t\t\t)\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"Please choose a different name or remove the existing directory\"\n\t\t\t\t)\n\t\t\t\tprocess.exit(1)\n\t\t\t}\n\t\t} catch {\n\t\t}\n\t} else {\n\t\ttry {\n\t\t\tconst existingFiles = await readdir(currentDir)\n\t\t\tconst significantFiles = existingFiles.filter(\n\t\t\t\t(file) =>\n\t\t\t\t\t!file.startsWith(\".\") &&\n\t\t\t\t\tfile !== \"node_modules\" &&\n\t\t\t\t\tfile !== \"package-lock.json\" &&\n\t\t\t\t\tfile !== \"yarn.lock\" &&\n\t\t\t\t\tfile !== \"pnpm-lock.yaml\"\n\t\t\t)\n\t\t\tif (significantFiles.length > 0) {\n\t\t\t\tconsole.error(`❌ Current directory already exists and is not empty`)\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"Please choose a different directory or remove existing files\"\n\t\t\t\t)\n\t\t\t\tprocess.exit(1)\n\t\t\t}\n\t\t} catch {\n\t\t}\n\t}\n\n\tconsole.log(\"📦 Copying template files...\")\n\t\n\tconst templateDir = join(__dirname, \"..\", \"templates\", \"agent\")\n\t\n\ttry {\n\t\tawait stat(templateDir)\n\t} catch {\n\t\tconsole.error(\"❌ Template directory not found\")\n\t\tprocess.exit(1)\n\t}\n\n\ttry {\n\t\tawait mkdir(projectDir, { recursive: true })\n\t\tawait copyTemplate(templateDir, projectDir)\n\t\tconsole.log(`✅ Template files copied to: ${sanitizedName}`)\n\t} catch (error) {\n\t\tconsole.error(\"❌ Failed to copy template files:\", error)\n\t\tprocess.exit(1)\n\t}\n\n\tconst variables = {\n\t\tprojectName: sanitizedName\n\t}\n\n\ttry {\n\t\tconst filesToUpdate = [\n\t\t\tjoin(projectDir, \"package.json\"),\n\t\t\tjoin(projectDir, \"README.md\"),\n\t\t\tjoin(projectDir, \"src\", \"agent.ts\")\n\t\t]\n\n\t\tfor (const filePath of filesToUpdate) {\n\t\t\ttry {\n\t\t\t\tlet content = await readFile(filePath, \"utf-8\")\n\t\t\t\tcontent = replaceTemplateVariables(content, variables)\n\t\t\t\tawait writeFile(filePath, content, \"utf-8\")\n\t\t\t} catch (error) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`⚠️ Could not update ${filePath.split(\"/\").pop()}: file not found`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(\"✅ Template variables updated\")\n\t} catch (error) {\n\t\tconsole.error(\"❌ Failed to update template variables:\", error)\n\t}\n\n\tconsole.log(\"\\n🎉 Hybrid project created successfully!\")\n\tconsole.log(`\\n📂 Project created in: ${projectDir}`)\n\tconsole.log(\"\\n📋 Next steps:\")\n\tconsole.log(`1. cd ${sanitizedName}`)\n\tconsole.log(\n\t\t\"2. Install dependencies (npm install, yarn install, or pnpm install)\"\n\t)\n\tconsole.log(\"3. Get your OpenRouter API key from https://openrouter.ai/keys\")\n\tconsole.log(\"4. Add your API key to the OPENROUTER_API_KEY in .env\")\n\tconsole.log(\"5. Set XMTP_ENV in .env (dev or production)\")\n\tconsole.log(\"6. Generate keys: npm run keys (or yarn/pnpm equivalent)\")\n\tconsole.log(\"7. Start development: npm run dev (or yarn/pnpm equivalent)\")\n\n\tconsole.log(\n\t\t\"\\n📖 For more information, see the README.md file in your project\"\n\t)\n}\n\nasync function main() {\n\tconst nodeVersion = process.versions.node\n\tconst [major] = nodeVersion.split(\".\").map(Number)\n\tif (!major || major < 20) {\n\t\tconsole.error(\"Error: Node.js version 20 or higher is required\")\n\t\tprocess.exit(1)\n\t}\n\n\ttry {\n\t\tawait initializeProject()\n\t} catch (error) {\n\t\tconsole.error(\"Failed to initialize project:\", error)\n\t\tconsole.error(\n\t\t\t\"Error details:\",\n\t\t\terror instanceof Error ? error.stack : String(error)\n\t\t)\n\t\tprocess.exit(1)\n\t}\n}\n\nmain().catch((error) => {\n\tconsole.error(\"CLI error:\", error)\n\tconsole.error(\n\t\t\"Error details:\",\n\t\terror instanceof Error ? error.stack : String(error)\n\t)\n\tprocess.exit(1)\n})\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Command } from \"commander\"\nimport degit from \"degit\"\nimport { readFile, readdir, writeFile } from \"node:fs/promises\"\nimport { join } from \"node:path\"\nimport prompts from \"prompts\"\n\ninterface Example {\n\tname: string\n\tdescription: string\n\tpath: string\n\tavailable: boolean\n\tmessage?: string\n}\n\n// Default to main branch, but allow override via REPO env var for CI/testing\nconst DEFAULT_REPO = \"ian/hybrid\"\nconst REPO = process.env.REPO || DEFAULT_REPO\n\nconst EXAMPLES: Example[] = [\n\t{\n\t\tname: \"basic\",\n\t\tdescription: \"Basic XMTP agent with message filtering and AI responses\",\n\t\tpath: \"basic\",\n\t\tavailable: true\n\t},\n\t{\n\t\tname: \"with-ponder\",\n\t\tdescription: \"Agent with Ponder integration for indexing blockchain data\",\n\t\tpath: \"with-ponder\",\n\t\tavailable: false,\n\t\tmessage: \"Coming soon\"\n\t},\n\t{\n\t\tname: \"with-foundry\",\n\t\tdescription:\n\t\t\t\"Agent with Foundry integration for smart contract development\",\n\t\tpath: \"with-foundry\",\n\t\tavailable: false,\n\t\tmessage: \"Coming soon\"\n\t}\n]\n\nfunction replaceTemplateVariables(\n\tcontent: string,\n\tvariables: Record<string, string>\n): string {\n\treturn content.replace(\n\t\t/\\{\\{(\\w+)\\}\\}/g,\n\t\t(match, key) => variables[key] || match\n\t)\n}\n\nasync function updateTemplateFiles(\n\tprojectDir: string,\n\tprojectName: string\n): Promise<void> {\n\tconst variables = { projectName }\n\n\tconst filesToUpdate = [\n\t\tjoin(projectDir, \"package.json\"),\n\t\tjoin(projectDir, \"README.md\"),\n\t\tjoin(projectDir, \"src\", \"agent.ts\")\n\t]\n\n\tfor (const filePath of filesToUpdate) {\n\t\ttry {\n\t\t\tlet content = await readFile(filePath, \"utf-8\")\n\n\t\t\t// First try template variable replacement\n\t\t\tcontent = replaceTemplateVariables(content, variables)\n\n\t\t\t// Special handling for package.json if template variables weren't found\n\t\t\tif (filePath.endsWith(\"package.json\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconst packageJson = JSON.parse(content)\n\t\t\t\t\tlet updated = false\n\n\t\t\t\t\t// If name is still a generic name, replace it\n\t\t\t\t\tif (\n\t\t\t\t\t\tpackageJson.name === \"agent\" ||\n\t\t\t\t\t\tpackageJson.name === \"hybrid-example-basic-agent\"\n\t\t\t\t\t) {\n\t\t\t\t\t\tpackageJson.name = projectName\n\t\t\t\t\t\tupdated = true\n\t\t\t\t\t}\n\n\t\t\t\t\t// Ensure required scripts exist\n\t\t\t\t\tif (!packageJson.scripts) {\n\t\t\t\t\t\tpackageJson.scripts = {}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add missing scripts and update old scripts to use hybrid CLI\n\t\t\t\t\tconst requiredScripts = {\n\t\t\t\t\t\tclean: \"hybrid clean\",\n\t\t\t\t\t\tdev: \"hybrid dev\",\n\t\t\t\t\t\tbuild: \"hybrid build\",\n\t\t\t\t\t\tstart: \"hybrid start\",\n\t\t\t\t\t\tkeys: \"hybrid keys --write\",\n\t\t\t\t\t\ttest: \"vitest\",\n\t\t\t\t\t\t\"test:watch\": \"vitest --watch\",\n\t\t\t\t\t\t\"test:coverage\": \"vitest --coverage\",\n\t\t\t\t\t\tlint: \"biome lint --write\",\n\t\t\t\t\t\t\"lint:check\": \"biome lint\",\n\t\t\t\t\t\tformat: \"biome format --write\",\n\t\t\t\t\t\t\"format:check\": \"biome format --check\",\n\t\t\t\t\t\ttypecheck: \"tsc --noEmit\"\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const [scriptName, scriptCommand] of Object.entries(\n\t\t\t\t\t\trequiredScripts\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Always update scripts to use the correct hybrid CLI commands\n\t\t\t\t\t\tpackageJson.scripts[scriptName] = scriptCommand\n\t\t\t\t\t\tupdated = true\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update dependencies to use independent packages\n\t\t\t\t\tif (packageJson.dependencies) {\n\t\t\t\t\t\tif (packageJson.dependencies.hybrid === \"workspace:*\") {\n\t\t\t\t\t\t\tpackageJson.dependencies.hybrid = \"latest\"\n\t\t\t\t\t\t\tupdated = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tpackageJson.dependencies[\"@openrouter/ai-sdk-provider\"] ===\n\t\t\t\t\t\t\t\"catalog:ai\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tpackageJson.dependencies[\"@openrouter/ai-sdk-provider\"] = \"^1.1.2\"\n\t\t\t\t\t\t\tupdated = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (packageJson.dependencies.zod === \"catalog:stack\") {\n\t\t\t\t\t\t\tpackageJson.dependencies.zod = \"^3.23.8\"\n\t\t\t\t\t\t\tupdated = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Remove workspace dependencies\n\t\t\t\t\t\tif (packageJson.dependencies[\"@hybrd/xmtp\"]) {\n\t\t\t\t\t\t\tpackageJson.dependencies[\"@hybrd/xmtp\"] = undefined\n\t\t\t\t\t\t\tupdated = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update devDependencies to use independent packages\n\t\t\t\t\tif (packageJson.devDependencies) {\n\t\t\t\t\t\tconst independentDevDeps = {\n\t\t\t\t\t\t\t\"@biomejs/biome\": \"^1.9.4\",\n\t\t\t\t\t\t\t\"@types/node\": \"^22.0.0\",\n\t\t\t\t\t\t\t\"@hybrd/cli\": \"latest\",\n\t\t\t\t\t\t\ttsx: \"^4.20.5\",\n\t\t\t\t\t\t\ttypescript: \"^5.8.3\",\n\t\t\t\t\t\t\tvitest: \"^3.2.4\"\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Remove workspace dependencies\n\t\t\t\t\t\tpackageJson.devDependencies[\"@config/biome\"] = undefined\n\t\t\t\t\t\tpackageJson.devDependencies[\"@config/tsconfig\"] = undefined\n\n\t\t\t\t\t\t// Add independent dependencies\n\t\t\t\t\t\tfor (const [depName, depVersion] of Object.entries(\n\t\t\t\t\t\t\tindependentDevDeps\n\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\tpackageJson.devDependencies[depName] = depVersion\n\t\t\t\t\t\t}\n\t\t\t\t\t\tupdated = true\n\t\t\t\t\t}\n\n\t\t\t\t\tif (updated) {\n\t\t\t\t\t\tcontent = `${JSON.stringify(packageJson, null, \"\\t\")}\\n`\n\t\t\t\t\t}\n\t\t\t\t} catch (parseError) {\n\t\t\t\t\tconsole.log(\"⚠️ Could not parse package.json for name update\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Special handling for README.md if template variables weren't found\n\t\t\tif (filePath.endsWith(\"README.md\")) {\n\t\t\t\t// Replace common README title patterns with project name\n\t\t\t\tcontent = content.replace(/^# .*$/m, `# ${projectName}`)\n\t\t\t}\n\n\t\t\tawait writeFile(filePath, content, \"utf-8\")\n\t\t} catch (error) {\n\t\t\tconsole.log(\n\t\t\t\t`⚠️ Could not update ${filePath.split(\"/\").pop()}: file not found or error occurred`\n\t\t\t)\n\t\t}\n\t}\n\n\t// Ensure .env file exists - create it if missing\n\tconst envPath = join(projectDir, \".env\")\n\ttry {\n\t\tawait readFile(envPath, \"utf-8\")\n\t} catch {\n\t\t// .env file doesn't exist, create it\n\t\tconst envContent = `# Required\nOPENROUTER_API_KEY=your_openrouter_api_key_here\nXMTP_WALLET_KEY=your_wallet_key_here\nXMTP_ENCRYPTION_KEY=your_encryption_key_here\n\n# Optional\nXMTP_ENV=dev\nPORT=8454`\n\t\tawait writeFile(envPath, envContent, \"utf-8\")\n\t\tconsole.log(\"📄 Created .env template file\")\n\t}\n\n\t// Ensure vitest.config.ts exists - create it if missing\n\tconst vitestConfigPath = join(projectDir, \"vitest.config.ts\")\n\ttry {\n\t\tawait readFile(vitestConfigPath, \"utf-8\")\n\t} catch {\n\t\t// vitest.config.ts doesn't exist, create it\n\t\tconst vitestConfigContent = `import { defineConfig } from \"vitest/config\"\n\nexport default defineConfig({\n\ttest: {\n\t\tenvironment: \"node\",\n\t\tglobals: true,\n\t\tsetupFiles: []\n\t},\n\tresolve: {\n\t\talias: {\n\t\t\t\"@\": \"./src\"\n\t\t}\n\t}\n})`\n\t\tawait writeFile(vitestConfigPath, vitestConfigContent, \"utf-8\")\n\t\tconsole.log(\"📄 Created vitest.config.ts file\")\n\t}\n\n\t// Ensure src/agent.test.ts exists - create it if missing\n\tconst agentTestPath = join(projectDir, \"src\", \"agent.test.ts\")\n\ttry {\n\t\tawait readFile(agentTestPath, \"utf-8\")\n\t} catch {\n\t\t// agent.test.ts doesn't exist, create it\n\t\tconst agentTestContent = `import { describe, expect, it } from \"vitest\"\n\n// Example test file - replace with actual tests for your agent\n\ndescribe(\"Agent\", () => {\n\tit(\"should be defined\", () => {\n\t\t// This is a placeholder test\n\t\t// Add real tests for your agent functionality\n\t\texpect(true).toBe(true)\n\t})\n})`\n\t\tawait writeFile(agentTestPath, agentTestContent, \"utf-8\")\n\t\tconsole.log(\"📄 Created src/agent.test.ts file\")\n\t}\n}\n\nasync function checkDirectoryEmpty(dirPath: string): Promise<boolean> {\n\ttry {\n\t\tconst files = await readdir(dirPath)\n\t\tconst significantFiles = files.filter(\n\t\t\t(file) =>\n\t\t\t\t!file.startsWith(\".\") &&\n\t\t\t\tfile !== \"node_modules\" &&\n\t\t\t\tfile !== \"package-lock.json\" &&\n\t\t\t\tfile !== \"yarn.lock\" &&\n\t\t\t\tfile !== \"pnpm-lock.yaml\"\n\t\t)\n\t\treturn significantFiles.length === 0\n\t} catch {\n\t\t// Directory doesn't exist, so it's \"empty\"\n\t\treturn true\n\t}\n}\n\nasync function createProject(\n\tprojectName: string,\n\texampleName?: string\n): Promise<void> {\n\tconsole.log(\"🚀 Creating a new Hybrid project...\")\n\n\t// Validate project name\n\tif (!projectName || projectName.trim() === \"\") {\n\t\tconsole.error(\"❌ Project name is required\")\n\t\tprocess.exit(1)\n\t}\n\n\tconst sanitizedName = projectName\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9-]/g, \"-\")\n\t\t.replace(/-+/g, \"-\")\n\t\t.replace(/^-|-$/g, \"\")\n\n\tconst currentDir = process.cwd()\n\tconst projectDir =\n\t\tprojectName === \".\" ? currentDir : join(currentDir, sanitizedName)\n\n\t// Check if directory is empty\n\tconst isEmpty = await checkDirectoryEmpty(projectDir)\n\tif (!isEmpty) {\n\t\tconsole.error(\n\t\t\t`❌ Directory \"${sanitizedName}\" already exists and is not empty`\n\t\t)\n\t\tconsole.error(\n\t\t\t\"Please choose a different name or remove the existing directory\"\n\t\t)\n\t\tprocess.exit(1)\n\t}\n\n\t// Select example if not provided\n\tlet selectedExample: Example\n\tif (exampleName) {\n\t\tconst example = EXAMPLES.find((ex) => ex.name === exampleName)\n\t\tif (!example) {\n\t\t\tconsole.error(`❌ Example \"${exampleName}\" not found`)\n\t\t\tconsole.error(\n\t\t\t\t`Available examples: ${EXAMPLES.map((ex) => ex.name).join(\", \")}`\n\t\t\t)\n\t\t\tprocess.exit(1)\n\t\t}\n\t\tselectedExample = example\n\t\tconsole.log(`📋 Using example: ${selectedExample.name}`)\n\t} else {\n\t\t// Check if we're running in a non-interactive environment (like CI)\n\t\tif (!process.stdin.isTTY) {\n\t\t\tconsole.error(\n\t\t\t\t\"❌ Example is required in non-interactive mode. Use --example <name>\"\n\t\t\t)\n\t\t\tconsole.error(\n\t\t\t\t`Available examples: ${EXAMPLES.map((ex) => ex.name).join(\", \")}`\n\t\t\t)\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\tconst { example } = await prompts({\n\t\t\ttype: \"select\",\n\t\t\tname: \"example\",\n\t\t\tmessage: \"Which example would you like to use?\",\n\t\t\tchoices: EXAMPLES.map((ex) => ({\n\t\t\t\ttitle: ex.name,\n\t\t\t\tdescription: ex.available\n\t\t\t\t\t? ex.description\n\t\t\t\t\t: `${ex.description} (${ex.message || \"Coming soon\"})`,\n\t\t\t\tvalue: ex,\n\t\t\t\tdisabled: !ex.available\n\t\t\t})),\n\t\t\tinitial: 0\n\t\t})\n\n\t\tif (!example) {\n\t\t\tconsole.log(\"❌ No example selected. Exiting...\")\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\t// Check if the selected example is available\n\t\tif (!example.available) {\n\t\t\tconsole.log(\n\t\t\t\t`❌ Example \"${example.name}\" is not yet available. ${example.message || \"Coming soon\"}`\n\t\t\t)\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\tselectedExample = example\n\t}\n\n\tconsole.log(`📦 Cloning ${selectedExample.name} example...`)\n\n\ttry {\n\t\t// For degit, the correct syntax is: repo#branch/subdirectory\n\t\t// But we need to be careful about the path construction\n\t\tlet degitSource: string\n\n\t\tif (REPO.includes(\"#\")) {\n\t\t\t// REPO is in format \"user/repo#branch\"\n\t\t\t// We need to construct: user/repo#branch/examples/basic\n\t\t\tconst [repoWithBranch] = REPO.split(\"/examples/\") // Remove any existing path\n\t\t\tdegitSource = `${repoWithBranch}/examples/${selectedExample.name}`\n\t\t} else {\n\t\t\t// No branch specified, use default format\n\t\t\tdegitSource = `${REPO}/examples/${selectedExample.name}`\n\t\t}\n\n\t\tconsole.log(`🔍 Degit source: ${degitSource}`)\n\t\tconst emitter = degit(degitSource)\n\t\tawait emitter.clone(projectDir)\n\t\tconsole.log(`✅ Template cloned to: ${sanitizedName}`)\n\t} catch (error) {\n\t\tconsole.error(\"❌ Failed to clone template:\", error)\n\t\tprocess.exit(1)\n\t}\n\n\t// Update template variables\n\tconsole.log(\"🔧 Updating template variables...\")\n\ttry {\n\t\tawait updateTemplateFiles(projectDir, sanitizedName)\n\t\tconsole.log(\"✅ Template variables updated\")\n\t} catch (error) {\n\t\tconsole.error(\"❌ Failed to update template variables:\", error)\n\t}\n\n\tconsole.log(\"\\n🎉 Hybrid project created successfully!\")\n\tconsole.log(`\\n📂 Project created in: ${projectDir}`)\n\tconsole.log(\"\\n📋 Next steps:\")\n\tif (projectName !== \".\") {\n\t\tconsole.log(`1. cd ${sanitizedName}`)\n\t}\n\tconsole.log(\n\t\t`${projectName !== \".\" ? \"2\" : \"1\"}. Install dependencies (npm install, yarn install, or pnpm install)`\n\t)\n\tconsole.log(\n\t\t`${projectName !== \".\" ? \"3\" : \"2\"}. Get your OpenRouter API key from https://openrouter.ai/keys`\n\t)\n\tconsole.log(\n\t\t`${projectName !== \".\" ? \"4\" : \"3\"}. Add your API key to the OPENROUTER_API_KEY in .env`\n\t)\n\tconsole.log(\n\t\t`${projectName !== \".\" ? \"5\" : \"4\"}. Set XMTP_ENV in .env (dev or production)`\n\t)\n\tconsole.log(\n\t\t`${projectName !== \".\" ? \"6\" : \"5\"}. Generate keys: npm run keys (or yarn/pnpm equivalent)`\n\t)\n\tconsole.log(\n\t\t`${projectName !== \".\" ? \"7\" : \"6\"}. Start development: npm run dev (or yarn/pnpm equivalent)`\n\t)\n\n\tconsole.log(\n\t\t\"\\n📖 For more information, see the README.md file in your project\"\n\t)\n}\n\nexport async function initializeProject(): Promise<void> {\n\tconst program = new Command()\n\n\tprogram\n\t\t.name(\"create-hybrid\")\n\t\t.description(\"Create a new Hybrid XMTP agent project\")\n\t\t.version(\"1.2.3\")\n\t\t.argument(\"[project-name]\", \"Name of the project\")\n\t\t.option(\n\t\t\t\"-e, --example <example>\",\n\t\t\t\"Example to use (basic, with-ponder, with-foundry)\"\n\t\t)\n\t\t.action(async (projectName?: string, options?: { example?: string }) => {\n\t\t\tlet finalProjectName = projectName\n\n\t\t\t// Debug logging for CI troubleshooting\n\t\t\tif (process.env.CI) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`🔍 Debug: projectName=\"${projectName}\", options.example=\"${options?.example}\"`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// If no project name provided or empty string, prompt for it\n\t\t\tif (!finalProjectName || finalProjectName.trim() === \"\") {\n\t\t\t\t// Check if we're running in a non-interactive environment (like tests)\n\t\t\t\tif (!process.stdin.isTTY) {\n\t\t\t\t\tconsole.error(\"❌ Project name is required\")\n\t\t\t\t\tprocess.exit(1)\n\t\t\t\t}\n\n\t\t\t\tconst { name } = await prompts({\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tname: \"name\",\n\t\t\t\t\tmessage: \"What is your project name?\",\n\t\t\t\t\tvalidate: (value: string) => {\n\t\t\t\t\t\tif (!value || !value.trim()) {\n\t\t\t\t\t\t\treturn \"Project name is required\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tif (!name) {\n\t\t\t\t\tconsole.log(\"❌ Project name is required. Exiting...\")\n\t\t\t\t\tprocess.exit(1)\n\t\t\t\t}\n\n\t\t\t\tfinalProjectName = name\n\t\t\t}\n\n\t\t\tawait createProject(finalProjectName as string, options?.example)\n\t\t})\n\n\tawait program.parseAsync()\n}\n\nasync function main(): Promise<void> {\n\tconst nodeVersion = process.versions.node\n\tconst [major] = nodeVersion.split(\".\").map(Number)\n\tif (!major || major < 20) {\n\t\tconsole.error(\"Error: Node.js version 20 or higher is required\")\n\t\tprocess.exit(1)\n\t}\n\n\ttry {\n\t\tawait initializeProject()\n\t} catch (error) {\n\t\tconsole.error(\"Failed to initialize project:\", error)\n\t\tconsole.error(\n\t\t\t\"Error details:\",\n\t\t\terror instanceof Error ? error.stack : String(error)\n\t\t)\n\t\tprocess.exit(1)\n\t}\n}\n\nmain().catch((error) => {\n\tconsole.error(\"CLI error:\", error)\n\tconsole.error(\n\t\t\"Error details:\",\n\t\terror instanceof Error ? error.stack : String(error)\n\t)\n\tprocess.exit(1)\n})\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-hybrid",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "Create a new Hybrid XMTP agent project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-hybrid": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
"templates"
|
|
10
|
+
"dist"
|
|
12
11
|
],
|
|
13
12
|
"exports": {
|
|
14
13
|
".": {
|
|
@@ -16,12 +15,17 @@
|
|
|
16
15
|
"import": "./dist/index.js"
|
|
17
16
|
}
|
|
18
17
|
},
|
|
19
|
-
"dependencies": {
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"degit": "^2.8.4",
|
|
20
|
+
"commander": "^12.1.0",
|
|
21
|
+
"prompts": "^2.4.2"
|
|
22
|
+
},
|
|
20
23
|
"devDependencies": {
|
|
21
24
|
"@types/node": "22.8.6",
|
|
25
|
+
"@types/prompts": "^2.4.9",
|
|
22
26
|
"tsup": "^8.5.0",
|
|
23
|
-
"@config/
|
|
24
|
-
"@config/
|
|
27
|
+
"@config/tsconfig": "0.0.0",
|
|
28
|
+
"@config/biome": "0.0.0"
|
|
25
29
|
},
|
|
26
30
|
"engines": {
|
|
27
31
|
"node": ">=20"
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
# {{projectName}}
|
|
2
|
-
|
|
3
|
-
A Hybrid XMTP agent built with TypeScript and AI capabilities.
|
|
4
|
-
|
|
5
|
-
## 🚀 Quick Start
|
|
6
|
-
|
|
7
|
-
### Prerequisites
|
|
8
|
-
|
|
9
|
-
- Node.js 20 or higher
|
|
10
|
-
- npm or yarn
|
|
11
|
-
|
|
12
|
-
### Installation
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
# Install dependencies
|
|
16
|
-
npm install
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### Setup
|
|
20
|
-
|
|
21
|
-
1. **Get your OpenRouter API key**
|
|
22
|
-
- Visit [OpenRouter](https://openrouter.ai/keys) and create an account
|
|
23
|
-
- Generate an API key
|
|
24
|
-
- Add it to your `.env` file
|
|
25
|
-
|
|
26
|
-
2. **Generate XMTP keys**
|
|
27
|
-
```bash
|
|
28
|
-
npm run keys
|
|
29
|
-
# or
|
|
30
|
-
npx hybrid gen:keys
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
3. **Update environment variables**
|
|
34
|
-
Edit the `.env` file with your API key and generated keys.
|
|
35
|
-
|
|
36
|
-
### Development
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
# Start development server with auto-reload
|
|
40
|
-
npm run dev
|
|
41
|
-
|
|
42
|
-
# Build for production
|
|
43
|
-
npm run build
|
|
44
|
-
|
|
45
|
-
# Start production server
|
|
46
|
-
npm start
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## 📁 Project Structure
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
{{projectName}}/
|
|
53
|
-
├── src/
|
|
54
|
-
│ └── agent.ts # Main agent implementation
|
|
55
|
-
├── dist/ # Compiled JavaScript (after build)
|
|
56
|
-
├── .env # Environment variables
|
|
57
|
-
├── package.json # Dependencies and scripts
|
|
58
|
-
├── tsconfig.json # TypeScript configuration
|
|
59
|
-
├── vitest.config.ts # Test configuration
|
|
60
|
-
└── README.md # This file
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## 🛠️ Available Scripts
|
|
64
|
-
|
|
65
|
-
- `npm run dev` - Start development server with hot reload
|
|
66
|
-
- `npm run build` - Build the project for production
|
|
67
|
-
- `npm run start` - Start the production server
|
|
68
|
-
- `npm run test` - Run tests
|
|
69
|
-
- `npm run test:watch` - Run tests in watch mode
|
|
70
|
-
- `npm run test:coverage` - Run tests with coverage report
|
|
71
|
-
- `npm run lint` - Lint and fix code
|
|
72
|
-
- `npm run format` - Format code
|
|
73
|
-
- `npm run typecheck` - Check TypeScript types
|
|
74
|
-
|
|
75
|
-
## 🤖 Agent Configuration
|
|
76
|
-
|
|
77
|
-
The agent is configured in `src/agent.ts`. You can customize:
|
|
78
|
-
|
|
79
|
-
- **AI Model**: Change the model in the `openrouter()` call
|
|
80
|
-
- **Instructions**: Modify the agent's system prompt
|
|
81
|
-
- **Message Filtering**: Adjust which messages the agent responds to
|
|
82
|
-
- **Port**: Change the server port in `.env`
|
|
83
|
-
|
|
84
|
-
### Example Customizations
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
const agent = new Agent({
|
|
88
|
-
name: "My Custom Agent",
|
|
89
|
-
model: openrouter("anthropic/claude-3-haiku"), // Different model
|
|
90
|
-
instructions: "You are a helpful assistant that specializes in..."
|
|
91
|
-
})
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## 🔧 Environment Variables
|
|
95
|
-
|
|
96
|
-
Create a `.env` file with:
|
|
97
|
-
|
|
98
|
-
```env
|
|
99
|
-
# Required
|
|
100
|
-
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
|
101
|
-
XMTP_WALLET_KEY=your_generated_wallet_key
|
|
102
|
-
XMTP_ENCRYPTION_KEY=your_generated_encryption_key
|
|
103
|
-
|
|
104
|
-
# Optional
|
|
105
|
-
XMTP_ENV=dev
|
|
106
|
-
PORT=8454
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## 🧪 Testing
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
# Run all tests
|
|
113
|
-
npm test
|
|
114
|
-
|
|
115
|
-
# Run tests in watch mode
|
|
116
|
-
npm run test:watch
|
|
117
|
-
|
|
118
|
-
# Run with coverage
|
|
119
|
-
npm run test:coverage
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## 📚 Key Concepts
|
|
123
|
-
|
|
124
|
-
### Message Filtering
|
|
125
|
-
|
|
126
|
-
The agent uses a filter function to determine which messages to respond to:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
const filter = async ({ message }) => {
|
|
130
|
-
// Return true to respond, false to ignore
|
|
131
|
-
const content = message.content?.toString()
|
|
132
|
-
|
|
133
|
-
// Example: Only respond to messages mentioning the bot
|
|
134
|
-
return content?.toLowerCase().includes('@bot')
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Agent Instructions
|
|
139
|
-
|
|
140
|
-
The system prompt tells the AI how to behave:
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
const agent = new Agent({
|
|
144
|
-
instructions: "You are a helpful XMTP agent that..."
|
|
145
|
-
})
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## 🔗 Useful Links
|
|
149
|
-
|
|
150
|
-
- [Hybrid Documentation](https://github.com/your-org/hybrid)
|
|
151
|
-
- [XMTP Documentation](https://docs.xmtp.org/)
|
|
152
|
-
- [OpenRouter Models](https://openrouter.ai/docs#models)
|
|
153
|
-
|
|
154
|
-
## 🤝 Contributing
|
|
155
|
-
|
|
156
|
-
1. Fork the repository
|
|
157
|
-
2. Create a feature branch
|
|
158
|
-
3. Make your changes
|
|
159
|
-
4. Add tests
|
|
160
|
-
5. Submit a pull request
|
|
161
|
-
|
|
162
|
-
## 📄 License
|
|
163
|
-
|
|
164
|
-
MIT License - see LICENSE file for details
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{projectName}}",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"description": "A Hybrid XMTP agent",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"clean": "hybrid clean",
|
|
9
|
-
"dev": "hybrid dev",
|
|
10
|
-
"build": "hybrid build",
|
|
11
|
-
"start": "hybrid start",
|
|
12
|
-
"keys": "hybrid gen:keys --write",
|
|
13
|
-
"test": "vitest",
|
|
14
|
-
"test:watch": "vitest --watch",
|
|
15
|
-
"lint": "biome lint --write",
|
|
16
|
-
"lint:check": "biome lint",
|
|
17
|
-
"format": "biome format --write",
|
|
18
|
-
"format:check": "biome format --check",
|
|
19
|
-
"typecheck": "tsc --noEmit"
|
|
20
|
-
},
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"@openrouter/ai-sdk-provider": "^1.1.2",
|
|
23
|
-
"hybrid": "^1.2.2"
|
|
24
|
-
},
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"@biomejs/biome": "^1.9.4",
|
|
27
|
-
"@types/node": "^22.0.0",
|
|
28
|
-
"@hybrd/cli": "^1.2.2",
|
|
29
|
-
"typescript": "^5.8.3",
|
|
30
|
-
"vitest": "^3.2.4"
|
|
31
|
-
},
|
|
32
|
-
"engines": {
|
|
33
|
-
"node": ">=20"
|
|
34
|
-
},
|
|
35
|
-
"author": "",
|
|
36
|
-
"license": "MIT"
|
|
37
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { agent } from "./agent.js"
|
|
3
|
-
|
|
4
|
-
describe("Agent", () => {
|
|
5
|
-
it("should generate a response", async () => {
|
|
6
|
-
const response = await agent.generate("Hello, how are you?")
|
|
7
|
-
expect(response).toBeDefined()
|
|
8
|
-
expect(typeof response).toBe("string")
|
|
9
|
-
expect(response.length).toBeGreaterThan(0)
|
|
10
|
-
})
|
|
11
|
-
})
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
|
|
2
|
-
import { Agent, type MessageListenerConfig, type Reaction } from "hybrid"
|
|
3
|
-
|
|
4
|
-
export const openrouter = createOpenRouter({
|
|
5
|
-
apiKey: process.env.OPENROUTER_API_KEY
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
const agent = new Agent({
|
|
9
|
-
name: "{{projectName}}",
|
|
10
|
-
model: openrouter("x-ai/grok-4"),
|
|
11
|
-
instructions:
|
|
12
|
-
"You are a XMTP agent that responds to messages and reactions. Try and be as conversational as possible."
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
const filter: MessageListenerConfig["filter"] = async ({ message }) => {
|
|
16
|
-
const messageContent = message.content?.toString()
|
|
17
|
-
const contentTypeId = message.contentType?.typeId
|
|
18
|
-
const isMessage = contentTypeId === "text"
|
|
19
|
-
const isReaction = contentTypeId === "reaction"
|
|
20
|
-
const isReply = contentTypeId === "reply"
|
|
21
|
-
|
|
22
|
-
if (isReply) {
|
|
23
|
-
return true
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (isReaction) {
|
|
27
|
-
const { content, action } = message.content as Reaction
|
|
28
|
-
|
|
29
|
-
if (action === "added") {
|
|
30
|
-
if (content.toLowerCase().includes("👍")) {
|
|
31
|
-
return true
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (isMessage) {
|
|
37
|
-
const lowerContent = messageContent?.toLowerCase()
|
|
38
|
-
const mentionPatterns = ["@bot"]
|
|
39
|
-
|
|
40
|
-
return mentionPatterns.some((pattern) => lowerContent?.includes(pattern))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return false
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
agent.listen({
|
|
47
|
-
port: process.env.PORT || "8454",
|
|
48
|
-
filter
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
export { agent }
|
|
52
|
-
export default agent
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"lib": ["ES2020"],
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"moduleResolution": "bundler",
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"rootDir": ".",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"noEmit": false,
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"isolatedModules": false,
|
|
14
|
-
"esModuleInterop": true,
|
|
15
|
-
"allowSyntheticDefaultImports": true,
|
|
16
|
-
"forceConsistentCasingInFileNames": true,
|
|
17
|
-
"resolveJsonModule": true,
|
|
18
|
-
"types": ["node"]
|
|
19
|
-
},
|
|
20
|
-
"include": ["src/**/*"],
|
|
21
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
22
|
-
}
|