├── .eslintrc.json ├── .gitignore ├── .windsurfrules ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── cli-reference │ ├── configuration.mdx │ └── create-command.mdx ├── core-concepts │ ├── project-structure.mdx │ └── templates.mdx ├── development │ ├── deployment.mdx │ ├── troubleshooting.mdx │ └── workflow.mdx ├── docs.json ├── favicon.svg ├── getting-started │ ├── installation.mdx │ ├── introduction.mdx │ └── quickstart.mdx ├── integrations │ ├── contracts │ │ ├── foundry.mdx │ │ └── hardhat.mdx │ └── wallets │ │ ├── rainbowkit.mdx │ │ └── thirdweb.mdx └── templates │ ├── farcaster-miniapp.mdx │ ├── minipay.mdx │ └── web-app-template.mdx ├── jest.config.cjs ├── package.json ├── pnpm-lock.yaml ├── scripts ├── postpack-restore.cjs ├── prepack-prune.cjs └── release.sh ├── src ├── commands │ └── create.ts ├── generators │ ├── plop-generator.ts │ └── project-generator.ts ├── index.ts ├── plopfile.ts └── utils │ ├── paths.ts │ ├── safe-copy.ts │ └── validation.ts ├── templates ├── ai │ └── chat-template │ │ ├── .env.example │ │ ├── .eslintrc.json │ │ ├── .github │ │ └── workflows │ │ │ ├── lint.yml │ │ │ └── playwright.yml │ │ ├── .gitignore │ │ ├── .vscode │ │ ├── extensions.json │ │ └── settings.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── app │ │ ├── (auth) │ │ │ ├── actions.ts │ │ │ ├── api │ │ │ │ └── auth │ │ │ │ │ ├── [...nextauth] │ │ │ │ │ └── route.ts │ │ │ │ │ └── guest │ │ │ │ │ └── route.ts │ │ │ ├── auth.config.ts │ │ │ ├── auth.ts │ │ │ ├── login │ │ │ │ └── page.tsx │ │ │ └── register │ │ │ │ └── page.tsx │ │ ├── (chat) │ │ │ ├── actions.ts │ │ │ ├── api │ │ │ │ ├── chat-model │ │ │ │ │ └── route.ts │ │ │ │ ├── chat │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── stream │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── route.ts │ │ │ │ │ └── schema.ts │ │ │ │ ├── document │ │ │ │ │ └── route.ts │ │ │ │ ├── files │ │ │ │ │ └── upload │ │ │ │ │ │ └── route.ts │ │ │ │ ├── history │ │ │ │ │ └── route.ts │ │ │ │ ├── models │ │ │ │ │ └── route.ts │ │ │ │ ├── suggestions │ │ │ │ │ └── route.ts │ │ │ │ ├── vote │ │ │ │ │ └── route.ts │ │ │ │ └── wallet │ │ │ │ │ └── route.ts │ │ │ ├── chat │ │ │ │ └── [id] │ │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── opengraph-image.png │ │ │ ├── page.tsx │ │ │ └── twitter-image.png │ │ ├── favicon.ico │ │ ├── globals.css │ │ └── layout.tsx │ │ ├── artifacts │ │ ├── actions.ts │ │ ├── code │ │ │ ├── client.tsx │ │ │ └── server.ts │ │ ├── image │ │ │ ├── client.tsx │ │ │ └── server.ts │ │ ├── sheet │ │ │ ├── client.tsx │ │ │ └── server.ts │ │ └── text │ │ │ ├── client.tsx │ │ │ └── server.ts │ │ ├── biome.jsonc │ │ ├── components.json │ │ ├── components │ │ ├── app-sidebar.tsx │ │ ├── artifact-actions.tsx │ │ ├── artifact-close-button.tsx │ │ ├── artifact-messages.tsx │ │ ├── artifact.tsx │ │ ├── auth-form.tsx │ │ ├── chat-header.tsx │ │ ├── chat.tsx │ │ ├── code-block.tsx │ │ ├── code-editor.tsx │ │ ├── console.tsx │ │ ├── create-artifact.tsx │ │ ├── data-stream-handler.tsx │ │ ├── data-stream-provider.tsx │ │ ├── diffview.tsx │ │ ├── document-preview.tsx │ │ ├── document-skeleton.tsx │ │ ├── document.tsx │ │ ├── greeting.tsx │ │ ├── icons.tsx │ │ ├── image-editor.tsx │ │ ├── markdown.tsx │ │ ├── message-actions.tsx │ │ ├── message-editor.tsx │ │ ├── message-reasoning.tsx │ │ ├── message.tsx │ │ ├── messages.tsx │ │ ├── model-selector.tsx │ │ ├── multimodal-input.tsx │ │ ├── preview-attachment.tsx │ │ ├── sheet-editor.tsx │ │ ├── sidebar-history-item.tsx │ │ ├── sidebar-history.tsx │ │ ├── sidebar-toggle.tsx │ │ ├── sidebar-user-nav.tsx │ │ ├── sign-out-form.tsx │ │ ├── submit-button.tsx │ │ ├── suggested-actions.tsx │ │ ├── suggestion.tsx │ │ ├── text-editor.tsx │ │ ├── theme-provider.tsx │ │ ├── thirdweb-client-provider.tsx │ │ ├── toast.tsx │ │ ├── toolbar.tsx │ │ ├── ui │ │ │ ├── alert-dialog.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── textarea.tsx │ │ │ └── tooltip.tsx │ │ ├── version-footer.tsx │ │ ├── visibility-selector.tsx │ │ └── wallet-connect.tsx │ │ ├── drizzle.config.ts │ │ ├── hooks │ │ ├── use-artifact.ts │ │ ├── use-auto-resume.ts │ │ ├── use-chat-visibility.ts │ │ ├── use-messages.tsx │ │ ├── use-mobile.tsx │ │ ├── use-scroll-to-bottom.tsx │ │ └── use-wallet-address.ts │ │ ├── instrumentation.ts │ │ ├── lib │ │ ├── ai │ │ │ ├── entitlements.ts │ │ │ ├── models.test.ts │ │ │ ├── models.ts │ │ │ ├── prompts.ts │ │ │ ├── providers.ts │ │ │ └── tools │ │ │ │ ├── create-document.ts │ │ │ │ ├── get-balance.ts │ │ │ │ ├── get-block-number.ts │ │ │ │ ├── get-token-balance.ts │ │ │ │ ├── lookup-ens-name.ts │ │ │ │ ├── resolve-ens-name.ts │ │ │ │ ├── resolve-token-address.ts │ │ │ │ └── update-document.ts │ │ ├── artifacts │ │ │ └── server.ts │ │ ├── constants.ts │ │ ├── db │ │ │ ├── helpers │ │ │ │ └── 01-core-to-parts.ts │ │ │ ├── migrate.ts │ │ │ ├── migrations │ │ │ │ ├── 0000_minor_daimon_hellstrom.sql │ │ │ │ └── meta │ │ │ │ │ ├── 0000_snapshot.json │ │ │ │ │ └── _journal.json │ │ │ ├── queries.ts │ │ │ ├── schema.ts │ │ │ └── utils.ts │ │ ├── editor │ │ │ ├── config.ts │ │ │ ├── diff.js │ │ │ ├── functions.tsx │ │ │ ├── react-renderer.tsx │ │ │ └── suggestions.tsx │ │ ├── errors.ts │ │ ├── thirdweb.ts │ │ ├── types.ts │ │ └── utils.ts │ │ ├── middleware.ts │ │ ├── next-env.d.ts │ │ ├── next.config.ts │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── postcss.config.mjs │ │ ├── public │ │ └── images │ │ │ ├── demo-thumbnail.png │ │ │ └── mouth of the seine, monet.jpg │ │ ├── tailwind.config.ts │ │ ├── tests │ │ ├── e2e │ │ │ ├── artifacts.test.ts │ │ │ ├── chat.test.ts │ │ │ ├── reasoning.test.ts │ │ │ └── session.test.ts │ │ ├── fixtures.ts │ │ ├── helpers.ts │ │ ├── pages │ │ │ ├── artifact.ts │ │ │ ├── auth.ts │ │ │ └── chat.ts │ │ ├── prompts │ │ │ ├── basic.ts │ │ │ ├── routes.ts │ │ │ └── utils.ts │ │ └── routes │ │ │ ├── chat.test.ts │ │ │ └── document.test.ts │ │ ├── tsconfig.json │ │ └── tsconfig.tsbuildinfo ├── base │ ├── .gitignore.hbs │ ├── README.md.hbs │ ├── apps │ │ └── web │ │ │ ├── .env.template.hbs │ │ │ ├── next.config.js.hbs │ │ │ ├── package.json.hbs │ │ │ ├── postcss.config.js.hbs │ │ │ ├── src │ │ │ ├── app │ │ │ │ ├── globals.css.hbs │ │ │ │ ├── layout.tsx.hbs │ │ │ │ └── page.tsx.hbs │ │ │ ├── components │ │ │ │ ├── navbar.tsx.hbs │ │ │ │ └── ui │ │ │ │ │ ├── button.tsx.hbs │ │ │ │ │ ├── card.tsx.hbs │ │ │ │ │ └── sheet.tsx.hbs │ │ │ └── lib │ │ │ │ ├── app-utils.ts.hbs │ │ │ │ └── utils.ts.hbs │ │ │ ├── tailwind.config.js.hbs │ │ │ └── tsconfig.json.hbs │ ├── package.json.hbs │ ├── pnpm-workspace.yaml.hbs │ ├── tsconfig.json.hbs │ └── turbo.json.hbs ├── contracts │ ├── foundry │ │ ├── .gitignore.hbs │ │ ├── foundry.toml.hbs │ │ ├── script │ │ │ └── Counter.s.sol.hbs │ │ ├── src │ │ │ └── Counter.sol.hbs │ │ └── test │ │ │ └── Counter.t.sol.hbs │ └── hardhat │ │ ├── .env.example.hbs │ │ ├── .gitignore.hbs │ │ ├── README.md.hbs │ │ ├── contracts │ │ └── Lock.sol.hbs │ │ ├── hardhat.config.ts.hbs │ │ ├── ignition │ │ └── modules │ │ │ └── Lock.ts.hbs │ │ ├── package.json.hbs │ │ ├── test │ │ └── Lock.ts.hbs │ │ └── tsconfig.json.hbs ├── farcaster-miniapp │ ├── FARCASTER_SETUP.md.hbs │ └── apps │ │ └── web │ │ ├── .eslintrc.json.hbs │ │ ├── public │ │ ├── icon.png │ │ └── opengraph-image.png │ │ └── src │ │ ├── app │ │ ├── .well-known │ │ │ └── farcaster.json │ │ │ │ └── route.ts.hbs │ │ └── api │ │ │ ├── auth │ │ │ └── sign-in │ │ │ │ └── route.ts.hbs │ │ │ ├── notify │ │ │ └── route.ts.hbs │ │ │ └── webhook │ │ │ └── route.ts.hbs │ │ ├── components │ │ ├── Eruda │ │ │ ├── eruda-provider.tsx.hbs │ │ │ └── index.tsx.hbs │ │ ├── connect-button.tsx.hbs │ │ └── providers.tsx.hbs │ │ ├── contexts │ │ ├── frame-wallet-context.tsx.hbs │ │ └── miniapp-context.tsx.hbs │ │ ├── hooks │ │ └── use-api.ts.hbs │ │ └── lib │ │ ├── env.ts.hbs │ │ ├── memory-store.ts.hbs │ │ ├── notification-client.ts.hbs │ │ └── warpcast.ts.hbs ├── minipay │ ├── components │ │ ├── connect-button.tsx.hbs │ │ ├── user-balance.tsx.hbs │ │ └── wallet-provider.tsx.hbs │ └── tailwind.config.js.hbs └── wallets │ ├── rainbowkit │ └── components │ │ ├── connect-button.tsx.hbs │ │ └── wallet-provider.tsx.hbs │ └── thirdweb │ ├── components │ ├── connect-button.tsx.hbs │ └── wallet-provider.tsx.hbs │ └── lib │ └── client.ts.hbs └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | "plugins": ["@typescript-eslint"], 9 | "parserOptions": { 10 | "ecmaVersion": 2020, 11 | "sourceType": "module", 12 | "project": "./tsconfig.json" 13 | }, 14 | "env": { 15 | "node": true, 16 | "es6": true 17 | }, 18 | "rules": { 19 | "@typescript-eslint/no-unused-vars": "error", 20 | "@typescript-eslint/explicit-function-return-type": "warn", 21 | "@typescript-eslint/no-explicit-any": "error", 22 | "prefer-const": "error", 23 | "no-var": "error" 24 | }, 25 | "ignorePatterns": ["dist/", "node_modules/", "*.js", "*.cjs", "scripts/*.cjs"] 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Celo Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Celo Composer CLI 2 | 3 | A powerful CLI tool for generating customizable Celo blockchain starter kits with modern monorepo architecture. 4 | 5 | ## Features 6 | 7 | - 🚀 **Modern Stack**: Next.js 14, TypeScript, Tailwind CSS, shadcn/ui 8 | - 📦 **Monorepo Ready**: Turborepo with PNPM workspaces 9 | - 🎨 **Beautiful UI**: Pre-configured shadcn/ui components 10 | - 🔧 **Developer Experience**: Interactive prompts and clear feedback 11 | - 🌍 **Celo Optimized**: Ready for Celo blockchain development 12 | 13 | ## Installation 14 | 15 | ```bash 16 | # Install dependencies 17 | pnpm install 18 | 19 | # Build the CLI 20 | pnpm build 21 | 22 | # Link for global usage (optional) 23 | npm link 24 | ``` 25 | 26 | ## Usage 27 | 28 | ### Create a new Celo project 29 | 30 | ```bash 31 | # Interactive mode 32 | pnpm dev create 33 | 34 | # With project name 35 | pnpm dev create my-celo-app 36 | 37 | # With options 38 | pnpm dev create my-celo-app --description "My awesome Celo app" --skip-install 39 | ``` 40 | 41 | ### Command Options 42 | 43 | - `--description ` - Project description 44 | - `--skip-install` - Skip package installation 45 | 46 | ## Generated Project Structure 47 | 48 | ``` 49 | my-celo-app/ 50 | ├── apps/ 51 | │ └── web/ # Next.js application 52 | ├── packages/ 53 | │ ├── ui/ # shadcn/ui components 54 | │ └── utils/ # Shared utilities 55 | ├── package.json # Root package.json 56 | ├── pnpm-workspace.yaml # PNPM workspace config 57 | ├── turbo.json # Turborepo configuration 58 | └── tsconfig.json # TypeScript configuration 59 | ``` 60 | 61 | ## Development 62 | 63 | ```bash 64 | # Start development 65 | pnpm dev 66 | 67 | # Build 68 | pnpm build 69 | 70 | # Lint 71 | pnpm lint 72 | 73 | # Run tests 74 | pnpm test 75 | ``` 76 | 77 | ## Tech Stack 78 | 79 | - **CLI Framework**: Commander.js + Inquirer.js 80 | - **Template Engine**: Plop.js 81 | - **Language**: TypeScript 82 | - **Generated Projects**: Next.js 14 + Turborepo + shadcn/ui 83 | 84 | ## License 85 | 86 | MIT 87 | -------------------------------------------------------------------------------- /docs/cli-reference/configuration.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Project Configuration 3 | description: "Learn how to configure your Celo Composer project." 4 | icon: "sliders" 5 | --- 6 | 7 | Your Celo Composer project is configured primarily through environment variables. This allows you to manage sensitive information like API keys and private keys securely, without hardcoding them into your application. 8 | 9 | ## Environment Variables 10 | 11 | When you create a new project, a `.env.example` file is generated in the root of your project. To set up your local environment, you should: 12 | 13 | 1. **Create a copy** of this file and name it `.env`. 14 | 2. **Fill in the required values** in the `.env` file. 15 | 16 | ```bash 17 | # Copy the example file 18 | cp .env.example .env 19 | ``` 20 | 21 | 22 | The `.env` file contains sensitive information and is included in the `.gitignore` file by default. **Never commit your `.env` file to version control.** 23 | 24 | 25 | 26 | 27 | ## Build & Deployment Configuration 28 | 29 | - **`turbo.json`**: This file configures the build pipeline for your monorepo using Turborepo. You can define the dependencies between your packages and applications here. 30 | - **`next.config.js`**: Located in `apps/web`, this file allows you to customize the configuration for your Next.js application. 31 | 32 | For most use cases, you won't need to modify these files, but they are available for advanced configuration if needed. 33 | -------------------------------------------------------------------------------- /docs/core-concepts/project-structure.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Project Structure 3 | description: "Understand the monorepo architecture of your Celo Composer project." 4 | icon: "backpack" 5 | --- 6 | 7 | Celo Composer generates projects with a modern monorepo structure, designed for scalability, code sharing, and efficient development. We use PNPM workspaces and Turborepo to manage the monorepo. 8 | 9 | Here's a typical directory layout: 10 | 11 | ```plaintext 12 | my-celo-app/ 13 | ├── apps/ 14 | │ ├── web/ # Next.js frontend application 15 | │ └── hardhat/ # Optional: Hardhat smart contracts 16 | ├── package.json # Root package.json 17 | ├── pnpm-workspace.yaml # Defines the workspace 18 | └── turbo.json # Turborepo configuration 19 | ``` 20 | 21 | ## `apps` Directory 22 | 23 | The `apps` directory contains the individual applications within your monorepo. All parts of your project, including the frontend and smart contracts, are treated as separate applications. 24 | 25 | - **`apps/web`**: This is the main Next.js web application that serves as the user-facing frontend of your dApp. 26 | 27 | - **`apps/hardhat`** (Optional): If you choose to include the Hardhat framework, it will be set up as a separate application here. It contains your Solidity smart contracts, deployment scripts, and tests. 28 | 29 | ## Tooling 30 | 31 | - **PNPM Workspaces**: PNPM is used to manage dependencies and link local packages together. The `pnpm-workspace.yaml` file at the root defines which directories are part of the workspace. 32 | 33 | - **Turborepo**: Turborepo is a high-performance build system for JavaScript and TypeScript monorepos. It speeds up your development workflow by caching build outputs and running tasks in parallel. The `turbo.json` file configures the task pipeline for your monorepo. 34 | -------------------------------------------------------------------------------- /docs/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /docs/getting-started/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: "Set up the Celo Composer CLI on your local machine." 4 | icon: "terminal" 5 | --- 6 | 7 | ## Prerequisites 8 | 9 | Before you begin, make sure you have the following installed on your system: 10 | 11 | - **Node.js**: Version 18.0 or higher. You can download it from [nodejs.org](https://nodejs.org/). 12 | - **PNPM**: We recommend using PNPM as the package manager for your projects. You can install it with: 13 | ```bash 14 | npm install -g pnpm 15 | ``` 16 | 17 | Celo Composer is a command-line tool that you can run directly using `npx`, which is included with Node.js. This allows you to use the CLI without needing to install it globally on your machine. 18 | 19 | ### How to Run 20 | 21 | To use Celo Composer, you will run the `create` command using `npx`. This tool will always use the latest version of Celo Composer available. 22 | 23 | ```bash 24 | npx @celo/celo-composer@latest create 25 | ``` 26 | 27 | This command will initiate the project creation process, guiding you through the setup of your new Celo dApp. 28 | 29 | ## Next Steps 30 | 31 | Now that you know how to run Celo Composer, you're ready to create your first project. Head over to the **[Quick Start](/getting-started/quickstart)** guide to get started. 32 | -------------------------------------------------------------------------------- /docs/getting-started/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: "Welcome to Celo Composer, your all-in-one tool for building on the Celo blockchain." 4 | icon: "sparkles" 5 | --- 6 | 7 | ## What is Celo Composer? 8 | 9 | Celo Composer is a powerful command-line interface (CLI) tool designed to simplify and accelerate the development of decentralized applications (dApps) on the Celo blockchain. Whether you're a seasoned developer or just starting your journey in Web3, Celo Composer provides a streamlined, efficient, and customizable workflow to bring your ideas to life. 10 | 11 | 12 | 13 | From project scaffolding to smart contract deployment, Celo Composer 14 | provides a unified developer experience. 15 | 16 | 17 | Tailored specifically for the Celo ecosystem, with built-in support for 18 | Celo-specific features and networks. 19 | 20 | 21 | 22 | ## Key Features 23 | 24 | - **Rapid Scaffolding**: Generate full-stack, production-ready dApps in minutes with a single command. 25 | - **Monorepo Architecture**: Built-in support for Turborepo and PNPM for efficient monorepo management. 26 | - **Template-Driven**: Choose from a variety of templates, including basic web apps, Minipay dApps, and Farcaster Miniapps. 27 | - **Customizable Integrations**: Easily add wallet providers like RainbowKit or Thirdweb, and integrate Hardhat for smart contract development. 28 | - **Next.js 14+ & shadcn/ui**: Modern, professional frontend with the latest technologies. 29 | 30 | ## Who is this for? 31 | 32 | Celo Composer is designed for any developer looking to build on the Celo blockchain. It's perfect for: 33 | 34 | - **Frontend Developers** who want to quickly build and deploy dApps without worrying about complex backend setup. 35 | - **Smart Contract Engineers** who need a robust environment for developing, testing, and deploying contracts. 36 | - **Web3 Startups & Teams** looking for a standardized, scalable, and efficient development workflow. 37 | - **Hobbyists & Learners** who want to explore the Celo ecosystem with a low-friction entry point. 38 | 39 | Ready to get started? Head over to the [Installation](/getting-started/installation) guide to set up the CLI. 40 | -------------------------------------------------------------------------------- /docs/getting-started/quickstart.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start 3 | description: "Create and run your first Celo dApp in under 5 minutes." 4 | icon: "rocket" 5 | --- 6 | 7 | This guide will walk you through creating a new Celo project, understanding its structure, and running it locally. 8 | 9 | ## 1. Create a New Project 10 | 11 | To create a new project, open your terminal and run: 12 | 13 | ```bash 14 | npx @celo/celo-composer@latest create my-celo-app 15 | ``` 16 | 17 | The CLI will guide you through a series of prompts to configure your project. For this quick start, you can accept the default options by pressing `Enter` for each prompt. 18 | 19 | 20 | **Pro Tip:** You can also run `npx @celo/celo-composer@latest create my-celo-app --yes` to skip all prompts and use the default settings automatically. 21 | 22 | 23 | ## 2. Navigate to Your Project 24 | 25 | Once the project is created, navigate into the project directory: 26 | 27 | ```bash 28 | cd my-celo-app 29 | ``` 30 | 31 | ## 3. Run the Development Server 32 | 33 | To start the local development server, run the following command: 34 | 35 | ```bash 36 | pnpm dev 37 | ``` 38 | 39 | This will start the frontend application on `http://localhost:3000`. Open this URL in your browser to see your new Celo dApp in action! 40 | 41 | ## Project Structure Overview 42 | 43 | Your new project has a monorepo structure, managed by Turborepo. Here's a quick look at the key directories: 44 | 45 | - **`apps/web`**: This is your Next.js frontend application. 46 | - **`apps/hardhat`**: If you chose to include a smart contract framework like Hardhat, your contracts and tests will be located here. 47 | 48 | ## What's Next? 49 | 50 | Congratulations! You've successfully created and run your first Celo dApp using Celo Composer. 51 | 52 | Now you can explore the other sections of these docs to learn more about: 53 | 54 | - **[Core Concepts](/core-concepts/project-structure)**: Understand the project structure in more detail. 55 | - **[CLI Reference](/cli-reference/create-command)**: Learn about all the available CLI commands and options. 56 | - **[Templates & Integrations](/templates/web-app-template)**: Discover the available templates and integrations. 57 | -------------------------------------------------------------------------------- /docs/integrations/contracts/foundry.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Foundry" 3 | description: "Learn how to use the Foundry smart contract template in Celo Composer." 4 | icon: "toolbox" 5 | tag: "NEW" 6 | --- 7 | 8 | The Foundry template provides a complete, pre-configured environment for developing, testing, and deploying smart contracts on Celo using the Foundry toolkit. 9 | 10 | Foundry is a blazing fast, portable, and modular toolkit for Ethereum application development written in Rust. 11 | 12 | ## How to Use 13 | 14 | To generate a project with the Foundry template, select `foundry` when prompted for a smart contract framework, or use the `--contracts foundry` flag: 15 | 16 | ```bash 17 | npx @celo/celo-composer@latest create my-foundry-project --contracts foundry 18 | ``` 19 | 20 | This will create a new directory at `packages/contracts` in your project, structured as a standard Foundry project. 21 | 22 | ## What's Included 23 | 24 | The template comes with the following features and configurations out of the box: 25 | 26 | - **Celo Network Configuration**: `foundry.toml` is pre-configured with RPC endpoints for Celo and Alfajores testnet. 27 | - **Sample Contract**: A simple `Counter.sol` contract to get you started. 28 | - **Deployment Scripts**: A `Counter.s.sol` script for deploying your contract using `forge script`. 29 | - **Tests**: A `Counter.t.sol` test file with examples of how to write tests for your contract using `forge test`. 30 | - **`.env.example`**: A template for your environment variables, including `PRIVATE_KEY` and `CELOSCAN_API_KEY`. 31 | 32 | ## Key Commands 33 | 34 | All commands should be run from the `packages/contracts` directory. 35 | 36 | ### Compile Contracts 37 | 38 | ```bash 39 | forge build 40 | ``` 41 | 42 | ### Run Tests 43 | 44 | ```bash 45 | forge test 46 | ``` 47 | 48 | ### Deploy Contract 49 | 50 | To deploy to a specific network, you'll need to have your `PRIVATE_KEY` set in a `.env` file. 51 | 52 | ```bash 53 | # Deploy to Alfajores 54 | forge script script/Counter.s.sol:CounterScript --rpc-url alfajores --broadcast --verify 55 | 56 | # Deploy to Celo Mainnet 57 | forge script script/Counter.s.sol:CounterScript --rpc-url mainnet --broadcast --verify 58 | ``` 59 | 60 | ### Local Development with Anvil 61 | 62 | Anvil is a local testnet node included with Foundry. You can start a local node with: 63 | 64 | ```bash 65 | anvil 66 | ``` 67 | 68 | This will start a local chain that you can deploy to and interact with for development and testing. 69 | -------------------------------------------------------------------------------- /docs/integrations/wallets/rainbowkit.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: RainbowKit 3 | description: "Integrate the RainbowKit wallet connector into your Celo dApp." 4 | icon: "rainbow" 5 | --- 6 | 7 | RainbowKit is a popular, easy-to-use, and customizable wallet connector for React applications. It provides a beautiful UI for connecting to a wide range of wallets, including MetaMask, Valora, and WalletConnect-compatible wallets. 8 | 9 | When you select RainbowKit, Celo Composer automatically installs the necessary dependencies, adds the provider components, and includes a ready-to-use `ConnectButton` in your UI. 10 | 11 | ## Environment Variables 12 | 13 | To use RainbowKit, you'll need a Project ID from WalletConnect Cloud. 14 | 15 | - **`NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID`**: Your project-specific ID. 16 | 17 | 18 | You can get your free Project ID from [WalletConnect 19 | Cloud](https://cloud.walletconnect.com/). 20 | 21 | -------------------------------------------------------------------------------- /docs/integrations/wallets/thirdweb.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Thirdweb 3 | description: "Integrate the Thirdweb wallet SDK into your Celo dApp." 4 | icon: "hotel" 5 | --- 6 | 7 | Thirdweb is a complete Web3 development framework that includes a powerful wallet SDK. It supports both external wallets (like MetaMask) and in-app wallets, which allow users to sign up with just an email or social account. 8 | 9 | When you select Thirdweb, Celo Composer automatically installs the necessary dependencies, adds the provider components, and includes a ready-to-use `ConnectButton` in your UI. 10 | 11 | ## Environment Variables 12 | 13 | To use the Thirdweb SDK, you'll need a Client ID from the Thirdweb Dashboard. 14 | 15 | - **`NEXT_PUBLIC_THIRDWEB_CLIENT_ID`**: Your project-specific client ID. 16 | 17 | 18 | You can get your free Client ID from the [Thirdweb 19 | Dashboard](https://thirdweb.com/dashboard/settings/api-keys). 20 | 21 | -------------------------------------------------------------------------------- /docs/templates/farcaster-miniapp.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Farcaster Miniapp Template 3 | description: "Learn how to build Farcaster Miniapps with Celo Composer." 4 | icon: "podcast" 5 | tag: "NEW" 6 | --- 7 | 8 | The Farcaster Miniapp template provides a powerful starting point for building applications that run directly inside the Farcaster ecosystem. These interactive apps can be embedded into casts, allowing users to engage with your dApp without leaving their feed—ideal for in-frame games, NFT mints, or governance votes. 9 | 10 | This template extends the Basic Web App template with essential Farcaster features: 11 | 12 | - **Farcaster SDK**: Pre-installed and configured for building and validating Frames. 13 | - **Example Frame**: A sample Frame route to get you started quickly. 14 | - **Authentication**: Basic Farcaster authentication to verify user identity. 15 | - **Manifest Endpoint**: Serves the required `.well-known/farcaster.json` manifest for app discovery. 16 | 17 | ## Environment Variables 18 | 19 | Create a `.env` file at the root of your project and add the following variables. For local development, use an `ngrok` URL for `NEXT_PUBLIC_URL`. 20 | 21 | - **`NEXT_PUBLIC_URL`**: Your app's public URL. 22 | - **`JWT_SECRET`**: A secret for signing JWTs. Generate one with `openssl rand -base64 32`. 23 | - **`NEXT_PUBLIC_APP_ENV`**: `development` or `production`. 24 | 25 | ### Manifest Signature 26 | 27 | To make your Miniapp discoverable, you must sign a manifest with your Farcaster account. This generates the following three variables: 28 | 29 | - `NEXT_PUBLIC_FARCASTER_HEADER` 30 | - `NEXT_PUBLIC_FARCASTER_PAYLOAD` 31 | - `NEXT_PUBLIC_FARCASTER_SIGNATURE` 32 | 33 | 34 | To generate these values, deploy your app and visit 35 | `https://farcaster.xyz/~/developers/mini-apps/manifest?domain=YOUR_DOMAIN` to 36 | sign the manifest. 37 | 38 | -------------------------------------------------------------------------------- /docs/templates/minipay.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Minipay Template 3 | description: "Learn how to build Minipay dApps with Celo Composer." 4 | icon: "phone" 5 | tag: "NEW" 6 | --- 7 | 8 | # Minipay Template 9 | 10 | The Minipay template is designed for building decentralized applications that integrate seamlessly with Minipay, a lightweight, mobile-first stablecoin wallet on the Celo network. This template is ideal for developers looking to create dApps targeting users in emerging markets, with a focus on fast, low-cost transactions and a user-friendly experience. 11 | 12 | 13 | The Minipay template comes pre-configured with a wallet connection that works 14 | out-of-the-box with the Minipay wallet. It automatically detects the Minipay 15 | environment and connects the user's wallet, providing a frictionless 16 | onboarding experience. 17 | 18 | 19 | ## Key Features 20 | 21 | - **Seamless Minipay Integration**: Automatically connects to the Minipay wallet when the dApp is opened within the Minipay browser. 22 | - **Mobile-First Design**: Built with a responsive layout that looks great on mobile devices. 23 | - **Built-in Balance Display**: Includes a component to display the user's balance for CELO and other Celo stablecoins (cUSD, USDC, USDT). 24 | - **Optimized for Performance**: A lightweight and fast template, perfect for users with low-end smartphones and limited data. 25 | 26 | ## When to Use This Template 27 | 28 | Use the Minipay template if you are building a dApp that: 29 | 30 | - Is intended to be used within the Minipay wallet. 31 | - Targets users in regions with high mobile usage. 32 | - Requires fast and cheap transactions for micro-payments or other use cases. 33 | - Benefits from a simple and intuitive user experience, without the complexities of wallet management. 34 | 35 | ## Getting Started 36 | 37 | To create a new project using the Minipay template, run the following command: 38 | 39 | ```bash 40 | npx @celo/composer create --template minipay 41 | ``` 42 | 43 | This will generate a new Celo Composer project with the Minipay template, ready for you to start building your dApp. 44 | -------------------------------------------------------------------------------- /docs/templates/web-app-template.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Web App Template 3 | description: "An overview of the default Web Application template." 4 | icon: "paperclip" 5 | --- 6 | 7 | The Basic Web App template is the default choice for Celo Composer. It provides a solid foundation for building a modern, full-stack decentralized application on Celo. 8 | 9 | ## Key Technologies 10 | 11 | This template comes pre-configured with a modern tech stack designed for performance and developer experience. 12 | 13 | 14 | 15 | The latest version of the popular React framework, featuring the App Router, 16 | Server Components, and other modern features. 17 | 18 | 19 | A beautiful, accessible, and customizable component library built on top of 20 | Radix UI and Tailwind CSS. 21 | 22 | 23 | End-to-end type safety to help you build robust and maintainable 24 | applications. 25 | 26 | 27 | A high-performance build system for monorepos, enabling fast development and 28 | build times. 29 | 30 | 31 | 32 | ## Features 33 | 34 | - **Production-Ready**: The template is structured and configured for production deployment from day one. 35 | - **Responsive Design**: The UI is fully responsive and looks great on all devices, from mobile to desktop. 36 | - **Monorepo Structure**: Organized as a monorepo with all applications located in the `apps` directory, promoting code sharing and scalability. 37 | 38 | ## When to Use 39 | 40 | This template is the perfect starting point for a wide range of Celo dApps, including: 41 | 42 | - DeFi applications 43 | - NFT marketplaces 44 | - Governance portals 45 | - DAO tooling 46 | - Any other dApp that requires a web-based user interface. 47 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | testMatch: [ 4 | '/src/**/*.test.ts' 5 | ], 6 | testPathIgnorePatterns: [ 7 | '/templates/', 8 | '/node_modules/', 9 | '/dist/' 10 | ], 11 | transform: { 12 | '^.+\\.ts$': 'ts-jest' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@celo/celo-composer", 3 | "version": "2.4.10", 4 | "description": "CLI tool for generating customizable Celo blockchain starter kits", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "bin": { 8 | "celo-composer": "dist/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "dev": "tsx src/index.ts", 13 | "start": "tsc && node dist/index.js", 14 | "test": "jest --passWithNoTests", 15 | "lint": "eslint src --ext .ts", 16 | "clean": "rimraf dist", 17 | "prepublishOnly": "pnpm clean && pnpm build && pnpm test", 18 | "prepack": "pnpm build && node scripts/prepack-prune.cjs", 19 | "postpack": "node scripts/postpack-restore.cjs" 20 | }, 21 | "keywords": [ 22 | "celo", 23 | "blockchain", 24 | "cli", 25 | "starter-kit", 26 | "monorepo" 27 | ], 28 | "author": "Celo Team", 29 | "license": "MIT", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/celo-org/celo-composer.git" 33 | }, 34 | "homepage": "https://github.com/celo-org/celo-composer", 35 | "bugs": { 36 | "url": "https://github.com/celo-org/celo-composer/issues" 37 | }, 38 | "files": [ 39 | "dist", 40 | "templates", 41 | "README.md", 42 | "LICENSE" 43 | ], 44 | "publishConfig": { 45 | "access": "public" 46 | }, 47 | "dependencies": { 48 | "chalk": "^5.3.0", 49 | "commander": "^11.1.0", 50 | "fs-extra": "^11.1.1", 51 | "inquirer": "^9.2.12", 52 | "node-plop": "^0.32.0", 53 | "ora": "^7.0.1", 54 | "path": "^0.12.7", 55 | "plop": "^4.0.1" 56 | }, 57 | "devDependencies": { 58 | "@types/fs-extra": "^11.0.4", 59 | "@types/inquirer": "^9.0.7", 60 | "@types/jest": "^29.5.5", 61 | "@types/node": "^20.8.0", 62 | "@typescript-eslint/eslint-plugin": "^6.7.0", 63 | "@typescript-eslint/parser": "^6.7.0", 64 | "cross-env": "^7.0.3", 65 | "eslint": "^8.50.0", 66 | "jest": "^29.7.0", 67 | "rimraf": "^5.0.5", 68 | "ts-jest": "^29.4.1", 69 | "tsx": "^3.14.0", 70 | "typescript": "^5.2.2" 71 | }, 72 | "engines": { 73 | "node": ">=18.0.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /scripts/postpack-restore.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | 6 | const ROOT = path.join(__dirname, '..'); 7 | const TEMPLATE_PATH = path.join(ROOT, 'templates', 'ai', 'chat-template'); 8 | const STASH_DIR = path.join(TEMPLATE_PATH, '.prune-stash'); 9 | 10 | (async () => { 11 | try { 12 | if (!(await fs.pathExists(STASH_DIR))) return; 13 | const manifestPath = path.join(STASH_DIR, 'manifest.json'); 14 | if (!(await fs.pathExists(manifestPath))) return; 15 | const manifest = await fs.readJson(manifestPath); 16 | for (const item of manifest) { 17 | const destRel = item.rel; 18 | const destPath = path.join(TEMPLATE_PATH, destRel); 19 | await fs.ensureDir(path.dirname(destPath)); 20 | await fs.move(item.dest, destPath, { overwrite: true }); 21 | console.log(`[postpack] restored ${destRel}`); 22 | } 23 | await fs.remove(STASH_DIR); 24 | } catch (err) { 25 | console.warn('[postpack] restore step failed (continuing):', err); 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /scripts/prepack-prune.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | 6 | const ROOT = path.join(__dirname, '..'); 7 | const TEMPLATE_PATH = path.join(ROOT, 'templates', 'ai', 'chat-template'); 8 | const STASH_DIR = path.join(TEMPLATE_PATH, '.prune-stash'); 9 | 10 | const pathsToPrune = [ 11 | '.git', 12 | '.next', 13 | 'node_modules', 14 | 'tsconfig.tsbuildinfo', 15 | '.env', 16 | '.env.local', 17 | ]; 18 | 19 | (async () => { 20 | try { 21 | if (!(await fs.pathExists(TEMPLATE_PATH))) return; 22 | await fs.ensureDir(STASH_DIR); 23 | 24 | const manifest = []; 25 | for (const rel of pathsToPrune) { 26 | const src = path.join(TEMPLATE_PATH, rel); 27 | if (await fs.pathExists(src)) { 28 | const dest = path.join(STASH_DIR, rel.replace(/[\/]/g, '__')); // flatten 29 | await fs.move(src, dest, { overwrite: true }); 30 | manifest.push({ rel, dest }); 31 | console.log(`[prepack] pruned ${rel}`); 32 | } 33 | } 34 | await fs.writeJson(path.join(STASH_DIR, 'manifest.json'), manifest, { 35 | spaces: 2, 36 | }); 37 | } catch (err) { 38 | console.warn('[prepack] prune step failed (continuing):', err); 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Celo Composer CLI Release Script 4 | # Usage: ./scripts/release.sh [version] [type] 5 | # Example: ./scripts/release.sh 2.2.0 stable 6 | # Example: ./scripts/release.sh 2.2.0-beta.1 beta 7 | 8 | set -e 9 | 10 | VERSION=$1 11 | TYPE=${2:-stable} 12 | 13 | if [ -z "$VERSION" ]; then 14 | echo "Error: Version is required" 15 | echo "Usage: ./scripts/release.sh [version] [type]" 16 | echo "Example: ./scripts/release.sh 2.2.0 stable" 17 | echo "Example: ./scripts/release.sh 2.2.0-beta.1 beta" 18 | exit 1 19 | fi 20 | 21 | echo "🚀 Starting release process for version $VERSION ($TYPE)" 22 | 23 | # Show current branch for reference 24 | CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 25 | echo "📍 Current branch: $CURRENT_BRANCH" 26 | 27 | # Check if working directory is clean 28 | if [ -n "$(git status --porcelain)" ]; then 29 | echo "Error: Working directory is not clean. Please commit or stash your changes." 30 | git status --short 31 | exit 1 32 | fi 33 | 34 | # Update version in package.json 35 | echo "📝 Updating version in package.json to $VERSION" 36 | npm version $VERSION --no-git-tag-version 37 | 38 | # Update CHANGELOG.md 39 | echo "📝 Please update CHANGELOG.md with the changes for version $VERSION" 40 | echo "Press Enter to continue after updating CHANGELOG.md..." 41 | read 42 | 43 | # Run tests and build 44 | echo "🧪 Running tests and build..." 45 | pnpm install 46 | pnpm lint 47 | pnpm test 48 | pnpm build 49 | 50 | # Commit version changes 51 | echo "💾 Committing version changes..." 52 | git add package.json CHANGELOG.md 53 | git commit -m "chore: bump version to $VERSION" 54 | 55 | # Create and push tag 56 | echo "🏷️ Creating and pushing tag v$VERSION..." 57 | git tag "v$VERSION" 58 | git push origin $CURRENT_BRANCH 59 | git push origin "v$VERSION" 60 | 61 | echo "✅ Release process completed!" 62 | echo "" 63 | echo "📦 The GitHub Action will now:" 64 | echo " - Run CI tests" 65 | echo " - Build the project" 66 | echo " - Publish to npm" 67 | echo " - Create GitHub release" 68 | echo "" 69 | echo "🔗 Monitor the progress at:" 70 | echo " https://github.com/celo-org/celo-composer/actions" 71 | echo "" 72 | echo "📋 After successful deployment:" 73 | echo " - Verify npm package: https://www.npmjs.com/package/@celo/celo-composer" 74 | echo " - Test installation: npm install -g @celo/celo-composer@$VERSION" 75 | echo " - Check GitHub release: https://github.com/celo-org/celo-composer/releases" 76 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Command } from 'commander'; 4 | import { createCommand } from './commands/create.js'; 5 | import chalk from 'chalk'; 6 | 7 | const program = new Command(); 8 | 9 | program 10 | .name('celo-composer') 11 | .description('CLI tool for generating customizable Celo blockchain starter kits') 12 | .version('1.0.0'); 13 | 14 | program 15 | .command('create') 16 | .description('Create a new Celo project') 17 | .argument('[project-name]', 'Name of the project') 18 | .option('-d, --description ', 'Project description') 19 | .option('-t, --template ', 'Template type (basic, farcaster-miniapp, minipay, ai-chat)') 20 | .option('--wallet-provider ', 'Wallet provider (rainbowkit, thirdweb, none)') 21 | .option('-c, --contracts ', 'Smart contract framework (hardhat, foundry, none)') 22 | .option('--skip-install', 'Skip package installation') 23 | .option('-y, --yes', 'Skip all prompts and use defaults') 24 | .action(createCommand); 25 | 26 | program.on('command:*', () => { 27 | console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`)); 28 | console.log(chalk.yellow('See --help for a list of available commands.')); 29 | process.exit(1); 30 | }); 31 | 32 | if (process.argv.length === 2) { 33 | program.help(); 34 | } 35 | 36 | program.parse(process.argv); 37 | -------------------------------------------------------------------------------- /src/utils/paths.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url'; 2 | import { dirname, join } from 'path'; 3 | 4 | /** 5 | * Get the directory name of the current module (ESM equivalent of __dirname) 6 | */ 7 | export function getDirname(importMetaUrl: string): string { 8 | return dirname(fileURLToPath(importMetaUrl)); 9 | } 10 | 11 | /** 12 | * Get the templates directory path from any source file 13 | */ 14 | export function getTemplatesPath(importMetaUrl: string): string { 15 | const currentDir = getDirname(importMetaUrl); 16 | // At runtime, we're in dist/, so go up to package root, then into templates/ 17 | // During development, we might be in src/, so handle both cases 18 | const isInDist = currentDir.includes('/dist') || currentDir.endsWith('/dist'); 19 | let templatesPath: string; 20 | if (isInDist) { 21 | // From dist/generators/ or dist/utils/ go up to package root, then into templates/ 22 | templatesPath = join(currentDir, '..', 'templates'); 23 | } else { 24 | // From src/ go up to package root, then into templates/ 25 | templatesPath = join(currentDir, '..', '..', 'templates'); 26 | } 27 | 28 | return templatesPath; 29 | } 30 | 31 | /** 32 | * Get the plopfile path from any source file 33 | */ 34 | export function getPlopfilePath(importMetaUrl: string): string { 35 | const currentDir = getDirname(importMetaUrl); 36 | // At runtime, we're in dist/generators/, so go up to dist/, then to plopfile.js 37 | // During development, we might be in src/generators/, so handle both cases 38 | const isInDist = currentDir.includes('/dist') || currentDir.endsWith('/dist'); 39 | if (isInDist) { 40 | // From dist/generators/ go up to dist/, then to plopfile.js 41 | return join(currentDir, '..', 'plopfile.js'); 42 | } else { 43 | // From src/generators/ go up to src/, then to plopfile.js 44 | return join(currentDir, '..', 'plopfile.js'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/safe-copy.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs-extra"; 2 | import path from "path"; 3 | 4 | export const safeCopyTemplate = async ( 5 | sourcePath: string, 6 | destPath: string 7 | ): Promise => { 8 | const excludedTopDirs = new Set([".git", ".next", "node_modules", "docs"]); 9 | const excludedFiles = new Set(["tsconfig.tsbuildinfo", ".env", ".env.local"]); 10 | 11 | const filter = (src: string): boolean => { 12 | const rel = path.relative(sourcePath, src); 13 | if (!rel) return true; // root 14 | 15 | const parts = rel.split(path.sep); 16 | const top = parts[0]; 17 | if (excludedTopDirs.has(top)) return false; 18 | 19 | const base = path.basename(src); 20 | if (excludedFiles.has(base)) return false; 21 | 22 | return true; 23 | }; 24 | 25 | await fs.copy(sourcePath, destPath, { filter }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/utils/validation.ts: -------------------------------------------------------------------------------- 1 | export function validateProjectName(name: string): string | true { 2 | if (!name || name.trim().length === 0) { 3 | return 'Project name cannot be empty'; 4 | } 5 | 6 | const trimmedName = name.trim(); 7 | 8 | // Check for valid characters (alphanumeric, hyphens, underscores) 9 | if (!/^[a-zA-Z0-9_-]+$/.test(trimmedName)) { 10 | return 'Project name can only contain letters, numbers, hyphens, and underscores'; 11 | } 12 | 13 | // Check if it starts with a letter or number 14 | if (!/^[a-zA-Z0-9]/.test(trimmedName)) { 15 | return 'Project name must start with a letter or number'; 16 | } 17 | 18 | // Check length 19 | if (trimmedName.length < 2) { 20 | return 'Project name must be at least 2 characters long'; 21 | } 22 | 23 | if (trimmedName.length > 50) { 24 | return 'Project name must be less than 50 characters long'; 25 | } 26 | 27 | // Check for reserved names 28 | const reservedNames = [ 29 | 'node_modules', 30 | 'package.json', 31 | 'package-lock.json', 32 | 'yarn.lock', 33 | 'pnpm-lock.yaml', 34 | '.git', 35 | '.gitignore', 36 | 'dist', 37 | 'build', 38 | 'src', 39 | 'public', 40 | 'assets', 41 | 'components', 42 | 'pages', 43 | 'api', 44 | 'lib', 45 | 'utils', 46 | 'types', 47 | 'styles', 48 | 'config', 49 | 'test', 50 | 'tests', 51 | '__tests__', 52 | 'spec', 53 | 'docs', 54 | 'documentation', 55 | ]; 56 | 57 | if (reservedNames.includes(trimmedName.toLowerCase())) { 58 | return `"${trimmedName}" is a reserved name and cannot be used as a project name`; 59 | } 60 | 61 | return true; 62 | } 63 | -------------------------------------------------------------------------------- /templates/ai/chat-template/.env.example: -------------------------------------------------------------------------------- 1 | # Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32` 2 | AUTH_SECRET=**** 3 | 4 | NEXT_PUBLIC_THIRDWEB_CLIENT_ID=**** 5 | 6 | # The following keys below are automatically created and 7 | # added to your environment when you deploy on vercel 8 | 9 | # === AI Provider API Keys === 10 | # Add any of the keys below to enable that provider's models dynamically. 11 | # Models appear in the chat model dropdown at runtime. 12 | 13 | # OpenAI: https://platform.openai.com/api-keys 14 | OPENAI_API_KEY= 15 | 16 | # Anthropic: https://console.anthropic.com/ 17 | ANTHROPIC_API_KEY= 18 | 19 | # Mistral: https://console.mistral.ai/api-keys/ 20 | MISTRAL_API_KEY= 21 | 22 | # DeepSeek (OpenAI-compatible): https://platform.deepseek.com/api-keys 23 | DEEPSEEK_API_KEY= 24 | 25 | # xAI (OpenAI-compatible): https://console.x.ai/ 26 | XAI_API_KEY= 27 | 28 | # Google Gemini: https://aistudio.google.com/app/apikey 29 | GOOGLE_GENERATIVE_AI_API_KEY= 30 | 31 | # Instructions to create a Vercel Blob Store here: https://vercel.com/docs/storage/vercel-blob 32 | BLOB_READ_WRITE_TOKEN=**** 33 | 34 | # Instructions to create a PostgreSQL database here: https://vercel.com/docs/storage/vercel-postgres/quickstart 35 | POSTGRES_URL=**** 36 | 37 | 38 | # Instructions to create a Redis store here: 39 | # https://vercel.com/docs/redis 40 | # Optional: 41 | REDIS_URL=**** 42 | -------------------------------------------------------------------------------- /templates/ai/chat-template/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:import/recommended", 5 | "plugin:import/typescript", 6 | "prettier", 7 | "plugin:tailwindcss/recommended" 8 | ], 9 | "plugins": ["tailwindcss"], 10 | "rules": { 11 | "tailwindcss/no-custom-classname": "off", 12 | "tailwindcss/classnames-order": "off", 13 | "import/no-unresolved": [ 14 | "error", 15 | { 16 | "ignore": ["^@/", "^thirdweb$", "^thirdweb/"] 17 | } 18 | ] 19 | }, 20 | "settings": { 21 | "import/resolver": { 22 | "typescript": { 23 | "alwaysTryTypes": true, 24 | "project": "./tsconfig.json" 25 | } 26 | } 27 | }, 28 | "ignorePatterns": ["**/components/ui/**"] 29 | } 30 | -------------------------------------------------------------------------------- /templates/ai/chat-template/.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-22.04 8 | strategy: 9 | matrix: 10 | node-version: [20] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Install pnpm 14 | uses: pnpm/action-setup@v4 15 | with: 16 | version: 9.12.3 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | cache: 'pnpm' 22 | - name: Install dependencies 23 | run: pnpm install 24 | - name: Run lint 25 | run: pnpm lint -------------------------------------------------------------------------------- /templates/ai/chat-template/.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [main, master] 5 | pull_request: 6 | branches: [main, master] 7 | 8 | jobs: 9 | test: 10 | timeout-minutes: 30 11 | runs-on: ubuntu-latest 12 | env: 13 | AUTH_SECRET: ${{ secrets.AUTH_SECRET }} 14 | POSTGRES_URL: ${{ secrets.POSTGRES_URL }} 15 | BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }} 16 | REDIS_URL: ${{ secrets.REDIS_URL }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 1 22 | 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: lts/* 26 | 27 | - name: Install pnpm 28 | uses: pnpm/action-setup@v2 29 | with: 30 | version: latest 31 | run_install: false 32 | 33 | - name: Get pnpm store directory 34 | id: pnpm-cache 35 | shell: bash 36 | run: | 37 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 38 | 39 | - uses: actions/cache@v3 40 | with: 41 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 42 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 43 | restore-keys: | 44 | ${{ runner.os }}-pnpm-store- 45 | 46 | - uses: actions/setup-node@v4 47 | with: 48 | node-version: lts/* 49 | cache: "pnpm" 50 | 51 | - name: Install dependencies 52 | run: pnpm install --frozen-lockfile 53 | 54 | - name: Cache Playwright browsers 55 | uses: actions/cache@v3 56 | id: playwright-cache 57 | with: 58 | path: ~/.cache/ms-playwright 59 | key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }} 60 | 61 | - name: Install Playwright Browsers 62 | if: steps.playwright-cache.outputs.cache-hit != 'true' 63 | run: pnpm exec playwright install --with-deps chromium 64 | 65 | - name: Run Playwright tests 66 | run: pnpm test 67 | 68 | - uses: actions/upload-artifact@v4 69 | if: always() && !cancelled() 70 | with: 71 | name: playwright-report 72 | path: playwright-report/ 73 | retention-days: 7 74 | -------------------------------------------------------------------------------- /templates/ai/chat-template/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .pnpm-debug.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | .env 32 | # turbo 33 | .turbo 34 | 35 | .env 36 | .vercel 37 | .env*.local 38 | 39 | # Playwright 40 | /test-results/ 41 | /playwright-report/ 42 | /blob-report/ 43 | /playwright/* 44 | -------------------------------------------------------------------------------- /templates/ai/chat-template/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /templates/ai/chat-template/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[javascript]": { 4 | "editor.defaultFormatter": "biomejs.biome" 5 | }, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "biomejs.biome" 8 | }, 9 | "[typescriptreact]": { 10 | "editor.defaultFormatter": "biomejs.biome" 11 | }, 12 | "typescript.tsdk": "node_modules/typescript/lib", 13 | "eslint.workingDirectories": [ 14 | { "pattern": "app/*" }, 15 | { "pattern": "packages/*" } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /templates/ai/chat-template/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Vercel, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(auth)/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { z } from "zod"; 4 | 5 | import { createUser, getUser } from "@/lib/db/queries"; 6 | 7 | // Auth disabled 8 | 9 | const authFormSchema = z.object({ 10 | email: z.string().email(), 11 | password: z.string().min(6), 12 | }); 13 | 14 | export interface LoginActionState { 15 | status: "idle" | "in_progress" | "success" | "failed" | "invalid_data"; 16 | } 17 | 18 | export const login = async ( 19 | _: LoginActionState, 20 | formData: FormData 21 | ): Promise => { 22 | try { 23 | const validatedData = authFormSchema.parse({ 24 | email: formData.get("email"), 25 | password: formData.get("password"), 26 | }); 27 | 28 | // Auth disabled 29 | 30 | return { status: "success" }; 31 | } catch (error) { 32 | if (error instanceof z.ZodError) { 33 | return { status: "invalid_data" }; 34 | } 35 | 36 | return { status: "failed" }; 37 | } 38 | }; 39 | 40 | export interface RegisterActionState { 41 | status: 42 | | "idle" 43 | | "in_progress" 44 | | "success" 45 | | "failed" 46 | | "user_exists" 47 | | "invalid_data"; 48 | } 49 | 50 | export const register = async ( 51 | _: RegisterActionState, 52 | formData: FormData 53 | ): Promise => { 54 | try { 55 | const validatedData = authFormSchema.parse({ 56 | email: formData.get("email"), 57 | password: formData.get("password"), 58 | }); 59 | 60 | const [user] = await getUser(validatedData.email); 61 | 62 | if (user) { 63 | return { status: "user_exists" } as RegisterActionState; 64 | } 65 | await createUser(validatedData.email, validatedData.password); 66 | // Auth disabled 67 | 68 | return { status: "success" }; 69 | } catch (error) { 70 | if (error instanceof z.ZodError) { 71 | return { status: "invalid_data" }; 72 | } 73 | 74 | return { status: "failed" }; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(auth)/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | // Auth removed 2 | export async function GET() { 3 | return new Response("Auth disabled", { status: 404 }); 4 | } 5 | export async function POST() { 6 | return new Response("Auth disabled", { status: 404 }); 7 | } 8 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(auth)/api/auth/guest/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET(request: Request) { 2 | const { searchParams } = new URL(request.url); 3 | const redirectUrl = searchParams.get("redirectUrl") || "/"; 4 | return Response.redirect(redirectUrl); 5 | } 6 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(auth)/auth.config.ts: -------------------------------------------------------------------------------- 1 | // Auth disabled 2 | export const authConfig = {} as any; 3 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(auth)/auth.ts: -------------------------------------------------------------------------------- 1 | // Auth disabled; export stubs for compatibility 2 | export type UserType = "guest" | "regular"; 3 | 4 | export async function auth() { 5 | return { user: { id: "public-user", type: "guest" as UserType } } as any; 6 | } 7 | 8 | export async function signIn() { 9 | return { ok: true } as any; 10 | } 11 | 12 | export async function signOut() { 13 | return { ok: true } as any; 14 | } 15 | 16 | export const handlers = { 17 | GET: async () => new Response("Auth disabled"), 18 | POST: async () => new Response("Auth disabled"), 19 | } as any; 20 | export const GET = handlers.GET; 21 | export const POST = handlers.POST; 22 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/actions.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { generateText, type UIMessage } from 'ai'; 4 | import { cookies } from 'next/headers'; 5 | import { 6 | deleteMessagesByChatIdAfterTimestamp, 7 | getMessageById, 8 | updateChatVisiblityById, 9 | } from '@/lib/db/queries'; 10 | import type { VisibilityType } from '@/components/visibility-selector'; 11 | import { myProvider } from '@/lib/ai/providers'; 12 | 13 | export async function saveChatModelAsCookie(model: string) { 14 | const cookieStore = await cookies(); 15 | cookieStore.set('chat-model', model); 16 | } 17 | 18 | export async function generateTitleFromUserMessage({ 19 | message, 20 | }: { 21 | message: UIMessage; 22 | }) { 23 | const { text: title } = await generateText({ 24 | model: myProvider.languageModel('title-model'), 25 | system: `\n 26 | - you will generate a short title based on the first message a user begins a conversation with 27 | - ensure it is not more than 80 characters long 28 | - the title should be a summary of the user's message 29 | - do not use quotes or colons`, 30 | prompt: JSON.stringify(message), 31 | }); 32 | 33 | return title; 34 | } 35 | 36 | export async function deleteTrailingMessages({ id }: { id: string }) { 37 | const [message] = await getMessageById({ id }); 38 | 39 | await deleteMessagesByChatIdAfterTimestamp({ 40 | chatId: message.chatId, 41 | timestamp: message.createdAt, 42 | }); 43 | } 44 | 45 | export async function updateChatVisibility({ 46 | chatId, 47 | visibility, 48 | }: { 49 | chatId: string; 50 | visibility: VisibilityType; 51 | }) { 52 | await updateChatVisiblityById({ chatId, visibility }); 53 | } 54 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/chat-model/route.ts: -------------------------------------------------------------------------------- 1 | import { cookies } from "next/headers"; 2 | import { NextResponse } from "next/server"; 3 | 4 | export async function POST(request: Request) { 5 | try { 6 | const { model } = await request.json(); 7 | if (typeof model !== "string" || model.length < 1 || model.length > 100) { 8 | return NextResponse.json({ ok: false, error: "invalid_model" }, { status: 400 }); 9 | } 10 | const cookieStore = await cookies(); 11 | cookieStore.set("chat-model", model); 12 | return NextResponse.json({ ok: true }); 13 | } catch { 14 | return NextResponse.json({ ok: false, error: "bad_request" }, { status: 400 }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/chat/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | const textPartSchema = z.object({ 4 | type: z.enum(['text']), 5 | text: z.string().min(1).max(2000), 6 | }); 7 | 8 | const filePartSchema = z.object({ 9 | type: z.enum(['file']), 10 | mediaType: z.enum(['image/jpeg', 'image/png']), 11 | name: z.string().min(1).max(100), 12 | url: z.string().url(), 13 | }); 14 | 15 | const partSchema = z.union([textPartSchema, filePartSchema]); 16 | 17 | export const postRequestBodySchema = z.object({ 18 | id: z.string().uuid(), 19 | message: z.object({ 20 | id: z.string().uuid(), 21 | role: z.enum(['user']), 22 | parts: z.array(partSchema), 23 | }), 24 | // Allow any model id; server will validate and fallback 25 | selectedChatModel: z.string().min(1).max(100), 26 | selectedVisibilityType: z.enum(['public', 'private']), 27 | walletAddress: z 28 | .string() 29 | .regex(/^0x[a-fA-F0-9]{40}$/i, 'Invalid wallet address') 30 | .optional(), 31 | }); 32 | 33 | export type PostRequestBody = z.infer; 34 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/files/upload/route.ts: -------------------------------------------------------------------------------- 1 | import { put } from '@vercel/blob'; 2 | import { NextResponse } from 'next/server'; 3 | import { z } from 'zod'; 4 | 5 | import { auth } from '@/app/(auth)/auth'; 6 | 7 | // Use Blob instead of File since File is not available in Node.js environment 8 | const FileSchema = z.object({ 9 | file: z 10 | .instanceof(Blob) 11 | .refine((file) => file.size <= 5 * 1024 * 1024, { 12 | message: 'File size should be less than 5MB', 13 | }) 14 | // Update the file type based on the kind of files you want to accept 15 | .refine((file) => ['image/jpeg', 'image/png'].includes(file.type), { 16 | message: 'File type should be JPEG or PNG', 17 | }), 18 | }); 19 | 20 | export async function POST(request: Request) { 21 | const session = await auth(); 22 | 23 | if (!session) { 24 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 25 | } 26 | 27 | if (request.body === null) { 28 | return new Response('Request body is empty', { status: 400 }); 29 | } 30 | 31 | try { 32 | const formData = await request.formData(); 33 | const file = formData.get('file') as Blob; 34 | 35 | if (!file) { 36 | return NextResponse.json({ error: 'No file uploaded' }, { status: 400 }); 37 | } 38 | 39 | const validatedFile = FileSchema.safeParse({ file }); 40 | 41 | if (!validatedFile.success) { 42 | const errorMessage = validatedFile.error.errors 43 | .map((error) => error.message) 44 | .join(', '); 45 | 46 | return NextResponse.json({ error: errorMessage }, { status: 400 }); 47 | } 48 | 49 | // Get filename from formData since Blob doesn't have name property 50 | const filename = (formData.get('file') as File).name; 51 | const fileBuffer = await file.arrayBuffer(); 52 | 53 | try { 54 | const data = await put(`${filename}`, fileBuffer, { 55 | access: 'public', 56 | }); 57 | 58 | return NextResponse.json(data); 59 | } catch (error) { 60 | return NextResponse.json({ error: 'Upload failed' }, { status: 500 }); 61 | } 62 | } catch (error) { 63 | return NextResponse.json( 64 | { error: 'Failed to process request' }, 65 | { status: 500 }, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/history/route.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getChatsByUserId, 3 | getChatsByWalletAddress, 4 | getOrCreatePublicUser, 5 | PUBLIC_USER_ID, 6 | getOrCreateUserByWalletAddress, 7 | } from "@/lib/db/queries"; 8 | import { ChatSDKError } from "@/lib/errors"; 9 | import type { NextRequest } from "next/server"; 10 | 11 | export async function GET(request: NextRequest) { 12 | const { searchParams } = request.nextUrl; 13 | 14 | const limit = Number.parseInt(searchParams.get("limit") || "10"); 15 | const startingAfter = searchParams.get("starting_after"); 16 | const endingBefore = searchParams.get("ending_before"); 17 | let walletAddress = searchParams.get("walletAddress") || undefined; 18 | 19 | if (startingAfter && endingBefore) { 20 | return new ChatSDKError( 21 | "bad_request:api", 22 | "Only one of starting_after or ending_before can be provided." 23 | ).toResponse(); 24 | } 25 | 26 | // If not provided via query, try cookie 27 | if (!walletAddress) { 28 | const cookieHeader = request.headers.get("cookie") || ""; 29 | const cookies = Object.fromEntries( 30 | cookieHeader 31 | .split(";") 32 | .map((p) => p.trim()) 33 | .filter(Boolean) 34 | .map((p) => { 35 | const idx = p.indexOf("="); 36 | const k = idx >= 0 ? p.slice(0, idx) : p; 37 | const v = idx >= 0 ? decodeURIComponent(p.slice(idx + 1)) : ""; 38 | return [k, v] as const; 39 | }) 40 | ); 41 | const fromCookie = cookies.walletAddress as string | undefined; 42 | if (fromCookie && /^0x[a-fA-F0-9]{40}$/.test(fromCookie)) { 43 | walletAddress = fromCookie.toLowerCase(); 44 | } 45 | } 46 | 47 | // If wallet provided, fetch by wallet (case-insensitive). Else use public user id. 48 | let chats; 49 | if (walletAddress) { 50 | chats = await getChatsByWalletAddress({ 51 | walletAddress, 52 | limit, 53 | startingAfter, 54 | endingBefore, 55 | }); 56 | } else { 57 | await getOrCreatePublicUser(); 58 | chats = await getChatsByUserId({ 59 | id: PUBLIC_USER_ID, 60 | limit, 61 | startingAfter, 62 | endingBefore, 63 | }); 64 | } 65 | 66 | return Response.json(chats); 67 | } 68 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/models/route.ts: -------------------------------------------------------------------------------- 1 | import { cookies } from "next/headers"; 2 | import { NextResponse } from "next/server"; 3 | import { availableChatModels, DEFAULT_ALIAS_CHAT_MODEL_ID, DEFAULT_ALIAS_REASONING_MODEL_ID } from "@/lib/ai/providers"; 4 | 5 | export const dynamic = "force-dynamic"; 6 | 7 | export async function GET() { 8 | const cookieStore = await cookies(); 9 | const current = cookieStore.get("chat-model")?.value || DEFAULT_ALIAS_CHAT_MODEL_ID; 10 | const models = [ 11 | { id: DEFAULT_ALIAS_CHAT_MODEL_ID, name: "Default Chat", description: "Alias to the current default chat model", reasoning: false }, 12 | { id: DEFAULT_ALIAS_REASONING_MODEL_ID, name: "Default Reasoning", description: "Alias to the current default reasoning model", reasoning: true }, 13 | ...availableChatModels, 14 | ]; 15 | return NextResponse.json({ models, selected: current }); 16 | } 17 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/suggestions/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from '@/app/(auth)/auth'; 2 | import { getSuggestionsByDocumentId } from '@/lib/db/queries'; 3 | import { ChatSDKError } from '@/lib/errors'; 4 | 5 | export async function GET(request: Request) { 6 | const { searchParams } = new URL(request.url); 7 | const documentId = searchParams.get('documentId'); 8 | 9 | if (!documentId) { 10 | return new ChatSDKError( 11 | 'bad_request:api', 12 | 'Parameter documentId is required.', 13 | ).toResponse(); 14 | } 15 | 16 | const session = await auth(); 17 | 18 | if (!session?.user) { 19 | return new ChatSDKError('unauthorized:suggestions').toResponse(); 20 | } 21 | 22 | const suggestions = await getSuggestionsByDocumentId({ 23 | documentId, 24 | }); 25 | 26 | const [suggestion] = suggestions; 27 | 28 | if (!suggestion) { 29 | return Response.json([], { status: 200 }); 30 | } 31 | 32 | if (suggestion.userId !== session.user.id) { 33 | return new ChatSDKError('forbidden:api').toResponse(); 34 | } 35 | 36 | return Response.json(suggestions, { status: 200 }); 37 | } 38 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/vote/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET() { 2 | return new Response('Votes API removed', { status: 410 }); 3 | } 4 | 5 | export async function PATCH() { 6 | return new Response('Votes API removed', { status: 410 }); 7 | } 8 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/api/wallet/route.ts: -------------------------------------------------------------------------------- 1 | import { ChatSDKError } from "@/lib/errors"; 2 | import { getOrCreateUserByWalletAddress } from "@/lib/db/queries"; 3 | 4 | export async function POST(request: Request) { 5 | try { 6 | const body = await request.json().catch(() => null); 7 | 8 | const walletAddress = (body?.walletAddress as string | undefined)?.toLowerCase(); 9 | 10 | if (!walletAddress || typeof walletAddress !== "string") { 11 | return new ChatSDKError("bad_request:api", "Missing walletAddress").toResponse(); 12 | } 13 | 14 | const user = await getOrCreateUserByWalletAddress({ walletAddress }); 15 | 16 | const headers = new Headers({ "content-type": "application/json" }); 17 | // Persist last known wallet on client for subsequent API calls 18 | headers.append( 19 | "set-cookie", 20 | `walletAddress=${walletAddress}; Path=/; Max-Age=2592000; SameSite=Lax` 21 | ); 22 | 23 | return new Response(JSON.stringify({ ok: true, userId: user.id }), { 24 | status: 200, 25 | headers, 26 | }); 27 | } catch (error) { 28 | if (error instanceof ChatSDKError) { 29 | return error.toResponse(); 30 | } 31 | 32 | console.error(error); 33 | return new Response( 34 | JSON.stringify({ code: "internal_error", message: "Unexpected error" }), 35 | { status: 500, headers: { "content-type": "application/json" } } 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/chat/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { cookies } from "next/headers"; 2 | import { notFound } from "next/navigation"; 3 | 4 | import { Chat } from "@/components/chat"; 5 | import { DataStreamHandler } from "@/components/data-stream-handler"; 6 | import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models"; 7 | import { getChatById, getMessagesByChatId } from "@/lib/db/queries"; 8 | import { convertToUIMessages } from "@/lib/utils"; 9 | 10 | export default async function Page(props: { params: Promise<{ id: string }> }) { 11 | const params = await props.params; 12 | const { id } = params; 13 | const chat = await getChatById({ id }); 14 | 15 | if (!chat) { 16 | notFound(); 17 | } 18 | 19 | // Public mode: allow viewing chats regardless of auth 20 | 21 | const messagesFromDb = await getMessagesByChatId({ 22 | id, 23 | }); 24 | 25 | const uiMessages = convertToUIMessages(messagesFromDb); 26 | 27 | const cookieStore = await cookies(); 28 | const chatModelFromCookie = cookieStore.get("chat-model"); 29 | 30 | if (!chatModelFromCookie) { 31 | return ( 32 | <> 33 | 42 | 43 | 44 | ); 45 | } 46 | 47 | return ( 48 | <> 49 | 58 | 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /templates/ai/chat-template/app/(chat)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { cookies } from "next/headers"; 2 | 3 | import { AppSidebar } from "@/components/app-sidebar"; 4 | import { DataStreamProvider } from "@/components/data-stream-provider"; 5 | import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; 6 | import Script from "next/script"; 7 | 8 | export const experimental_ppr = true; 9 | 10 | export default async function Layout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | const cookieStore = await cookies(); 16 | const isCollapsed = cookieStore.get("sidebar:state")?.value !== "true"; 17 | 18 | return ( 19 | <> 20 |