create-hybrid 1.4.4 → 2.0.0

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/package.json CHANGED
@@ -1,58 +1,30 @@
1
1
  {
2
2
  "name": "create-hybrid",
3
- "version": "1.4.4",
4
- "description": "Create a new Hybrid XMTP agent project",
3
+ "version": "2.0.0",
5
4
  "type": "module",
6
5
  "bin": {
7
6
  "create-hybrid": "./dist/index.js"
8
7
  },
9
8
  "files": [
10
9
  "dist",
11
- "src"
10
+ "templates"
12
11
  ],
13
- "exports": {
14
- ".": {
15
- "types": "./src/index.ts",
16
- "import": "./dist/index.js",
17
- "require": "./dist/index.cjs"
18
- }
19
- },
20
12
  "dependencies": {
21
- "degit": "^2.8.4",
22
- "commander": "^12.1.0",
23
13
  "prompts": "^2.4.2"
24
14
  },
25
15
  "devDependencies": {
26
16
  "@types/node": "22.8.6",
27
17
  "@types/prompts": "^2.4.9",
28
18
  "tsup": "^8.5.0",
29
- "@config/biome": "0.0.0",
19
+ "tsx": "^4.19.3",
20
+ "vitest": "^4.0.18",
30
21
  "@config/tsconfig": "0.0.0"
31
22
  },
32
- "engines": {
33
- "node": ">=20"
34
- },
35
- "keywords": [
36
- "hybrid",
37
- "xmtp",
38
- "agent",
39
- "cli",
40
- "create"
41
- ],
42
- "author": "Ian Hunter",
43
- "license": "MIT",
44
- "repository": {
45
- "type": "git",
46
- "url": "https://github.com/ian/hybrid.git",
47
- "directory": "packages/create-hybrid"
48
- },
49
23
  "scripts": {
50
24
  "build": "tsup",
51
- "build:watch": "tsup --watch",
52
- "clean": "rm -rf .turbo dist",
25
+ "dev": "tsx src/index.ts",
53
26
  "typecheck": "tsc --noEmit",
54
- "lint": "biome lint --unsafe",
55
- "lint:fix": "biome lint --write --unsafe",
56
- "format": "biome format --write"
27
+ "test": "vitest run",
28
+ "test:watch": "vitest"
57
29
  }
58
30
  }
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- declare function initializeProject(): Promise<void>;
2
-
3
- export { initializeProject };
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
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: \"miniapp\",\n\t\tdescription:\n\t\t\t\"Hybrid agent with miniapp integration for onchain interactions\",\n\t\tpath: \"miniapp\",\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_DB_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// Correct degit syntax is: user/repo/subdirectory#branch\n\t\t\tconst [userRepo, branch] = REPO.split(\"#\")\n\t\t\tdegitSource = `${userRepo}/examples/${selectedExample.name}#${branch}`\n\t\t} else {\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/src/index.ts DELETED
@@ -1,513 +0,0 @@
1
- import { Command } from "commander"
2
- import degit from "degit"
3
- import { readFile, readdir, writeFile } from "node:fs/promises"
4
- import { join } from "node:path"
5
- import prompts from "prompts"
6
-
7
- interface Example {
8
- name: string
9
- description: string
10
- path: string
11
- available: boolean
12
- message?: string
13
- }
14
-
15
- // Default to main branch, but allow override via REPO env var for CI/testing
16
- const DEFAULT_REPO = "ian/hybrid"
17
- const REPO = process.env.REPO || DEFAULT_REPO
18
-
19
- const EXAMPLES: Example[] = [
20
- {
21
- name: "basic",
22
- description: "Basic XMTP agent with message filtering and AI responses",
23
- path: "basic",
24
- available: true
25
- },
26
- {
27
- name: "miniapp",
28
- description:
29
- "Hybrid agent with miniapp integration for onchain interactions",
30
- path: "miniapp",
31
- available: true
32
- },
33
- {
34
- name: "with-ponder",
35
- description: "Agent with Ponder integration for indexing blockchain data",
36
- path: "with-ponder",
37
- available: false,
38
- message: "Coming soon"
39
- },
40
- {
41
- name: "with-foundry",
42
- description:
43
- "Agent with Foundry integration for smart contract development",
44
- path: "with-foundry",
45
- available: false,
46
- message: "Coming soon"
47
- }
48
- ]
49
-
50
- function replaceTemplateVariables(
51
- content: string,
52
- variables: Record<string, string>
53
- ): string {
54
- return content.replace(
55
- /\{\{(\w+)\}\}/g,
56
- (match, key) => variables[key] || match
57
- )
58
- }
59
-
60
- async function updateTemplateFiles(
61
- projectDir: string,
62
- projectName: string
63
- ): Promise<void> {
64
- const variables = { projectName }
65
-
66
- const filesToUpdate = [
67
- join(projectDir, "package.json"),
68
- join(projectDir, "README.md"),
69
- join(projectDir, "src", "agent.ts")
70
- ]
71
-
72
- for (const filePath of filesToUpdate) {
73
- try {
74
- let content = await readFile(filePath, "utf-8")
75
-
76
- // First try template variable replacement
77
- content = replaceTemplateVariables(content, variables)
78
-
79
- // Special handling for package.json if template variables weren't found
80
- if (filePath.endsWith("package.json")) {
81
- try {
82
- const packageJson = JSON.parse(content)
83
- let updated = false
84
-
85
- // If name is still a generic name, replace it
86
- if (
87
- packageJson.name === "agent" ||
88
- packageJson.name === "hybrid-example-basic-agent"
89
- ) {
90
- packageJson.name = projectName
91
- updated = true
92
- }
93
-
94
- // Ensure required scripts exist
95
- if (!packageJson.scripts) {
96
- packageJson.scripts = {}
97
- }
98
-
99
- // Add missing scripts and update old scripts to use hybrid CLI
100
- const requiredScripts = {
101
- clean: "hybrid clean",
102
- dev: "hybrid dev",
103
- build: "hybrid build",
104
- start: "hybrid start",
105
- keys: "hybrid keys --write",
106
- test: "vitest",
107
- "test:watch": "vitest --watch",
108
- "test:coverage": "vitest --coverage",
109
- lint: "biome lint --write",
110
- "lint:check": "biome lint",
111
- format: "biome format --write",
112
- "format:check": "biome format --check",
113
- typecheck: "tsc --noEmit"
114
- }
115
-
116
- for (const [scriptName, scriptCommand] of Object.entries(
117
- requiredScripts
118
- )) {
119
- // Always update scripts to use the correct hybrid CLI commands
120
- packageJson.scripts[scriptName] = scriptCommand
121
- updated = true
122
- }
123
-
124
- // Update dependencies to use independent packages
125
- if (packageJson.dependencies) {
126
- if (packageJson.dependencies.hybrid === "workspace:*") {
127
- packageJson.dependencies.hybrid = "latest"
128
- updated = true
129
- }
130
- if (
131
- packageJson.dependencies["@openrouter/ai-sdk-provider"] ===
132
- "catalog:ai"
133
- ) {
134
- packageJson.dependencies["@openrouter/ai-sdk-provider"] = "^1.1.2"
135
- updated = true
136
- }
137
- if (packageJson.dependencies.zod === "catalog:stack") {
138
- packageJson.dependencies.zod = "^3.23.8"
139
- updated = true
140
- }
141
- // Remove workspace dependencies
142
- if (packageJson.dependencies["@hybrd/xmtp"]) {
143
- packageJson.dependencies["@hybrd/xmtp"] = undefined
144
- updated = true
145
- }
146
- }
147
-
148
- // Update devDependencies to use independent packages
149
- if (packageJson.devDependencies) {
150
- const independentDevDeps = {
151
- "@biomejs/biome": "^1.9.4",
152
- "@types/node": "^22.0.0",
153
- "@hybrd/cli": "latest",
154
- tsx: "^4.20.5",
155
- typescript: "^5.8.3",
156
- vitest: "^3.2.4"
157
- }
158
-
159
- // Remove workspace dependencies
160
- packageJson.devDependencies["@config/biome"] = undefined
161
- packageJson.devDependencies["@config/tsconfig"] = undefined
162
-
163
- // Add independent dependencies
164
- for (const [depName, depVersion] of Object.entries(
165
- independentDevDeps
166
- )) {
167
- packageJson.devDependencies[depName] = depVersion
168
- }
169
- updated = true
170
- }
171
-
172
- if (updated) {
173
- content = `${JSON.stringify(packageJson, null, "\t")}\n`
174
- }
175
- } catch (parseError) {
176
- console.log("⚠️ Could not parse package.json for name update")
177
- }
178
- }
179
-
180
- // Special handling for README.md if template variables weren't found
181
- if (filePath.endsWith("README.md")) {
182
- // Replace common README title patterns with project name
183
- content = content.replace(/^# .*$/m, `# ${projectName}`)
184
- }
185
-
186
- await writeFile(filePath, content, "utf-8")
187
- } catch (error) {
188
- console.log(
189
- `⚠️ Could not update ${filePath.split("/").pop()}: file not found or error occurred`
190
- )
191
- }
192
- }
193
-
194
- // Ensure .env file exists - create it if missing
195
- const envPath = join(projectDir, ".env")
196
- try {
197
- await readFile(envPath, "utf-8")
198
- } catch {
199
- // .env file doesn't exist, create it
200
- const envContent = `# Required
201
- OPENROUTER_API_KEY=your_openrouter_api_key_here
202
- XMTP_WALLET_KEY=your_wallet_key_here
203
- XMTP_DB_ENCRYPTION_KEY=your_encryption_key_here
204
-
205
- # Optional
206
- XMTP_ENV=dev
207
- PORT=8454`
208
- await writeFile(envPath, envContent, "utf-8")
209
- console.log("📄 Created .env template file")
210
- }
211
-
212
- // Ensure vitest.config.ts exists - create it if missing
213
- const vitestConfigPath = join(projectDir, "vitest.config.ts")
214
- try {
215
- await readFile(vitestConfigPath, "utf-8")
216
- } catch {
217
- // vitest.config.ts doesn't exist, create it
218
- const vitestConfigContent = `import { defineConfig } from "vitest/config"
219
-
220
- export default defineConfig({
221
- test: {
222
- environment: "node",
223
- globals: true,
224
- setupFiles: []
225
- },
226
- resolve: {
227
- alias: {
228
- "@": "./src"
229
- }
230
- }
231
- })`
232
- await writeFile(vitestConfigPath, vitestConfigContent, "utf-8")
233
- console.log("📄 Created vitest.config.ts file")
234
- }
235
-
236
- // Ensure src/agent.test.ts exists - create it if missing
237
- const agentTestPath = join(projectDir, "src", "agent.test.ts")
238
- try {
239
- await readFile(agentTestPath, "utf-8")
240
- } catch {
241
- // agent.test.ts doesn't exist, create it
242
- const agentTestContent = `import { describe, expect, it } from "vitest"
243
-
244
- // Example test file - replace with actual tests for your agent
245
-
246
- describe("Agent", () => {
247
- it("should be defined", () => {
248
- // This is a placeholder test
249
- // Add real tests for your agent functionality
250
- expect(true).toBe(true)
251
- })
252
- })`
253
- await writeFile(agentTestPath, agentTestContent, "utf-8")
254
- console.log("📄 Created src/agent.test.ts file")
255
- }
256
- }
257
-
258
- async function checkDirectoryEmpty(dirPath: string): Promise<boolean> {
259
- try {
260
- const files = await readdir(dirPath)
261
- const significantFiles = files.filter(
262
- (file) =>
263
- !file.startsWith(".") &&
264
- file !== "node_modules" &&
265
- file !== "package-lock.json" &&
266
- file !== "yarn.lock" &&
267
- file !== "pnpm-lock.yaml"
268
- )
269
- return significantFiles.length === 0
270
- } catch {
271
- // Directory doesn't exist, so it's "empty"
272
- return true
273
- }
274
- }
275
-
276
- async function createProject(
277
- projectName: string,
278
- exampleName?: string
279
- ): Promise<void> {
280
- console.log("🚀 Creating a new Hybrid project...")
281
-
282
- // Validate project name
283
- if (!projectName || projectName.trim() === "") {
284
- console.error("❌ Project name is required")
285
- process.exit(1)
286
- }
287
-
288
- const sanitizedName = projectName
289
- .toLowerCase()
290
- .replace(/[^a-z0-9-]/g, "-")
291
- .replace(/-+/g, "-")
292
- .replace(/^-|-$/g, "")
293
-
294
- const currentDir = process.cwd()
295
- const projectDir =
296
- projectName === "." ? currentDir : join(currentDir, sanitizedName)
297
-
298
- // Check if directory is empty
299
- const isEmpty = await checkDirectoryEmpty(projectDir)
300
- if (!isEmpty) {
301
- console.error(
302
- `❌ Directory "${sanitizedName}" already exists and is not empty`
303
- )
304
- console.error(
305
- "Please choose a different name or remove the existing directory"
306
- )
307
- process.exit(1)
308
- }
309
-
310
- // Select example if not provided
311
- let selectedExample: Example
312
- if (exampleName) {
313
- const example = EXAMPLES.find((ex) => ex.name === exampleName)
314
- if (!example) {
315
- console.error(`❌ Example "${exampleName}" not found`)
316
- console.error(
317
- `Available examples: ${EXAMPLES.map((ex) => ex.name).join(", ")}`
318
- )
319
- process.exit(1)
320
- }
321
- selectedExample = example
322
- console.log(`📋 Using example: ${selectedExample.name}`)
323
- } else {
324
- // Check if we're running in a non-interactive environment (like CI)
325
- if (!process.stdin.isTTY) {
326
- console.error(
327
- "❌ Example is required in non-interactive mode. Use --example <name>"
328
- )
329
- console.error(
330
- `Available examples: ${EXAMPLES.map((ex) => ex.name).join(", ")}`
331
- )
332
- process.exit(1)
333
- }
334
-
335
- const { example } = await prompts({
336
- type: "select",
337
- name: "example",
338
- message: "Which example would you like to use?",
339
- choices: EXAMPLES.map((ex) => ({
340
- title: ex.name,
341
- description: ex.available
342
- ? ex.description
343
- : `${ex.description} (${ex.message || "Coming soon"})`,
344
- value: ex,
345
- disabled: !ex.available
346
- })),
347
- initial: 0
348
- })
349
-
350
- if (!example) {
351
- console.log("❌ No example selected. Exiting...")
352
- process.exit(1)
353
- }
354
-
355
- // Check if the selected example is available
356
- if (!example.available) {
357
- console.log(
358
- `❌ Example "${example.name}" is not yet available. ${example.message || "Coming soon"}`
359
- )
360
- process.exit(1)
361
- }
362
-
363
- selectedExample = example
364
- }
365
-
366
- console.log(`📦 Cloning ${selectedExample.name} example...`)
367
-
368
- try {
369
- // For degit, the correct syntax is: repo#branch/subdirectory
370
- // But we need to be careful about the path construction
371
- let degitSource: string
372
-
373
- if (REPO.includes("#")) {
374
- // REPO is in format "user/repo#branch"
375
- // Correct degit syntax is: user/repo/subdirectory#branch
376
- const [userRepo, branch] = REPO.split("#")
377
- degitSource = `${userRepo}/examples/${selectedExample.name}#${branch}`
378
- } else {
379
- degitSource = `${REPO}/examples/${selectedExample.name}`
380
- }
381
-
382
- console.log(`🔍 Degit source: ${degitSource}`)
383
- const emitter = degit(degitSource)
384
- await emitter.clone(projectDir)
385
- console.log(`✅ Template cloned to: ${sanitizedName}`)
386
- } catch (error) {
387
- console.error("❌ Failed to clone template:", error)
388
- process.exit(1)
389
- }
390
-
391
- // Update template variables
392
- console.log("🔧 Updating template variables...")
393
- try {
394
- await updateTemplateFiles(projectDir, sanitizedName)
395
- console.log("✅ Template variables updated")
396
- } catch (error) {
397
- console.error("❌ Failed to update template variables:", error)
398
- }
399
-
400
- console.log("\n🎉 Hybrid project created successfully!")
401
- console.log(`\n📂 Project created in: ${projectDir}`)
402
- console.log("\n📋 Next steps:")
403
- if (projectName !== ".") {
404
- console.log(`1. cd ${sanitizedName}`)
405
- }
406
- console.log(
407
- `${projectName !== "." ? "2" : "1"}. Install dependencies (npm install, yarn install, or pnpm install)`
408
- )
409
- console.log(
410
- `${projectName !== "." ? "3" : "2"}. Get your OpenRouter API key from https://openrouter.ai/keys`
411
- )
412
- console.log(
413
- `${projectName !== "." ? "4" : "3"}. Add your API key to the OPENROUTER_API_KEY in .env`
414
- )
415
- console.log(
416
- `${projectName !== "." ? "5" : "4"}. Set XMTP_ENV in .env (dev or production)`
417
- )
418
- console.log(
419
- `${projectName !== "." ? "6" : "5"}. Generate keys: npm run keys (or yarn/pnpm equivalent)`
420
- )
421
- console.log(
422
- `${projectName !== "." ? "7" : "6"}. Start development: npm run dev (or yarn/pnpm equivalent)`
423
- )
424
-
425
- console.log(
426
- "\n📖 For more information, see the README.md file in your project"
427
- )
428
- }
429
-
430
- export async function initializeProject(): Promise<void> {
431
- const program = new Command()
432
-
433
- program
434
- .name("create-hybrid")
435
- .description("Create a new Hybrid XMTP agent project")
436
- .version("1.2.3")
437
- .argument("[project-name]", "Name of the project")
438
- .option(
439
- "-e, --example <example>",
440
- "Example to use (basic, with-ponder, with-foundry)"
441
- )
442
- .action(async (projectName?: string, options?: { example?: string }) => {
443
- let finalProjectName = projectName
444
-
445
- // Debug logging for CI troubleshooting
446
- if (process.env.CI) {
447
- console.log(
448
- `🔍 Debug: projectName="${projectName}", options.example="${options?.example}"`
449
- )
450
- }
451
-
452
- // If no project name provided or empty string, prompt for it
453
- if (!finalProjectName || finalProjectName.trim() === "") {
454
- // Check if we're running in a non-interactive environment (like tests)
455
- if (!process.stdin.isTTY) {
456
- console.error("❌ Project name is required")
457
- process.exit(1)
458
- }
459
-
460
- const { name } = await prompts({
461
- type: "text",
462
- name: "name",
463
- message: "What is your project name?",
464
- validate: (value: string) => {
465
- if (!value || !value.trim()) {
466
- return "Project name is required"
467
- }
468
- return true
469
- }
470
- })
471
-
472
- if (!name) {
473
- console.log("❌ Project name is required. Exiting...")
474
- process.exit(1)
475
- }
476
-
477
- finalProjectName = name
478
- }
479
-
480
- await createProject(finalProjectName as string, options?.example)
481
- })
482
-
483
- await program.parseAsync()
484
- }
485
-
486
- async function main(): Promise<void> {
487
- const nodeVersion = process.versions.node
488
- const [major] = nodeVersion.split(".").map(Number)
489
- if (!major || major < 20) {
490
- console.error("Error: Node.js version 20 or higher is required")
491
- process.exit(1)
492
- }
493
-
494
- try {
495
- await initializeProject()
496
- } catch (error) {
497
- console.error("Failed to initialize project:", error)
498
- console.error(
499
- "Error details:",
500
- error instanceof Error ? error.stack : String(error)
501
- )
502
- process.exit(1)
503
- }
504
- }
505
-
506
- main().catch((error) => {
507
- console.error("CLI error:", error)
508
- console.error(
509
- "Error details:",
510
- error instanceof Error ? error.stack : String(error)
511
- )
512
- process.exit(1)
513
- })
package/src/types.d.ts DELETED
@@ -1,8 +0,0 @@
1
- declare module "degit" {
2
- interface DegitEmitter {
3
- clone(dest: string): Promise<void>
4
- }
5
-
6
- function degit(src: string): DegitEmitter
7
- export = degit
8
- }