Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions typescript/.changeset/slick-points-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-onchain-agent": patch
---

Added multi framework support
8 changes: 5 additions & 3 deletions typescript/create-onchain-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This command will guide you through setting up an onchain agent project by promp
## Features

- **Guided setup**: Interactive prompts help configure the project.
- **Supports multiple AI frameworks**.
- **Supports multiple blockchain networks**.
- **Select your preferred wallet provider**.
- **Preconfigured with Next.js, React, Tailwind CSS, and ESLint**.
Expand All @@ -34,9 +35,10 @@ 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. **Network**: Choose from available blockchain networks.
4. **Chain ID**: Specify if using a custom network.
5. **Wallet Provider**: Select a preferred method for wallet management.
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:

Expand Down
74 changes: 53 additions & 21 deletions typescript/create-onchain-agent/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import path from "path";
import pc from "picocolors";
import prompts from "prompts";
import { EVM_NETWORKS, NetworkToWalletProviders, SVM_NETWORKS } from "./constants.js";
import { Network, WalletProviderChoice } from "./types.js";
import { Network, WalletProviderChoice, Framework } from "./types.js";
import { copyTemplate } from "./fileSystem.js";
import {
handleSelection,
handleNextSelection,
isValidPackageName,
toValidPackageName,
getWalletProviders,
} from "./utils.js";
import { Frameworks, FrameworkToTemplates } from "./constants.js";

/**
* Initializes the project creation process.
Expand Down Expand Up @@ -47,6 +48,8 @@ async function init() {
| "chainId"
| "rpcUrl"
| "walletProvider"
| "framework"
| "template"
>;

try {
Expand Down Expand Up @@ -76,6 +79,26 @@ async function init() {
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",
Expand Down Expand Up @@ -207,38 +230,47 @@ async function init() {
}
process.exit(1);
}
const { projectName, network, chainId, rpcUrl } = result;
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);
const root = await copyTemplate(projectName, packageName, template);

// Handle selection-specific logic over copied-template
await handleSelection(root, walletProvider, network, chainId, rpcUrl);
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}`));
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("- React"));
console.log(pc.gray("- Next.js"));
console.log(pc.gray("- Tailwind CSS"));
console.log(pc.gray("- ESLint"));
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(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(`\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;
default:
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");

console.log(pc.bold("\nLearn more"));
console.log(" - Checkout the docs");
Expand Down
93 changes: 61 additions & 32 deletions typescript/create-onchain-agent/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
Network,
SVMNetwork,
WalletProviderChoice,
WalletProviderRouteConfiguration,
AgentkitRouteConfiguration,
Framework,
Template,
} from "./types";

export const EVM_NETWORKS: EVMNetwork[] = [
Expand Down Expand Up @@ -56,58 +58,58 @@ export const WalletProviderChoices: WalletProviderChoice[] = [
]),
];

export const WalletProviderRouteConfigurations: Record<
export const AgentkitRouteConfigurations: Record<
"EVM" | "CUSTOM_EVM" | "SVM",
Partial<Record<WalletProviderChoice, WalletProviderRouteConfiguration>>
Partial<Record<WalletProviderChoice, AgentkitRouteConfiguration>>
> = {
EVM: {
CDP: {
env: {
topComments: ["Get keys from CDP Portal: https://portal.cdp.coinbase.com/"],
required: ["CDP_API_KEY_NAME=", "CDP_API_KEY_PRIVATE_KEY="],
required: ["CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"],
optional: [],
},
apiRoute: "evm/cdp/route.ts",
prepareAgentkitRoute: "evm/cdp/prepare-agentkit.ts",
},
Viem: {
env: {
topComments: [
"Export private key from your Ethereum wallet and save",
"Get keys from CDP Portal: https://portal.cdp.coinbase.com/",
],
required: ["PRIVATE_KEY="],
optional: ["CDP_API_KEY_NAME=", "CDP_API_KEY_PRIVATE_KEY="],
required: ["PRIVATE_KEY"],
optional: ["CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"],
},
apiRoute: "evm/viem/route.ts",
prepareAgentkitRoute: "evm/viem/prepare-agentkit.ts",
},
Privy: {
env: {
topComments: [
"Get keys from Privy Dashboard: https://dashboard.privy.io/",
"Get keys from CDP Portal: https://portal.cdp.coinbase.com/",
],
required: ["PRIVY_APP_ID=", "PRIVY_APP_SECRET="],
required: ["PRIVY_APP_ID", "PRIVY_APP_SECRET"],
optional: [
"CHAIN_ID=",
"PRIVY_WALLET_ID=",
"PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY=",
"PRIVY_WALLET_AUTHORIZATION_KEY_ID=",
"CDP_API_KEY_NAME=",
"CDP_API_KEY_PRIVATE_KEY=",
"CHAIN_ID",
"PRIVY_WALLET_ID",
"PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY",
"PRIVY_WALLET_AUTHORIZATION_KEY_ID",
"CDP_API_KEY_NAME",
"CDP_API_KEY_PRIVATE_KEY",
],
},
apiRoute: "evm/privy/route.ts",
prepareAgentkitRoute: "evm/privy/prepare-agentkit.ts",
},
SmartWallet: {
env: {
topComments: [
"Get keys from CDP Portal: https://portal.cdp.coinbase.com/",
"Optionally provide a private key, otherwise one will be generated",
],
required: ["CDP_API_KEY_NAME=", "CDP_API_KEY_PRIVATE_KEY="],
optional: ["PRIVATE_KEY="],
required: ["CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"],
optional: ["PRIVATE_KEY"],
},
apiRoute: "evm/smart/route.ts",
prepareAgentkitRoute: "evm/smart/prepare-agentkit.ts",
},
},
CUSTOM_EVM: {
Expand All @@ -117,10 +119,10 @@ export const WalletProviderRouteConfigurations: Record<
"Export private key from your Ethereum wallet and save",
"Get keys from CDP Portal: https://portal.cdp.coinbase.com/",
],
required: ["PRIVATE_KEY="],
optional: ["CDP_API_KEY_NAME=", "CDP_API_KEY_PRIVATE_KEY="],
required: ["PRIVATE_KEY"],
optional: ["CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"],
},
apiRoute: "custom-evm/viem/route.ts",
prepareAgentkitRoute: "custom-evm/viem/prepare-agentkit.ts",
},
},
SVM: {
Expand All @@ -130,27 +132,54 @@ export const WalletProviderRouteConfigurations: Record<
"Export private key from your Solana wallet and save",
"Get keys from CDP Portal: https://portal.cdp.coinbase.com/",
],
required: ["SOLANA_PRIVATE_KEY="],
optional: ["SOLANA_RPC_URL=", "CDP_API_KEY_NAME=", "CDP_API_KEY_PRIVATE_KEY="],
required: ["SOLANA_PRIVATE_KEY"],
optional: ["SOLANA_RPC_URL", "CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"],
},
apiRoute: "svm/solanaKeypair/route.ts",
prepareAgentkitRoute: "svm/solanaKeypair/prepare-agentkit.ts",
},
Privy: {
env: {
topComments: [
"Get keys from Privy Dashboard: https://dashboard.privy.io/",
"Get keys from CDP Portal: https://portal.cdp.coinbase.com/",
],
required: ["PRIVY_APP_ID=", "PRIVY_APP_SECRET="],
required: ["PRIVY_APP_ID", "PRIVY_APP_SECRET"],
optional: [
"PRIVY_WALLET_ID=",
"PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY=",
"PRIVY_WALLET_AUTHORIZATION_KEY_ID=",
"CDP_API_KEY_NAME=",
"CDP_API_KEY_PRIVATE_KEY=",
"PRIVY_WALLET_ID",
"PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY",
"PRIVY_WALLET_AUTHORIZATION_KEY_ID",
"CDP_API_KEY_NAME",
"CDP_API_KEY_PRIVATE_KEY",
],
},
apiRoute: "svm/privy/route.ts",
prepareAgentkitRoute: "svm/privy/prepare-agentkit.ts",
},
},
};

export const Frameworks: Framework[] = ["Langchain", "Vercel AI SDK"];

export const Templates: Template[] = ["next"];

export const FrameworkToTemplates: Record<Framework, Template[]> = {
Langchain: ["next"],
"Vercel AI SDK": ["next"],
};

export type NextTemplateRouteConfiguration = {
createAgentRoute: `${string}.ts`;
apiRoute: `${string}.ts`;
};

export const NextTemplateRouteConfigurations: Partial<
Record<Framework, NextTemplateRouteConfiguration>
> = {
Langchain: {
apiRoute: "langchain/route.ts",
createAgentRoute: "langchain/create-agent.ts",
},
"Vercel AI SDK": {
apiRoute: "vercel-ai-sdk/route.ts",
createAgentRoute: "vercel-ai-sdk/create-agent.ts",
},
};
17 changes: 12 additions & 5 deletions typescript/create-onchain-agent/src/fileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { optimizedCopy } from "./utils.js";
import { Template } from "./types.js";

const sourceDir = path.resolve(fileURLToPath(import.meta.url), "../../../templates/next");
const sourceDir = path.resolve(fileURLToPath(import.meta.url), "../../../templates");

const renameFiles: Record<string, string | undefined> = {
_gitignore: ".gitignore",
Expand All @@ -16,10 +17,11 @@ const excludeFiles = [".DS_Store", "Thumbs.db"];
/**
* Retrieves the source directory for copying template files.
*
* @param template - The template to use.
* @returns {string} The source directory path.
*/
function getSourceDir(): string {
return sourceDir;
function getSourceDir(template: Template): string {
return path.join(sourceDir, template);
}

/**
Expand Down Expand Up @@ -62,11 +64,16 @@ async function copyDir(src: string, dest: string): Promise<void> {
*
* @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.
* @returns {Promise<string>} The path of the newly created project directory.
*/
export async function copyTemplate(projectName: string, packageName: string): Promise<string> {
export async function copyTemplate(
projectName: string,
packageName: string,
template: Template,
): Promise<string> {
const root = path.join(process.cwd(), projectName);
await copyDir(getSourceDir(), root);
await copyDir(getSourceDir(template), root);

const pkgPath = path.join(root, "package.json");
const pkg = JSON.parse(await fs.promises.readFile(pkgPath, "utf-8"));
Expand Down
8 changes: 6 additions & 2 deletions typescript/create-onchain-agent/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export type Network = EVMNetwork | SVMNetwork;

export type WalletProviderChoice = "CDP" | "Viem" | "Privy" | "SolanaKeypair" | "SmartWallet";

export type WalletProviderRouteConfiguration = {
export type AgentkitRouteConfiguration = {
env: {
topComments: string[];
required: string[];
optional: string[];
};
apiRoute: string;
prepareAgentkitRoute: `${string}.ts`;
};

export type NetworkSelection = {
Expand All @@ -32,3 +32,7 @@ export type NetworkSelection = {
chainId?: string;
rpcUrl?: string;
};

export type Framework = "Langchain" | "Vercel AI SDK";

export type Template = "next";
Loading