create-hybrid 1.2.3 → 1.2.4
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/dist/index.js +296 -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/dist/index.js
CHANGED
|
@@ -1,127 +1,274 @@
|
|
|
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
|
+
// Path is now handled separately in degit options
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "crypto-agent",
|
|
20
|
+
description: "Advanced agent with blockchain integration and crypto tools",
|
|
21
|
+
path: "crypto-agent"
|
|
22
|
+
// Path is now handled separately in degit options
|
|
23
|
+
}
|
|
24
|
+
];
|
|
21
25
|
function replaceTemplateVariables(content, variables) {
|
|
22
26
|
return content.replace(
|
|
23
27
|
/\{\{(\w+)\}\}/g,
|
|
24
28
|
(match, key) => variables[key] || match
|
|
25
29
|
);
|
|
26
30
|
}
|
|
27
|
-
async function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await
|
|
31
|
+
async function updateTemplateFiles(projectDir, projectName) {
|
|
32
|
+
const variables = { projectName };
|
|
33
|
+
const filesToUpdate = [
|
|
34
|
+
join(projectDir, "package.json"),
|
|
35
|
+
join(projectDir, "README.md"),
|
|
36
|
+
join(projectDir, "src", "agent.ts")
|
|
37
|
+
];
|
|
38
|
+
for (const filePath of filesToUpdate) {
|
|
39
|
+
try {
|
|
40
|
+
let content = await readFile(filePath, "utf-8");
|
|
41
|
+
content = replaceTemplateVariables(content, variables);
|
|
42
|
+
if (filePath.endsWith("package.json")) {
|
|
43
|
+
try {
|
|
44
|
+
const packageJson = JSON.parse(content);
|
|
45
|
+
let updated = false;
|
|
46
|
+
if (packageJson.name === "agent" || packageJson.name === "hybrid-example-basic-agent") {
|
|
47
|
+
packageJson.name = projectName;
|
|
48
|
+
updated = true;
|
|
49
|
+
}
|
|
50
|
+
if (!packageJson.scripts) {
|
|
51
|
+
packageJson.scripts = {};
|
|
52
|
+
}
|
|
53
|
+
const requiredScripts = {
|
|
54
|
+
clean: "hybrid clean",
|
|
55
|
+
dev: "hybrid dev",
|
|
56
|
+
build: "hybrid build",
|
|
57
|
+
start: "hybrid start",
|
|
58
|
+
keys: "hybrid gen:keys --write",
|
|
59
|
+
test: "vitest",
|
|
60
|
+
"test:watch": "vitest --watch",
|
|
61
|
+
"test:coverage": "vitest --coverage",
|
|
62
|
+
lint: "biome lint --write",
|
|
63
|
+
"lint:check": "biome lint",
|
|
64
|
+
format: "biome format --write",
|
|
65
|
+
"format:check": "biome format --check",
|
|
66
|
+
typecheck: "tsc --noEmit"
|
|
67
|
+
};
|
|
68
|
+
for (const [scriptName, scriptCommand] of Object.entries(
|
|
69
|
+
requiredScripts
|
|
70
|
+
)) {
|
|
71
|
+
packageJson.scripts[scriptName] = scriptCommand;
|
|
72
|
+
updated = true;
|
|
73
|
+
}
|
|
74
|
+
if (packageJson.dependencies) {
|
|
75
|
+
if (packageJson.dependencies.hybrid === "workspace:*") {
|
|
76
|
+
packageJson.dependencies.hybrid = "latest";
|
|
77
|
+
updated = true;
|
|
78
|
+
}
|
|
79
|
+
if (packageJson.dependencies["@openrouter/ai-sdk-provider"] === "catalog:ai") {
|
|
80
|
+
packageJson.dependencies["@openrouter/ai-sdk-provider"] = "^1.1.2";
|
|
81
|
+
updated = true;
|
|
82
|
+
}
|
|
83
|
+
if (packageJson.dependencies.zod === "catalog:stack") {
|
|
84
|
+
packageJson.dependencies.zod = "^3.23.8";
|
|
85
|
+
updated = true;
|
|
86
|
+
}
|
|
87
|
+
if (packageJson.dependencies["@hybrd/xmtp"]) {
|
|
88
|
+
packageJson.dependencies["@hybrd/xmtp"] = void 0;
|
|
89
|
+
updated = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (packageJson.devDependencies) {
|
|
93
|
+
const independentDevDeps = {
|
|
94
|
+
"@biomejs/biome": "^1.9.4",
|
|
95
|
+
"@types/node": "^22.0.0",
|
|
96
|
+
"@hybrd/cli": "latest",
|
|
97
|
+
tsx: "^4.20.5",
|
|
98
|
+
typescript: "^5.8.3",
|
|
99
|
+
vitest: "^3.2.4"
|
|
100
|
+
};
|
|
101
|
+
packageJson.devDependencies["@config/biome"] = void 0;
|
|
102
|
+
packageJson.devDependencies["@config/tsconfig"] = void 0;
|
|
103
|
+
for (const [depName, depVersion] of Object.entries(
|
|
104
|
+
independentDevDeps
|
|
105
|
+
)) {
|
|
106
|
+
packageJson.devDependencies[depName] = depVersion;
|
|
107
|
+
}
|
|
108
|
+
updated = true;
|
|
109
|
+
}
|
|
110
|
+
if (updated) {
|
|
111
|
+
content = `${JSON.stringify(packageJson, null, " ")}
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
} catch (parseError) {
|
|
115
|
+
console.log("\u26A0\uFE0F Could not parse package.json for name update");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (filePath.endsWith("README.md")) {
|
|
119
|
+
content = content.replace(/^# .*$/m, `# ${projectName}`);
|
|
120
|
+
}
|
|
121
|
+
await writeFile(filePath, content, "utf-8");
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.log(
|
|
124
|
+
`\u26A0\uFE0F Could not update ${filePath.split("/").pop()}: file not found or error occurred`
|
|
125
|
+
);
|
|
37
126
|
}
|
|
38
127
|
}
|
|
128
|
+
const envPath = join(projectDir, ".env");
|
|
129
|
+
try {
|
|
130
|
+
await readFile(envPath, "utf-8");
|
|
131
|
+
} catch {
|
|
132
|
+
const envContent = `# Required
|
|
133
|
+
OPENROUTER_API_KEY=your_openrouter_api_key_here
|
|
134
|
+
XMTP_WALLET_KEY=your_wallet_key_here
|
|
135
|
+
XMTP_ENCRYPTION_KEY=your_encryption_key_here
|
|
136
|
+
|
|
137
|
+
# Optional
|
|
138
|
+
XMTP_ENV=dev
|
|
139
|
+
PORT=8454`;
|
|
140
|
+
await writeFile(envPath, envContent, "utf-8");
|
|
141
|
+
console.log("\u{1F4C4} Created .env template file");
|
|
142
|
+
}
|
|
143
|
+
const vitestConfigPath = join(projectDir, "vitest.config.ts");
|
|
144
|
+
try {
|
|
145
|
+
await readFile(vitestConfigPath, "utf-8");
|
|
146
|
+
} catch {
|
|
147
|
+
const vitestConfigContent = `import { defineConfig } from "vitest/config"
|
|
148
|
+
|
|
149
|
+
export default defineConfig({
|
|
150
|
+
test: {
|
|
151
|
+
environment: "node",
|
|
152
|
+
globals: true,
|
|
153
|
+
setupFiles: []
|
|
154
|
+
},
|
|
155
|
+
resolve: {
|
|
156
|
+
alias: {
|
|
157
|
+
"@": "./src"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
})`;
|
|
161
|
+
await writeFile(vitestConfigPath, vitestConfigContent, "utf-8");
|
|
162
|
+
console.log("\u{1F4C4} Created vitest.config.ts file");
|
|
163
|
+
}
|
|
164
|
+
const agentTestPath = join(projectDir, "src", "agent.test.ts");
|
|
165
|
+
try {
|
|
166
|
+
await readFile(agentTestPath, "utf-8");
|
|
167
|
+
} catch {
|
|
168
|
+
const agentTestContent = `import { describe, expect, it } from "vitest"
|
|
169
|
+
|
|
170
|
+
// Example test file - replace with actual tests for your agent
|
|
171
|
+
|
|
172
|
+
describe("Agent", () => {
|
|
173
|
+
it("should be defined", () => {
|
|
174
|
+
// This is a placeholder test
|
|
175
|
+
// Add real tests for your agent functionality
|
|
176
|
+
expect(true).toBe(true)
|
|
177
|
+
})
|
|
178
|
+
})`;
|
|
179
|
+
await writeFile(agentTestPath, agentTestContent, "utf-8");
|
|
180
|
+
console.log("\u{1F4C4} Created src/agent.test.ts file");
|
|
181
|
+
}
|
|
39
182
|
}
|
|
40
|
-
async function
|
|
183
|
+
async function checkDirectoryEmpty(dirPath) {
|
|
184
|
+
try {
|
|
185
|
+
const files = await readdir(dirPath);
|
|
186
|
+
const significantFiles = files.filter(
|
|
187
|
+
(file) => !file.startsWith(".") && file !== "node_modules" && file !== "package-lock.json" && file !== "yarn.lock" && file !== "pnpm-lock.yaml"
|
|
188
|
+
);
|
|
189
|
+
return significantFiles.length === 0;
|
|
190
|
+
} catch {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function createProject(projectName, exampleName) {
|
|
41
195
|
console.log("\u{1F680} Creating a new Hybrid project...");
|
|
42
|
-
|
|
43
|
-
let projectName = projectNameArg;
|
|
44
|
-
if (projectNameArg === "") {
|
|
196
|
+
if (!projectName || projectName.trim() === "") {
|
|
45
197
|
console.error("\u274C Project name is required");
|
|
46
198
|
process.exit(1);
|
|
47
199
|
}
|
|
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
200
|
const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
57
201
|
const currentDir = process.cwd();
|
|
58
202
|
const projectDir = projectName === "." ? currentDir : join(currentDir, sanitizedName);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
203
|
+
const isEmpty = await checkDirectoryEmpty(projectDir);
|
|
204
|
+
if (!isEmpty) {
|
|
205
|
+
console.error(
|
|
206
|
+
`\u274C Directory "${sanitizedName}" already exists and is not empty`
|
|
207
|
+
);
|
|
208
|
+
console.error(
|
|
209
|
+
"Please choose a different name or remove the existing directory"
|
|
210
|
+
);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
let selectedExample;
|
|
214
|
+
if (exampleName) {
|
|
215
|
+
const example = EXAMPLES.find((ex) => ex.name === exampleName);
|
|
216
|
+
if (!example) {
|
|
217
|
+
console.error(`\u274C Example "${exampleName}" not found`);
|
|
218
|
+
console.error(
|
|
219
|
+
`Available examples: ${EXAMPLES.map((ex) => ex.name).join(", ")}`
|
|
220
|
+
);
|
|
221
|
+
process.exit(1);
|
|
72
222
|
}
|
|
223
|
+
selectedExample = example;
|
|
224
|
+
console.log(`\u{1F4CB} Using example: ${selectedExample.name}`);
|
|
73
225
|
} else {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
(file) => !file.startsWith(".") && file !== "node_modules" && file !== "package-lock.json" && file !== "yarn.lock" && file !== "pnpm-lock.yaml"
|
|
226
|
+
if (!process.stdin.isTTY) {
|
|
227
|
+
console.error(
|
|
228
|
+
"\u274C Example is required in non-interactive mode. Use --example <name>"
|
|
78
229
|
);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
230
|
+
console.error(
|
|
231
|
+
`Available examples: ${EXAMPLES.map((ex) => ex.name).join(", ")}`
|
|
232
|
+
);
|
|
233
|
+
process.exit(1);
|
|
87
234
|
}
|
|
235
|
+
const { example } = await prompts({
|
|
236
|
+
type: "select",
|
|
237
|
+
name: "example",
|
|
238
|
+
message: "Which example would you like to use?",
|
|
239
|
+
choices: EXAMPLES.map((ex) => ({
|
|
240
|
+
title: ex.name,
|
|
241
|
+
description: ex.description,
|
|
242
|
+
value: ex
|
|
243
|
+
})),
|
|
244
|
+
initial: 0
|
|
245
|
+
});
|
|
246
|
+
if (!example) {
|
|
247
|
+
console.log("\u274C No example selected. Exiting...");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
selectedExample = example;
|
|
88
251
|
}
|
|
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
|
-
}
|
|
252
|
+
console.log(`\u{1F4E6} Cloning ${selectedExample.name} example...`);
|
|
97
253
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
254
|
+
let degitSource;
|
|
255
|
+
if (REPO.includes("#")) {
|
|
256
|
+
const [repoWithBranch] = REPO.split("/examples/");
|
|
257
|
+
degitSource = `${repoWithBranch}/examples/${selectedExample.name}`;
|
|
258
|
+
} else {
|
|
259
|
+
degitSource = `${REPO}/examples/${selectedExample.name}`;
|
|
260
|
+
}
|
|
261
|
+
console.log(`\u{1F50D} Degit source: ${degitSource}`);
|
|
262
|
+
const emitter = degit(degitSource);
|
|
263
|
+
await emitter.clone(projectDir);
|
|
264
|
+
console.log(`\u2705 Template cloned to: ${sanitizedName}`);
|
|
101
265
|
} catch (error) {
|
|
102
|
-
console.error("\u274C Failed to
|
|
266
|
+
console.error("\u274C Failed to clone template:", error);
|
|
103
267
|
process.exit(1);
|
|
104
268
|
}
|
|
105
|
-
|
|
106
|
-
projectName: sanitizedName
|
|
107
|
-
};
|
|
269
|
+
console.log("\u{1F527} Updating template variables...");
|
|
108
270
|
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
|
-
}
|
|
271
|
+
await updateTemplateFiles(projectDir, sanitizedName);
|
|
125
272
|
console.log("\u2705 Template variables updated");
|
|
126
273
|
} catch (error) {
|
|
127
274
|
console.error("\u274C Failed to update template variables:", error);
|
|
@@ -130,19 +277,66 @@ async function initializeProject() {
|
|
|
130
277
|
console.log(`
|
|
131
278
|
\u{1F4C2} Project created in: ${projectDir}`);
|
|
132
279
|
console.log("\n\u{1F4CB} Next steps:");
|
|
133
|
-
|
|
280
|
+
if (projectName !== ".") {
|
|
281
|
+
console.log(`1. cd ${sanitizedName}`);
|
|
282
|
+
}
|
|
134
283
|
console.log(
|
|
135
|
-
"2. Install dependencies (npm install, yarn install, or pnpm install)
|
|
284
|
+
`${projectName !== "." ? "2" : "1"}. Install dependencies (npm install, yarn install, or pnpm install)`
|
|
285
|
+
);
|
|
286
|
+
console.log(
|
|
287
|
+
`${projectName !== "." ? "3" : "2"}. Get your OpenRouter API key from https://openrouter.ai/keys`
|
|
288
|
+
);
|
|
289
|
+
console.log(
|
|
290
|
+
`${projectName !== "." ? "4" : "3"}. Add your API key to the OPENROUTER_API_KEY in .env`
|
|
291
|
+
);
|
|
292
|
+
console.log(
|
|
293
|
+
`${projectName !== "." ? "5" : "4"}. Set XMTP_ENV in .env (dev or production)`
|
|
294
|
+
);
|
|
295
|
+
console.log(
|
|
296
|
+
`${projectName !== "." ? "6" : "5"}. Generate keys: npm run keys (or yarn/pnpm equivalent)`
|
|
297
|
+
);
|
|
298
|
+
console.log(
|
|
299
|
+
`${projectName !== "." ? "7" : "6"}. Start development: npm run dev (or yarn/pnpm equivalent)`
|
|
136
300
|
);
|
|
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
301
|
console.log(
|
|
143
302
|
"\n\u{1F4D6} For more information, see the README.md file in your project"
|
|
144
303
|
);
|
|
145
304
|
}
|
|
305
|
+
async function initializeProject() {
|
|
306
|
+
const program = new Command();
|
|
307
|
+
program.name("create-hybrid").description("Create a new Hybrid XMTP agent project").version("1.2.3").argument("[project-name]", "Name of the project").option("-e, --example <example>", "Example to use (basic, crypto-agent)").action(async (projectName, options) => {
|
|
308
|
+
let finalProjectName = projectName;
|
|
309
|
+
if (process.env.CI) {
|
|
310
|
+
console.log(
|
|
311
|
+
`\u{1F50D} Debug: projectName="${projectName}", options.example="${options?.example}"`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (!finalProjectName || finalProjectName.trim() === "") {
|
|
315
|
+
if (!process.stdin.isTTY) {
|
|
316
|
+
console.error("\u274C Project name is required");
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
const { name } = await prompts({
|
|
320
|
+
type: "text",
|
|
321
|
+
name: "name",
|
|
322
|
+
message: "What is your project name?",
|
|
323
|
+
validate: (value) => {
|
|
324
|
+
if (!value || !value.trim()) {
|
|
325
|
+
return "Project name is required";
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
if (!name) {
|
|
331
|
+
console.log("\u274C Project name is required. Exiting...");
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
finalProjectName = name;
|
|
335
|
+
}
|
|
336
|
+
await createProject(finalProjectName, options?.example);
|
|
337
|
+
});
|
|
338
|
+
await program.parseAsync();
|
|
339
|
+
}
|
|
146
340
|
async function main() {
|
|
147
341
|
const nodeVersion = process.versions.node;
|
|
148
342
|
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}\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\" // Path is now handled separately in degit options\n\t},\n\t{\n\t\tname: \"crypto-agent\",\n\t\tdescription: \"Advanced agent with blockchain integration and crypto tools\",\n\t\tpath: \"crypto-agent\" // Path is now handled separately in degit options\n\t}\n]\n\nfunction isInMonorepo(): boolean {\n\t// Check if we're running from within the monorepo (for development/testing)\n\tconst currentDir = process.cwd()\n\tconst packageJsonPath = join(currentDir, \"package.json\")\n\ttry {\n\t\tconst packageJson = require(packageJsonPath)\n\t\treturn (\n\t\t\tpackageJson.name === \"hybrid\" || currentDir.includes(\"/hybrid/packages/\")\n\t\t)\n\t} catch {\n\t\treturn false\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 gen: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.description,\n\t\t\t\tvalue: ex\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\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(\"-e, --example <example>\", \"Example to use (basic, crypto-agent)\")\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.4",
|
|
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
|
-
}
|