From ccf62fdb54a42c1cc210069577219ae9889cd04f Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 17 Mar 2025 11:26:33 -0700 Subject: [PATCH 01/29] feat: refactor CLI, stub out generates, and implement createActionProvider --- typescript/create-onchain-agent/package.json | 2 + typescript/create-onchain-agent/src/cli.ts | 344 ++---------------- .../src/cli/create-action-provider.ts | 114 ++++++ .../src/cli/create-agent.ts | 6 + .../src/cli/create-agentkit.ts | 7 + .../src/cli/create-wallet-provider.ts | 5 + .../src/cli/init-project.ts | 316 ++++++++++++++++ .../templates/action-provider/schema.njk | 11 + .../templates/action-provider/template.njk | 49 +++ typescript/package-lock.json | 70 ++-- 10 files changed, 576 insertions(+), 348 deletions(-) create mode 100644 typescript/create-onchain-agent/src/cli/create-action-provider.ts create mode 100644 typescript/create-onchain-agent/src/cli/create-agent.ts create mode 100644 typescript/create-onchain-agent/src/cli/create-agentkit.ts create mode 100644 typescript/create-onchain-agent/src/cli/create-wallet-provider.ts create mode 100644 typescript/create-onchain-agent/src/cli/init-project.ts create mode 100644 typescript/create-onchain-agent/templates/action-provider/schema.njk create mode 100644 typescript/create-onchain-agent/templates/action-provider/template.njk diff --git a/typescript/create-onchain-agent/package.json b/typescript/create-onchain-agent/package.json index f40169353..789358e58 100644 --- a/typescript/create-onchain-agent/package.json +++ b/typescript/create-onchain-agent/package.json @@ -33,8 +33,10 @@ "./package.json": "./package.json" }, "dependencies": { + "@types/nunjucks": "^3.2.6", "cac": "^6.7.14", "cross-spawn": "^7.0.3", + "nunjucks": "^3.2.4", "ora": "^8.1.0", "picocolors": "^1.1.0", "prompts": "^2.4.2" diff --git a/typescript/create-onchain-agent/src/cli.ts b/typescript/create-onchain-agent/src/cli.ts index 8bf28f960..0fa7f495c 100644 --- a/typescript/create-onchain-agent/src/cli.ts +++ b/typescript/create-onchain-agent/src/cli.ts @@ -1,320 +1,46 @@ #!/usr/bin/env node -import fs from "fs"; -import ora from "ora"; -import path from "path"; -import pc from "picocolors"; -import prompts from "prompts"; -import { EVM_NETWORKS, NetworkToWalletProviders, SVM_NETWORKS } from "./constants.js"; -import { Network, WalletProviderChoice, Framework } from "./types.js"; -import { copyTemplate } from "./fileSystem.js"; -import { - handleNextSelection, - isValidPackageName, - toValidPackageName, - getWalletProviders, - handleMcpSelection, -} from "./utils.js"; -import { Frameworks, FrameworkToTemplates } from "./constants.js"; - -/** - * Initializes the project creation process. - * - * - Prompts the user for project details including project name, package name, network, chain ID, and wallet provider. - * - Validates user input, ensuring directories do not already exist and package names are valid. - * - Copies the selected template to the new project directory. - * - Handles network and wallet provider selection logic. - * - Displays a summary of the created project along with next steps. - */ -async function init() { - console.log( - `${pc.blue(` - █████ ██████ ███████ ███ ██ ████████ ██ ██ ██ ████████ -██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ -███████ ██ ███ █████ ██ ██ ██ ██ █████ ██ ██ -██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -██ ██ ██████ ███████ ██ ████ ██ ██ ██ ██ ██ - - Giving every AI agent a crypto wallet -`)}`, - ); - - const defaultProjectName = "onchain-agent"; - - let result: prompts.Answers< - | "projectName" - | "packageName" - | "networkFamily" - | "networkType" - | "network" - | "chainId" - | "rpcUrl" - | "walletProvider" - | "framework" - | "template" - >; - - try { - result = await prompts( - [ - { - type: "text", - name: "projectName", - message: pc.reset("Project name:"), - initial: defaultProjectName, - onState: state => { - state.value = state.value.trim(); - }, - validate: value => { - const targetDir = path.join(process.cwd(), value); - if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) { - return "Directory already exists and is not empty. Please choose a different name."; - } - return true; - }, - }, - { - type: (_, { projectName }: { projectName: string }) => - isValidPackageName(projectName) ? null : "text", - name: "packageName", - message: pc.reset("Package name:"), - initial: (_, { projectName }: { projectName: string }) => toValidPackageName(projectName), - validate: dir => isValidPackageName(dir) || "Invalid package.json name", - }, - { - type: "select", - name: "framework", - message: pc.reset("Choose a framework:"), - choices: Frameworks.map(framework => ({ - title: framework, - value: framework, - })), - }, - { - type: (prev, { framework }) => - FrameworkToTemplates[framework as Framework].length > 1 ? "select" : null, - name: "template", - message: pc.reset("Choose a template:"), - choices: (prev, { framework }) => - FrameworkToTemplates[framework as Framework].map(template => ({ - title: template, - value: template, - })), - }, - { - type: "select", - name: "networkFamily", - message: pc.reset("Choose a network family:"), - choices: [ - { title: "Ethereum Virtual Machine (EVM)", value: "EVM" }, - { title: "Solana Virtual Machine (SVM)", value: "SVM" }, - ], - }, - { - type: (prev, { networkFamily }) => (networkFamily === "EVM" ? "select" : null), - name: "networkType", - message: pc.reset("Choose network type:"), - choices: [ - { title: "Mainnet", value: "mainnet" }, - { title: "Testnet", value: "testnet" }, - { title: "Custom Chain ID", value: "custom" }, - ], - }, - { - type: (prev, { networkFamily, networkType }) => { - // For SVM, always show network selection - if (networkFamily === "SVM") return "select"; - // For EVM, show network selection only if not custom - return networkType === "custom" ? null : "select"; - }, - name: "network", - message: pc.reset("Choose a network:"), - choices: (prev, { networkFamily, networkType }) => { - if (networkFamily === "SVM") { - // Show all Solana networks - return SVM_NETWORKS.map(network => ({ - title: network, - value: network as Network, - })); - } else { - // Filter EVM networks by mainnet/testnet - return EVM_NETWORKS.filter(n => { - const isMainnet = n.includes("mainnet"); - return networkType === "mainnet" ? isMainnet : !isMainnet; - }).map(network => ({ - title: network === "base-sepolia" ? `${network} (default)` : network, - value: network as Network, - })); - } - }, - }, - { - type: (prev, { networkFamily, networkType }) => - networkFamily === "EVM" && networkType === "custom" ? "text" : null, - name: "chainId", - message: pc.reset("Enter your chain ID:"), - validate: value => - value.trim() - ? Number.parseInt(value) - ? true - : "Chain ID must be a number." - : "Chain ID cannot be empty.", - }, - { - type: (prev, { networkFamily, networkType }) => - networkFamily === "EVM" && networkType === "custom" ? "text" : null, - name: "rpcUrl", - message: pc.reset("Enter your RPC URL:"), - validate: value => - value.trim() - ? value.startsWith("http") - ? true - : "RPC URL must start with http:// or https://" - : "RPC URL cannot be empty.", - }, - { - type: (prev, { networkFamily, networkType }) => { - // For custom EVM networks, auto-select Viem by returning null - if (networkFamily === "EVM" && networkType === "custom") { - return null; - } - // For all other cases (regular EVM networks and SVM networks), show selection - return "select"; - }, - name: "walletProvider", - initial: (prev, { networkFamily, networkType, network }) => { - // For custom EVM networks, use Viem - if (networkFamily === "EVM" && networkType === "custom") { - return "Viem"; - } - // For networks with one provider, use that provider - if (network && NetworkToWalletProviders[network as Network].length === 1) { - return NetworkToWalletProviders[network as Network][0]; - } - return 0; - }, - message: (prev, { network }) => { - const walletDescriptions: Record = { - CDP: "Uses Coinbase Developer Platform (CDP)'s managed wallet.", - SmartWallet: "Uses Coinbase Developer Platform (CDP)'s Smart Wallet.", - Viem: "Client-side Ethereum wallet.", - Privy: "Authentication and wallet infrastructure.", - SolanaKeypair: "Client-side Solana wallet.", - }; - - const providerDescriptions = getWalletProviders(network as Network) - .map(provider => ` - ${provider}: ${walletDescriptions[provider]}`) - .join("\n"); - - return pc.reset(`Choose a wallet provider:\n${providerDescriptions}\n`); - }, - choices: (prev, { network }) => { - const walletProviders = getWalletProviders(network as Network); - return walletProviders.map(provider => ({ - title: provider === walletProviders[0] ? `${provider} (default)` : provider, - value: provider, - })); - }, - }, - ], - { - onCancel: () => { - console.log("\nProject creation cancelled."); - process.exit(0); - }, - }, - ); - } catch (cancelled: unknown) { - if (cancelled instanceof Error) { - console.info(cancelled.message); - } else { - console.info("An unknown error occurred"); - } - process.exit(1); +import { createActionProvider } from "./cli/create-action-provider.js"; +import { createAgent } from "./cli/create-agent.js"; +import { createAgentkit } from "./cli/create-agentkit.js"; +import { createWalletProvider } from "./cli/create-wallet-provider.js"; +import { initProject } from "./cli/init-project.js"; + +async function handleArgs() { + console.log(process.argv); + const type = process.argv[2]; + if (!type) { + await initProject(); } - const { projectName, network, chainId, rpcUrl, framework } = result; - const packageName = result.packageName || toValidPackageName(projectName); - const walletProvider = result.walletProvider || "Viem"; - // If template wasn't selected (because there was only one option), use the first template - const template = result.template || FrameworkToTemplates[framework as Framework][0]; - - const spinner = ora(`Creating ${projectName}...`).start(); - - // Copy template over to new project - const root = await copyTemplate(projectName, packageName, template); - - // Handle selection-specific logic over copied-template - switch (template) { - case "next": - await handleNextSelection(root, framework, walletProvider, network, chainId, rpcUrl); - - spinner.succeed(); - console.log(pc.blueBright(`\nSuccessfully created your AgentKit project in ${root}`)); - - console.log(`\nFrameworks:`); - console.log(pc.gray("- AgentKit")); - console.log(pc.gray(`- ${framework}`)); - console.log(pc.gray("- React")); - console.log(pc.gray("- Next.js")); - console.log(pc.gray("- Tailwind CSS")); - console.log(pc.gray("- ESLint")); - - console.log(pc.bold("\nWhat's Next?")); - - console.log(`\nTo get started, run the following commands:\n`); - if (root !== process.cwd()) { - console.log(` - cd ${path.relative(process.cwd(), root)}`); + else { + switch(type) { + case 'init': { + await initProject(); + break; } - console.log(" - npm install"); - console.log(pc.gray(" - # Open .env.local and configure your API keys")); - console.log(" - mv .env.local .env"); - console.log(" - npm run dev"); - break; - case "mcp": - await handleMcpSelection(root, walletProvider, network, chainId); - - spinner.succeed(); - console.log(pc.blueBright(`\nSuccessfully created your AgentKit project in ${root}`)); - - console.log(`\nTo get started, run the following commands:\n`); - if (root !== process.cwd()) { - console.log(` - cd ${path.relative(process.cwd(), root)}`); + case 'action-provider': { + await createActionProvider(); + break; } - console.log(" - npm install"); - console.log(" - npm run build"); - console.log( - " - cp claude_desktop_config.json ~/Library/Application\\ Support/Claude/claude_desktop_config.json", - ); - - if (walletProvider === "CDP" || walletProvider === "SmartWallet") { - console.log( - " - # Make sure to open claude_desktop_config.json and configure your CDP API keys!", - ); + case 'wallet-provider': { + await createWalletProvider(); + break; } - - if (walletProvider === "Privy") { - console.log( - " - # Make sure to open claude_desktop_config.json and configure your Privy API keys!", - ); + case 'agentkit': { + await createAgentkit(); + break; } - - console.log( - "\nNow open Claude Desktop and start prompting Claude to do things onchain", - "\nFor example, ask it to print your wallet details", - ); - break; - default: - break; + case 'agent': { + await createAgent(); + break; + } + default: { + console.log('Unknown command:', type); + break; + } + } } - - console.log(pc.bold("\nLearn more")); - console.log(" - Checkout the docs"); - console.log(pc.blueBright(" - https://docs.cdp.coinbase.com/agentkit/docs/welcome")); - console.log(" - Visit the repo"); - console.log(pc.blueBright(" - https://github.com/coinbase/agentkit")); - console.log(" - Join the community"); - console.log(pc.blueBright(" - https://discord.gg/CDP\n")); } -init().catch(e => { +handleArgs().catch(e => { console.error(e); }); diff --git a/typescript/create-onchain-agent/src/cli/create-action-provider.ts b/typescript/create-onchain-agent/src/cli/create-action-provider.ts new file mode 100644 index 000000000..78994ad23 --- /dev/null +++ b/typescript/create-onchain-agent/src/cli/create-action-provider.ts @@ -0,0 +1,114 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import nunjucks from 'nunjucks'; +import prompts from 'prompts'; +import { getNetworkType } from '../utils'; +import { Network, EVMNetwork, SVMNetwork } from '../types'; + +// Get current file's directory in ES modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Utility functions for string transformations +function toKebabCase(str: string): string { + return str + .replace(/([a-z])([A-Z])/g, '$1-$2') // Add hyphen between lower & upper + .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') // Add hyphen between consecutive uppers if followed by lower + .toLowerCase(); +} + +function toSnakeCase(str: string): string { + return str + .replace(/([a-z])([A-Z])/g, '$1_$2') // Add underscore between lower & upper + .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') // Add underscore between consecutive uppers if followed by lower + .toLowerCase(); +} + +export async function createActionProvider() { + // Configure nunjucks with the correct path + nunjucks.configure(path.join(__dirname, '../../../templates'), { + autoescape: false, + trimBlocks: true, + lstripBlocks: true + }); + + // Get user input + const answers = await prompts([ + { + type: 'text', + name: 'name', + message: 'What is the name of your action provider?', + validate: (value: string) => value.length > 0 ? true : 'Name is required' + }, + { + type: 'text', + name: 'description', + message: 'Provide a brief description:', + initial: (prev: string) => `${prev} operations` + }, + { + type: 'select', + name: 'networkFamily', + message: 'Which network family will this support?', + choices: [ + { title: 'EVM', value: 'EVM' }, + { title: 'SVM', value: 'SVM' }, + { title: 'Both', value: 'Both' } + ] + } + ]); + + // Process the name variations + const className = answers.name.charAt(0).toUpperCase() + answers.name.slice(1); + const kebabName = `${toKebabCase(answers.name)}-action-provider`; + const snakeName = toSnakeCase(answers.name); + + // Determine wallet provider and networks based on network family + let walletProvider = 'EvmWalletProvider'; + let networkSupport = 'true'; + + switch(answers.networkFamily) { + case 'EVM': + walletProvider = 'EvmWalletProvider'; + networkSupport = 'network.protocolFamily === "EVM"'; + break; + case 'SVM': + walletProvider = 'SvmWalletProvider'; + networkSupport = 'network.protocolFamily === "SVM"'; + break; + case 'Both': + walletProvider = 'WalletProvider'; + networkSupport = 'true'; + break; + } + + // Generate code using nunjucks + const generatedCode = nunjucks.render('action-provider/template.njk', { + name: snakeName, + className, + description: answers.description, + walletProvider, + networks: '[]', + networkSupport, + actionName: `${snakeName}_action`, + schemaName: `${className}ActionSchema` + }); + + // Create directory if it doesn't exist + const dirPath = `./${kebabName}`; + await fs.mkdir(dirPath, { recursive: true }); + + // Write files + await fs.writeFile(`${dirPath}/${kebabName}.ts`, generatedCode); + + // Generate schema file using nunjucks with updated schema name + const schemaCode = nunjucks.render('action-provider/schema.njk', { + className: `${className}Action`, + description: answers.description + }); + + await fs.writeFile(`${dirPath}/schemas.ts`, schemaCode); + + console.log(`Successfully created ${kebabName}!`); +} \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/create-agent.ts b/typescript/create-onchain-agent/src/cli/create-agent.ts new file mode 100644 index 000000000..beff9aafc --- /dev/null +++ b/typescript/create-onchain-agent/src/cli/create-agent.ts @@ -0,0 +1,6 @@ + + +export async function createAgent() { + // prompts for framework, assumes prepare-agentkit.ts + // generates create-agent.ts +} \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/create-agentkit.ts b/typescript/create-onchain-agent/src/cli/create-agentkit.ts new file mode 100644 index 000000000..ecf6f7a7e --- /dev/null +++ b/typescript/create-onchain-agent/src/cli/create-agentkit.ts @@ -0,0 +1,7 @@ + + +export async function createAgentkit() { + // prompts for network + // prompts for wallet provider + // generates prepare-agentkit.ts +} \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/create-wallet-provider.ts b/typescript/create-onchain-agent/src/cli/create-wallet-provider.ts new file mode 100644 index 000000000..1d90b8605 --- /dev/null +++ b/typescript/create-onchain-agent/src/cli/create-wallet-provider.ts @@ -0,0 +1,5 @@ + + +export async function createWalletProvider() { + // generates wallet-provider.ts +} \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/init-project.ts b/typescript/create-onchain-agent/src/cli/init-project.ts new file mode 100644 index 000000000..d35774e02 --- /dev/null +++ b/typescript/create-onchain-agent/src/cli/init-project.ts @@ -0,0 +1,316 @@ +import fs from "fs"; +import ora from "ora"; +import path from "path"; +import pc from "picocolors"; +import prompts from "prompts"; +import { EVM_NETWORKS, NetworkToWalletProviders, SVM_NETWORKS } from "../constants.js"; +import { Network, WalletProviderChoice, Framework } from "../types.js"; +import { copyTemplate } from "../fileSystem.js"; +import { + handleNextSelection, + isValidPackageName, + toValidPackageName, + getWalletProviders, + handleMcpSelection, +} from "../utils.js"; +import { Frameworks, FrameworkToTemplates } from "../constants.js"; + +/** + * Initializes the project creation process. + * + * - Prompts the user for project details including project name, package name, network, chain ID, and wallet provider. + * - Validates user input, ensuring directories do not already exist and package names are valid. + * - Copies the selected template to the new project directory. + * - Handles network and wallet provider selection logic. + * - Displays a summary of the created project along with next steps. + */ +export async function initProject() { + + console.log( + `${pc.blue(` + █████ ██████ ███████ ███ ██ ████████ ██ ██ ██ ████████ +██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ +███████ ██ ███ █████ ██ ██ ██ ██ █████ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██████ ███████ ██ ████ ██ ██ ██ ██ ██ + + Giving every AI agent a crypto wallet +`)}`, + ); + + const defaultProjectName = "onchain-agent"; + + let result: prompts.Answers< + | "projectName" + | "packageName" + | "networkFamily" + | "networkType" + | "network" + | "chainId" + | "rpcUrl" + | "walletProvider" + | "framework" + | "template" + >; + + try { + result = await prompts( + [ + { + type: "text", + name: "projectName", + message: pc.reset("Project name:"), + initial: defaultProjectName, + onState: state => { + state.value = state.value.trim(); + }, + validate: value => { + const targetDir = path.join(process.cwd(), value); + if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) { + return "Directory already exists and is not empty. Please choose a different name."; + } + return true; + }, + }, + { + type: (_, { projectName }: { projectName: string }) => + isValidPackageName(projectName) ? null : "text", + name: "packageName", + message: pc.reset("Package name:"), + initial: (_, { projectName }: { projectName: string }) => toValidPackageName(projectName), + validate: dir => isValidPackageName(dir) || "Invalid package.json name", + }, + { + type: "select", + name: "framework", + message: pc.reset("Choose a framework:"), + choices: Frameworks.map(framework => ({ + title: framework, + value: framework, + })), + }, + { + type: (prev, { framework }) => + FrameworkToTemplates[framework as Framework].length > 1 ? "select" : null, + name: "template", + message: pc.reset("Choose a template:"), + choices: (prev, { framework }) => + FrameworkToTemplates[framework as Framework].map(template => ({ + title: template, + value: template, + })), + }, + { + type: "select", + name: "networkFamily", + message: pc.reset("Choose a network family:"), + choices: [ + { title: "Ethereum Virtual Machine (EVM)", value: "EVM" }, + { title: "Solana Virtual Machine (SVM)", value: "SVM" }, + ], + }, + { + type: (prev, { networkFamily }) => (networkFamily === "EVM" ? "select" : null), + name: "networkType", + message: pc.reset("Choose network type:"), + choices: [ + { title: "Mainnet", value: "mainnet" }, + { title: "Testnet", value: "testnet" }, + { title: "Custom Chain ID", value: "custom" }, + ], + }, + { + type: (prev, { networkFamily, networkType }) => { + // For SVM, always show network selection + if (networkFamily === "SVM") return "select"; + // For EVM, show network selection only if not custom + return networkType === "custom" ? null : "select"; + }, + name: "network", + message: pc.reset("Choose a network:"), + choices: (prev, { networkFamily, networkType }) => { + if (networkFamily === "SVM") { + // Show all Solana networks + return SVM_NETWORKS.map(network => ({ + title: network, + value: network as Network, + })); + } else { + // Filter EVM networks by mainnet/testnet + return EVM_NETWORKS.filter(n => { + const isMainnet = n.includes("mainnet"); + return networkType === "mainnet" ? isMainnet : !isMainnet; + }).map(network => ({ + title: network === "base-sepolia" ? `${network} (default)` : network, + value: network as Network, + })); + } + }, + }, + { + type: (prev, { networkFamily, networkType }) => + networkFamily === "EVM" && networkType === "custom" ? "text" : null, + name: "chainId", + message: pc.reset("Enter your chain ID:"), + validate: value => + value.trim() + ? Number.parseInt(value) + ? true + : "Chain ID must be a number." + : "Chain ID cannot be empty.", + }, + { + type: (prev, { networkFamily, networkType }) => + networkFamily === "EVM" && networkType === "custom" ? "text" : null, + name: "rpcUrl", + message: pc.reset("Enter your RPC URL:"), + validate: value => + value.trim() + ? value.startsWith("http") + ? true + : "RPC URL must start with http:// or https://" + : "RPC URL cannot be empty.", + }, + { + type: (prev, { networkFamily, networkType }) => { + // For custom EVM networks, auto-select Viem by returning null + if (networkFamily === "EVM" && networkType === "custom") { + return null; + } + // For all other cases (regular EVM networks and SVM networks), show selection + return "select"; + }, + name: "walletProvider", + initial: (prev, { networkFamily, networkType, network }) => { + // For custom EVM networks, use Viem + if (networkFamily === "EVM" && networkType === "custom") { + return "Viem"; + } + // For networks with one provider, use that provider + if (network && NetworkToWalletProviders[network as Network].length === 1) { + return NetworkToWalletProviders[network as Network][0]; + } + return 0; + }, + message: (prev, { network }) => { + const walletDescriptions: Record = { + CDP: "Uses Coinbase Developer Platform (CDP)'s managed wallet.", + SmartWallet: "Uses Coinbase Developer Platform (CDP)'s Smart Wallet.", + Viem: "Client-side Ethereum wallet.", + Privy: "Authentication and wallet infrastructure.", + SolanaKeypair: "Client-side Solana wallet.", + }; + + const providerDescriptions = getWalletProviders(network as Network) + .map(provider => ` - ${provider}: ${walletDescriptions[provider]}`) + .join("\n"); + + return pc.reset(`Choose a wallet provider:\n${providerDescriptions}\n`); + }, + choices: (prev, { network }) => { + const walletProviders = getWalletProviders(network as Network); + return walletProviders.map(provider => ({ + title: provider === walletProviders[0] ? `${provider} (default)` : provider, + value: provider, + })); + }, + }, + ], + { + onCancel: () => { + console.log("\nProject creation cancelled."); + process.exit(0); + }, + }, + ); + } catch (cancelled: unknown) { + if (cancelled instanceof Error) { + console.info(cancelled.message); + } else { + console.info("An unknown error occurred"); + } + process.exit(1); + } + const { projectName, network, chainId, rpcUrl, framework } = result; + const packageName = result.packageName || toValidPackageName(projectName); + const walletProvider = result.walletProvider || "Viem"; + // If template wasn't selected (because there was only one option), use the first template + const template = result.template || FrameworkToTemplates[framework as Framework][0]; + + const spinner = ora(`Creating ${projectName}...`).start(); + + // Copy template over to new project + const root = await copyTemplate(projectName, packageName, template); + + // Handle selection-specific logic over copied-template + switch (template) { + case "next": + await handleNextSelection(root, framework, walletProvider, network, chainId, rpcUrl); + + spinner.succeed(); + console.log(pc.blueBright(`\nSuccessfully created your AgentKit project in ${root}`)); + + console.log(`\nFrameworks:`); + console.log(pc.gray("- AgentKit")); + console.log(pc.gray(`- ${framework}`)); + console.log(pc.gray("- React")); + console.log(pc.gray("- Next.js")); + console.log(pc.gray("- Tailwind CSS")); + console.log(pc.gray("- ESLint")); + + console.log(pc.bold("\nWhat's Next?")); + + console.log(`\nTo get started, run the following commands:\n`); + if (root !== process.cwd()) { + console.log(` - cd ${path.relative(process.cwd(), root)}`); + } + console.log(" - npm install"); + console.log(pc.gray(" - # Open .env.local and configure your API keys")); + console.log(" - mv .env.local .env"); + console.log(" - npm run dev"); + break; + case "mcp": + await handleMcpSelection(root, walletProvider, network, chainId); + + spinner.succeed(); + console.log(pc.blueBright(`\nSuccessfully created your AgentKit project in ${root}`)); + + console.log(`\nTo get started, run the following commands:\n`); + if (root !== process.cwd()) { + console.log(` - cd ${path.relative(process.cwd(), root)}`); + } + console.log(" - npm install"); + console.log(" - npm run build"); + console.log( + " - cp claude_desktop_config.json ~/Library/Application\\ Support/Claude/claude_desktop_config.json", + ); + + if (walletProvider === "CDP" || walletProvider === "SmartWallet") { + console.log( + " - # Make sure to open claude_desktop_config.json and configure your CDP API keys!", + ); + } + + if (walletProvider === "Privy") { + console.log( + " - # Make sure to open claude_desktop_config.json and configure your Privy API keys!", + ); + } + + console.log( + "\nNow open Claude Desktop and start prompting Claude to do things onchain", + "\nFor example, ask it to print your wallet details", + ); + break; + default: + break; + } + + console.log(pc.bold("\nLearn more")); + console.log(" - Checkout the docs"); + console.log(pc.blueBright(" - https://docs.cdp.coinbase.com/agentkit/docs/welcome")); + console.log(" - Visit the repo"); + console.log(pc.blueBright(" - https://github.com/coinbase/agentkit")); + console.log(" - Join the community"); + console.log(pc.blueBright(" - https://discord.gg/CDP\n")); +} \ No newline at end of file diff --git a/typescript/create-onchain-agent/templates/action-provider/schema.njk b/typescript/create-onchain-agent/templates/action-provider/schema.njk new file mode 100644 index 000000000..eebe94087 --- /dev/null +++ b/typescript/create-onchain-agent/templates/action-provider/schema.njk @@ -0,0 +1,11 @@ +import { z } from "zod"; + +/** + * Input schema for {{ description }}. + */ +export const {{ className }}Schema = z + .object({ + payload: z.string().describe("The payload to send to the action provider"), + }) + .strip() + .describe("Instructions for {{ description }}"); \ No newline at end of file diff --git a/typescript/create-onchain-agent/templates/action-provider/template.njk b/typescript/create-onchain-agent/templates/action-provider/template.njk new file mode 100644 index 000000000..9777ab779 --- /dev/null +++ b/typescript/create-onchain-agent/templates/action-provider/template.njk @@ -0,0 +1,49 @@ +import { ActionProvider, {{ walletProvider }}, CreateAction, Network } from "@coinbase/agentkit"; +import { {{ schemaName }} } from "./schemas"; +import { z } from "zod"; + +/** + * Action provider for {{ description }}. + */ +export class {{ className }}Provider extends ActionProvider<{{ walletProvider }}> { + /** + * Constructs a new {{ className }}Provider. + */ + constructor() { + super("{{ name }}", {{ networks | safe }}); + } + + /** + * Performs {{ description }}. + * + * @param wallet - The wallet to use for the work. + * @param args - The arguments for the work. + * @returns A string indicating the success or failure of the work. + */ + @CreateAction({ + name: "{{ actionName }}", + description: `This tool will perform {{ description }}.`, + schema: {{ schemaName }}, + }) + async {{ name }}Action( + wallet: {{ walletProvider }}, + args: z.infer, + ): Promise { + try { + // Do work here + return `Successfully performed {{ description }} and returned the response`; + } catch (error) { + return `Error performing {{ description }}: Error: ${error}`; + } + } + + /** + * Checks if the {{ name }} action provider supports the given network. + * + * @param network - The network to check. + * @returns True if the {{ name }} action provider supports the network, false otherwise. + */ + supportsNetwork = (network: Network) => {{ networkSupport | safe }}; +} + +export const {{ name }}ActionProvider = () => new {{ className }}Provider(); \ No newline at end of file diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 64f29e5df..89ed555a3 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -254,8 +254,10 @@ "version": "0.2.0", "license": "Apache-2.0", "dependencies": { + "@types/nunjucks": "^3.2.6", "cac": "^6.7.14", "cross-spawn": "^7.0.3", + "nunjucks": "^3.2.4", "ora": "^8.1.0", "picocolors": "^1.1.0", "prompts": "^2.4.2" @@ -4854,9 +4856,7 @@ "node_modules/@types/nunjucks": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.2.6.tgz", - "integrity": "sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==", - "dev": true, - "license": "MIT" + "integrity": "sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==" }, "node_modules/@types/ora": { "version": "3.2.0", @@ -5894,7 +5894,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", - "dev": true, "license": "MIT" }, "node_modules/abitype": { @@ -6340,7 +6339,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, "license": "MIT" }, "node_modules/ast-types-flow": { @@ -6682,7 +6680,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -6802,7 +6800,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -7198,7 +7196,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -7223,7 +7221,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -9899,7 +9897,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -11017,7 +11015,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -11148,7 +11146,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -11206,7 +11204,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -11252,7 +11250,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12.0" } @@ -14189,8 +14187,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", - "dev": true, - "license": "BSD-2-Clause", "dependencies": { "a-sync-waterfall": "^1.0.0", "asap": "^2.0.3", @@ -14215,7 +14211,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -16052,7 +16047,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -17786,7 +17781,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -22653,8 +22648,7 @@ "@types/nunjucks": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.2.6.tgz", - "integrity": "sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==", - "dev": true + "integrity": "sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==" }, "@types/ora": { "version": "3.2.0", @@ -23337,8 +23331,7 @@ "a-sync-waterfall": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", - "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", - "dev": true + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" }, "abitype": { "version": "1.0.8", @@ -23632,8 +23625,7 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "ast-types-flow": { "version": "0.0.8", @@ -23879,7 +23871,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true + "devOptional": true }, "bindings": { "version": "1.5.0", @@ -23978,7 +23970,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "requires": { "fill-range": "^7.1.1" } @@ -24228,7 +24220,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "devOptional": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -24244,7 +24236,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "requires": { "is-glob": "^4.0.1" } @@ -24452,9 +24444,11 @@ "requires": { "@types/cross-spawn": "^6.0.6", "@types/node": "^20.12.10", + "@types/nunjucks": "^3.2.6", "@types/prompts": "^2.4.9", "cac": "^6.7.14", "cross-spawn": "^7.0.3", + "nunjucks": "*", "ora": "^8.1.0", "picocolors": "^1.1.0", "prompts": "^2.4.2" @@ -26201,7 +26195,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -26958,7 +26952,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "devOptional": true, "requires": { "binary-extensions": "^2.0.0" } @@ -27031,7 +27025,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "devOptional": true }, "is-finalizationregistry": { "version": "1.1.1", @@ -27068,7 +27062,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "requires": { "is-extglob": "^2.1.1" } @@ -27093,7 +27087,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "devOptional": true }, "is-number-object": { "version": "1.1.1", @@ -29098,7 +29092,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", - "dev": true, "requires": { "a-sync-waterfall": "^1.0.0", "asap": "^2.0.3", @@ -29108,8 +29101,7 @@ "commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" } } }, @@ -30360,7 +30352,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "devOptional": true, "requires": { "picomatch": "^2.2.1" } @@ -31586,7 +31578,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "requires": { "is-number": "^7.0.0" } From 7de003914f3054d524cd4fd32802b2066bf946b7 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 17 Mar 2025 11:32:55 -0700 Subject: [PATCH 02/29] feat: refactored naming from kebbab case to camel case --- typescript/create-onchain-agent/src/cli.ts | 9 ++++---- ...on-provider.ts => createActionProvider.ts} | 23 +++++++------------ .../cli/{create-agent.ts => createAgent.ts} | 0 .../{create-agentkit.ts => createAgentkit.ts} | 0 ...et-provider.ts => createWalletProvider.ts} | 0 .../schema.njk | 0 .../template.njk | 0 7 files changed, 12 insertions(+), 20 deletions(-) rename typescript/create-onchain-agent/src/cli/{create-action-provider.ts => createActionProvider.ts} (77%) rename typescript/create-onchain-agent/src/cli/{create-agent.ts => createAgent.ts} (100%) rename typescript/create-onchain-agent/src/cli/{create-agentkit.ts => createAgentkit.ts} (100%) rename typescript/create-onchain-agent/src/cli/{create-wallet-provider.ts => createWalletProvider.ts} (100%) rename typescript/create-onchain-agent/templates/{action-provider => actionProvider}/schema.njk (100%) rename typescript/create-onchain-agent/templates/{action-provider => actionProvider}/template.njk (100%) diff --git a/typescript/create-onchain-agent/src/cli.ts b/typescript/create-onchain-agent/src/cli.ts index 0fa7f495c..061ea8234 100644 --- a/typescript/create-onchain-agent/src/cli.ts +++ b/typescript/create-onchain-agent/src/cli.ts @@ -1,12 +1,11 @@ #!/usr/bin/env node -import { createActionProvider } from "./cli/create-action-provider.js"; -import { createAgent } from "./cli/create-agent.js"; -import { createAgentkit } from "./cli/create-agentkit.js"; -import { createWalletProvider } from "./cli/create-wallet-provider.js"; +import { createActionProvider } from "./cli/createActionProvider.js"; +import { createAgent } from "./cli/createAgent.js"; +import { createAgentkit } from "./cli/createAgentkit.js"; +import { createWalletProvider } from "./cli/createWalletProvider.js"; import { initProject } from "./cli/init-project.js"; async function handleArgs() { - console.log(process.argv); const type = process.argv[2]; if (!type) { await initProject(); diff --git a/typescript/create-onchain-agent/src/cli/create-action-provider.ts b/typescript/create-onchain-agent/src/cli/createActionProvider.ts similarity index 77% rename from typescript/create-onchain-agent/src/cli/create-action-provider.ts rename to typescript/create-onchain-agent/src/cli/createActionProvider.ts index 78994ad23..53001753e 100644 --- a/typescript/create-onchain-agent/src/cli/create-action-provider.ts +++ b/typescript/create-onchain-agent/src/cli/createActionProvider.ts @@ -11,17 +11,10 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Utility functions for string transformations -function toKebabCase(str: string): string { - return str - .replace(/([a-z])([A-Z])/g, '$1-$2') // Add hyphen between lower & upper - .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') // Add hyphen between consecutive uppers if followed by lower - .toLowerCase(); -} - function toSnakeCase(str: string): string { return str - .replace(/([a-z])([A-Z])/g, '$1_$2') // Add underscore between lower & upper - .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') // Add underscore between consecutive uppers if followed by lower + .replace(/([a-z])([A-Z])/g, '$1_$2') + .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') .toLowerCase(); } @@ -61,7 +54,7 @@ export async function createActionProvider() { // Process the name variations const className = answers.name.charAt(0).toUpperCase() + answers.name.slice(1); - const kebabName = `${toKebabCase(answers.name)}-action-provider`; + const fileName = `${className}ActionProvider`; const snakeName = toSnakeCase(answers.name); // Determine wallet provider and networks based on network family @@ -84,7 +77,7 @@ export async function createActionProvider() { } // Generate code using nunjucks - const generatedCode = nunjucks.render('action-provider/template.njk', { + const generatedCode = nunjucks.render('actionProvider/template.njk', { name: snakeName, className, description: answers.description, @@ -96,19 +89,19 @@ export async function createActionProvider() { }); // Create directory if it doesn't exist - const dirPath = `./${kebabName}`; + const dirPath = `./${fileName}`; await fs.mkdir(dirPath, { recursive: true }); // Write files - await fs.writeFile(`${dirPath}/${kebabName}.ts`, generatedCode); + await fs.writeFile(`${dirPath}/${fileName}.ts`, generatedCode); // Generate schema file using nunjucks with updated schema name - const schemaCode = nunjucks.render('action-provider/schema.njk', { + const schemaCode = nunjucks.render('actionProvider/schema.njk', { className: `${className}Action`, description: answers.description }); await fs.writeFile(`${dirPath}/schemas.ts`, schemaCode); - console.log(`Successfully created ${kebabName}!`); + console.log(`Successfully created ${fileName}!`); } \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/create-agent.ts b/typescript/create-onchain-agent/src/cli/createAgent.ts similarity index 100% rename from typescript/create-onchain-agent/src/cli/create-agent.ts rename to typescript/create-onchain-agent/src/cli/createAgent.ts diff --git a/typescript/create-onchain-agent/src/cli/create-agentkit.ts b/typescript/create-onchain-agent/src/cli/createAgentkit.ts similarity index 100% rename from typescript/create-onchain-agent/src/cli/create-agentkit.ts rename to typescript/create-onchain-agent/src/cli/createAgentkit.ts diff --git a/typescript/create-onchain-agent/src/cli/create-wallet-provider.ts b/typescript/create-onchain-agent/src/cli/createWalletProvider.ts similarity index 100% rename from typescript/create-onchain-agent/src/cli/create-wallet-provider.ts rename to typescript/create-onchain-agent/src/cli/createWalletProvider.ts diff --git a/typescript/create-onchain-agent/templates/action-provider/schema.njk b/typescript/create-onchain-agent/templates/actionProvider/schema.njk similarity index 100% rename from typescript/create-onchain-agent/templates/action-provider/schema.njk rename to typescript/create-onchain-agent/templates/actionProvider/schema.njk diff --git a/typescript/create-onchain-agent/templates/action-provider/template.njk b/typescript/create-onchain-agent/templates/actionProvider/template.njk similarity index 100% rename from typescript/create-onchain-agent/templates/action-provider/template.njk rename to typescript/create-onchain-agent/templates/actionProvider/template.njk From dc9bfec7bc2dd1bb4801ffa91f26eabecd4016d7 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 17 Mar 2025 11:40:47 -0700 Subject: [PATCH 03/29] feat: added wallet provider generation logic --- .../src/cli/createActionProvider.ts | 11 +- .../src/cli/createWalletProvider.ts | 70 ++++++- .../{template.njk => actionProvider.njk} | 0 .../walletProvider/walletProvider.njk | 182 ++++++++++++++++++ 4 files changed, 259 insertions(+), 4 deletions(-) rename typescript/create-onchain-agent/templates/actionProvider/{template.njk => actionProvider.njk} (100%) create mode 100644 typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk diff --git a/typescript/create-onchain-agent/src/cli/createActionProvider.ts b/typescript/create-onchain-agent/src/cli/createActionProvider.ts index 53001753e..2c2ec74cd 100644 --- a/typescript/create-onchain-agent/src/cli/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/cli/createActionProvider.ts @@ -11,6 +11,11 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Utility functions for string transformations +function toCamelCase(str: string): string { + // First character should be lowercase + return str.charAt(0).toLowerCase() + str.slice(1); +} + function toSnakeCase(str: string): string { return str .replace(/([a-z])([A-Z])/g, '$1_$2') @@ -54,7 +59,7 @@ export async function createActionProvider() { // Process the name variations const className = answers.name.charAt(0).toUpperCase() + answers.name.slice(1); - const fileName = `${className}ActionProvider`; + const fileName = `${toCamelCase(answers.name)}ActionProvider`; const snakeName = toSnakeCase(answers.name); // Determine wallet provider and networks based on network family @@ -77,9 +82,9 @@ export async function createActionProvider() { } // Generate code using nunjucks - const generatedCode = nunjucks.render('actionProvider/template.njk', { + const generatedCode = nunjucks.render('actionProvider/actionProvider.njk', { name: snakeName, - className, + className: fileName, description: answers.description, walletProvider, networks: '[]', diff --git a/typescript/create-onchain-agent/src/cli/createWalletProvider.ts b/typescript/create-onchain-agent/src/cli/createWalletProvider.ts index 1d90b8605..d93fbb64f 100644 --- a/typescript/create-onchain-agent/src/cli/createWalletProvider.ts +++ b/typescript/create-onchain-agent/src/cli/createWalletProvider.ts @@ -1,5 +1,73 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import nunjucks from 'nunjucks'; +import prompts from 'prompts'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function toCamelCase(str: string): string { + // First character should be lowercase + return str.charAt(0).toLowerCase() + str.slice(1); +} + +function toSnakeCase(str: string): string { + return str + .replace(/([a-z])([A-Z])/g, '$1_$2') + .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') + .toLowerCase(); +} export async function createWalletProvider() { - // generates wallet-provider.ts + nunjucks.configure(path.join(__dirname, '../../../templates'), { + autoescape: false, + trimBlocks: true, + lstripBlocks: true + }); + + const answers = await prompts([ + { + type: 'text', + name: 'name', + message: 'What is the name of your wallet provider?', + validate: (value: string) => value.length > 0 ? true : 'Name is required' + }, + { + type: 'text', + name: 'description', + message: 'Provide a brief description:', + initial: (prev: string) => `${prev} wallet operations` + }, + { + type: 'select', + name: 'protocolFamily', + message: 'Which protocol family will this support?', + choices: [ + { title: 'EVM', value: 'EVM' }, + { title: 'SVM', value: 'SVM' } + ] + } + ]); + + const className = answers.name.charAt(0).toUpperCase() + answers.name.slice(1); + const fileName = `${toCamelCase(answers.name)}WalletProvider`; + const snakeName = toSnakeCase(answers.name); + + const baseClass = answers.protocolFamily === 'EVM' ? 'EvmWalletProvider' : 'SvmWalletProvider'; + + const generatedCode = nunjucks.render('walletProvider/walletProvider.njk', { + name: snakeName, + className: fileName, + description: answers.description, + protocolFamily: answers.protocolFamily, + baseClass + }); + + const dirPath = `./${fileName}`; + await fs.mkdir(dirPath, { recursive: true }); + + await fs.writeFile(`${dirPath}/${fileName}.ts`, generatedCode); + + console.log(`Successfully created ${fileName}!`); } \ No newline at end of file diff --git a/typescript/create-onchain-agent/templates/actionProvider/template.njk b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk similarity index 100% rename from typescript/create-onchain-agent/templates/actionProvider/template.njk rename to typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk diff --git a/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk new file mode 100644 index 000000000..f24614138 --- /dev/null +++ b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk @@ -0,0 +1,182 @@ +import { {{ baseClass }} } from "@coinbase/agentkit"; +import { Network } from "@coinbase/agentkit"; +{% if protocolFamily === "EVM" %} +import { TransactionRequest, ReadContractParameters, ReadContractReturnType, Abi, ContractFunctionName, ContractFunctionArgs } from "viem"; +{% else %} +import { Connection, PublicKey, RpcResponseAndContext, SignatureStatus, SignatureStatusConfig, VersionedTransaction, SignatureResult } from "@solana/web3.js"; +{% endif %} + +/** + * {{ className }} implements a wallet provider for {{ description }}. + */ +export class {{ className }} extends {{ baseClass }} { +{% if protocolFamily === "EVM" %} + /** + * Get the address of the wallet. + */ + getAddress(): string { + throw new Error("Method not implemented."); + } + + /** + * Get the current network. + */ + getNetwork(): Network { + throw new Error("Method not implemented."); + } + + /** + * Get the name of the wallet provider. + */ + getName(): string { + return "{{ name }}"; + } + + /** + * Get the balance of the native asset. + */ + async getBalance(): Promise { + throw new Error("Method not implemented."); + } + + /** + * Transfer the native asset. + */ + async nativeTransfer(to: string, value: string): Promise { + throw new Error("Method not implemented."); + } + + /** + * Sign a message. + */ + async signMessage(message: string | Uint8Array): Promise<`0x${string}`> { + throw new Error("Method not implemented."); + } + + /** + * Sign typed data. + */ + async signTypedData(typedData: any): Promise<`0x${string}`> { + throw new Error("Method not implemented."); + } + + /** + * Sign a transaction. + */ + async signTransaction(transaction: TransactionRequest): Promise<`0x${string}`> { + throw new Error("Method not implemented."); + } + + /** + * Send a transaction. + */ + async sendTransaction(transaction: TransactionRequest): Promise<`0x${string}`> { + throw new Error("Method not implemented."); + } + + /** + * Wait for a transaction receipt. + */ + async waitForTransactionReceipt(txHash: `0x${string}`): Promise { + throw new Error("Method not implemented."); + } + + /** + * Read a contract. + */ + async readContract< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + const args extends ContractFunctionArgs, + >(params: ReadContractParameters): Promise> { + throw new Error("Method not implemented."); + } +{% else %} + /** + * Get the address of the wallet. + */ + getAddress(): string { + throw new Error("Method not implemented."); + } + + /** + * Get the current network. + */ + getNetwork(): Network { + throw new Error("Method not implemented."); + } + + /** + * Get the name of the wallet provider. + */ + getName(): string { + return "{{ name }}"; + } + + /** + * Get the balance of the native asset. + */ + async getBalance(): Promise { + throw new Error("Method not implemented."); + } + + /** + * Transfer the native asset. + */ + async nativeTransfer(to: string, value: string): Promise { + throw new Error("Method not implemented."); + } + + /** + * Get the connection instance. + */ + getConnection(): Connection { + throw new Error("Method not implemented."); + } + + /** + * Get the public key of the wallet. + */ + getPublicKey(): PublicKey { + throw new Error("Method not implemented."); + } + + /** + * Sign a transaction. + */ + async signTransaction(transaction: VersionedTransaction): Promise { + throw new Error("Method not implemented."); + } + + /** + * Send a transaction. + */ + async sendTransaction(transaction: VersionedTransaction): Promise { + throw new Error("Method not implemented."); + } + + /** + * Sign and send a transaction. + */ + async signAndSendTransaction(transaction: VersionedTransaction): Promise { + throw new Error("Method not implemented."); + } + + /** + * Get the status of a transaction. + */ + async getSignatureStatus( + signature: string, + options?: SignatureStatusConfig + ): Promise> { + throw new Error("Method not implemented."); + } + + /** + * Wait for signature receipt. + */ + async waitForSignatureResult(signature: string): Promise> { + throw new Error("Method not implemented."); + } +{% endif %} +} From b033c75cfff579492762b735061979f05b9f2490 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 17 Mar 2025 13:37:35 -0700 Subject: [PATCH 04/29] feat: added create agentkit path --- typescript/create-onchain-agent/src/cli.ts | 2 +- .../src/cli/createAgentkit.ts | 170 +++++++++++++++++- .../cli/{init-project.ts => initProject.ts} | 2 +- .../create-onchain-agent/src/constants.ts | 38 +++- .../create-onchain-agent/src/fileSystem.ts | 27 ++- typescript/create-onchain-agent/src/types.ts | 4 + .../customEvm/viem/prepareAgentkit.ts | 129 +++++++++++++ .../agentkit/evm/cdp/prepareAgentkit.ts | 109 +++++++++++ .../agentkit/evm/privy/prepareAgentkit.ts | 118 ++++++++++++ .../agentkit/evm/smart/prepareAgentkit.ts | 135 ++++++++++++++ .../agentkit/evm/viem/prepareAgentkit.ts | 116 ++++++++++++ .../agentkit/svm/privy/prepareAgentkit.ts | 109 +++++++++++ .../svm/solanaKeypair/prepareAgentkit.ts | 116 ++++++++++++ 13 files changed, 1058 insertions(+), 17 deletions(-) rename typescript/create-onchain-agent/src/cli/{init-project.ts => initProject.ts} (99%) create mode 100644 typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/customEvm/viem/prepareAgentkit.ts create mode 100644 typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts create mode 100644 typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/privy/prepareAgentkit.ts create mode 100644 typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts create mode 100644 typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/viem/prepareAgentkit.ts create mode 100644 typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/privy/prepareAgentkit.ts create mode 100644 typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/solanaKeypair/prepareAgentkit.ts diff --git a/typescript/create-onchain-agent/src/cli.ts b/typescript/create-onchain-agent/src/cli.ts index 061ea8234..a918b1b5c 100644 --- a/typescript/create-onchain-agent/src/cli.ts +++ b/typescript/create-onchain-agent/src/cli.ts @@ -3,7 +3,7 @@ import { createActionProvider } from "./cli/createActionProvider.js"; import { createAgent } from "./cli/createAgent.js"; import { createAgentkit } from "./cli/createAgentkit.js"; import { createWalletProvider } from "./cli/createWalletProvider.js"; -import { initProject } from "./cli/init-project.js"; +import { initProject } from "./cli/initProject.js"; async function handleArgs() { const type = process.argv[2]; diff --git a/typescript/create-onchain-agent/src/cli/createAgentkit.ts b/typescript/create-onchain-agent/src/cli/createAgentkit.ts index ecf6f7a7e..7acb2dcf3 100644 --- a/typescript/create-onchain-agent/src/cli/createAgentkit.ts +++ b/typescript/create-onchain-agent/src/cli/createAgentkit.ts @@ -1,7 +1,169 @@ +import prompts from "prompts"; +import pc from "picocolors"; +import { AgentkitRouteConfigurations, EVM_NETWORKS, NetworkToWalletProviders, PrepareAgentkitRouteConfigurations, SVM_NETWORKS } from "../constants.js"; +import { Network, WalletProviderChoice } from "../types.js"; +import { getNetworkType, getWalletProviders } from "../utils.js"; +import fs from "fs/promises"; +import path from "path"; +import { copyTemplate } from "../fileSystem.js"; - +/** + * Prompts user for network and wallet provider selection, then sets up the prepare-agentkit file + * by promoting the correct template and cleaning up unused files. + */ export async function createAgentkit() { - // prompts for network - // prompts for wallet provider - // generates prepare-agentkit.ts + let result: prompts.Answers< + "networkFamily" | "networkType" | "network" | "chainId" | "rpcUrl" | "walletProvider" + >; + + try { + result = await prompts( + [ + { + type: "select", + name: "networkFamily", + message: pc.reset("Choose a network family:"), + choices: [ + { title: "Ethereum Virtual Machine (EVM)", value: "EVM" }, + { title: "Solana Virtual Machine (SVM)", value: "SVM" }, + ], + }, + { + type: (prev, { networkFamily }) => (networkFamily === "EVM" ? "select" : null), + name: "networkType", + message: pc.reset("Choose network type:"), + choices: [ + { title: "Mainnet", value: "mainnet" }, + { title: "Testnet", value: "testnet" }, + { title: "Custom Chain ID", value: "custom" }, + ], + }, + { + type: (prev, { networkFamily, networkType }) => { + // For SVM, always show network selection + if (networkFamily === "SVM") return "select"; + // For EVM, show network selection only if not custom + return networkType === "custom" ? null : "select"; + }, + name: "network", + message: pc.reset("Choose a network:"), + choices: (prev, { networkFamily, networkType }) => { + if (networkFamily === "SVM") { + return SVM_NETWORKS.map(network => ({ + title: network, + value: network as Network, + })); + } else { + return EVM_NETWORKS.filter(n => { + const isMainnet = n.includes("mainnet"); + return networkType === "mainnet" ? isMainnet : !isMainnet; + }).map(network => ({ + title: network === "base-sepolia" ? `${network} (default)` : network, + value: network as Network, + })); + } + }, + }, + { + type: (prev, { networkFamily, networkType }) => + networkFamily === "EVM" && networkType === "custom" ? "text" : null, + name: "chainId", + message: pc.reset("Enter your chain ID:"), + validate: value => + value.trim() + ? Number.parseInt(value) + ? true + : "Chain ID must be a number." + : "Chain ID cannot be empty.", + }, + { + type: (prev, { networkFamily, networkType }) => + networkFamily === "EVM" && networkType === "custom" ? "text" : null, + name: "rpcUrl", + message: pc.reset("Enter your RPC URL:"), + validate: value => + value.trim() + ? value.startsWith("http") + ? true + : "RPC URL must start with http:// or https://" + : "RPC URL cannot be empty.", + }, + { + type: (prev, { networkFamily, networkType }) => { + // For custom EVM networks, auto-select Viem by returning null + if (networkFamily === "EVM" && networkType === "custom") { + return null; + } + return "select"; + }, + name: "walletProvider", + message: (prev, { network }) => { + const walletDescriptions: Record = { + CDP: "Uses Coinbase Developer Platform (CDP)'s managed wallet.", + SmartWallet: "Uses Coinbase Developer Platform (CDP)'s Smart Wallet.", + Viem: "Client-side Ethereum wallet.", + Privy: "Authentication and wallet infrastructure.", + SolanaKeypair: "Client-side Solana wallet.", + }; + + const providerDescriptions = getWalletProviders(network as Network) + .map(provider => ` - ${provider}: ${walletDescriptions[provider]}`) + .join("\n"); + + return pc.reset(`Choose a wallet provider:\n${providerDescriptions}\n`); + }, + choices: (prev, { network }) => { + const walletProviders = getWalletProviders(network as Network); + return walletProviders.map(provider => ({ + title: provider === walletProviders[0] ? `${provider} (default)` : provider, + value: provider, + })); + }, + }, + ], + { + onCancel: () => { + console.log("\nPrepare-agentkit creation cancelled."); + process.exit(0); + }, + }, + ); + } catch (cancelled: unknown) { + console.error("An error occurred during prepare-agentkit creation"); + process.exit(1); + } + + const { network, chainId, walletProvider = "Viem" } = result; + + // Determine network type (EVM, CUSTOM_EVM, or SVM) + const networkFamily = getNetworkType(network, chainId); + if (!networkFamily) { + throw new Error("Unsupported network and chainId selected"); + } + + // Get the route configuration for the selected wallet provider + const routeConfig = PrepareAgentkitRouteConfigurations[networkFamily][walletProvider as WalletProviderChoice]; + if (!routeConfig) { + throw new Error("Selected invalid network & wallet provider combination"); + } + + try { + // Copy template and get root directory - no package name needed + const root = await copyTemplate("prepareAgentkit", "prepareAgentkit"); + + // Copy the selected route to the destination + const selectedRoutePath = path.join(root, "agentkit", routeConfig.route); + const newRoutePath = path.join(root, "prepareAgentkit.ts"); + + await fs.copyFile(selectedRoutePath, newRoutePath); + + // Clean up the agentkit directory + const agentkitDir = path.join(root, "agentkit"); + await fs.rm(agentkitDir, { recursive: true, force: true }); + + console.log(pc.green("\nSuccessfully created prepare-agentkit.ts")); + } catch (error) { + console.error("Error setting up prepare-agentkit:", error); + process.exit(1); + } } \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/init-project.ts b/typescript/create-onchain-agent/src/cli/initProject.ts similarity index 99% rename from typescript/create-onchain-agent/src/cli/init-project.ts rename to typescript/create-onchain-agent/src/cli/initProject.ts index d35774e02..d35197d1c 100644 --- a/typescript/create-onchain-agent/src/cli/init-project.ts +++ b/typescript/create-onchain-agent/src/cli/initProject.ts @@ -240,7 +240,7 @@ export async function initProject() { const spinner = ora(`Creating ${projectName}...`).start(); // Copy template over to new project - const root = await copyTemplate(projectName, packageName, template); + const root = await copyTemplate(projectName, template, packageName); // Handle selection-specific logic over copied-template switch (template) { diff --git a/typescript/create-onchain-agent/src/constants.ts b/typescript/create-onchain-agent/src/constants.ts index 16f12cd3a..eecb7d9c5 100644 --- a/typescript/create-onchain-agent/src/constants.ts +++ b/typescript/create-onchain-agent/src/constants.ts @@ -1,4 +1,4 @@ -import { AgentkitRouteConfiguration, MCPRouteConfiguration } from "./types"; +import { AgentkitRouteConfiguration, PrepareAgentkitRouteConfiguration, MCPRouteConfiguration } from "./types"; export const EVM_NETWORKS = [ "base-mainnet", @@ -159,7 +159,7 @@ export const Frameworks = ["Langchain", "Vercel AI SDK", "Model Context Protocol export type Framework = (typeof Frameworks)[number]; -export const Templates = ["next", "mcp"] as const; +export const Templates = ["next", "mcp", "prepareAgentkit"] as const; export type Template = (typeof Templates)[number]; @@ -226,3 +226,37 @@ export const MCPRouteConfigurations: Record< }, }, }; + + +export const PrepareAgentkitRouteConfigurations: Record< + "EVM" | "CUSTOM_EVM" | "SVM", + Partial> +> = { + EVM: { + CDP: { + route: "evm/cdp/prepareAgentkit.ts", + }, + Viem: { + route: "evm/viem/prepareAgentkit.ts", + }, + Privy: { + route: "evm/privy/prepareAgentkit.ts", + }, + SmartWallet: { + route: "evm/smart/prepareAgentkit.ts", + }, + }, + CUSTOM_EVM: { + Viem: { + route: "custom-evm/viem/prepareAgentkit.ts", + }, + }, + SVM: { + SolanaKeypair: { + route: "svm/solana-keypair/prepareAgentkit.ts", + }, + Privy: { + route: "svm/privy/prepareAgentkit.ts", + }, + }, +}; \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/fileSystem.ts b/typescript/create-onchain-agent/src/fileSystem.ts index c4e54432a..fb9fbbd14 100644 --- a/typescript/create-onchain-agent/src/fileSystem.ts +++ b/typescript/create-onchain-agent/src/fileSystem.ts @@ -57,29 +57,38 @@ async function copyDir(src: string, dest: string): Promise { } /** - * Copies a project template to a new directory and updates the package name. + * Copies a project template to a new directory and optionally updates the package name. * * - Copies the template from the source directory. - * - Updates the `package.json` file with the provided package name. + * - If packageName is provided, updates the `package.json` file with the provided package name. * * @param {string} projectName - The name of the new project directory. - * @param {string} packageName - The npm package name to use (auto-formatted if empty). * @param {Template} template - The template to use. + * @param {string} [packageName] - Optional npm package name to use. * @returns {Promise} The path of the newly created project directory. */ export async function copyTemplate( projectName: string, - packageName: string, template: Template, + packageName?: string, ): Promise { const root = path.join(process.cwd(), projectName); await copyDir(getSourceDir(template), root); - const pkgPath = path.join(root, "package.json"); - const pkg = JSON.parse(await fs.promises.readFile(pkgPath, "utf-8")); - pkg.name = packageName; - - await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2)); + // Only update package.json if packageName is provided + if (packageName) { + const pkgPath = path.join(root, "package.json"); + try { + const pkg = JSON.parse(await fs.promises.readFile(pkgPath, "utf-8")); + pkg.name = packageName; + await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2)); + } catch (error) { + // Ignore errors if package.json doesn't exist + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + throw error; + } + } + } return root; } diff --git a/typescript/create-onchain-agent/src/types.ts b/typescript/create-onchain-agent/src/types.ts index add17ac10..6c0af92f0 100644 --- a/typescript/create-onchain-agent/src/types.ts +++ b/typescript/create-onchain-agent/src/types.ts @@ -14,6 +14,10 @@ export type MCPRouteConfiguration = { configRoute: `${string}.json`; }; +export type PrepareAgentkitRouteConfiguration = { + route: `${string}/prepareAgentkit.ts`; +} + export type NetworkSelection = { networkFamily: "EVM" | "SVM"; networkType: "mainnet" | "testnet" | "custom"; diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/customEvm/viem/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/customEvm/viem/prepareAgentkit.ts new file mode 100644 index 000000000..cede3ead2 --- /dev/null +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/customEvm/viem/prepareAgentkit.ts @@ -0,0 +1,129 @@ +import { + ActionProvider, + AgentKit, + cdpApiActionProvider, + erc20ActionProvider, + pythActionProvider, + ViemWalletProvider, + walletActionProvider, + WalletProvider, + wethActionProvider, +} from "@coinbase/agentkit"; +import fs from "fs"; +import { createWalletClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; + +/** + * AgentKit Integration Route + * + * This file is your gateway to integrating AgentKit with your product. + * It defines the core capabilities of your agent through WalletProvider + * and ActionProvider configuration. + * + * Key Components: + * 1. WalletProvider Setup: + * - Configures the blockchain wallet integration + * - Learn more: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#evm-wallet-providers + * + * 2. ActionProviders Setup: + * - Defines the specific actions your agent can perform + * - Choose from built-in providers or create custom ones: + * - Built-in: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#action-providers + * - Custom: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#creating-an-action-provider + * + * # Next Steps: + * - Explore the AgentKit README: https://github.com/coinbase/agentkit + * - Experiment with different LLM configurations + * - Fine-tune agent parameters for your use case + * + * ## Want to contribute? + * Join us in shaping AgentKit! Check out the contribution guide: + * - https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md + * - https://discord.gg/CDP + */ + +// Configure a file to persist a user's private key if none provided +const WALLET_DATA_FILE = "wallet_data.txt"; + +/** + * Prepares the AgentKit and WalletProvider. + * + * @function prepareAgentkitAndWalletProvider + * @returns {Promise<{ agentkit: AgentKit, walletProvider: WalletProvider }>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function prepareAgentkitAndWalletProvider(): Promise<{ + agentkit: AgentKit; + walletProvider: WalletProvider; +}> { + try { + // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management + let privateKey = process.env.PRIVATE_KEY as `0x${string}`; + if (!privateKey) { + if (fs.existsSync(WALLET_DATA_FILE)) { + privateKey = JSON.parse(fs.readFileSync(WALLET_DATA_FILE, "utf8")).privateKey; + console.info("Found private key in wallet_data.txt"); + } else { + privateKey = generatePrivateKey(); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify({ privateKey })); + console.log("Created new private key and saved to wallet_data.txt"); + console.log( + "We recommend you save this private key to your .env file and delete wallet_data.txt afterwards.", + ); + } + } + const account = privateKeyToAccount(privateKey); + + const rpcUrl = process.env.RPC_URL as string; + const chainId = process.env.CHAIN_ID as string; + const client = createWalletClient({ + account, + // Customize the chain metadata to match your custom chain + chain: { + id: parseInt(chainId), + rpcUrls: { + default: { + http: [rpcUrl], + }, + }, + name: "Custom Chain", + nativeCurrency: { + name: "Ether", + symbol: "ETH", + decimals: 18, + }, + }, + transport: http(), + }); + const walletProvider = new ViemWalletProvider(client); + + // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions + const actionProviders: ActionProvider[] = [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + ]; + const canUseCdpApi = process.env.CDP_API_KEY_NAME && process.env.CDP_API_KEY_PRIVATE_KEY; + if (canUseCdpApi) { + actionProviders.push( + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + ); + } + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders, + }); + + return { agentkit, walletProvider }; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts new file mode 100644 index 000000000..b0fca2bce --- /dev/null +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts @@ -0,0 +1,109 @@ +import { + AgentKit, + cdpApiActionProvider, + cdpWalletActionProvider, + CdpWalletProvider, + erc20ActionProvider, + pythActionProvider, + walletActionProvider, + WalletProvider, + wethActionProvider, +} from "@coinbase/agentkit"; +import * as fs from "fs"; + +/** + * AgentKit Integration Route + * + * This file is your gateway to integrating AgentKit with your product. + * It defines the core capabilities of your agent through WalletProvider + * and ActionProvider configuration. + * + * Key Components: + * 1. WalletProvider Setup: + * - Configures the blockchain wallet integration + * - Learn more: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#evm-wallet-providers + * + * 2. ActionProviders Setup: + * - Defines the specific actions your agent can perform + * - Choose from built-in providers or create custom ones: + * - Built-in: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#action-providers + * - Custom: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#creating-an-action-provider + * + * # Next Steps: + * - Explore the AgentKit README: https://github.com/coinbase/agentkit + * - Experiment with different LLM configurations + * - Fine-tune agent parameters for your use case + * + * ## Want to contribute? + * Join us in shaping AgentKit! Check out the contribution guide: + * - https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md + * - https://discord.gg/CDP + */ + +// Configure a file to persist the agent's CDP MPC Wallet Data +const WALLET_DATA_FILE = "wallet_data.txt"; + +/** + * Prepares the AgentKit and WalletProvider. + * + * @function prepareAgentkitAndWalletProvider + * @returns {Promise<{ agentkit: AgentKit, walletProvider: WalletProvider }>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function prepareAgentkitAndWalletProvider(): Promise<{ + agentkit: AgentKit; + walletProvider: WalletProvider; +}> { + try { + let walletDataStr: string | null = null; + + // Read existing wallet data if available + if (fs.existsSync(WALLET_DATA_FILE)) { + try { + walletDataStr = fs.readFileSync(WALLET_DATA_FILE, "utf8"); + } catch (error) { + console.error("Error reading wallet data:", error); + // Continue without wallet data + } + } + + // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management + const walletProvider = await CdpWalletProvider.configureWithWallet({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + networkId: process.env.NETWORK_ID || "base-sepolia", + cdpWalletData: walletDataStr || undefined, + }); + + // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders: [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + cdpWalletActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + ], + }); + + // Save wallet data + const exportedWallet = await walletProvider.exportWallet(); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify(exportedWallet)); + + return { agentkit, walletProvider }; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/privy/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/privy/prepareAgentkit.ts new file mode 100644 index 000000000..aac839956 --- /dev/null +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/privy/prepareAgentkit.ts @@ -0,0 +1,118 @@ +import { + ActionProvider, + AgentKit, + cdpApiActionProvider, + erc20ActionProvider, + PrivyWalletConfig, + PrivyWalletProvider, + pythActionProvider, + walletActionProvider, + WalletProvider, + wethActionProvider, +} from "@coinbase/agentkit"; +import fs from "fs"; + +/** + * AgentKit Integration Route + * + * This file is your gateway to integrating AgentKit with your product. + * It defines the core capabilities of your agent through WalletProvider + * and ActionProvider configuration. + * + * Key Components: + * 1. WalletProvider Setup: + * - Configures the blockchain wallet integration + * - Learn more: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#evm-wallet-providers + * + * 2. ActionProviders Setup: + * - Defines the specific actions your agent can perform + * - Choose from built-in providers or create custom ones: + * - Built-in: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#action-providers + * - Custom: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#creating-an-action-provider + * + * # Next Steps: + * - Explore the AgentKit README: https://github.com/coinbase/agentkit + * - Experiment with different LLM configurations + * - Fine-tune agent parameters for your use case + * + * ## Want to contribute? + * Join us in shaping AgentKit! Check out the contribution guide: + * - https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md + * - https://discord.gg/CDP + */ + +// Configure a file to persist the agent's Privy Wallet Data +const WALLET_DATA_FILE = "wallet_data.txt"; + +/** + * Prepares the AgentKit and WalletProvider. + * + * @function prepareAgentkitAndWalletProvider + * @returns {Promise<{ agentkit: AgentKit, walletProvider: WalletProvider }>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function prepareAgentkitAndWalletProvider(): Promise<{ + agentkit: AgentKit; + walletProvider: WalletProvider; +}> { + try { + // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management + const config: PrivyWalletConfig = { + appId: process.env.PRIVY_APP_ID as string, + appSecret: process.env.PRIVY_APP_SECRET as string, + walletId: process.env.PRIVY_WALLET_ID as string, + chainId: process.env.CHAIN_ID, + authorizationPrivateKey: process.env.PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY, + authorizationKeyId: process.env.PRIVY_WALLET_AUTHORIZATION_KEY_ID, + }; + // Try to load saved wallet data + if (fs.existsSync(WALLET_DATA_FILE)) { + const savedWallet = JSON.parse(fs.readFileSync(WALLET_DATA_FILE, "utf8")); + config.walletId = savedWallet.walletId; + config.authorizationPrivateKey = savedWallet.authorizationPrivateKey; + + if (savedWallet.chainId) { + console.log("Found chainId in wallet_data.txt:", savedWallet.chainId); + config.chainId = savedWallet.chainId; + } + } + if (!config.chainId) { + console.log("Warning: CHAIN_ID not set, defaulting to 84532 (base-sepolia)"); + config.chainId = "84532"; + } + const walletProvider = await PrivyWalletProvider.configureWithWallet(config); + + // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions + const actionProviders: ActionProvider[] = [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + ]; + const canUseCdpApi = process.env.CDP_API_KEY_NAME && process.env.CDP_API_KEY_PRIVATE_KEY; + if (canUseCdpApi) { + actionProviders.push( + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + ); + } + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders, + }); + + // Save wallet data + const exportedWallet = walletProvider.exportWallet(); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify(exportedWallet)); + + return { agentkit, walletProvider }; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts new file mode 100644 index 000000000..e872ac6f8 --- /dev/null +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts @@ -0,0 +1,135 @@ +import { + AgentKit, + cdpApiActionProvider, + cdpWalletActionProvider, + erc20ActionProvider, + pythActionProvider, + SmartWalletProvider, + walletActionProvider, + WalletProvider, + wethActionProvider, +} from "@coinbase/agentkit"; +import * as fs from "fs"; +import { Address, Hex } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; + +/** + * AgentKit Integration Route + * + * This file is your gateway to integrating AgentKit with your product. + * It defines the core capabilities of your agent through WalletProvider + * and ActionProvider configuration. + * + * Key Components: + * 1. WalletProvider Setup: + * - Configures the blockchain wallet integration + * - Learn more: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#evm-wallet-providers + * + * 2. ActionProviders Setup: + * - Defines the specific actions your agent can perform + * - Choose from built-in providers or create custom ones: + * - Built-in: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#action-providers + * - Custom: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#creating-an-action-provider + * + * # Next Steps: + * - Explore the AgentKit README: https://github.com/coinbase/agentkit + * - Experiment with different LLM configurations + * - Fine-tune agent parameters for your use case + * + * ## Want to contribute? + * Join us in shaping AgentKit! Check out the contribution guide: + * - https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md + * - https://discord.gg/CDP + */ + +// Configure a file to persist the agent's Smart Wallet + Private Key data +const WALLET_DATA_FILE = "wallet_data.txt"; + +type WalletData = { + privateKey: Hex; + smartWalletAddress: Address; +}; + +/** + * Prepares the AgentKit and WalletProvider. + * + * @function prepareAgentkitAndWalletProvider + * @returns {Promise<{ agentkit: AgentKit, walletProvider: WalletProvider }>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function prepareAgentkitAndWalletProvider(): Promise<{ + agentkit: AgentKit; + walletProvider: WalletProvider; +}> { + try { + let walletData: WalletData | null = null; + let privateKey: Hex | null = null; + + // Read existing wallet data if available + if (fs.existsSync(WALLET_DATA_FILE)) { + try { + walletData = JSON.parse(fs.readFileSync(WALLET_DATA_FILE, "utf8")) as WalletData; + privateKey = walletData.privateKey; + } catch (error) { + console.error("Error reading wallet data:", error); + // Continue without wallet data + } + } + + if (!privateKey) { + if (walletData?.smartWalletAddress) { + throw new Error( + `Smart wallet found but no private key provided. Either provide the private key, or delete ${WALLET_DATA_FILE} and try again.`, + ); + } + privateKey = (process.env.PRIVATE_KEY || generatePrivateKey()) as Hex; + } + + const signer = privateKeyToAccount(privateKey); + + // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management + const walletProvider = await SmartWalletProvider.configureWithWallet({ + networkId: process.env.NETWORK_ID || "base-sepolia", + signer, + smartWalletAddress: walletData?.smartWalletAddress, + paymasterUrl: undefined, // Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome + }); + + // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders: [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + cdpWalletActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + ], + }); + + // Save wallet data + const smartWalletAddress = await walletProvider.getAddress(); + fs.writeFileSync( + WALLET_DATA_FILE, + JSON.stringify({ + privateKey, + smartWalletAddress, + } as WalletData), + ); + + return { agentkit, walletProvider }; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/viem/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/viem/prepareAgentkit.ts new file mode 100644 index 000000000..83a3af09c --- /dev/null +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/viem/prepareAgentkit.ts @@ -0,0 +1,116 @@ +import { + ActionProvider, + AgentKit, + cdpApiActionProvider, + erc20ActionProvider, + NETWORK_ID_TO_VIEM_CHAIN, + pythActionProvider, + ViemWalletProvider, + walletActionProvider, + WalletProvider, + wethActionProvider, +} from "@coinbase/agentkit"; +import fs from "fs"; +import { createWalletClient, Hex, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; + +/** + * AgentKit Integration Route + * + * This file is your gateway to integrating AgentKit with your product. + * It defines the core capabilities of your agent through WalletProvider + * and ActionProvider configuration. + * + * Key Components: + * 1. WalletProvider Setup: + * - Configures the blockchain wallet integration + * - Learn more: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#evm-wallet-providers + * + * 2. ActionProviders Setup: + * - Defines the specific actions your agent can perform + * - Choose from built-in providers or create custom ones: + * - Built-in: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#action-providers + * - Custom: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#creating-an-action-provider + * + * # Next Steps: + * - Explore the AgentKit README: https://github.com/coinbase/agentkit + * - Experiment with different LLM configurations + * - Fine-tune agent parameters for your use case + * + * ## Want to contribute? + * Join us in shaping AgentKit! Check out the contribution guide: + * - https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md + * - https://discord.gg/CDP + */ + +// Configure a file to persist a user's private key if none provided +const WALLET_DATA_FILE = "wallet_data.txt"; + +/** + * Prepares the AgentKit and WalletProvider. + * + * @function prepareAgentkitAndWalletProvider + * @returns {Promise<{ agentkit: AgentKit, walletProvider: WalletProvider }>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function prepareAgentkitAndWalletProvider(): Promise<{ + agentkit: AgentKit; + walletProvider: WalletProvider; +}> { + try { + // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management + let privateKey = process.env.PRIVATE_KEY as Hex; + if (!privateKey) { + if (fs.existsSync(WALLET_DATA_FILE)) { + privateKey = JSON.parse(fs.readFileSync(WALLET_DATA_FILE, "utf8")).privateKey; + console.info("Found private key in wallet_data.txt"); + } else { + privateKey = generatePrivateKey(); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify({ privateKey })); + console.log("Created new private key and saved to wallet_data.txt"); + console.log( + "We recommend you save this private key to your .env file and delete wallet_data.txt afterwards.", + ); + } + } + + const account = privateKeyToAccount(privateKey); + const networkId = process.env.NETWORK_ID as string; + + const client = createWalletClient({ + account, + chain: NETWORK_ID_TO_VIEM_CHAIN[networkId], + transport: http(), + }); + const walletProvider = new ViemWalletProvider(client); + + // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions + const actionProviders: ActionProvider[] = [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + ]; + const canUseCdpApi = process.env.CDP_API_KEY_NAME && process.env.CDP_API_KEY_PRIVATE_KEY; + if (canUseCdpApi) { + actionProviders.push( + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + ); + } + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders, + }); + + return { agentkit, walletProvider }; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/privy/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/privy/prepareAgentkit.ts new file mode 100644 index 000000000..eb304fbac --- /dev/null +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/privy/prepareAgentkit.ts @@ -0,0 +1,109 @@ +import { + ActionProvider, + AgentKit, + cdpApiActionProvider, + jupiterActionProvider, + PrivyWalletConfig, + PrivyWalletProvider, + splActionProvider, + walletActionProvider, + WalletProvider, +} from "@coinbase/agentkit"; +import fs from "fs"; + +/** + * AgentKit Integration Route + * + * This file is your gateway to integrating AgentKit with your product. + * It defines the core capabilities of your agent through WalletProvider + * and ActionProvider configuration. + * + * Key Components: + * 1. WalletProvider Setup: + * - Configures the blockchain wallet integration + * - Learn more: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#evm-wallet-providers + * + * 2. ActionProviders Setup: + * - Defines the specific actions your agent can perform + * - Choose from built-in providers or create custom ones: + * - Built-in: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#action-providers + * - Custom: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#creating-an-action-provider + * + * # Next Steps: + * - Explore the AgentKit README: https://github.com/coinbase/agentkit + * - Experiment with different LLM configurations + * - Fine-tune agent parameters for your use case + * + * ## Want to contribute? + * Join us in shaping AgentKit! Check out the contribution guide: + * - https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md + * - https://discord.gg/CDP + */ + +// Configure a file to persist the agent's Prviy Wallet Data +const WALLET_DATA_FILE = "wallet_data.txt"; + +/** + * Prepares the AgentKit and WalletProvider. + * + * @function prepareAgentkitAndWalletProvider + * @returns {Promise<{ agentkit: AgentKit, walletProvider: WalletProvider }>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function prepareAgentkitAndWalletProvider(): Promise<{ + agentkit: AgentKit; + walletProvider: WalletProvider; +}> { + try { + // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management + const config: PrivyWalletConfig = { + appId: process.env.PRIVY_APP_ID as string, + appSecret: process.env.PRIVY_APP_SECRET as string, + walletId: process.env.PRIVY_WALLET_ID as string, + authorizationPrivateKey: process.env.PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY, + authorizationKeyId: process.env.PRIVY_WALLET_AUTHORIZATION_KEY_ID, + chainType: "solana", + networkId: process.env.NETWORK_ID, + }; + // Try to load saved wallet data + if (fs.existsSync(WALLET_DATA_FILE)) { + const savedWallet = JSON.parse(fs.readFileSync(WALLET_DATA_FILE, "utf8")); + config.walletId = savedWallet.walletId; + config.authorizationPrivateKey = savedWallet.authorizationPrivateKey; + config.networkId = savedWallet.networkId; + } + const walletProvider = await PrivyWalletProvider.configureWithWallet(config); + + // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions + const actionProviders: ActionProvider[] = [ + walletActionProvider(), + splActionProvider(), + jupiterActionProvider(), + ]; + const canUseCdpApi = process.env.CDP_API_KEY_NAME && process.env.CDP_API_KEY_PRIVATE_KEY; + if (canUseCdpApi) { + actionProviders.push( + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + ); + } + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders, + }); + + // Save wallet data + const exportedWallet = walletProvider.exportWallet(); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify(exportedWallet)); + + return { agentkit, walletProvider }; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/solanaKeypair/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/solanaKeypair/prepareAgentkit.ts new file mode 100644 index 000000000..05f12f62e --- /dev/null +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/svm/solanaKeypair/prepareAgentkit.ts @@ -0,0 +1,116 @@ +import { + ActionProvider, + AgentKit, + cdpApiActionProvider, + jupiterActionProvider, + SOLANA_NETWORK_ID, + SolanaKeypairWalletProvider, + splActionProvider, + walletActionProvider, + WalletProvider, +} from "@coinbase/agentkit"; +import { Keypair } from "@solana/web3.js"; +import bs58 from "bs58"; +import fs from "fs"; + +/** + * AgentKit Integration Route + * + * This file is your gateway to integrating AgentKit with your product. + * It defines the core capabilities of your agent through WalletProvider + * and ActionProvider configuration. + * + * Key Components: + * 1. WalletProvider Setup: + * - Configures the blockchain wallet integration + * - Learn more: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#evm-wallet-providers + * + * 2. ActionProviders Setup: + * - Defines the specific actions your agent can perform + * - Choose from built-in providers or create custom ones: + * - Built-in: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#action-providers + * - Custom: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit#creating-an-action-provider + * + * # Next Steps: + * - Explore the AgentKit README: https://github.com/coinbase/agentkit + * - Experiment with different LLM configurations + * - Fine-tune agent parameters for your use case + * + * ## Want to contribute? + * Join us in shaping AgentKit! Check out the contribution guide: + * - https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md + * - https://discord.gg/CDP + */ + +// Configure a file to persist a user's private key if none provided +const WALLET_DATA_FILE = "wallet_data.txt"; + +/** + * Prepares the AgentKit and WalletProvider. + * + * @function prepareAgentkitAndWalletProvider + * @returns {Promise<{ agentkit: AgentKit, walletProvider: WalletProvider }>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function prepareAgentkitAndWalletProvider(): Promise<{ + agentkit: AgentKit; + walletProvider: WalletProvider; +}> { + try { + // Setup Private Key + let privateKey = process.env.SOLANA_PRIVATE_KEY as string; + if (!privateKey) { + if (fs.existsSync(WALLET_DATA_FILE)) { + privateKey = JSON.parse(fs.readFileSync(WALLET_DATA_FILE, "utf8")).privateKey; + console.info("Found private key in wallet_data.txt"); + } else { + const keypair = Keypair.generate(); + privateKey = bs58.encode(keypair.secretKey); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify({ privateKey })); + console.log("Created new private key and saved to wallet_data.txt"); + console.log( + "We recommend you save this private key to your .env file and delete wallet_data.txt afterwards.", + ); + } + } + + // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management + // Configure Solana Keypair Wallet Provider + const rpcUrl = process.env.SOLANA_RPC_URL; + let walletProvider: SolanaKeypairWalletProvider; + if (rpcUrl) { + walletProvider = await SolanaKeypairWalletProvider.fromRpcUrl(rpcUrl, privateKey); + } else { + const network = (process.env.NETWORK_ID ?? "solana-devnet") as SOLANA_NETWORK_ID; + walletProvider = await SolanaKeypairWalletProvider.fromNetwork(network, privateKey); + } + + // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions + const actionProviders: ActionProvider[] = [ + walletActionProvider(), + splActionProvider(), + jupiterActionProvider(), + ]; + const canUseCdpApi = process.env.CDP_API_KEY_NAME && process.env.CDP_API_KEY_PRIVATE_KEY; + if (canUseCdpApi) { + actionProviders.push( + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY, + }), + ); + } + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders, + }); + + return { agentkit, walletProvider }; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} From 3ed44f988be4be4ae753ebecb668e1a47192d453 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 17 Mar 2025 14:23:14 -0700 Subject: [PATCH 05/29] fix: improve createActionProvider & template --- .../src/cli/createActionProvider.ts | 37 +++++++------- .../actionProvider/actionProvider.njk | 50 +++++++------------ .../templates/actionProvider/schema.njk | 4 +- 3 files changed, 41 insertions(+), 50 deletions(-) diff --git a/typescript/create-onchain-agent/src/cli/createActionProvider.ts b/typescript/create-onchain-agent/src/cli/createActionProvider.ts index 2c2ec74cd..624f04d9d 100644 --- a/typescript/create-onchain-agent/src/cli/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/cli/createActionProvider.ts @@ -23,6 +23,15 @@ function toSnakeCase(str: string): string { .toLowerCase(); } +function toClassName(str: string): string { + // Capitalize first letter of each word, remove any "Provider" suffix + return str + .split(/(?=[A-Z])|[\s_-]/) + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join('') + .replace(/Provider$/, ''); +} + export async function createActionProvider() { // Configure nunjucks with the correct path nunjucks.configure(path.join(__dirname, '../../../templates'), { @@ -39,12 +48,6 @@ export async function createActionProvider() { message: 'What is the name of your action provider?', validate: (value: string) => value.length > 0 ? true : 'Name is required' }, - { - type: 'text', - name: 'description', - message: 'Provide a brief description:', - initial: (prev: string) => `${prev} operations` - }, { type: 'select', name: 'networkFamily', @@ -58,9 +61,11 @@ export async function createActionProvider() { ]); // Process the name variations - const className = answers.name.charAt(0).toUpperCase() + answers.name.slice(1); - const fileName = `${toCamelCase(answers.name)}ActionProvider`; + const baseName = toClassName(answers.name); + const className = `${baseName}ActionProvider`; + const fileName = `${toCamelCase(baseName)}ActionProvider`; const snakeName = toSnakeCase(answers.name); + const exportName = `${toCamelCase(baseName)}ActionProvider`; // Determine wallet provider and networks based on network family let walletProvider = 'EvmWalletProvider'; @@ -83,30 +88,28 @@ export async function createActionProvider() { // Generate code using nunjucks const generatedCode = nunjucks.render('actionProvider/actionProvider.njk', { - name: snakeName, - className: fileName, - description: answers.description, + name: exportName, + className, walletProvider, networks: '[]', networkSupport, actionName: `${snakeName}_action`, - schemaName: `${className}ActionSchema` + schemaName: `${baseName}ActionSchema` }); // Create directory if it doesn't exist - const dirPath = `./${fileName}`; + const dirPath = `./${fileName}`; // Use camelCase fileName for directory await fs.mkdir(dirPath, { recursive: true }); // Write files - await fs.writeFile(`${dirPath}/${fileName}.ts`, generatedCode); + await fs.writeFile(`${dirPath}/${fileName}.ts`, generatedCode); // Use camelCase fileName for the file // Generate schema file using nunjucks with updated schema name const schemaCode = nunjucks.render('actionProvider/schema.njk', { - className: `${className}Action`, - description: answers.description + className: `${baseName}Action`, }); await fs.writeFile(`${dirPath}/schemas.ts`, schemaCode); - console.log(`Successfully created ${fileName}!`); + console.log(`Successfully created ${className}!`); } \ No newline at end of file diff --git a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk index 9777ab779..05b54e8a0 100644 --- a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk +++ b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk @@ -1,44 +1,32 @@ -import { ActionProvider, {{ walletProvider }}, CreateAction, Network } from "@coinbase/agentkit"; +import { CustomActionProvider, {{ walletProvider }}, Network } from "@coinbase/agentkit"; import { {{ schemaName }} } from "./schemas"; import { z } from "zod"; /** - * Action provider for {{ description }}. + * Action provider for {{ className }} operations. */ -export class {{ className }}Provider extends ActionProvider<{{ walletProvider }}> { +export class {{ className }} extends CustomActionProvider<{{ walletProvider }}> { /** - * Constructs a new {{ className }}Provider. + * Constructs a new {{ className }}. */ constructor() { - super("{{ name }}", {{ networks | safe }}); + super([{ + name: "{{ actionName }}", + description: `This tool will perform a {{ className }} operation.`, + schema: {{ schemaName }}, + invoke: async (wallet: {{ walletProvider }}, args: z.infer) => { + try { + // Do work here + return `Successfully performed {{ actionName }} and returned the response`; + } catch (error) { + return `Error performing {{ actionName }}: Error: ${error}`; + } + } + }]); } /** - * Performs {{ description }}. - * - * @param wallet - The wallet to use for the work. - * @param args - The arguments for the work. - * @returns A string indicating the success or failure of the work. - */ - @CreateAction({ - name: "{{ actionName }}", - description: `This tool will perform {{ description }}.`, - schema: {{ schemaName }}, - }) - async {{ name }}Action( - wallet: {{ walletProvider }}, - args: z.infer, - ): Promise { - try { - // Do work here - return `Successfully performed {{ description }} and returned the response`; - } catch (error) { - return `Error performing {{ description }}: Error: ${error}`; - } - } - - /** - * Checks if the {{ name }} action provider supports the given network. + * Checks if the {{ name }} supports the given network. * * @param network - The network to check. * @returns True if the {{ name }} action provider supports the network, false otherwise. @@ -46,4 +34,4 @@ export class {{ className }}Provider extends ActionProvider<{{ walletProvider }} supportsNetwork = (network: Network) => {{ networkSupport | safe }}; } -export const {{ name }}ActionProvider = () => new {{ className }}Provider(); \ No newline at end of file +export const {{ name }} = () => new {{ className }}(); \ No newline at end of file diff --git a/typescript/create-onchain-agent/templates/actionProvider/schema.njk b/typescript/create-onchain-agent/templates/actionProvider/schema.njk index eebe94087..3516780ae 100644 --- a/typescript/create-onchain-agent/templates/actionProvider/schema.njk +++ b/typescript/create-onchain-agent/templates/actionProvider/schema.njk @@ -1,11 +1,11 @@ import { z } from "zod"; /** - * Input schema for {{ description }}. + * Input schema for {{ className }}'s {{ actionName }} action. */ export const {{ className }}Schema = z .object({ payload: z.string().describe("The payload to send to the action provider"), }) .strip() - .describe("Instructions for {{ description }}"); \ No newline at end of file + .describe("Instructions for {{ actionName }}"); \ No newline at end of file From 690724cd9898ed7833792698a22809a03f978b3d Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 17 Mar 2025 14:37:31 -0700 Subject: [PATCH 06/29] feat: generate files in the current folder --- .../src/cli/createActionProvider.ts | 10 +++------- .../src/cli/createWalletProvider.ts | 6 ++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/typescript/create-onchain-agent/src/cli/createActionProvider.ts b/typescript/create-onchain-agent/src/cli/createActionProvider.ts index 624f04d9d..9206350ab 100644 --- a/typescript/create-onchain-agent/src/cli/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/cli/createActionProvider.ts @@ -97,19 +97,15 @@ export async function createActionProvider() { schemaName: `${baseName}ActionSchema` }); - // Create directory if it doesn't exist - const dirPath = `./${fileName}`; // Use camelCase fileName for directory - await fs.mkdir(dirPath, { recursive: true }); - - // Write files - await fs.writeFile(`${dirPath}/${fileName}.ts`, generatedCode); // Use camelCase fileName for the file + // Write files directly to current directory + await fs.writeFile(`./${fileName}.ts`, generatedCode); // Generate schema file using nunjucks with updated schema name const schemaCode = nunjucks.render('actionProvider/schema.njk', { className: `${baseName}Action`, }); - await fs.writeFile(`${dirPath}/schemas.ts`, schemaCode); + await fs.writeFile(`./schemas.ts`, schemaCode); console.log(`Successfully created ${className}!`); } \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/createWalletProvider.ts b/typescript/create-onchain-agent/src/cli/createWalletProvider.ts index d93fbb64f..49c6e856c 100644 --- a/typescript/create-onchain-agent/src/cli/createWalletProvider.ts +++ b/typescript/create-onchain-agent/src/cli/createWalletProvider.ts @@ -64,10 +64,8 @@ export async function createWalletProvider() { baseClass }); - const dirPath = `./${fileName}`; - await fs.mkdir(dirPath, { recursive: true }); - - await fs.writeFile(`${dirPath}/${fileName}.ts`, generatedCode); + // Write file directly to current directory + await fs.writeFile(`./${fileName}.ts`, generatedCode); console.log(`Successfully created ${fileName}!`); } \ No newline at end of file From f5842a2acfaae31c122ac93a550f8b31212fd25c Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Tue, 18 Mar 2025 13:36:11 -0700 Subject: [PATCH 07/29] feat: implemented createAgent path --- .../src/cli/createAgent.ts | 61 +++++++++++++- .../src/cli/createAgentkit.ts | 16 ++-- .../create-onchain-agent/src/constants.ts | 2 +- .../framework/langchain/createAgent.ts | 77 ++++++++++++++++++ .../framework/vercelAISDK/createAgent.ts | 81 +++++++++++++++++++ 5 files changed, 225 insertions(+), 12 deletions(-) create mode 100644 typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts create mode 100644 typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts diff --git a/typescript/create-onchain-agent/src/cli/createAgent.ts b/typescript/create-onchain-agent/src/cli/createAgent.ts index beff9aafc..b9f86b20b 100644 --- a/typescript/create-onchain-agent/src/cli/createAgent.ts +++ b/typescript/create-onchain-agent/src/cli/createAgent.ts @@ -1,6 +1,63 @@ +import fs from "fs/promises"; +import path from "path"; +import pc from "picocolors"; +import prompts from "prompts"; +import { copyTemplate } from "../fileSystem.js"; +type AgentFramework = "langchain" | "vercelAISDK"; +/** + * Creates an agent by selecting a framework and copying the appropriate template. + * + * - Prompts user to select between LangChain and Vercel AI SDK frameworks + * - Copies the selected framework's agent implementation + * - Cleans up unused framework files + */ export async function createAgent() { - // prompts for framework, assumes prepare-agentkit.ts - // generates create-agent.ts + let result: prompts.Answers<"framework">; + + try { + result = await prompts( + [ + { + type: "select", + name: "framework", + message: pc.reset("Choose a framework:"), + choices: [ + { title: "LangChain", value: "langchain" }, + { title: "Vercel AI SDK", value: "vercel-ai-sdk" } + ] as { title: string; value: AgentFramework }[], + } + ], + { + onCancel: () => { + console.log("\nAgent creation cancelled."); + process.exit(0); + }, + } + ); + } catch (cancelled: unknown) { + console.error("An error occurred during agent creation"); + process.exit(1); + } + + const { framework } = result; + + try { + const root = await copyTemplate("createAgent", "createAgent"); + + // Copy the selected framework's implementation to the destination + const selectedRoutePath = path.join(root, "framework", framework, "createAgent.ts"); + const newRoutePath = path.join(process.cwd(), "createAgent.ts"); + + await fs.copyFile(selectedRoutePath, newRoutePath); + + // Clean up the temporary directory + await fs.rm(root, { recursive: true, force: true }); + + console.log(pc.green("\nSuccessfully created createAgent.ts")); + } catch (error) { + console.error("Error setting up createAgent:", error); + process.exit(1); + } } \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/createAgentkit.ts b/typescript/create-onchain-agent/src/cli/createAgentkit.ts index 7acb2dcf3..dd2381887 100644 --- a/typescript/create-onchain-agent/src/cli/createAgentkit.ts +++ b/typescript/create-onchain-agent/src/cli/createAgentkit.ts @@ -1,11 +1,11 @@ -import prompts from "prompts"; -import pc from "picocolors"; -import { AgentkitRouteConfigurations, EVM_NETWORKS, NetworkToWalletProviders, PrepareAgentkitRouteConfigurations, SVM_NETWORKS } from "../constants.js"; -import { Network, WalletProviderChoice } from "../types.js"; -import { getNetworkType, getWalletProviders } from "../utils.js"; import fs from "fs/promises"; import path from "path"; +import pc from "picocolors"; +import prompts from "prompts"; +import { EVM_NETWORKS, PrepareAgentkitRouteConfigurations, SVM_NETWORKS } from "../constants.js"; import { copyTemplate } from "../fileSystem.js"; +import { Network, WalletProviderChoice } from "../types.js"; +import { getNetworkType, getWalletProviders } from "../utils.js"; /** * Prompts user for network and wallet provider selection, then sets up the prepare-agentkit file @@ -148,17 +148,15 @@ export async function createAgentkit() { } try { - // Copy template and get root directory - no package name needed const root = await copyTemplate("prepareAgentkit", "prepareAgentkit"); // Copy the selected route to the destination const selectedRoutePath = path.join(root, "agentkit", routeConfig.route); - const newRoutePath = path.join(root, "prepareAgentkit.ts"); + const newRoutePath = path.join(process.cwd(), "prepareAgentkit.ts"); await fs.copyFile(selectedRoutePath, newRoutePath); - // Clean up the agentkit directory - const agentkitDir = path.join(root, "agentkit"); + const agentkitDir = path.join(root); await fs.rm(agentkitDir, { recursive: true, force: true }); console.log(pc.green("\nSuccessfully created prepare-agentkit.ts")); diff --git a/typescript/create-onchain-agent/src/constants.ts b/typescript/create-onchain-agent/src/constants.ts index eecb7d9c5..8c5bac978 100644 --- a/typescript/create-onchain-agent/src/constants.ts +++ b/typescript/create-onchain-agent/src/constants.ts @@ -159,7 +159,7 @@ export const Frameworks = ["Langchain", "Vercel AI SDK", "Model Context Protocol export type Framework = (typeof Frameworks)[number]; -export const Templates = ["next", "mcp", "prepareAgentkit"] as const; +export const Templates = ["next", "mcp", "prepareAgentkit", "createAgent"] as const; export type Template = (typeof Templates)[number]; diff --git a/typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts b/typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts new file mode 100644 index 000000000..58199bd1d --- /dev/null +++ b/typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts @@ -0,0 +1,77 @@ +import { getLangChainTools } from "@coinbase/agentkit-langchain"; +import { MemorySaver } from "@langchain/langgraph"; +import { createReactAgent } from "@langchain/langgraph/prebuilt"; +import { ChatOpenAI } from "@langchain/openai"; +import { prepareAgentkitAndWalletProvider } from "./prepareAgentkit"; + +/** + * Agent Configuration Guide + * + * This file handles the core configuration of your AI agent's behavior and capabilities. + * + * Key Steps to Customize Your Agent: + * + * 1. Select your LLM: + * - Modify the `ChatOpenAI` instantiation to choose your preferred LLM + * - Configure model parameters like temperature and max tokens + * + * 2. Instantiate your Agent: + * - Pass the LLM, tools, and memory into `createReactAgent()` + * - Configure agent-specific parameters + */ + +// The agent +let agent: ReturnType; + +/** + * Initializes and returns an instance of the AI agent. + * If an agent instance already exists, it returns the existing one. + * + * @function getOrInitializeAgent + * @returns {Promise>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function createAgent(): Promise> { + // If agent has already been initialized, return it + if (agent) { + return agent; + } + + try { + const { agentkit, walletProvider } = await prepareAgentkitAndWalletProvider(); + + // Initialize LLM: https://platform.openai.com/docs/models#gpt-4o + const llm = new ChatOpenAI({ model: "gpt-4o-mini" }); + + const tools = await getLangChainTools(agentkit); + const memory = new MemorySaver(); + + // Initialize Agent + const canUseFaucet = walletProvider.getNetwork().networkId == "base-sepolia"; + const faucetMessage = `If you ever need funds, you can request them from the faucet.`; + const cantUseFaucetMessage = `If you need funds, you can provide your wallet details and request funds from the user.`; + agent = createReactAgent({ + llm, + tools, + checkpointSaver: memory, + messageModifier: ` + You are a helpful agent that can interact onchain using the Coinbase Developer Platform AgentKit. You are + empowered to interact onchain using your tools. ${canUseFaucet ? faucetMessage : cantUseFaucetMessage}. + Before executing your first action, get the wallet details to see what network + you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone + asks you to do something you can't do with your currently available tools, you must say so, and + encourage them to implement it themselves using the CDP SDK + Agentkit, recommend they go to + docs.cdp.coinbase.com for more information. Be concise and helpful with your responses. Refrain from + restating your tools' descriptions unless it is explicitly requested. + `, + }); + + return agent; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts b/typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts new file mode 100644 index 000000000..ee0726b31 --- /dev/null +++ b/typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts @@ -0,0 +1,81 @@ +import { openai } from "@ai-sdk/openai"; +import { getVercelAITools } from "@coinbase/agentkit-vercel-ai-sdk"; +import { prepareAgentkitAndWalletProvider } from "./prepareAgentkit"; + +/** + * Agent Configuration Guide + * + * This file handles the core configuration of your AI agent's behavior and capabilities. + * + * Key Steps to Customize Your Agent: + * + * 1. Select your LLM: + * - Modify the `openai` instantiation to choose your preferred LLM + * - Configure model parameters like temperature and max tokens + * + * 2. Instantiate your Agent: + * - Pass the LLM, tools, and memory into `createReactAgent()` + * - Configure agent-specific parameters + */ + +// The agent +type Agent = { + tools: ReturnType; + system: string; + model: ReturnType; + maxSteps?: number; +}; +let agent: Agent; + +/** + * Initializes and returns an instance of the AI agent. + * If an agent instance already exists, it returns the existing one. + * + * @function getOrInitializeAgent + * @returns {Promise>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function createAgent(): Promise { + // If agent has already been initialized, return it + if (agent) { + return agent; + } + + try { + // Initialize LLM: https://platform.openai.com/docs/models#gpt-4o + const model = openai("gpt-4o-mini"); + + const { agentkit, walletProvider } = await prepareAgentkitAndWalletProvider(); + + // Initialize Agent + const canUseFaucet = walletProvider.getNetwork().networkId == "base-sepolia"; + const faucetMessage = `If you ever need funds, you can request them from the faucet.`; + const cantUseFaucetMessage = `If you need funds, you can provide your wallet details and request funds from the user.`; + const system = ` + You are a helpful agent that can interact onchain using the Coinbase Developer Platform AgentKit. You are + empowered to interact onchain using your tools. ${canUseFaucet ? faucetMessage : cantUseFaucetMessage}. + Before executing your first action, get the wallet details to see what network + you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone + asks you to do something you can't do with your currently available tools, you must say so, and + encourage them to implement it themselves using the CDP SDK + Agentkit, recommend they go to + docs.cdp.coinbase.com for more information. Be concise and helpful with your responses. Refrain from + restating your tools' descriptions unless it is explicitly requested. + `; + const tools = getVercelAITools(agentkit); + + agent = { + tools, + system, + model, + maxSteps: 10, + }; + + return agent; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} From e286388595e644e807934887869fc8c8e1575b0f Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 13:14:52 -0700 Subject: [PATCH 08/29] chore: reorganize file structure --- .../create-onchain-agent/createAgent.ts | 81 +++++++++++++++++++ .../{cli => actions}/createActionProvider.ts | 9 +-- .../src/{cli => actions}/createAgent.ts | 6 +- .../src/{cli => actions}/createAgentkit.ts | 10 +-- .../{cli => actions}/createWalletProvider.ts | 7 +- .../src/{cli => actions}/initProject.ts | 13 ++- typescript/create-onchain-agent/src/cli.ts | 10 +-- .../src/{ => common}/constants.ts | 2 +- .../src/{ => common}/fileSystem.ts | 0 .../src/{ => common}/types.ts | 0 .../src/{ => common}/utils.ts | 0 11 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 typescript/create-onchain-agent/createAgent.ts rename typescript/create-onchain-agent/src/{cli => actions}/createActionProvider.ts (95%) rename typescript/create-onchain-agent/src/{cli => actions}/createAgent.ts (93%) rename typescript/create-onchain-agent/src/{cli => actions}/createAgentkit.ts (95%) rename typescript/create-onchain-agent/src/{cli => actions}/createWalletProvider.ts (95%) rename typescript/create-onchain-agent/src/{cli => actions}/initProject.ts (97%) rename typescript/create-onchain-agent/src/{ => common}/constants.ts (99%) rename typescript/create-onchain-agent/src/{ => common}/fileSystem.ts (100%) rename typescript/create-onchain-agent/src/{ => common}/types.ts (100%) rename typescript/create-onchain-agent/src/{ => common}/utils.ts (100%) diff --git a/typescript/create-onchain-agent/createAgent.ts b/typescript/create-onchain-agent/createAgent.ts new file mode 100644 index 000000000..ee0726b31 --- /dev/null +++ b/typescript/create-onchain-agent/createAgent.ts @@ -0,0 +1,81 @@ +import { openai } from "@ai-sdk/openai"; +import { getVercelAITools } from "@coinbase/agentkit-vercel-ai-sdk"; +import { prepareAgentkitAndWalletProvider } from "./prepareAgentkit"; + +/** + * Agent Configuration Guide + * + * This file handles the core configuration of your AI agent's behavior and capabilities. + * + * Key Steps to Customize Your Agent: + * + * 1. Select your LLM: + * - Modify the `openai` instantiation to choose your preferred LLM + * - Configure model parameters like temperature and max tokens + * + * 2. Instantiate your Agent: + * - Pass the LLM, tools, and memory into `createReactAgent()` + * - Configure agent-specific parameters + */ + +// The agent +type Agent = { + tools: ReturnType; + system: string; + model: ReturnType; + maxSteps?: number; +}; +let agent: Agent; + +/** + * Initializes and returns an instance of the AI agent. + * If an agent instance already exists, it returns the existing one. + * + * @function getOrInitializeAgent + * @returns {Promise>} The initialized AI agent. + * + * @description Handles agent setup + * + * @throws {Error} If the agent initialization fails. + */ +export async function createAgent(): Promise { + // If agent has already been initialized, return it + if (agent) { + return agent; + } + + try { + // Initialize LLM: https://platform.openai.com/docs/models#gpt-4o + const model = openai("gpt-4o-mini"); + + const { agentkit, walletProvider } = await prepareAgentkitAndWalletProvider(); + + // Initialize Agent + const canUseFaucet = walletProvider.getNetwork().networkId == "base-sepolia"; + const faucetMessage = `If you ever need funds, you can request them from the faucet.`; + const cantUseFaucetMessage = `If you need funds, you can provide your wallet details and request funds from the user.`; + const system = ` + You are a helpful agent that can interact onchain using the Coinbase Developer Platform AgentKit. You are + empowered to interact onchain using your tools. ${canUseFaucet ? faucetMessage : cantUseFaucetMessage}. + Before executing your first action, get the wallet details to see what network + you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone + asks you to do something you can't do with your currently available tools, you must say so, and + encourage them to implement it themselves using the CDP SDK + Agentkit, recommend they go to + docs.cdp.coinbase.com for more information. Be concise and helpful with your responses. Refrain from + restating your tools' descriptions unless it is explicitly requested. + `; + const tools = getVercelAITools(agentkit); + + agent = { + tools, + system, + model, + maxSteps: 10, + }; + + return agent; + } catch (error) { + console.error("Error initializing agent:", error); + throw new Error("Failed to initialize agent"); + } +} diff --git a/typescript/create-onchain-agent/src/cli/createActionProvider.ts b/typescript/create-onchain-agent/src/actions/createActionProvider.ts similarity index 95% rename from typescript/create-onchain-agent/src/cli/createActionProvider.ts rename to typescript/create-onchain-agent/src/actions/createActionProvider.ts index 9206350ab..ae318b2e9 100644 --- a/typescript/create-onchain-agent/src/cli/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createActionProvider.ts @@ -1,10 +1,9 @@ import fs from 'fs/promises'; -import path from 'path'; -import { fileURLToPath } from 'url'; import nunjucks from 'nunjucks'; +import path from 'path'; +import pc from "picocolors"; import prompts from 'prompts'; -import { getNetworkType } from '../utils'; -import { Network, EVMNetwork, SVMNetwork } from '../types'; +import { fileURLToPath } from 'url'; // Get current file's directory in ES modules const __filename = fileURLToPath(import.meta.url); @@ -107,5 +106,5 @@ export async function createActionProvider() { await fs.writeFile(`./schemas.ts`, schemaCode); - console.log(`Successfully created ${className}!`); + console.log(pc.green(`Successfully created ${className}!`)); } \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/createAgent.ts b/typescript/create-onchain-agent/src/actions/createAgent.ts similarity index 93% rename from typescript/create-onchain-agent/src/cli/createAgent.ts rename to typescript/create-onchain-agent/src/actions/createAgent.ts index b9f86b20b..293005178 100644 --- a/typescript/create-onchain-agent/src/cli/createAgent.ts +++ b/typescript/create-onchain-agent/src/actions/createAgent.ts @@ -2,7 +2,7 @@ import fs from "fs/promises"; import path from "path"; import pc from "picocolors"; import prompts from "prompts"; -import { copyTemplate } from "../fileSystem.js"; +import { copyTemplate } from "../common/fileSystem.js"; type AgentFramework = "langchain" | "vercelAISDK"; @@ -25,7 +25,7 @@ export async function createAgent() { message: pc.reset("Choose a framework:"), choices: [ { title: "LangChain", value: "langchain" }, - { title: "Vercel AI SDK", value: "vercel-ai-sdk" } + { title: "Vercel AI SDK", value: "vercelAISDK" } ] as { title: string; value: AgentFramework }[], } ], @@ -55,7 +55,7 @@ export async function createAgent() { // Clean up the temporary directory await fs.rm(root, { recursive: true, force: true }); - console.log(pc.green("\nSuccessfully created createAgent.ts")); + console.log(pc.green("Successfully created createAgent.ts")); } catch (error) { console.error("Error setting up createAgent:", error); process.exit(1); diff --git a/typescript/create-onchain-agent/src/cli/createAgentkit.ts b/typescript/create-onchain-agent/src/actions/createAgentkit.ts similarity index 95% rename from typescript/create-onchain-agent/src/cli/createAgentkit.ts rename to typescript/create-onchain-agent/src/actions/createAgentkit.ts index dd2381887..fdb0584f0 100644 --- a/typescript/create-onchain-agent/src/cli/createAgentkit.ts +++ b/typescript/create-onchain-agent/src/actions/createAgentkit.ts @@ -2,10 +2,10 @@ import fs from "fs/promises"; import path from "path"; import pc from "picocolors"; import prompts from "prompts"; -import { EVM_NETWORKS, PrepareAgentkitRouteConfigurations, SVM_NETWORKS } from "../constants.js"; -import { copyTemplate } from "../fileSystem.js"; -import { Network, WalletProviderChoice } from "../types.js"; -import { getNetworkType, getWalletProviders } from "../utils.js"; +import { EVM_NETWORKS, PrepareAgentkitRouteConfigurations, SVM_NETWORKS } from "../common/constants.js"; +import { copyTemplate } from "../common/fileSystem.js"; +import { Network, WalletProviderChoice } from "../common/types.js"; +import { getNetworkType, getWalletProviders } from "../common/utils.js"; /** * Prompts user for network and wallet provider selection, then sets up the prepare-agentkit file @@ -159,7 +159,7 @@ export async function createAgentkit() { const agentkitDir = path.join(root); await fs.rm(agentkitDir, { recursive: true, force: true }); - console.log(pc.green("\nSuccessfully created prepare-agentkit.ts")); + console.log(pc.green("Successfully created prepare-agentkit.ts")); } catch (error) { console.error("Error setting up prepare-agentkit:", error); process.exit(1); diff --git a/typescript/create-onchain-agent/src/cli/createWalletProvider.ts b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts similarity index 95% rename from typescript/create-onchain-agent/src/cli/createWalletProvider.ts rename to typescript/create-onchain-agent/src/actions/createWalletProvider.ts index 49c6e856c..ce30beae2 100644 --- a/typescript/create-onchain-agent/src/cli/createWalletProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts @@ -1,8 +1,9 @@ import fs from 'fs/promises'; -import path from 'path'; -import { fileURLToPath } from 'url'; import nunjucks from 'nunjucks'; +import path from 'path'; +import pc from "picocolors"; import prompts from 'prompts'; +import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -67,5 +68,5 @@ export async function createWalletProvider() { // Write file directly to current directory await fs.writeFile(`./${fileName}.ts`, generatedCode); - console.log(`Successfully created ${fileName}!`); + console.log(pc.green(`Successfully created ${fileName}!`)); } \ No newline at end of file diff --git a/typescript/create-onchain-agent/src/cli/initProject.ts b/typescript/create-onchain-agent/src/actions/initProject.ts similarity index 97% rename from typescript/create-onchain-agent/src/cli/initProject.ts rename to typescript/create-onchain-agent/src/actions/initProject.ts index d35197d1c..7afbfef24 100644 --- a/typescript/create-onchain-agent/src/cli/initProject.ts +++ b/typescript/create-onchain-agent/src/actions/initProject.ts @@ -3,17 +3,16 @@ import ora from "ora"; import path from "path"; import pc from "picocolors"; import prompts from "prompts"; -import { EVM_NETWORKS, NetworkToWalletProviders, SVM_NETWORKS } from "../constants.js"; -import { Network, WalletProviderChoice, Framework } from "../types.js"; -import { copyTemplate } from "../fileSystem.js"; +import { EVM_NETWORKS, Frameworks, FrameworkToTemplates, NetworkToWalletProviders, SVM_NETWORKS } from "../common/constants.js"; +import { copyTemplate } from "../common/fileSystem.js"; +import { Framework, Network, WalletProviderChoice } from "../common/types.js"; import { + getWalletProviders, + handleMcpSelection, handleNextSelection, isValidPackageName, toValidPackageName, - getWalletProviders, - handleMcpSelection, -} from "../utils.js"; -import { Frameworks, FrameworkToTemplates } from "../constants.js"; +} from "../common/utils.js"; /** * Initializes the project creation process. diff --git a/typescript/create-onchain-agent/src/cli.ts b/typescript/create-onchain-agent/src/cli.ts index a918b1b5c..6060319f7 100644 --- a/typescript/create-onchain-agent/src/cli.ts +++ b/typescript/create-onchain-agent/src/cli.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node -import { createActionProvider } from "./cli/createActionProvider.js"; -import { createAgent } from "./cli/createAgent.js"; -import { createAgentkit } from "./cli/createAgentkit.js"; -import { createWalletProvider } from "./cli/createWalletProvider.js"; -import { initProject } from "./cli/initProject.js"; +import { createActionProvider } from "./actions/createActionProvider.js"; +import { createAgent } from "./actions/createAgent.js"; +import { createAgentkit } from "./actions/createAgentkit.js"; +import { createWalletProvider } from "./actions/createWalletProvider.js"; +import { initProject } from "./actions/initProject.js"; async function handleArgs() { const type = process.argv[2]; diff --git a/typescript/create-onchain-agent/src/constants.ts b/typescript/create-onchain-agent/src/common/constants.ts similarity index 99% rename from typescript/create-onchain-agent/src/constants.ts rename to typescript/create-onchain-agent/src/common/constants.ts index 8c5bac978..9d948c51f 100644 --- a/typescript/create-onchain-agent/src/constants.ts +++ b/typescript/create-onchain-agent/src/common/constants.ts @@ -1,4 +1,4 @@ -import { AgentkitRouteConfiguration, PrepareAgentkitRouteConfiguration, MCPRouteConfiguration } from "./types"; +import { AgentkitRouteConfiguration, PrepareAgentkitRouteConfiguration, MCPRouteConfiguration } from "./types.js"; export const EVM_NETWORKS = [ "base-mainnet", diff --git a/typescript/create-onchain-agent/src/fileSystem.ts b/typescript/create-onchain-agent/src/common/fileSystem.ts similarity index 100% rename from typescript/create-onchain-agent/src/fileSystem.ts rename to typescript/create-onchain-agent/src/common/fileSystem.ts diff --git a/typescript/create-onchain-agent/src/types.ts b/typescript/create-onchain-agent/src/common/types.ts similarity index 100% rename from typescript/create-onchain-agent/src/types.ts rename to typescript/create-onchain-agent/src/common/types.ts diff --git a/typescript/create-onchain-agent/src/utils.ts b/typescript/create-onchain-agent/src/common/utils.ts similarity index 100% rename from typescript/create-onchain-agent/src/utils.ts rename to typescript/create-onchain-agent/src/common/utils.ts From b491f8559cf615f59c30dcf25e3525a3dba1dcd2 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 13:44:00 -0700 Subject: [PATCH 09/29] chore: ran lint/format --- .../src/actions/createActionProvider.ts | 173 ++++++++---------- .../src/actions/createAgent.ts | 96 +++++----- .../src/actions/createAgentkit.ts | 17 +- .../src/actions/createWalletProvider.ts | 114 ++++++------ .../src/actions/initProject.ts | 11 +- typescript/create-onchain-agent/src/cli.ts | 20 +- .../src/common/constants.ts | 9 +- .../src/common/fileSystem.ts | 2 +- .../create-onchain-agent/src/common/types.ts | 2 +- .../create-onchain-agent/src/common/utils.ts | 38 ++++ 10 files changed, 255 insertions(+), 227 deletions(-) diff --git a/typescript/create-onchain-agent/src/actions/createActionProvider.ts b/typescript/create-onchain-agent/src/actions/createActionProvider.ts index ae318b2e9..7c71f5721 100644 --- a/typescript/create-onchain-agent/src/actions/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createActionProvider.ts @@ -1,110 +1,93 @@ -import fs from 'fs/promises'; -import nunjucks from 'nunjucks'; -import path from 'path'; +import fs from "fs/promises"; +import nunjucks from "nunjucks"; +import path from "path"; import pc from "picocolors"; -import prompts from 'prompts'; -import { fileURLToPath } from 'url'; +import prompts from "prompts"; +import { fileURLToPath } from "url"; +import { toCamelCase, toSnakeCase } from "../common/utils"; +import { toClassName } from "../common/utils"; // Get current file's directory in ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -// Utility functions for string transformations -function toCamelCase(str: string): string { - // First character should be lowercase - return str.charAt(0).toLowerCase() + str.slice(1); -} - -function toSnakeCase(str: string): string { - return str - .replace(/([a-z])([A-Z])/g, '$1_$2') - .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') - .toLowerCase(); -} - -function toClassName(str: string): string { - // Capitalize first letter of each word, remove any "Provider" suffix - return str - .split(/(?=[A-Z])|[\s_-]/) - .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join('') - .replace(/Provider$/, ''); -} - +/** + * Creates a new action provider + */ export async function createActionProvider() { - // Configure nunjucks with the correct path - nunjucks.configure(path.join(__dirname, '../../../templates'), { - autoescape: false, - trimBlocks: true, - lstripBlocks: true - }); + // Configure nunjucks with the correct path + nunjucks.configure(path.join(__dirname, "../../../templates"), { + autoescape: false, + trimBlocks: true, + lstripBlocks: true, + }); - // Get user input - const answers = await prompts([ - { - type: 'text', - name: 'name', - message: 'What is the name of your action provider?', - validate: (value: string) => value.length > 0 ? true : 'Name is required' - }, - { - type: 'select', - name: 'networkFamily', - message: 'Which network family will this support?', - choices: [ - { title: 'EVM', value: 'EVM' }, - { title: 'SVM', value: 'SVM' }, - { title: 'Both', value: 'Both' } - ] - } - ]); + // Get user input + const answers = await prompts([ + { + type: "text", + name: "name", + message: "What is the name of your action provider?", + validate: (value: string) => (value.length > 0 ? true : "Name is required"), + }, + { + type: "select", + name: "networkFamily", + message: "Which network family will this support?", + choices: [ + { title: "EVM", value: "EVM" }, + { title: "SVM", value: "SVM" }, + { title: "Both", value: "Both" }, + ], + }, + ]); - // Process the name variations - const baseName = toClassName(answers.name); - const className = `${baseName}ActionProvider`; - const fileName = `${toCamelCase(baseName)}ActionProvider`; - const snakeName = toSnakeCase(answers.name); - const exportName = `${toCamelCase(baseName)}ActionProvider`; - - // Determine wallet provider and networks based on network family - let walletProvider = 'EvmWalletProvider'; - let networkSupport = 'true'; + // Process the name variations + const baseName = toClassName(answers.name); + const className = `${baseName}ActionProvider`; + const fileName = `${toCamelCase(baseName)}ActionProvider`; + const snakeName = toSnakeCase(answers.name); + const exportName = `${toCamelCase(baseName)}ActionProvider`; - switch(answers.networkFamily) { - case 'EVM': - walletProvider = 'EvmWalletProvider'; - networkSupport = 'network.protocolFamily === "EVM"'; - break; - case 'SVM': - walletProvider = 'SvmWalletProvider'; - networkSupport = 'network.protocolFamily === "SVM"'; - break; - case 'Both': - walletProvider = 'WalletProvider'; - networkSupport = 'true'; - break; - } + // Determine wallet provider and networks based on network family + let walletProvider = "EvmWalletProvider"; + let networkSupport = "true"; - // Generate code using nunjucks - const generatedCode = nunjucks.render('actionProvider/actionProvider.njk', { - name: exportName, - className, - walletProvider, - networks: '[]', - networkSupport, - actionName: `${snakeName}_action`, - schemaName: `${baseName}ActionSchema` - }); + switch (answers.networkFamily) { + case "EVM": + walletProvider = "EvmWalletProvider"; + networkSupport = 'network.protocolFamily === "EVM"'; + break; + case "SVM": + walletProvider = "SvmWalletProvider"; + networkSupport = 'network.protocolFamily === "SVM"'; + break; + case "Both": + walletProvider = "WalletProvider"; + networkSupport = "true"; + break; + } - // Write files directly to current directory - await fs.writeFile(`./${fileName}.ts`, generatedCode); + // Generate code using nunjucks + const generatedCode = nunjucks.render("actionProvider/actionProvider.njk", { + name: exportName, + className, + walletProvider, + networks: "[]", + networkSupport, + actionName: `${snakeName}_action`, + schemaName: `${baseName}ActionSchema`, + }); - // Generate schema file using nunjucks with updated schema name - const schemaCode = nunjucks.render('actionProvider/schema.njk', { - className: `${baseName}Action`, - }); + // Write files directly to current directory + await fs.writeFile(`./${fileName}.ts`, generatedCode); - await fs.writeFile(`./schemas.ts`, schemaCode); + // Generate schema file using nunjucks with updated schema name + const schemaCode = nunjucks.render("actionProvider/schema.njk", { + className: `${baseName}Action`, + }); - console.log(pc.green(`Successfully created ${className}!`)); -} \ No newline at end of file + await fs.writeFile(`./schemas.ts`, schemaCode); + + console.log(pc.green(`Successfully created ${className}!`)); +} diff --git a/typescript/create-onchain-agent/src/actions/createAgent.ts b/typescript/create-onchain-agent/src/actions/createAgent.ts index 293005178..a94b87022 100644 --- a/typescript/create-onchain-agent/src/actions/createAgent.ts +++ b/typescript/create-onchain-agent/src/actions/createAgent.ts @@ -8,56 +8,56 @@ type AgentFramework = "langchain" | "vercelAISDK"; /** * Creates an agent by selecting a framework and copying the appropriate template. - * + * * - Prompts user to select between LangChain and Vercel AI SDK frameworks * - Copies the selected framework's agent implementation * - Cleans up unused framework files */ export async function createAgent() { - let result: prompts.Answers<"framework">; - - try { - result = await prompts( - [ - { - type: "select", - name: "framework", - message: pc.reset("Choose a framework:"), - choices: [ - { title: "LangChain", value: "langchain" }, - { title: "Vercel AI SDK", value: "vercelAISDK" } - ] as { title: string; value: AgentFramework }[], - } - ], - { - onCancel: () => { - console.log("\nAgent creation cancelled."); - process.exit(0); - }, - } - ); - } catch (cancelled: unknown) { - console.error("An error occurred during agent creation"); - process.exit(1); - } - - const { framework } = result; - - try { - const root = await copyTemplate("createAgent", "createAgent"); - - // Copy the selected framework's implementation to the destination - const selectedRoutePath = path.join(root, "framework", framework, "createAgent.ts"); - const newRoutePath = path.join(process.cwd(), "createAgent.ts"); - - await fs.copyFile(selectedRoutePath, newRoutePath); - - // Clean up the temporary directory - await fs.rm(root, { recursive: true, force: true }); - - console.log(pc.green("Successfully created createAgent.ts")); - } catch (error) { - console.error("Error setting up createAgent:", error); - process.exit(1); - } -} \ No newline at end of file + let result: prompts.Answers<"framework">; + + try { + result = await prompts( + [ + { + type: "select", + name: "framework", + message: pc.reset("Choose a framework:"), + choices: [ + { title: "LangChain", value: "langchain" }, + { title: "Vercel AI SDK", value: "vercelAISDK" }, + ] as { title: string; value: AgentFramework }[], + }, + ], + { + onCancel: () => { + console.log("\nAgent creation cancelled."); + process.exit(0); + }, + }, + ); + } catch (error) { + console.error("An error occurred during agent creation", error); + process.exit(1); + } + + const { framework } = result; + + try { + const root = await copyTemplate("createAgent", "createAgent"); + + // Copy the selected framework's implementation to the destination + const selectedRoutePath = path.join(root, "framework", framework, "createAgent.ts"); + const newRoutePath = path.join(process.cwd(), "createAgent.ts"); + + await fs.copyFile(selectedRoutePath, newRoutePath); + + // Clean up the temporary directory + await fs.rm(root, { recursive: true, force: true }); + + console.log(pc.green("Successfully created createAgent.ts")); + } catch (error) { + console.error("Error setting up createAgent:", error); + process.exit(1); + } +} diff --git a/typescript/create-onchain-agent/src/actions/createAgentkit.ts b/typescript/create-onchain-agent/src/actions/createAgentkit.ts index fdb0584f0..e1498ccbb 100644 --- a/typescript/create-onchain-agent/src/actions/createAgentkit.ts +++ b/typescript/create-onchain-agent/src/actions/createAgentkit.ts @@ -2,7 +2,11 @@ import fs from "fs/promises"; import path from "path"; import pc from "picocolors"; import prompts from "prompts"; -import { EVM_NETWORKS, PrepareAgentkitRouteConfigurations, SVM_NETWORKS } from "../common/constants.js"; +import { + EVM_NETWORKS, + PrepareAgentkitRouteConfigurations, + SVM_NETWORKS, +} from "../common/constants.js"; import { copyTemplate } from "../common/fileSystem.js"; import { Network, WalletProviderChoice } from "../common/types.js"; import { getNetworkType, getWalletProviders } from "../common/utils.js"; @@ -128,8 +132,8 @@ export async function createAgentkit() { }, }, ); - } catch (cancelled: unknown) { - console.error("An error occurred during prepare-agentkit creation"); + } catch (error) { + console.error("An error occurred during prepare-agentkit creation", error); process.exit(1); } @@ -142,7 +146,8 @@ export async function createAgentkit() { } // Get the route configuration for the selected wallet provider - const routeConfig = PrepareAgentkitRouteConfigurations[networkFamily][walletProvider as WalletProviderChoice]; + const routeConfig = + PrepareAgentkitRouteConfigurations[networkFamily][walletProvider as WalletProviderChoice]; if (!routeConfig) { throw new Error("Selected invalid network & wallet provider combination"); } @@ -155,7 +160,7 @@ export async function createAgentkit() { const newRoutePath = path.join(process.cwd(), "prepareAgentkit.ts"); await fs.copyFile(selectedRoutePath, newRoutePath); - + const agentkitDir = path.join(root); await fs.rm(agentkitDir, { recursive: true, force: true }); @@ -164,4 +169,4 @@ export async function createAgentkit() { console.error("Error setting up prepare-agentkit:", error); process.exit(1); } -} \ No newline at end of file +} diff --git a/typescript/create-onchain-agent/src/actions/createWalletProvider.ts b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts index ce30beae2..19f7c87b5 100644 --- a/typescript/create-onchain-agent/src/actions/createWalletProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts @@ -1,72 +1,64 @@ -import fs from 'fs/promises'; -import nunjucks from 'nunjucks'; -import path from 'path'; +import fs from "fs/promises"; +import nunjucks from "nunjucks"; +import path from "path"; import pc from "picocolors"; -import prompts from 'prompts'; -import { fileURLToPath } from 'url'; +import prompts from "prompts"; +import { fileURLToPath } from "url"; +import { toCamelCase, toClassName, toSnakeCase } from "../common/utils"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -function toCamelCase(str: string): string { - // First character should be lowercase - return str.charAt(0).toLowerCase() + str.slice(1); -} - -function toSnakeCase(str: string): string { - return str - .replace(/([a-z])([A-Z])/g, '$1_$2') - .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') - .toLowerCase(); -} - +/** + * Creates a new wallet provider + */ export async function createWalletProvider() { - nunjucks.configure(path.join(__dirname, '../../../templates'), { - autoescape: false, - trimBlocks: true, - lstripBlocks: true - }); + nunjucks.configure(path.join(__dirname, "../../../templates"), { + autoescape: false, + trimBlocks: true, + lstripBlocks: true, + }); + + const answers = await prompts([ + { + type: "text", + name: "name", + message: "What is the name of your wallet provider?", + validate: (value: string) => (value.length > 0 ? true : "Name is required"), + }, + { + type: "text", + name: "description", + message: "Provide a brief description:", + initial: (prev: string) => `${prev} wallet operations`, + }, + { + type: "select", + name: "protocolFamily", + message: "Which protocol family will this support?", + choices: [ + { title: "EVM", value: "EVM" }, + { title: "SVM", value: "SVM" }, + ], + }, + ]); - const answers = await prompts([ - { - type: 'text', - name: 'name', - message: 'What is the name of your wallet provider?', - validate: (value: string) => value.length > 0 ? true : 'Name is required' - }, - { - type: 'text', - name: 'description', - message: 'Provide a brief description:', - initial: (prev: string) => `${prev} wallet operations` - }, - { - type: 'select', - name: 'protocolFamily', - message: 'Which protocol family will this support?', - choices: [ - { title: 'EVM', value: 'EVM' }, - { title: 'SVM', value: 'SVM' } - ] - } - ]); + const className = `${toClassName(answers.name)}WalletProvider`; + const fileName = `${toCamelCase(answers.name)}WalletProvider`; + const snakeName = toSnakeCase(answers.name); - const className = answers.name.charAt(0).toUpperCase() + answers.name.slice(1); - const fileName = `${toCamelCase(answers.name)}WalletProvider`; - const snakeName = toSnakeCase(answers.name); - - const baseClass = answers.protocolFamily === 'EVM' ? 'EvmWalletProvider' : 'SvmWalletProvider'; + const baseClass = answers.protocolFamily === "EVM" ? "EvmWalletProvider" : "SvmWalletProvider"; - const generatedCode = nunjucks.render('walletProvider/walletProvider.njk', { - name: snakeName, - className: fileName, - description: answers.description, - protocolFamily: answers.protocolFamily, - baseClass - }); + const generatedCode = nunjucks.render("walletProvider/walletProvider.njk", { + name: snakeName, + className, + description: answers.description, + protocolFamily: answers.protocolFamily, + baseClass, + }); - // Write file directly to current directory - await fs.writeFile(`./${fileName}.ts`, generatedCode); + // Write file directly to current directory + await fs.writeFile(`./${fileName}.ts`, generatedCode); - console.log(pc.green(`Successfully created ${fileName}!`)); -} \ No newline at end of file + console.log(pc.green(`Successfully created ${fileName}!`)); +} diff --git a/typescript/create-onchain-agent/src/actions/initProject.ts b/typescript/create-onchain-agent/src/actions/initProject.ts index 7afbfef24..e3e4730fa 100644 --- a/typescript/create-onchain-agent/src/actions/initProject.ts +++ b/typescript/create-onchain-agent/src/actions/initProject.ts @@ -3,7 +3,13 @@ import ora from "ora"; import path from "path"; import pc from "picocolors"; import prompts from "prompts"; -import { EVM_NETWORKS, Frameworks, FrameworkToTemplates, NetworkToWalletProviders, SVM_NETWORKS } from "../common/constants.js"; +import { + EVM_NETWORKS, + Frameworks, + FrameworkToTemplates, + NetworkToWalletProviders, + SVM_NETWORKS, +} from "../common/constants.js"; import { copyTemplate } from "../common/fileSystem.js"; import { Framework, Network, WalletProviderChoice } from "../common/types.js"; import { @@ -24,7 +30,6 @@ import { * - Displays a summary of the created project along with next steps. */ export async function initProject() { - console.log( `${pc.blue(` █████ ██████ ███████ ███ ██ ████████ ██ ██ ██ ████████ @@ -312,4 +317,4 @@ export async function initProject() { console.log(pc.blueBright(" - https://github.com/coinbase/agentkit")); console.log(" - Join the community"); console.log(pc.blueBright(" - https://discord.gg/CDP\n")); -} \ No newline at end of file +} diff --git a/typescript/create-onchain-agent/src/cli.ts b/typescript/create-onchain-agent/src/cli.ts index 6060319f7..1451c8b8d 100644 --- a/typescript/create-onchain-agent/src/cli.ts +++ b/typescript/create-onchain-agent/src/cli.ts @@ -5,35 +5,37 @@ import { createAgentkit } from "./actions/createAgentkit.js"; import { createWalletProvider } from "./actions/createWalletProvider.js"; import { initProject } from "./actions/initProject.js"; +/** + * Handles command line arguments and executes the appropriate action + */ async function handleArgs() { const type = process.argv[2]; if (!type) { await initProject(); - } - else { - switch(type) { - case 'init': { + } else { + switch (type) { + case "init": { await initProject(); break; } - case 'action-provider': { + case "action-provider": { await createActionProvider(); break; } - case 'wallet-provider': { + case "wallet-provider": { await createWalletProvider(); break; } - case 'agentkit': { + case "agentkit": { await createAgentkit(); break; } - case 'agent': { + case "agent": { await createAgent(); break; } default: { - console.log('Unknown command:', type); + console.log("Unknown command:", type); break; } } diff --git a/typescript/create-onchain-agent/src/common/constants.ts b/typescript/create-onchain-agent/src/common/constants.ts index 9d948c51f..e6bd44936 100644 --- a/typescript/create-onchain-agent/src/common/constants.ts +++ b/typescript/create-onchain-agent/src/common/constants.ts @@ -1,4 +1,8 @@ -import { AgentkitRouteConfiguration, PrepareAgentkitRouteConfiguration, MCPRouteConfiguration } from "./types.js"; +import { + AgentkitRouteConfiguration, + PrepareAgentkitRouteConfiguration, + MCPRouteConfiguration, +} from "./types.js"; export const EVM_NETWORKS = [ "base-mainnet", @@ -227,7 +231,6 @@ export const MCPRouteConfigurations: Record< }, }; - export const PrepareAgentkitRouteConfigurations: Record< "EVM" | "CUSTOM_EVM" | "SVM", Partial> @@ -259,4 +262,4 @@ export const PrepareAgentkitRouteConfigurations: Record< route: "svm/privy/prepareAgentkit.ts", }, }, -}; \ No newline at end of file +}; diff --git a/typescript/create-onchain-agent/src/common/fileSystem.ts b/typescript/create-onchain-agent/src/common/fileSystem.ts index fb9fbbd14..d8adafd85 100644 --- a/typescript/create-onchain-agent/src/common/fileSystem.ts +++ b/typescript/create-onchain-agent/src/common/fileSystem.ts @@ -84,7 +84,7 @@ export async function copyTemplate( await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2)); } catch (error) { // Ignore errors if package.json doesn't exist - if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") { throw error; } } diff --git a/typescript/create-onchain-agent/src/common/types.ts b/typescript/create-onchain-agent/src/common/types.ts index 6c0af92f0..32d6b0536 100644 --- a/typescript/create-onchain-agent/src/common/types.ts +++ b/typescript/create-onchain-agent/src/common/types.ts @@ -16,7 +16,7 @@ export type MCPRouteConfiguration = { export type PrepareAgentkitRouteConfiguration = { route: `${string}/prepareAgentkit.ts`; -} +}; export type NetworkSelection = { networkFamily: "EVM" | "SVM"; diff --git a/typescript/create-onchain-agent/src/common/utils.ts b/typescript/create-onchain-agent/src/common/utils.ts index e326e2b8e..1f1bd55ec 100644 --- a/typescript/create-onchain-agent/src/common/utils.ts +++ b/typescript/create-onchain-agent/src/common/utils.ts @@ -376,3 +376,41 @@ export async function handleMcpSelection( await fs.rm(path.join(srcDir, "agentkit"), { recursive: true, force: true }); } + +/** + * Converts a string to camel case + * + * @param str - The string to convert to camel case + * @returns The camel case version of the string + */ +export function toCamelCase(str: string): string { + // First character should be lowercase + return str.charAt(0).toLowerCase() + str.slice(1); +} + +/** + * Converts a string to snake case + * + * @param str - The string to convert to snake case + * @returns The snake case version of the string + */ +export function toSnakeCase(str: string): string { + return str + .replace(/([a-z])([A-Z])/g, "$1_$2") + .replace(/([A-Z])([A-Z][a-z])/g, "$1_$2") + .toLowerCase(); +} + +/** + * Converts a string to a class name + * + * @param str - The string to convert to class name + * @returns The class name version of the string + */ +export function toClassName(str: string): string { + // Capitalize first letter of each word, remove any "Provider" suffix + return str + .split(/(?=[A-Z])|[\s_-]/) + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(""); +} From 32c06433c27cc04d984b899ea49851d8af6fdf2c Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 13:45:06 -0700 Subject: [PATCH 10/29] chore: changelog --- typescript/.changeset/wide-lions-fly.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 typescript/.changeset/wide-lions-fly.md diff --git a/typescript/.changeset/wide-lions-fly.md b/typescript/.changeset/wide-lions-fly.md new file mode 100644 index 000000000..ca9773ff0 --- /dev/null +++ b/typescript/.changeset/wide-lions-fly.md @@ -0,0 +1,5 @@ +--- +"create-onchain-agent": minor +--- + +Added code generation for individual building blocks From 1dd0e8111b171900f784de07c9c827066ba11936 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 13:54:36 -0700 Subject: [PATCH 11/29] fix: package-lock.json --- typescript/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 89ed555a3..160f28a6f 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -24448,7 +24448,7 @@ "@types/prompts": "^2.4.9", "cac": "^6.7.14", "cross-spawn": "^7.0.3", - "nunjucks": "*", + "nunjucks": "^3.2.4", "ora": "^8.1.0", "picocolors": "^1.1.0", "prompts": "^2.4.2" From a2dbe0c2728ca106ca1ba978cedf814b842f8594 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 14:14:26 -0700 Subject: [PATCH 12/29] fix: imports --- .../create-onchain-agent/src/actions/createActionProvider.ts | 3 +-- .../create-onchain-agent/src/actions/createWalletProvider.ts | 2 +- typescript/create-onchain-agent/src/common/types.ts | 2 +- typescript/create-onchain-agent/src/common/utils.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/typescript/create-onchain-agent/src/actions/createActionProvider.ts b/typescript/create-onchain-agent/src/actions/createActionProvider.ts index 7c71f5721..fdbc32455 100644 --- a/typescript/create-onchain-agent/src/actions/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createActionProvider.ts @@ -4,8 +4,7 @@ import path from "path"; import pc from "picocolors"; import prompts from "prompts"; import { fileURLToPath } from "url"; -import { toCamelCase, toSnakeCase } from "../common/utils"; -import { toClassName } from "../common/utils"; +import { toCamelCase, toClassName, toSnakeCase } from "../common/utils.js"; // Get current file's directory in ES modules const __filename = fileURLToPath(import.meta.url); diff --git a/typescript/create-onchain-agent/src/actions/createWalletProvider.ts b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts index 19f7c87b5..75ea3b27d 100644 --- a/typescript/create-onchain-agent/src/actions/createWalletProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts @@ -4,7 +4,7 @@ import path from "path"; import pc from "picocolors"; import prompts from "prompts"; import { fileURLToPath } from "url"; -import { toCamelCase, toClassName, toSnakeCase } from "../common/utils"; +import { toCamelCase, toClassName, toSnakeCase } from "../common/utils.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/typescript/create-onchain-agent/src/common/types.ts b/typescript/create-onchain-agent/src/common/types.ts index 32d6b0536..81b366dff 100644 --- a/typescript/create-onchain-agent/src/common/types.ts +++ b/typescript/create-onchain-agent/src/common/types.ts @@ -1,4 +1,4 @@ -import { Network } from "./constants"; +import { Network } from "./constants.js"; export type AgentkitRouteConfiguration = { env: { diff --git a/typescript/create-onchain-agent/src/common/utils.ts b/typescript/create-onchain-agent/src/common/utils.ts index 1f1bd55ec..7543f1b75 100644 --- a/typescript/create-onchain-agent/src/common/utils.ts +++ b/typescript/create-onchain-agent/src/common/utils.ts @@ -1,6 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import { EVMNetwork, Framework, Network, SVMNetwork, WalletProviderChoice } from "./types"; +import { EVMNetwork, Framework, Network, SVMNetwork, WalletProviderChoice } from "./types.js"; import { EVM_NETWORKS, NetworkToWalletProviders, From 6bbd7bbaecf3c12a56d9e3d031716d7b45869de5 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 14:47:22 -0700 Subject: [PATCH 13/29] fix: cli path to templates --- typescript/create-onchain-agent/src/common/fileSystem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/create-onchain-agent/src/common/fileSystem.ts b/typescript/create-onchain-agent/src/common/fileSystem.ts index d8adafd85..9debca45f 100644 --- a/typescript/create-onchain-agent/src/common/fileSystem.ts +++ b/typescript/create-onchain-agent/src/common/fileSystem.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from "url"; import { optimizedCopy } from "./utils.js"; import { Template } from "./types.js"; -const sourceDir = path.resolve(fileURLToPath(import.meta.url), "../../../templates"); +const sourceDir = path.resolve(fileURLToPath(import.meta.url), "../../../../templates"); const renameFiles: Record = { _gitignore: ".gitignore", From 1faecd5629e23f5dde83f1abbb038b68d786cecd Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 15:35:20 -0700 Subject: [PATCH 14/29] chore: updated readme --- typescript/create-onchain-agent/README.md | 70 +++++++++-------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/typescript/create-onchain-agent/README.md b/typescript/create-onchain-agent/README.md index 02153a3e6..c40596d0b 100644 --- a/typescript/create-onchain-agent/README.md +++ b/typescript/create-onchain-agent/README.md @@ -2,7 +2,7 @@ ## Overview -`create-onchain-agent` is a CLI tool powered by [AgentKit](https://github.com/coinbase/agentkit) that allows developers to quickly scaffold an **onchain agent** project. This tool simplifies the setup process by generating a project with predefined configurations, including blockchain network selection, wallet providers, and framework setup. +`create-onchain-agent` is a CLI tool powered by [AgentKit](https://github.com/coinbase/agentkit) that provides multiple utilities for creating and integrating onchain agents into your projects. Whether you're starting a new project or adding AgentKit to an existing application, this tool offers flexible options to meet your needs. ## Prerequisites @@ -13,61 +13,47 @@ Before using `create-onchain-agent`, ensure you have the following installed: ## Usage -To use `create-onchain-agent`, simply run: +### Quickstart Templates + +To create a new project from scratch: ```sh npm create onchain-agent@latest ``` -This command will guide you through setting up an onchain agent project by prompting for necessary configuration options. - -## Features - -- **Guided setup**: Interactive prompts help configure the project. -- **Supports multiple AI frameworks**. -- **Supports multiple blockchain networks**. -- **Select your preferred wallet provider**. -- **Supports a preconfigured Next.js project with React, Tailwind CSS, and ESLint**. -- **Supports a preconfigured Model Context Protocol Server project**. -- **Automates environment setup**. - -### **Setup Process** -The CLI will prompt you for the following details: - -1. **Project Name**: The name of your new onchain agent project. -2. **Package Name**: The npm package name (auto-formatted if needed). -3. **Framework**: Choose from available AI frameworks. -4. **Network**: Choose from available blockchain networks. -5. **Chain ID**: Specify if using a custom network. -6. **Wallet Provider**: Select a preferred method for wallet management. - -After answering the prompts, the CLI will: +This command lets you choose between two project templates: +- **Next.js Template**: A full-stack web application with React, Tailwind CSS, and ESLint +- **MCP Template**: A Model Context Protocol server project -- Generate the project structure. -- Copy necessary template files. -- Configure the selected settings. -- Display next steps to get started. +### Component Generation -## Getting Started - -Once your project is created, navigate into the directory and install dependencies: +To add AgentKit components to an existing project: ```sh -cd my-project -npm install -``` +# Generate a custom wallet provider +npm create onchain-agent wallet-provider -Then, configure your environment variables: +# Generate a custom action provider +npm create onchain-agent action-provider -```sh -mv .env.local .env +# Generate framework-agnostic AgentKit setup. +npm create onchain-agent agentkit + +# Generate framework-specific agent creation +npm create onchain-agent agent ``` -Run the development server: +## Features -```sh -npm run dev -``` +- **Multiple project templates**: + - Next.js template for web applications + - MCP template for Model Context Protocol servers +- **Component generators** for existing projects +- **Guided setup** with interactive prompts +- **Support for multiple AI frameworks** +- **Support for multiple blockchain networks** +- **Flexible wallet provider options** +- **Automated environment setup** ## Documentation & Support From c1b074d555db670833d7ca1de502bfd6bb784d2a Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 15:50:40 -0700 Subject: [PATCH 15/29] chore: cleaned up messaging --- .../src/actions/createActionProvider.ts | 2 +- .../src/actions/createWalletProvider.ts | 9 +-------- .../templates/walletProvider/walletProvider.njk | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/typescript/create-onchain-agent/src/actions/createActionProvider.ts b/typescript/create-onchain-agent/src/actions/createActionProvider.ts index fdbc32455..cbdb95ba9 100644 --- a/typescript/create-onchain-agent/src/actions/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createActionProvider.ts @@ -88,5 +88,5 @@ export async function createActionProvider() { await fs.writeFile(`./schemas.ts`, schemaCode); - console.log(pc.green(`Successfully created ${className}!`)); + console.log(pc.green(`Successfully created ${className}.ts and schemas.ts`)); } diff --git a/typescript/create-onchain-agent/src/actions/createWalletProvider.ts b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts index 75ea3b27d..aa79f68ae 100644 --- a/typescript/create-onchain-agent/src/actions/createWalletProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createWalletProvider.ts @@ -26,12 +26,6 @@ export async function createWalletProvider() { message: "What is the name of your wallet provider?", validate: (value: string) => (value.length > 0 ? true : "Name is required"), }, - { - type: "text", - name: "description", - message: "Provide a brief description:", - initial: (prev: string) => `${prev} wallet operations`, - }, { type: "select", name: "protocolFamily", @@ -52,7 +46,6 @@ export async function createWalletProvider() { const generatedCode = nunjucks.render("walletProvider/walletProvider.njk", { name: snakeName, className, - description: answers.description, protocolFamily: answers.protocolFamily, baseClass, }); @@ -60,5 +53,5 @@ export async function createWalletProvider() { // Write file directly to current directory await fs.writeFile(`./${fileName}.ts`, generatedCode); - console.log(pc.green(`Successfully created ${fileName}!`)); + console.log(pc.green(`Successfully created ${fileName}.ts`)); } diff --git a/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk index f24614138..72293b2db 100644 --- a/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk +++ b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk @@ -7,7 +7,7 @@ import { Connection, PublicKey, RpcResponseAndContext, SignatureStatus, Signatur {% endif %} /** - * {{ className }} implements a wallet provider for {{ description }}. + * {{ className }} wallet provider. */ export class {{ className }} extends {{ baseClass }} { {% if protocolFamily === "EVM" %} From 2cf3060d391faada6d45cf28211e7398b7f9273b Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Wed, 19 Mar 2025 15:53:03 -0700 Subject: [PATCH 16/29] fix: cli message's generated file name --- typescript/create-onchain-agent/src/actions/createAgentkit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/create-onchain-agent/src/actions/createAgentkit.ts b/typescript/create-onchain-agent/src/actions/createAgentkit.ts index e1498ccbb..735a64c54 100644 --- a/typescript/create-onchain-agent/src/actions/createAgentkit.ts +++ b/typescript/create-onchain-agent/src/actions/createAgentkit.ts @@ -164,9 +164,9 @@ export async function createAgentkit() { const agentkitDir = path.join(root); await fs.rm(agentkitDir, { recursive: true, force: true }); - console.log(pc.green("Successfully created prepare-agentkit.ts")); + console.log(pc.green("Successfully created prepareAgentkit.ts")); } catch (error) { - console.error("Error setting up prepare-agentkit:", error); + console.error("Error setting up prepareAgentkit:", error); process.exit(1); } } From 501dc8047a011f60731498d802db6bb3ababd820 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Fri, 21 Mar 2025 10:15:11 -0700 Subject: [PATCH 17/29] feat: split generate cli into separate bin export from default create --- typescript/create-onchain-agent/package.json | 3 +- typescript/create-onchain-agent/src/cli.ts | 47 --------------- typescript/create-onchain-agent/src/create.ts | 13 +++++ .../create-onchain-agent/src/generate.ts | 57 +++++++++++++++++++ 4 files changed, 72 insertions(+), 48 deletions(-) delete mode 100644 typescript/create-onchain-agent/src/cli.ts create mode 100644 typescript/create-onchain-agent/src/create.ts create mode 100644 typescript/create-onchain-agent/src/generate.ts diff --git a/typescript/create-onchain-agent/package.json b/typescript/create-onchain-agent/package.json index 789358e58..54cb791a2 100644 --- a/typescript/create-onchain-agent/package.json +++ b/typescript/create-onchain-agent/package.json @@ -25,7 +25,8 @@ "templates/**" ], "bin": { - "create-onchain-agent": "./dist/esm/cli.js" + "create-onchain-agent": "./dist/esm/create.js", + "agentkit": "./dist/esm/generate.js" }, "sideEffects": false, "type": "module", diff --git a/typescript/create-onchain-agent/src/cli.ts b/typescript/create-onchain-agent/src/cli.ts deleted file mode 100644 index 1451c8b8d..000000000 --- a/typescript/create-onchain-agent/src/cli.ts +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env node -import { createActionProvider } from "./actions/createActionProvider.js"; -import { createAgent } from "./actions/createAgent.js"; -import { createAgentkit } from "./actions/createAgentkit.js"; -import { createWalletProvider } from "./actions/createWalletProvider.js"; -import { initProject } from "./actions/initProject.js"; - -/** - * Handles command line arguments and executes the appropriate action - */ -async function handleArgs() { - const type = process.argv[2]; - if (!type) { - await initProject(); - } else { - switch (type) { - case "init": { - await initProject(); - break; - } - case "action-provider": { - await createActionProvider(); - break; - } - case "wallet-provider": { - await createWalletProvider(); - break; - } - case "agentkit": { - await createAgentkit(); - break; - } - case "agent": { - await createAgent(); - break; - } - default: { - console.log("Unknown command:", type); - break; - } - } - } -} - -handleArgs().catch(e => { - console.error(e); -}); diff --git a/typescript/create-onchain-agent/src/create.ts b/typescript/create-onchain-agent/src/create.ts new file mode 100644 index 000000000..6e0891b45 --- /dev/null +++ b/typescript/create-onchain-agent/src/create.ts @@ -0,0 +1,13 @@ +#!/usr/bin/env node +import { initProject } from "./actions/initProject.js"; + +/** + * Handles command line arguments and executes the appropriate action + */ +async function handleArgs() { + await initProject(); +} + +handleArgs().catch(e => { + console.error(e); +}); diff --git a/typescript/create-onchain-agent/src/generate.ts b/typescript/create-onchain-agent/src/generate.ts new file mode 100644 index 000000000..72a395cfc --- /dev/null +++ b/typescript/create-onchain-agent/src/generate.ts @@ -0,0 +1,57 @@ +#!/usr/bin/env node +import { createActionProvider } from "./actions/createActionProvider.js"; +import { createAgent } from "./actions/createAgent.js"; +import { createAgentkit } from "./actions/createAgentkit.js"; +import { createWalletProvider } from "./actions/createWalletProvider.js"; +import { initProject } from "./actions/initProject.js"; + +/** + * Handles command line arguments and executes the appropriate action + */ +async function handleArgs() { + const command = process.argv[2]; + if (command) { + switch(command) { + case 'new': { + await initProject(); + break; + } + case 'generate': { + const type = process.argv[3]; + switch (type) { + case "action-provider": { + await createActionProvider(); + break; + } + case "wallet-provider": { + await createWalletProvider(); + break; + } + case "agentkit": { + await createAgentkit(); + break; + } + case "agent": { + await createAgent(); + break; + } + default: { + console.log("Unknown command:", command, type); + break; + } + } + } + default: { + console.log("Unknown command:", command); + break; + } + } + } + else { + console.log("No command provided"); + } +} + +handleArgs().catch(e => { + console.error(e); +}); From 9fdca7a1fc415b624414b0c21efdbf4a07cdb2ce Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Fri, 21 Mar 2025 10:19:13 -0700 Subject: [PATCH 18/29] chore: lint/format --- typescript/create-onchain-agent/src/generate.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/typescript/create-onchain-agent/src/generate.ts b/typescript/create-onchain-agent/src/generate.ts index 72a395cfc..7947a3e9b 100644 --- a/typescript/create-onchain-agent/src/generate.ts +++ b/typescript/create-onchain-agent/src/generate.ts @@ -11,12 +11,12 @@ import { initProject } from "./actions/initProject.js"; async function handleArgs() { const command = process.argv[2]; if (command) { - switch(command) { - case 'new': { + switch (command) { + case "new": { await initProject(); break; } - case 'generate': { + case "generate": { const type = process.argv[3]; switch (type) { case "action-provider": { @@ -40,14 +40,14 @@ async function handleArgs() { break; } } + break; } default: { console.log("Unknown command:", command); break; } } - } - else { + } else { console.log("No command provided"); } } From a157d2aa8bfdf00bd097960afde8488cc3779052 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Fri, 21 Mar 2025 10:22:01 -0700 Subject: [PATCH 19/29] chore: fix package-lock.json --- typescript/package-lock.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 160f28a6f..708654bf5 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -263,7 +263,8 @@ "prompts": "^2.4.2" }, "bin": { - "create-onchain-agent": "dist/esm/cli.js" + "agentkit": "dist/esm/generate.js", + "create-onchain-agent": "dist/esm/create.js" }, "devDependencies": { "@types/cross-spawn": "^6.0.6", From b09fea55938cd436fca3d2e9ec7a9135d217f1d4 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Fri, 21 Mar 2025 10:38:40 -0700 Subject: [PATCH 20/29] feat: improved robustness of arg handling --- .../create-onchain-agent/src/generate.ts | 99 ++++++++++++------- 1 file changed, 65 insertions(+), 34 deletions(-) diff --git a/typescript/create-onchain-agent/src/generate.ts b/typescript/create-onchain-agent/src/generate.ts index 7947a3e9b..1bbdd223d 100644 --- a/typescript/create-onchain-agent/src/generate.ts +++ b/typescript/create-onchain-agent/src/generate.ts @@ -5,53 +5,84 @@ import { createAgentkit } from "./actions/createAgentkit.js"; import { createWalletProvider } from "./actions/createWalletProvider.js"; import { initProject } from "./actions/initProject.js"; +const VALID_GENERATE_TYPES = ["action-provider", "wallet-provider", "agentkit", "agent"] as const; +type GenerateType = (typeof VALID_GENERATE_TYPES)[number]; + +/** + * Finds command arguments regardless of script invocation method + * + * @param args - The command line arguments + * @returns The command and type + */ +function findCommands(args: string[]): { command: string | null; type: string | null } { + // Handle direct "new" command + if (args.includes("new")) { + return { command: "new", type: null }; + } + + // Handle generate commands + const generateIndex = args.findIndex(arg => arg === "generate"); + if (generateIndex === -1) { + return { command: null, type: null }; + } + + const type = args[generateIndex + 1]; + return { command: "generate", type }; +} + /** * Handles command line arguments and executes the appropriate action */ async function handleArgs() { - const command = process.argv[2]; - if (command) { - switch (command) { - case "new": { - await initProject(); + const { command, type } = findCommands(process.argv); + + if (!command) { + console.error("Error: Please provide a valid command (new or generate)"); + process.exit(1); + } + + switch (command) { + case "new": { + await initProject(); + return; + } + case "generate": { + if (!type) { + console.error("Error: Please specify what to generate"); + console.error(`Valid options: ${VALID_GENERATE_TYPES.join(", ")}`); break; } - case "generate": { - const type = process.argv[3]; - switch (type) { - case "action-provider": { - await createActionProvider(); - break; - } - case "wallet-provider": { - await createWalletProvider(); - break; - } - case "agentkit": { - await createAgentkit(); - break; - } - case "agent": { - await createAgent(); - break; - } - default: { - console.log("Unknown command:", command, type); - break; - } - } + + if (!VALID_GENERATE_TYPES.includes(type as GenerateType)) { + console.error(`Error: Unknown generate type: ${type}`); + console.error(`Valid options: ${VALID_GENERATE_TYPES.join(", ")}`); break; } - default: { - console.log("Unknown command:", command); - break; + + switch (type as GenerateType) { + case "action-provider": + await createActionProvider(); + break; + case "wallet-provider": + await createWalletProvider(); + break; + case "agentkit": + await createAgentkit(); + break; + case "agent": + await createAgent(); + break; } + break; + } + default: { + console.error(`Error: Unknown command: ${command}`); + break; } - } else { - console.log("No command provided"); } } handleArgs().catch(e => { console.error(e); + process.exit(1); }); From fc60dac965c816478071f304e288173e9bdb622a Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Fri, 21 Mar 2025 10:47:35 -0700 Subject: [PATCH 21/29] chore: updated README --- typescript/create-onchain-agent/README.md | 13 +-- .../create-onchain-agent/createAgent.ts | 81 ------------------- 2 files changed, 8 insertions(+), 86 deletions(-) delete mode 100644 typescript/create-onchain-agent/createAgent.ts diff --git a/typescript/create-onchain-agent/README.md b/typescript/create-onchain-agent/README.md index c40596d0b..a05a5bb06 100644 --- a/typescript/create-onchain-agent/README.md +++ b/typescript/create-onchain-agent/README.md @@ -27,20 +27,23 @@ This command lets you choose between two project templates: ### Component Generation -To add AgentKit components to an existing project: +After installing the `create-onchain-agent` CLI, you will also have the `agenkit` CLI installed. This allows you to generate components for your project. ```sh +# Identical to `npm create onchain-agent` +agentkit new + # Generate a custom wallet provider -npm create onchain-agent wallet-provider +agentkit generate wallet-provider # Generate a custom action provider -npm create onchain-agent action-provider +agentkit generate action-provider # Generate framework-agnostic AgentKit setup. -npm create onchain-agent agentkit +agentkit generate agentkit # Generate framework-specific agent creation -npm create onchain-agent agent +agentkit generate agent ``` ## Features diff --git a/typescript/create-onchain-agent/createAgent.ts b/typescript/create-onchain-agent/createAgent.ts deleted file mode 100644 index ee0726b31..000000000 --- a/typescript/create-onchain-agent/createAgent.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { openai } from "@ai-sdk/openai"; -import { getVercelAITools } from "@coinbase/agentkit-vercel-ai-sdk"; -import { prepareAgentkitAndWalletProvider } from "./prepareAgentkit"; - -/** - * Agent Configuration Guide - * - * This file handles the core configuration of your AI agent's behavior and capabilities. - * - * Key Steps to Customize Your Agent: - * - * 1. Select your LLM: - * - Modify the `openai` instantiation to choose your preferred LLM - * - Configure model parameters like temperature and max tokens - * - * 2. Instantiate your Agent: - * - Pass the LLM, tools, and memory into `createReactAgent()` - * - Configure agent-specific parameters - */ - -// The agent -type Agent = { - tools: ReturnType; - system: string; - model: ReturnType; - maxSteps?: number; -}; -let agent: Agent; - -/** - * Initializes and returns an instance of the AI agent. - * If an agent instance already exists, it returns the existing one. - * - * @function getOrInitializeAgent - * @returns {Promise>} The initialized AI agent. - * - * @description Handles agent setup - * - * @throws {Error} If the agent initialization fails. - */ -export async function createAgent(): Promise { - // If agent has already been initialized, return it - if (agent) { - return agent; - } - - try { - // Initialize LLM: https://platform.openai.com/docs/models#gpt-4o - const model = openai("gpt-4o-mini"); - - const { agentkit, walletProvider } = await prepareAgentkitAndWalletProvider(); - - // Initialize Agent - const canUseFaucet = walletProvider.getNetwork().networkId == "base-sepolia"; - const faucetMessage = `If you ever need funds, you can request them from the faucet.`; - const cantUseFaucetMessage = `If you need funds, you can provide your wallet details and request funds from the user.`; - const system = ` - You are a helpful agent that can interact onchain using the Coinbase Developer Platform AgentKit. You are - empowered to interact onchain using your tools. ${canUseFaucet ? faucetMessage : cantUseFaucetMessage}. - Before executing your first action, get the wallet details to see what network - you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone - asks you to do something you can't do with your currently available tools, you must say so, and - encourage them to implement it themselves using the CDP SDK + Agentkit, recommend they go to - docs.cdp.coinbase.com for more information. Be concise and helpful with your responses. Refrain from - restating your tools' descriptions unless it is explicitly requested. - `; - const tools = getVercelAITools(agentkit); - - agent = { - tools, - system, - model, - maxSteps: 10, - }; - - return agent; - } catch (error) { - console.error("Error initializing agent:", error); - throw new Error("Failed to initialize agent"); - } -} From 0deed65d839307df02d0885e55c89789ef63127e Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Fri, 21 Mar 2025 13:24:38 -0700 Subject: [PATCH 22/29] chore: renamed prepare agentkit work --- typescript/create-onchain-agent/README.md | 4 ++-- .../actions/{createAgentkit.ts => prepareAgentkit.ts} | 2 +- typescript/create-onchain-agent/src/generate.ts | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) rename typescript/create-onchain-agent/src/actions/{createAgentkit.ts => prepareAgentkit.ts} (99%) diff --git a/typescript/create-onchain-agent/README.md b/typescript/create-onchain-agent/README.md index a05a5bb06..fe4e91dcf 100644 --- a/typescript/create-onchain-agent/README.md +++ b/typescript/create-onchain-agent/README.md @@ -40,10 +40,10 @@ agentkit generate wallet-provider agentkit generate action-provider # Generate framework-agnostic AgentKit setup. -agentkit generate agentkit +agentkit generate prepare # Generate framework-specific agent creation -agentkit generate agent +agentkit generate create-agent ``` ## Features diff --git a/typescript/create-onchain-agent/src/actions/createAgentkit.ts b/typescript/create-onchain-agent/src/actions/prepareAgentkit.ts similarity index 99% rename from typescript/create-onchain-agent/src/actions/createAgentkit.ts rename to typescript/create-onchain-agent/src/actions/prepareAgentkit.ts index 735a64c54..ae6ee514a 100644 --- a/typescript/create-onchain-agent/src/actions/createAgentkit.ts +++ b/typescript/create-onchain-agent/src/actions/prepareAgentkit.ts @@ -15,7 +15,7 @@ import { getNetworkType, getWalletProviders } from "../common/utils.js"; * Prompts user for network and wallet provider selection, then sets up the prepare-agentkit file * by promoting the correct template and cleaning up unused files. */ -export async function createAgentkit() { +export async function prepareAgentKit() { let result: prompts.Answers< "networkFamily" | "networkType" | "network" | "chainId" | "rpcUrl" | "walletProvider" >; diff --git a/typescript/create-onchain-agent/src/generate.ts b/typescript/create-onchain-agent/src/generate.ts index 1bbdd223d..6ab9cbb36 100644 --- a/typescript/create-onchain-agent/src/generate.ts +++ b/typescript/create-onchain-agent/src/generate.ts @@ -1,11 +1,11 @@ #!/usr/bin/env node import { createActionProvider } from "./actions/createActionProvider.js"; import { createAgent } from "./actions/createAgent.js"; -import { createAgentkit } from "./actions/createAgentkit.js"; +import { prepareAgentKit } from "./actions/prepareAgentkit.js"; import { createWalletProvider } from "./actions/createWalletProvider.js"; import { initProject } from "./actions/initProject.js"; -const VALID_GENERATE_TYPES = ["action-provider", "wallet-provider", "agentkit", "agent"] as const; +const VALID_GENERATE_TYPES = ["action-provider", "wallet-provider", "prepare", "create-agent"] as const; type GenerateType = (typeof VALID_GENERATE_TYPES)[number]; /** @@ -66,10 +66,10 @@ async function handleArgs() { case "wallet-provider": await createWalletProvider(); break; - case "agentkit": - await createAgentkit(); + case "prepare": + await prepareAgentKit(); break; - case "agent": + case "create-agent": await createAgent(); break; } From a2a4e66a82853bbfa36b01030941df5df6459dea Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Fri, 21 Mar 2025 13:30:14 -0700 Subject: [PATCH 23/29] chore: format/lint --- typescript/create-onchain-agent/src/generate.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/typescript/create-onchain-agent/src/generate.ts b/typescript/create-onchain-agent/src/generate.ts index 6ab9cbb36..bc4d4763b 100644 --- a/typescript/create-onchain-agent/src/generate.ts +++ b/typescript/create-onchain-agent/src/generate.ts @@ -5,7 +5,12 @@ import { prepareAgentKit } from "./actions/prepareAgentkit.js"; import { createWalletProvider } from "./actions/createWalletProvider.js"; import { initProject } from "./actions/initProject.js"; -const VALID_GENERATE_TYPES = ["action-provider", "wallet-provider", "prepare", "create-agent"] as const; +const VALID_GENERATE_TYPES = [ + "action-provider", + "wallet-provider", + "prepare", + "create-agent", +] as const; type GenerateType = (typeof VALID_GENERATE_TYPES)[number]; /** From f410cf9f726e529176f9a9762594e8849a53157c Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 24 Mar 2025 08:29:00 -0400 Subject: [PATCH 24/29] chore: pr feedback --- .../create-onchain-agent/src/generate.ts | 30 ++++++------ .../actionProvider/actionProvider.njk | 47 ++++++------------- .../framework/langchain/createAgent.ts | 1 - .../framework/vercelAISDK/createAgent.ts | 1 - .../walletProvider/walletProvider.njk | 12 ++--- 5 files changed, 36 insertions(+), 55 deletions(-) diff --git a/typescript/create-onchain-agent/src/generate.ts b/typescript/create-onchain-agent/src/generate.ts index bc4d4763b..1d3d4df64 100644 --- a/typescript/create-onchain-agent/src/generate.ts +++ b/typescript/create-onchain-agent/src/generate.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node + import { createActionProvider } from "./actions/createActionProvider.js"; import { createAgent } from "./actions/createAgent.js"; import { prepareAgentKit } from "./actions/prepareAgentkit.js"; import { createWalletProvider } from "./actions/createWalletProvider.js"; -import { initProject } from "./actions/initProject.js"; const VALID_GENERATE_TYPES = [ "action-provider", @@ -20,12 +20,6 @@ type GenerateType = (typeof VALID_GENERATE_TYPES)[number]; * @returns The command and type */ function findCommands(args: string[]): { command: string | null; type: string | null } { - // Handle direct "new" command - if (args.includes("new")) { - return { command: "new", type: null }; - } - - // Handle generate commands const generateIndex = args.findIndex(arg => arg === "generate"); if (generateIndex === -1) { return { command: null, type: null }; @@ -35,6 +29,16 @@ function findCommands(args: string[]): { command: string | null; type: string | return { command: "generate", type }; } +/** + * Checks if a value is a valid generate type + * + * @param value - The value to check + * @returns True if the value is a valid generate type, false otherwise + */ +function isGenerateType(value: string | null): value is GenerateType { + return Boolean(value && VALID_GENERATE_TYPES.includes(value as GenerateType)); +} + /** * Handles command line arguments and executes the appropriate action */ @@ -42,15 +46,11 @@ async function handleArgs() { const { command, type } = findCommands(process.argv); if (!command) { - console.error("Error: Please provide a valid command (new or generate)"); + console.error("Error: Please provide a valid command (generate)"); process.exit(1); } switch (command) { - case "new": { - await initProject(); - return; - } case "generate": { if (!type) { console.error("Error: Please specify what to generate"); @@ -58,13 +58,13 @@ async function handleArgs() { break; } - if (!VALID_GENERATE_TYPES.includes(type as GenerateType)) { - console.error(`Error: Unknown generate type: ${type}`); + if (!isGenerateType(type)) { + console.error(`Error: Unknown generate argument: ${type}`); console.error(`Valid options: ${VALID_GENERATE_TYPES.join(", ")}`); break; } - switch (type as GenerateType) { + switch (type) { case "action-provider": await createActionProvider(); break; diff --git a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk index 05b54e8a0..999cb4375 100644 --- a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk +++ b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk @@ -1,37 +1,20 @@ -import { CustomActionProvider, {{ walletProvider }}, Network } from "@coinbase/agentkit"; +import { customActionProvider, {{ walletProvider }} } from "@coinbase/agentkit"; import { {{ schemaName }} } from "./schemas"; import { z } from "zod"; /** - * Action provider for {{ className }} operations. + * Creates a {{ className }} action provider. */ -export class {{ className }} extends CustomActionProvider<{{ walletProvider }}> { - /** - * Constructs a new {{ className }}. - */ - constructor() { - super([{ - name: "{{ actionName }}", - description: `This tool will perform a {{ className }} operation.`, - schema: {{ schemaName }}, - invoke: async (wallet: {{ walletProvider }}, args: z.infer) => { - try { - // Do work here - return `Successfully performed {{ actionName }} and returned the response`; - } catch (error) { - return `Error performing {{ actionName }}: Error: ${error}`; - } - } - }]); - } - - /** - * Checks if the {{ name }} supports the given network. - * - * @param network - The network to check. - * @returns True if the {{ name }} action provider supports the network, false otherwise. - */ - supportsNetwork = (network: Network) => {{ networkSupport | safe }}; -} - -export const {{ name }} = () => new {{ className }}(); \ No newline at end of file +export const {{ name }} = () => customActionProvider<{{ walletProvider }}>({ + name: "{{ actionName }}", + description: `This tool will perform a {{ className }} operation.`, + schema: {{ schemaName }}, + invoke: async (wallet: {{ walletProvider }}, args: z.infer) => { + try { + // Do work here + return `Successfully performed {{ actionName }} and returned the response`; + } catch (error) { + return `Error performing {{ actionName }}: Error: ${error}`; + } + }, +}); \ No newline at end of file diff --git a/typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts b/typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts index 58199bd1d..2ed9d4728 100644 --- a/typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts +++ b/typescript/create-onchain-agent/templates/createAgent/framework/langchain/createAgent.ts @@ -49,7 +49,6 @@ export async function createAgent(): Promise const tools = await getLangChainTools(agentkit); const memory = new MemorySaver(); - // Initialize Agent const canUseFaucet = walletProvider.getNetwork().networkId == "base-sepolia"; const faucetMessage = `If you ever need funds, you can request them from the faucet.`; const cantUseFaucetMessage = `If you need funds, you can provide your wallet details and request funds from the user.`; diff --git a/typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts b/typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts index ee0726b31..860a15402 100644 --- a/typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts +++ b/typescript/create-onchain-agent/templates/createAgent/framework/vercelAISDK/createAgent.ts @@ -50,7 +50,6 @@ export async function createAgent(): Promise { const { agentkit, walletProvider } = await prepareAgentkitAndWalletProvider(); - // Initialize Agent const canUseFaucet = walletProvider.getNetwork().networkId == "base-sepolia"; const faucetMessage = `If you ever need funds, you can request them from the faucet.`; const cantUseFaucetMessage = `If you need funds, you can provide your wallet details and request funds from the user.`; diff --git a/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk index 72293b2db..76f18872b 100644 --- a/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk +++ b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk @@ -1,7 +1,7 @@ import { {{ baseClass }} } from "@coinbase/agentkit"; import { Network } from "@coinbase/agentkit"; {% if protocolFamily === "EVM" %} -import { TransactionRequest, ReadContractParameters, ReadContractReturnType, Abi, ContractFunctionName, ContractFunctionArgs } from "viem"; +import { TransactionRequest, ReadContractParameters, ReadContractReturnType, Abi, ContractFunctionName, ContractFunctionArgs, Hex } from "viem"; {% else %} import { Connection, PublicKey, RpcResponseAndContext, SignatureStatus, SignatureStatusConfig, VersionedTransaction, SignatureResult } from "@solana/web3.js"; {% endif %} @@ -49,35 +49,35 @@ export class {{ className }} extends {{ baseClass }} { /** * Sign a message. */ - async signMessage(message: string | Uint8Array): Promise<`0x${string}`> { + async signMessage(message: string | Uint8Array): Promise { throw new Error("Method not implemented."); } /** * Sign typed data. */ - async signTypedData(typedData: any): Promise<`0x${string}`> { + async signTypedData(typedData: any): Promise { throw new Error("Method not implemented."); } /** * Sign a transaction. */ - async signTransaction(transaction: TransactionRequest): Promise<`0x${string}`> { + async signTransaction(transaction: TransactionRequest): Promise { throw new Error("Method not implemented."); } /** * Send a transaction. */ - async sendTransaction(transaction: TransactionRequest): Promise<`0x${string}`> { + async sendTransaction(transaction: TransactionRequest): Promise { throw new Error("Method not implemented."); } /** * Wait for a transaction receipt. */ - async waitForTransactionReceipt(txHash: `0x${string}`): Promise { + async waitForTransactionReceipt(txHash: Hex): Promise { throw new Error("Method not implemented."); } From 8489c7f9af7edfb3c6febfdabc7985b8f87b438d Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 24 Mar 2025 08:32:07 -0400 Subject: [PATCH 25/29] chore: updated README --- typescript/create-onchain-agent/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/typescript/create-onchain-agent/README.md b/typescript/create-onchain-agent/README.md index fe4e91dcf..8798f66b9 100644 --- a/typescript/create-onchain-agent/README.md +++ b/typescript/create-onchain-agent/README.md @@ -30,9 +30,6 @@ This command lets you choose between two project templates: After installing the `create-onchain-agent` CLI, you will also have the `agenkit` CLI installed. This allows you to generate components for your project. ```sh -# Identical to `npm create onchain-agent` -agentkit new - # Generate a custom wallet provider agentkit generate wallet-provider From d5331b9a52ca71d77d60d115d84f65f29d126e24 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 24 Mar 2025 08:40:49 -0400 Subject: [PATCH 26/29] chore: cleanup cli selection --- .../src/actions/createActionProvider.ts | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/typescript/create-onchain-agent/src/actions/createActionProvider.ts b/typescript/create-onchain-agent/src/actions/createActionProvider.ts index cbdb95ba9..10e409a69 100644 --- a/typescript/create-onchain-agent/src/actions/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createActionProvider.ts @@ -31,12 +31,12 @@ export async function createActionProvider() { }, { type: "select", - name: "networkFamily", - message: "Which network family will this support?", + name: "walletProvider", + message: "Which wallet provider is expected?", choices: [ - { title: "EVM", value: "EVM" }, - { title: "SVM", value: "SVM" }, - { title: "Both", value: "Both" }, + { title: "EvmWalletProvider (EVM networks)", value: "EvmWalletProvider" }, + { title: "SvmWalletProvider (Solana networks)", value: "SvmWalletProvider" }, + { title: "WalletProvider (Any network)", value: "WalletProvider" }, ], }, ]); @@ -48,32 +48,13 @@ export async function createActionProvider() { const snakeName = toSnakeCase(answers.name); const exportName = `${toCamelCase(baseName)}ActionProvider`; - // Determine wallet provider and networks based on network family - let walletProvider = "EvmWalletProvider"; - let networkSupport = "true"; - - switch (answers.networkFamily) { - case "EVM": - walletProvider = "EvmWalletProvider"; - networkSupport = 'network.protocolFamily === "EVM"'; - break; - case "SVM": - walletProvider = "SvmWalletProvider"; - networkSupport = 'network.protocolFamily === "SVM"'; - break; - case "Both": - walletProvider = "WalletProvider"; - networkSupport = "true"; - break; - } + const walletProvider = answers.walletProvider; // Generate code using nunjucks const generatedCode = nunjucks.render("actionProvider/actionProvider.njk", { name: exportName, className, walletProvider, - networks: "[]", - networkSupport, actionName: `${snakeName}_action`, schemaName: `${baseName}ActionSchema`, }); From 75ab0c212f8d242a768c7147e8302e16342d9db1 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Mon, 24 Mar 2025 12:27:12 -0400 Subject: [PATCH 27/29] feat: added None option to createActionProvider's walletProvider selection and propogated through the template --- .../src/actions/createActionProvider.ts | 3 ++- .../templates/actionProvider/actionProvider.njk | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/typescript/create-onchain-agent/src/actions/createActionProvider.ts b/typescript/create-onchain-agent/src/actions/createActionProvider.ts index 10e409a69..cc6f6b1ce 100644 --- a/typescript/create-onchain-agent/src/actions/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createActionProvider.ts @@ -34,9 +34,10 @@ export async function createActionProvider() { name: "walletProvider", message: "Which wallet provider is expected?", choices: [ + { title: "None (No wallet/network required)", value: "none" }, + { title: "WalletProvider (Any network)", value: "WalletProvider" }, { title: "EvmWalletProvider (EVM networks)", value: "EvmWalletProvider" }, { title: "SvmWalletProvider (Solana networks)", value: "SvmWalletProvider" }, - { title: "WalletProvider (Any network)", value: "WalletProvider" }, ], }, ]); diff --git a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk index 999cb4375..dae210c23 100644 --- a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk +++ b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk @@ -1,15 +1,15 @@ -import { customActionProvider, {{ walletProvider }} } from "@coinbase/agentkit"; +import { customActionProvider{% if walletProvider !== "none" %}, {{ walletProvider }}{% endif %} } from "@coinbase/agentkit"; import { {{ schemaName }} } from "./schemas"; import { z } from "zod"; /** * Creates a {{ className }} action provider. */ -export const {{ name }} = () => customActionProvider<{{ walletProvider }}>({ +export const {{ name }} = () => customActionProvider{% if walletProvider !== "none" %}<{{ walletProvider }}>{% endif %}({ name: "{{ actionName }}", description: `This tool will perform a {{ className }} operation.`, schema: {{ schemaName }}, - invoke: async (wallet: {{ walletProvider }}, args: z.infer) => { + invoke: async ({% if walletProvider !== "none" %}wallet: {{ walletProvider }}, {% endif %}args: z.infer) => { try { // Do work here return `Successfully performed {{ actionName }} and returned the response`; From e8c3ec06a5db6da39c3053137db47c646a7f7ea7 Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Tue, 25 Mar 2025 08:41:18 -0400 Subject: [PATCH 28/29] chore: PR feedback --- .../src/actions/createActionProvider.ts | 1 + .../actionProvider/actionProvider.njk | 28 ++++++++++--------- .../templates/actionProvider/schema.njk | 2 +- .../walletProvider/walletProvider.njk | 23 ++++++++++++--- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/typescript/create-onchain-agent/src/actions/createActionProvider.ts b/typescript/create-onchain-agent/src/actions/createActionProvider.ts index cc6f6b1ce..99b9caec5 100644 --- a/typescript/create-onchain-agent/src/actions/createActionProvider.ts +++ b/typescript/create-onchain-agent/src/actions/createActionProvider.ts @@ -66,6 +66,7 @@ export async function createActionProvider() { // Generate schema file using nunjucks with updated schema name const schemaCode = nunjucks.render("actionProvider/schema.njk", { className: `${baseName}Action`, + actionName: `${snakeName}_action`, }); await fs.writeFile(`./schemas.ts`, schemaCode); diff --git a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk index dae210c23..e1750b293 100644 --- a/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk +++ b/typescript/create-onchain-agent/templates/actionProvider/actionProvider.njk @@ -4,17 +4,19 @@ import { z } from "zod"; /** * Creates a {{ className }} action provider. + * To create multiple actions, pass in an array of actions to createActionProvider. */ -export const {{ name }} = () => customActionProvider{% if walletProvider !== "none" %}<{{ walletProvider }}>{% endif %}({ - name: "{{ actionName }}", - description: `This tool will perform a {{ className }} operation.`, - schema: {{ schemaName }}, - invoke: async ({% if walletProvider !== "none" %}wallet: {{ walletProvider }}, {% endif %}args: z.infer) => { - try { - // Do work here - return `Successfully performed {{ actionName }} and returned the response`; - } catch (error) { - return `Error performing {{ actionName }}: Error: ${error}`; - } - }, -}); \ No newline at end of file +export const {{ name }} = () => + customActionProvider{% if walletProvider !== "none" %}<{{ walletProvider }}>{% endif %}({ + name: "{{ actionName }}", + description: `This tool will perform a {{ className }} operation.`, + schema: {{ schemaName }}, + invoke: async ({% if walletProvider !== "none" %}wallet: {{ walletProvider }}, {% endif %}args: z.infer) => { + try { + // Do work here + return `Successfully performed {{ actionName }} and returned the response`; + } catch (error) { + return `Error performing {{ actionName }}: Error: ${error}`; + } + }, + }); diff --git a/typescript/create-onchain-agent/templates/actionProvider/schema.njk b/typescript/create-onchain-agent/templates/actionProvider/schema.njk index 3516780ae..0a460808c 100644 --- a/typescript/create-onchain-agent/templates/actionProvider/schema.njk +++ b/typescript/create-onchain-agent/templates/actionProvider/schema.njk @@ -8,4 +8,4 @@ export const {{ className }}Schema = z payload: z.string().describe("The payload to send to the action provider"), }) .strip() - .describe("Instructions for {{ actionName }}"); \ No newline at end of file + .describe("Instructions for {{ actionName }}"); diff --git a/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk index 76f18872b..733b67cb9 100644 --- a/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk +++ b/typescript/create-onchain-agent/templates/walletProvider/walletProvider.njk @@ -1,9 +1,24 @@ -import { {{ baseClass }} } from "@coinbase/agentkit"; -import { Network } from "@coinbase/agentkit"; +import { {{ baseClass }}, Network } from "@coinbase/agentkit"; {% if protocolFamily === "EVM" %} -import { TransactionRequest, ReadContractParameters, ReadContractReturnType, Abi, ContractFunctionName, ContractFunctionArgs, Hex } from "viem"; +import { + TransactionRequest, + ReadContractParameters, + ReadContractReturnType, + Abi, + ContractFunctionName, + ContractFunctionArgs, + Hex, +} from "viem"; {% else %} -import { Connection, PublicKey, RpcResponseAndContext, SignatureStatus, SignatureStatusConfig, VersionedTransaction, SignatureResult } from "@solana/web3.js"; +import { + Connection, + PublicKey, + RpcResponseAndContext, + SignatureStatus, + SignatureStatusConfig, + VersionedTransaction, + SignatureResult, +} from "@solana/web3.js"; {% endif %} /** From b63f51df6ad34212eb66d2e56bf765aae82a770f Mon Sep 17 00:00:00 2001 From: CarsonRoscoe Date: Tue, 25 Mar 2025 12:06:02 -0400 Subject: [PATCH 29/29] chore: updated changelog entry --- typescript/.changeset/wide-lions-fly.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/typescript/.changeset/wide-lions-fly.md b/typescript/.changeset/wide-lions-fly.md index ca9773ff0..e55e95110 100644 --- a/typescript/.changeset/wide-lions-fly.md +++ b/typescript/.changeset/wide-lions-fly.md @@ -3,3 +3,9 @@ --- Added code generation for individual building blocks + +Projects bootstrapped with the `create-onchain-agent` CLI will also have the `agenkit` CLI installed. +- `agentkit generate wallet-provider`: Generate a custom wallet provider +- `agentkit generate action-provider`: Generate a custom action provider +- `agentkit generate prepare`: Generate framework-agnostic AgentKit setup +- `agentkit generate create-agent`: Generate framework-specific agent creation