This file is a merged representation of the entire codebase, combined into a single document by Repomix. # File Summary ## Purpose This file contains a packed representation of the entire repository's contents. It is designed to be easily consumable by AI systems for analysis, code review, or other automated processes. ## File Format The content is organized as follows: 1. This summary section 2. Repository information 3. Directory structure 4. Repository files (if enabled) 4. Multiple file entries, each consisting of: a. A header with the file path (## File: path/to/file) b. The full contents of the file in a code block ## Usage Guidelines - This file should be treated as read-only. Any changes should be made to the original repository files, not this packed version. - When processing this file, use the file path to distinguish between different files in the repository. - Be aware that this file may contain sensitive information. Handle it with the same level of security as you would the original repository. ## Notes - Some files may have been excluded based on .gitignore rules and Repomix's configuration - Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files - Files matching patterns in .gitignore are excluded - Files matching default ignore patterns are excluded - Files are sorted by Git change count (files with more changes are at the bottom) ## Additional Info # Directory Structure ``` .devcontainer/ devcontainer.json Dockerfile .github/ ISSUE_TEMPLATE/ bug_report.md feature_request.md workflows/ deployment-broadcaster.yml deployment.yml generate-changelog.yml generate-readme-translations.yml greetings.yml image.yaml pre-release.yml release.yaml stale.yml pull_request_template.md agent/ src/ index.ts .gitignore jest.config.js package.json tsconfig.json characters/ moxie.character.json client/ src/ components/ ui/ chat/ hooks/ useAutoScroll.tsx chat-bubble.tsx chat-input.tsx chat-message-list.tsx chat-tts-button.tsx expandable-chat.tsx message-loading.tsx avatar.tsx badge.tsx breadcrumb.tsx button.tsx card.tsx collapsible.tsx input.tsx label.tsx separator.tsx sheet.tsx sidebar.tsx skeleton.tsx tabs.tsx textarea.tsx toast.tsx toaster.tsx tooltip.tsx app-sidebar.tsx array-input.tsx audio-recorder.tsx chat.tsx connection-status.tsx copy-button.tsx input-copy.tsx message.tsx overview.tsx page-title.tsx hooks/ use-mobile.tsx use-toast.ts use-version.tsx useTextTypingEffect.ts lib/ api.ts info.json utils.ts routes/ chat.tsx home.tsx overview.tsx types/ index.ts App.tsx index.css main.tsx vite-env.d.ts .gitignore components.json eslint.config.js index.html package.json postcss.config.js README.md tailwind.config.ts tsconfig.app.json tsconfig.json tsconfig.node.json version.sh vite.config.ts packages/ _examples/ plugin/ src/ actions/ balanceAction.ts transferAction.ts utils/ balance.ts index.ts templates.ts types.ts .npmignore eslint.config.mjs package.json README.md tsconfig.json tsup.config.ts client-moxie/ src/ constants/ constants.ts middleware/ traceId.ts types/ express/ index.d.ts types.ts helpers.ts index.ts moxieApis.ts README.md responseHelper.ts .npmignore eslint.config.mjs package.json tsconfig.json tsup.config.ts core/ __tests__/ actions.test.ts cache.test.ts context.test.ts database.test.ts defaultCharacters.test.ts embedding.test.ts env.test.ts environment.test.ts evaluators.test.ts goals.test.ts knowledge.test.ts memory.test.ts messages.test.ts models.test.ts parsing.test.ts posts.test.ts providers.test.ts relationships.test.ts runtime.test.ts uuid.test.ts videoGeneration.test.ts src/ database/ CircuitBreaker.ts test_resources/ constants.ts createRuntime.ts testSetup.ts types.ts actions.ts cache.ts config.ts context.ts database.ts defaultCharacter.ts embedding.ts environment.ts evaluators.ts generation.ts goals.ts index.ts knowledge.ts localembeddingManager.ts logger.ts memory.ts messages.ts models.ts parsing.ts posts.ts providers.ts ragknowledge.ts relationships.ts runtime.ts settings.ts types.ts utils.ts uuid.ts types/ index.d.ts .env.test .gitignore .npmignore elizaConfig.example.yaml eslint.config.mjs nodemon.json package.json README-TESTS.md renovate.json tsconfig.build.json tsconfig.json tsup.config.ts vitest.config.ts moxie-agent-lib/ src/ services/ balanceValidator.ts constants.ts cowService.ts fta.ts MoxieAgentDBAdapter.ts moxieUserService.ts portfolio.ts portfolioService.ts RedisClient.ts tokenDetails.ts tokenSymbol.ts types.ts zapperService.ts index.ts wallet.ts .env.test .npmignore package.json README.md tsconfig.json tsup.config.ts plugin-betswirl/ src/ actions/ coinToss.ts dice.ts getBets.ts info.ts roulette.ts providers/ casinoGames.ts casinoTokens.ts utils/ betswirl.ts moxie.ts index.ts types.ts .npmignore eslint.config.mjs package.json README.md tsconfig.json tsup.config.ts plugin-bootstrap/ src/ actions/ continue.ts followRoom.ts ignore.ts index.ts muteRoom.ts none.ts unfollowRoom.ts unmuteRoom.ts evaluators/ fact.ts goal.ts index.ts providers/ boredom.ts facts.ts index.ts time.ts index.ts .npmignore eslint.config.mjs package.json README.md tsconfig.json tsup.config.ts plugin-degenfans-alfafrens/ src/ actions/ gasUsageAction.ts infoAction.ts stakingConsultantAction.ts utils/ degenfansApi.ts moxieSubgraphApi.ts index.ts templates.ts types.ts .npmignore eslint.config.mjs package.json README.md tsconfig.json tsup.config.ts plugin-moxie-balance/ images/ logo.svg src/ actions/ getMoxieFanTokenPortfolio/ examples.ts index.ts template.ts getMoxiePortfolio/ examples.ts index.ts template.ts util/ config.ts index.ts commonTemplate.ts index.ts types.ts package.json README.md tsconfig.json tsup.config.ts plugin-moxie-limit-order/ images/ logo.svg src/ actions/ limitOrderAction.ts constants/ constants.ts service/ cowLimitOrder.ts erc20.ts templates/ callBackTemplate.ts limitOrderPrompt.ts types/ types.ts utils/ 0xApis.ts callbackTemplates.ts checkAndApproveTransaction.ts common.ts cowUsdPrice.ts constants.ts index.ts package.json tsconfig.json tsup.config.ts plugin-moxie-social-alpha/ images/ logo.svg src/ actions/ farcasterSummaryAction.ts socialSummaryAction.ts swapSummaryAction.ts twitterSummaryAction.ts utils.ts constants/ constants.ts plugins/ bigFanPlugin.ts services/ farcasterService.ts twitterService.ts cache.ts config.ts index.ts templates.ts utils.ts .env.test .npmignore package.json README.md tsconfig.json tsup.config.ts plugin-moxie-swap/ images/ logo.svg src/ actions/ examples.ts tokenSwapAction.ts constants/ constants.ts providers/ wallet.ts templates/ tokenSwapTemplate.ts utils/ 0xApis.ts callbackTemplates.ts checkAndApproveTransaction.ts codexApis.ts common.ts constants.ts erc20.ts moxieBondingCurve.ts subgraph.ts swapCreatorCoins.ts index.ts types.ts eslint.config.mjs package.json tsconfig.json tsup.config.ts plugin-moxie-token-details/ images/ logo.svg src/ actions/ getTokenDetails/ examples.ts index.ts template.ts util/ index.ts config.ts index.ts package.json README.md tsconfig.json tsup.config.ts plugin-moxie-token-social-sentiment/ images/ logo.svg src/ actions/ getTokenSocialSentiment/ index.ts template.ts constants/ constants.ts services/ neynarService.ts twitterService.ts util/ index.ts index.ts package.json README.md tsconfig.json tsup.config.ts plugin-moxie-token-transfer/ images/ logo.svg src/ actions/ transferAction.ts constants/ constants.ts service/ erc20.ts templates/ callBackTemplate.ts template.ts types/ types.ts utils/ 0xApis.ts common.ts subgraph.ts constants.ts index.ts eslint.config.mjs package.json tsconfig.json tsup.config.ts plugin-moxie-whale-hunter/ images/ logo.svg src/ actions/ topBaseTraders.ts topTokenHoldersAction.ts topTradersOfAToken.ts constants/ constants.ts plugins/ whaleHunterPlugin.ts services/ topBaseTraders.ts types/ whales.ts config.ts index.ts protocolSubgraph.ts templates.ts utils.ts .npmignore package.json README.md tsconfig.json tsup.config.ts registry/ public/ vite.svg src/ assets/ react.svg components/ ui/ button.tsx lib/ utils.ts App.tsx index.css main.tsx skills.json vite-env.d.ts .eslintrc.cjs .gitignore components.json index.html LICENSE package.json postcss.config.js README.md tailwind.config.js tsconfig.json tsconfig.node.json vite.config.ts scripts/ jsdoc-automation/ src/ AIService/ generators/ FullDocumentationGenerator.ts types/ index.ts utils/ CodeFormatter.ts DocumentOrganizer.ts AIService.ts index.ts types/ index.ts utils/ prompts.ts Configuration.ts DirectoryTraversal.ts DocumentationGenerator.ts GitManager.ts index.ts JsDocAnalyzer.ts JsDocGenerator.ts JSDocValidator.ts PluginDocumentationGenerator.ts TypeScriptFileIdentifier.ts TypeScriptParser.ts .example.env .gitignore package.json pnpm-workspace.yaml README.md tsconfig.json tsup.config.ts clean.sh create_new_skills.js derive-keys.js dev-broadcaster.sh dev.sh docker.sh extracttweets.js generatecharacter.js gettweets.mjs integrationTests.sh lint.sh migrateCache.js smokeTests.sh start.sh test.sh tweet_scraped_clean.json tweet_scraped.json update-versions.js .editorconfig .env.example .eslintrc.json .gitignore .gitpod.yml .npmrc .nvmrc .prettierignore CODE_OF_CONDUCT.md codecov.yml commitlint.config.js CONTRIBUTING.md DEV.md docker-compose-docs.yaml docker-compose.yaml Dockerfile Dockerfile_broadcaster Dockerfile.docs eliza.manifest.template eslint.config.mjs jest.config.json lerna.json LICENSE Makefile package.json pnpm-workspace.yaml prettier.config.cjs README.md renovate.json run.sh SECURITY.md tsconfig.json turbo.json ``` # Files ## File: .devcontainer/devcontainer.json ````json // See https://aka.ms/vscode-remote/devcontainer.json for format details. { "name": "elizaos-dev", "dockerFile": "Dockerfile", "build": { "args": { "NODE_VER": "23.5.0", "PNPM_VER": "9.15.2" } }, "privileged": true, "runArgs": [ "-p=3000:3000", // Add port for server api "-p=5173:5173", // Add port for client //"--volume=/usr/lib/wsl:/usr/lib/wsl", // uncomment for WSL //"--volume=/mnt/wslg:/mnt/wslg", // uncomment for WSL "--gpus=all", // ! uncomment for vGPU //"--device=/dev/dxg", // uncomment this for vGPU under WSL "--device=/dev/dri" ], "containerEnv": { //"MESA_D3D12_DEFAULT_ADAPTER_NAME": "NVIDIA", // uncomment for WSL //"LD_LIBRARY_PATH": "/usr/lib/wsl/lib" // uncomment for WSL }, "customizations": { "vscode": { "extensions": [ "vscode.json-language-features", "vscode.css-language-features", // "foxundermoon.shell-format", // "dbaeumer.vscode-eslint", // "esbenp.prettier-vscode" "ms-python.python" ] } }, "features": {} } ```` ## File: .devcontainer/Dockerfile ```` ARG NODE_VER=23.5.0 ARG BASE_IMAGE=node:${NODE_VER} FROM $BASE_IMAGE ENV DEBIAN_FRONTEND=noninteractive # Install pnpm globally and install necessary build tools RUN apt-get update \ && apt-get install -y \ git \ python3 \ make \ g++ \ nano \ vim \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* ARG PNPM_VER=9.15.2 RUN npm install -g pnpm@${PNPM_VER} # Set Python 3 as the default python RUN ln -s /usr/bin/python3 /usr/bin/python ENV DEBIAN_FRONTEND=dialog ```` ## File: .github/ISSUE_TEMPLATE/bug_report.md ````markdown --- name: Bug report about: Create a report to help us improve title: "" labels: "bug" assignees: "" --- **Describe the bug** **To Reproduce** **Expected behavior** **Screenshots** **Additional context** ```` ## File: .github/ISSUE_TEMPLATE/feature_request.md ````markdown --- name: Feature request about: Suggest an idea for this project title: "" labels: "enhancement" assignees: "" --- **Is your feature request related to a problem? Please describe.** **Describe the solution you'd like** **Describe alternatives you've considered** **Additional context** ```` ## File: .github/workflows/deployment-broadcaster.yml ````yaml name: Twitter-Broadcaster-Agent Deployment on: workflow_dispatch: inputs: environment: description: "dev/uat/prod env" default: "development" type: choice required: true options: - development - uat - production env: AWS_REGION: us-east-1 ECR_REPOSITORY: broadcaster-agent permissions: id-token: write contents: read jobs: setup-environment: runs-on: ubuntu-latest outputs: environment: ${{ github.event.inputs.environment }} steps: - run: echo "null" deploy: name: Deploy needs: [setup-environment] runs-on: ubuntu-latest environment: ${{ needs.setup-environment.outputs.environment }} steps: - name: Echo output run: echo ${{ needs.setup-environment.outputs.environment }} - name: Checkout uses: actions/checkout@v3 - name: Configure AWS credentials (Development) if: github.event.inputs.environment == 'development' uses: aws-actions/configure-aws-credentials@v1 with: role-session-name: Github aws-region: ${{ env.AWS_REGION }} role-to-assume: arn:aws:iam::679464682883:role/githubactions - name: Configure AWS credentials (UAT) if: github.event.inputs.environment == 'uat' uses: aws-actions/configure-aws-credentials@v1 with: role-session-name: Github aws-region: ${{ env.AWS_REGION }} role-to-assume: arn:aws:iam::114810186946:role/githubactions - name: Configure AWS credentials (Production) if: github.event.inputs.environment == 'production' uses: aws-actions/configure-aws-credentials@v1 with: role-session-name: Github aws-region: ${{ env.AWS_REGION }} role-to-assume: arn:aws:iam::114810186946:role/githubactions - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ github.sha }} run: | docker buildx build --platform=linux/amd64 -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile_broadcaster . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" ```` ## File: .github/workflows/deployment.yml ````yaml name: Creator-Agent Deployment on: workflow_dispatch: inputs: environment: description: "dev/uat/prod env" default: "development" type: choice required: true options: - development - uat - production env: AWS_REGION: us-east-1 ECR_REPOSITORY: creator-agent permissions: id-token: write contents: read jobs: setup-environment: runs-on: ubuntu-latest outputs: environment: ${{ github.event.inputs.environment }} steps: - run: echo "null" deploy: name: Deploy needs: [setup-environment] runs-on: ubuntu-latest environment: ${{ needs.setup-environment.outputs.environment }} steps: - name: Echo output run: echo ${{ needs.setup-environment.outputs.environment }} - name: Checkout uses: actions/checkout@v3 - name: Configure AWS credentials (Development) if: github.event.inputs.environment == 'development' uses: aws-actions/configure-aws-credentials@v1 with: role-session-name: Github aws-region: ${{ env.AWS_REGION }} role-to-assume: arn:aws:iam::679464682883:role/githubactions - name: Configure AWS credentials (UAT) if: github.event.inputs.environment == 'uat' uses: aws-actions/configure-aws-credentials@v1 with: role-session-name: Github aws-region: ${{ env.AWS_REGION }} role-to-assume: arn:aws:iam::114810186946:role/githubactions - name: Configure AWS credentials (Production) if: github.event.inputs.environment == 'production' uses: aws-actions/configure-aws-credentials@v1 with: role-session-name: Github aws-region: ${{ env.AWS_REGION }} role-to-assume: arn:aws:iam::114810186946:role/githubactions - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ github.sha }} run: | docker buildx build --platform=linux/amd64 --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" ```` ## File: .github/workflows/generate-changelog.yml ````yaml name: Generate Changelog on: push: tags: - "*" jobs: changelog: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 with: ref: main token: ${{ secrets.CHANGELOG_GITHUB_TOKEN }} - name: Generate Changelog run: | export PATH="$PATH:/home/runner/.local/share/gem/ruby/3.0.0/bin" gem install --user-install github_changelog_generator github_changelog_generator \ -u ${{ github.repository_owner }} \ -p ${{ github.event.repository.name }} \ --token ${{ secrets.CHANGELOG_GITHUB_TOKEN }} - name: Commit Changelog uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "chore: update changelog" branch: main file_pattern: "CHANGELOG.md" commit_author: "GitHub Action " ```` ## File: .github/workflows/generate-readme-translations.yml ````yaml name: Generate Readme Translations on: push: branches: - "1222--README-ci-auto-translation" jobs: translation: runs-on: ubuntu-latest strategy: matrix: language: [ { code: "CN", name: "Chinese" }, { code: "DE", name: "German" }, { code: "ES", name: "Spanish" }, { code: "FR", name: "French" }, { code: "HE", name: "Hebrew" }, { code: "IT", name: "Italian" }, { code: "JA", name: "Japanese" }, { code: "KOR", name: "Korean" }, { code: "PTBR", name: "Portuguese (Brazil)" }, { code: "RU", name: "Russian" }, { code: "TH", name: "Thai" }, { code: "TR", name: "Turkish" }, { code: "VI", name: "Vietnamese" }, ] permissions: contents: write steps: - uses: actions/checkout@v4 with: ref: main token: ${{ secrets.GH_TOKEN }} - name: Translate to ${{ matrix.language.name }} uses: 0xjord4n/aixion@v1.2.1 id: aixion with: config: > { "provider": "openai", "provider_options": { "api_key": "${{ secrets.OPENAI_API_KEY }}" }, "messages": [ { "role": "system", "content": "You will be provided with a markdown file in English, and your task is to translate it into ${{ matrix.language.name }}." }, { "role": "user", "content_path": "README.md" } ], "save_path": "README_${{ matrix.language.code }}.md", "model": "gpt-4o" } # Upload each translated file as an artifact - name: Upload translation uses: actions/upload-artifact@v4 with: name: readme-${{ matrix.language.code }} path: README_${{ matrix.language.code }}.md commit: needs: translation runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: main token: ${{ secrets.GH_TOKEN }} # Download all translation artifacts - name: Download all translations uses: actions/download-artifact@v4 with: pattern: readme-* merge-multiple: true - name: Commit all translations uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "chore: update all README translations" branch: main file_pattern: "README_*.md" commit_author: "GitHub Action " ```` ## File: .github/workflows/greetings.yml ````yaml name: Greetings on: [pull_request_target, issues] jobs: greeting: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: "Hello @${{ github.actor }}! Welcome to the moxie community. Thank you for opening your first issue; we appreciate your contribution. You are now a moxie contributor!" pr-message: "Hi @${{ github.actor }}! Welcome to the moxie community. Thanks for submitting your first pull request; your efforts are helping us accelerate towards AGI. We'll review it shortly. You are now a moxie contributor!" ```` ## File: .github/workflows/image.yaml ````yaml # name: Create and publish a Docker image # Configures this workflow to run every time a change is pushed to the branch called `release`. on: release: types: [created] workflow_dispatch: # Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. jobs: build-and-push-image: runs-on: ubuntu-latest # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: contents: read packages: write attestations: write id-token: write # steps: - name: Checkout repository uses: actions/checkout@v4 # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - name: Build and push Docker image id: push uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." - name: Generate artifact attestation uses: actions/attest-build-provenance@v1 with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true # This step makes the Docker image public, so users can pull it without authentication. - name: Make Docker image public run: | curl \ -X PATCH \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/user/packages/container/${{ env.IMAGE_NAME }}/visibility \ -d '{"visibility":"public"}' ```` ## File: .github/workflows/pre-release.yml ````yaml name: Pre-Release on: workflow_dispatch: inputs: release_type: description: "Type of release (prerelease, prepatch, patch, minor, preminor, major)" required: true default: "prerelease" jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 22 - uses: pnpm/action-setup@v3 with: version: 8 - name: Configure Git run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: "Setup npm for npmjs" run: | npm config set registry https://registry.npmjs.org/ echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - name: Install Protobuf Compiler run: sudo apt-get install -y protobuf-compiler - name: Install dependencies run: pnpm install - name: Build packages run: pnpm run build - name: Tag and Publish Packages id: tag_publish run: | RELEASE_TYPE=${{ github.event_name == 'push' && 'prerelease' || github.event.inputs.release_type }} npx lerna version $RELEASE_TYPE --conventional-commits --yes --no-private --force-publish npx lerna publish from-git --yes --dist-tag next - name: Get Version Tag id: get_tag run: echo "TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT - name: Generate Release Body id: release_body run: | if [ -f CHANGELOG.md ]; then echo "body=$(cat CHANGELOG.md)" >> $GITHUB_OUTPUT else echo "body=No changelog provided for this release." >> $GITHUB_OUTPUT fi - name: Create GitHub Release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin with: tag_name: ${{ steps.get_tag.outputs.TAG }} release_name: Release body_path: CHANGELOG.md draft: false prerelease: ${{ github.event_name == 'push' }} ```` ## File: .github/workflows/release.yaml ````yaml name: Release on: release: types: [created] workflow_dispatch: jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 23.3.0 - uses: pnpm/action-setup@v3 with: version: 9.15.0 - name: Configure Git run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: "Setup npm for npmjs" run: | npm config set registry https://registry.npmjs.org/ echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - name: Install Protobuf Compiler run: sudo apt-get install -y protobuf-compiler - name: Install dependencies run: pnpm install -r --no-frozen-lockfile - name: Build packages run: pnpm run build - name: Publish Packages id: publish run: | # Get the latest release tag LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) # Force clean the working directory and reset any changes echo "Cleaning working directory and resetting any changes" git clean -fd git reset --hard HEAD # Force checkout the latest tag echo "Checking out latest tag: $LATEST_TAG" git checkout -b temp-publish-branch $LATEST_TAG echo "Publishing version: $LATEST_TAG" npx lerna publish from-package --yes --dist-tag latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ```` ## File: .github/workflows/stale.yml ````yaml name: Mark stale issues and pull requests on: schedule: - cron: "25 18 * * *" jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write env: DAYS_BEFORE_STALE: 30 # Define the days-before-stale value DAYS_BEFORE_CLOSE: 7 # Define the days-before-close value steps: - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | This issue has been automatically marked as stale due to ${{ env.DAYS_BEFORE_STALE }} days of inactivity. If no further activity occurs within ${{ env.DAYS_BEFORE_CLOSE }} days, it will be closed automatically. Please take action if this issue is still relevant. stale-pr-message: | This pull request has been automatically marked as stale due to ${{ env.DAYS_BEFORE_STALE }} days of inactivity. If no further activity occurs within ${{ env.DAYS_BEFORE_CLOSE }} days, it will be closed automatically. Please take action if this pull request is still relevant. stale-issue-label: "no-issue-activity" stale-pr-label: "no-pr-activity" days-before-stale: ${{ env.DAYS_BEFORE_STALE }} days-before-close: ${{ env.DAYS_BEFORE_CLOSE }} ```` ## File: .github/pull_request_template.md ````markdown # Relates Issues # Background # PR Category - [] Register New Skills (first time adding new skills to moxie.xyz) - [] Bug Fixes (non-breaking change which fixes an issue) - [] Low - [] Medium - [] High - [] Improvements (misc. changes to existing features) - [] New Features (non-breaking change which adds functionality) - [] Updates (new versions of included code) # Description # Detailed Testing Steps ```` ## File: agent/src/index.ts ````typescript import { SqliteDatabaseAdapter } from "@elizaos/adapter-sqlite"; import { AutoClientInterface } from "@elizaos/client-auto"; import Database from "better-sqlite3"; import { AgentRuntime, CacheManager, type Character, type Client, Clients, DbCacheAdapter, defaultCharacter, elizaLogger, type IAgentRuntime, type ICacheManager, type IDatabaseAdapter, type IDatabaseCacheAdapter, ModelProviderName, settings, stringToUuid, validateCharacterConfig, } from "@moxie-protocol/core"; import { MoxieClient } from "@moxie-protocol/client-moxie"; import fs from "node:fs"; import net from "node:net"; import path from "node:path"; import { fileURLToPath } from "node:url"; import yargs from "yargs"; import samplePlugin from "@moxie-protocol/plugin-sample"; import degenfansAlfaFrensPlugin from "@moxie-protocol/plugin-degenfans-alfafrens"; import bootstrapPlugin from "@elizaos/plugin-bootstrap"; const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file const __dirname = path.dirname(__filename); // get the name of the directory export const wait = (minTime: number = 1000, maxTime: number = 3000) => { const waitTime = Math.floor(Math.random() * (maxTime - minTime + 1)) + minTime; return new Promise((resolve) => setTimeout(resolve, waitTime)); }; const logFetch = async (url: string, options: any) => { elizaLogger.debug(`Fetching ${url}`); // Disabled to avoid disclosure of sensitive information such as API keys // elizaLogger.debug(JSON.stringify(options, null, 2)); return fetch(url, options); }; export function parseArguments(): { character?: string; characters?: string; } { try { return yargs(process.argv.slice(3)) .option("character", { type: "string", description: "Path to the character JSON file", }) .option("characters", { type: "string", description: "Comma separated list of paths to character JSON files", }) .parseSync(); } catch (error) { elizaLogger.error("Error parsing arguments:", error); return {}; } } function tryLoadFile(filePath: string): string | null { try { return fs.readFileSync(filePath, "utf8"); } catch (e) { return null; } } function mergeCharacters(base: Character, child: Character): Character { const mergeObjects = (baseObj: any, childObj: any) => { const result: any = {}; const keys = new Set([ ...Object.keys(baseObj || {}), ...Object.keys(childObj || {}), ]); keys.forEach((key) => { if ( typeof baseObj[key] === "object" && typeof childObj[key] === "object" && !Array.isArray(baseObj[key]) && !Array.isArray(childObj[key]) ) { result[key] = mergeObjects(baseObj[key], childObj[key]); } else if ( Array.isArray(baseObj[key]) || Array.isArray(childObj[key]) ) { result[key] = [ ...(baseObj[key] || []), ...(childObj[key] || []), ]; } else { result[key] = childObj[key] !== undefined ? childObj[key] : baseObj[key]; } }); return result; }; return mergeObjects(base, child); } async function loadCharacter(filePath: string): Promise { const content = tryLoadFile(filePath); if (!content) { throw new Error(`Character file not found: ${filePath}`); } let character = JSON.parse(content); validateCharacterConfig(character); // .id isn't really valid const characterId = character.id || character.name; const characterPrefix = `CHARACTER.${characterId.toUpperCase().replace(/ /g, "_")}.`; const characterSettings = Object.entries(process.env) .filter(([key]) => key.startsWith(characterPrefix)) .reduce((settings, [key, value]) => { const settingKey = key.slice(characterPrefix.length); return { ...settings, [settingKey]: value }; }, {}); if (Object.keys(characterSettings).length > 0) { character.settings = character.settings || {}; character.settings.secrets = { ...characterSettings, ...character.settings.secrets, }; } // Handle plugins character.plugins = await handlePluginImporting(character.plugins); if (character.extends) { elizaLogger.info( `Merging ${character.name} character with parent characters` ); for (const extendPath of character.extends) { const baseCharacter = await loadCharacter( path.resolve(path.dirname(filePath), extendPath) ); character = mergeCharacters(baseCharacter, character); elizaLogger.info( `Merged ${character.name} with ${baseCharacter.name}` ); } } return character; } export async function loadCharacters( charactersArg: string ): Promise { let characterPaths = charactersArg ?.split(",") .map((filePath) => filePath.trim()); const loadedCharacters: Character[] = []; if (characterPaths?.length > 0) { for (const characterPath of characterPaths) { let content: string | null = null; let resolvedPath = ""; // Try different path resolutions in order const pathsToTry = [ characterPath, // exact path as specified path.resolve(process.cwd(), characterPath), // relative to cwd path.resolve(process.cwd(), "agent", characterPath), // Add this path.resolve(__dirname, characterPath), // relative to current script path.resolve( __dirname, "characters", path.basename(characterPath) ), // relative to agent/characters path.resolve( __dirname, "../characters", path.basename(characterPath) ), // relative to characters dir from agent path.resolve( __dirname, "../../characters", path.basename(characterPath) ), // relative to project root characters dir ]; elizaLogger.info( "Trying paths:", pathsToTry.map((p) => ({ path: p, exists: fs.existsSync(p), })) ); for (const tryPath of pathsToTry) { content = tryLoadFile(tryPath); if (content !== null) { resolvedPath = tryPath; break; } } if (content === null) { elizaLogger.error( `Error loading character from ${characterPath}: File not found in any of the expected locations` ); elizaLogger.error("Tried the following paths:"); pathsToTry.forEach((p) => elizaLogger.error(` - ${p}`)); process.exit(1); } try { const character: Character = await loadCharacter(resolvedPath); loadedCharacters.push(character); elizaLogger.info( `Successfully loaded character from: ${resolvedPath}` ); } catch (e) { elizaLogger.error( `Error parsing character from ${resolvedPath}: ${e}` ); process.exit(1); } } } if (loadedCharacters.length === 0) { elizaLogger.info("No characters found, using default character"); loadedCharacters.push(defaultCharacter); } return loadedCharacters; } async function handlePluginImporting(plugins: string[]) { if (plugins.length > 0) { elizaLogger.info("Plugins are: ", plugins); const importedPlugins = await Promise.all( plugins.map(async (plugin) => { try { const importedPlugin = await import(plugin); const functionName = plugin .replace("@moxie-protocol/plugin-", "") .replace(/-./g, (x) => x[1].toUpperCase()) + "Plugin"; // Assumes plugin function is camelCased with Plugin suffix return ( importedPlugin.default || importedPlugin[functionName] ); } catch (importError) { elizaLogger.error( `Failed to import plugin: ${plugin}`, importError ); return []; // Return null for failed imports } }) ); return importedPlugins; } else { return []; } } export function getTokenForProvider( provider: ModelProviderName, character: Character ): string | undefined { switch (provider) { // no key needed for llama_local or gaianet case ModelProviderName.LLAMALOCAL: return ""; case ModelProviderName.OLLAMA: return ""; case ModelProviderName.GAIANET: return ""; case ModelProviderName.OPENAI: return ( character.settings?.secrets?.OPENAI_API_KEY || settings.OPENAI_API_KEY ); case ModelProviderName.ETERNALAI: return ( character.settings?.secrets?.ETERNALAI_API_KEY || settings.ETERNALAI_API_KEY ); case ModelProviderName.NINETEEN_AI: return ( character.settings?.secrets?.NINETEEN_AI_API_KEY || settings.NINETEEN_AI_API_KEY ); case ModelProviderName.LLAMACLOUD: case ModelProviderName.TOGETHER: return ( character.settings?.secrets?.LLAMACLOUD_API_KEY || settings.LLAMACLOUD_API_KEY || character.settings?.secrets?.TOGETHER_API_KEY || settings.TOGETHER_API_KEY || character.settings?.secrets?.OPENAI_API_KEY || settings.OPENAI_API_KEY ); case ModelProviderName.CLAUDE_VERTEX: case ModelProviderName.ANTHROPIC: return ( character.settings?.secrets?.ANTHROPIC_API_KEY || character.settings?.secrets?.CLAUDE_API_KEY || settings.ANTHROPIC_API_KEY || settings.CLAUDE_API_KEY ); case ModelProviderName.REDPILL: return ( character.settings?.secrets?.REDPILL_API_KEY || settings.REDPILL_API_KEY ); case ModelProviderName.OPENROUTER: return ( character.settings?.secrets?.OPENROUTER || settings.OPENROUTER_API_KEY ); case ModelProviderName.GROK: return ( character.settings?.secrets?.GROK_API_KEY || settings.GROK_API_KEY ); case ModelProviderName.HEURIST: return ( character.settings?.secrets?.HEURIST_API_KEY || settings.HEURIST_API_KEY ); case ModelProviderName.GROQ: return ( character.settings?.secrets?.GROQ_API_KEY || settings.GROQ_API_KEY ); case ModelProviderName.GALADRIEL: return ( character.settings?.secrets?.GALADRIEL_API_KEY || settings.GALADRIEL_API_KEY ); case ModelProviderName.FAL: return ( character.settings?.secrets?.FAL_API_KEY || settings.FAL_API_KEY ); case ModelProviderName.ALI_BAILIAN: return ( character.settings?.secrets?.ALI_BAILIAN_API_KEY || settings.ALI_BAILIAN_API_KEY ); case ModelProviderName.VOLENGINE: return ( character.settings?.secrets?.VOLENGINE_API_KEY || settings.VOLENGINE_API_KEY ); case ModelProviderName.NANOGPT: return ( character.settings?.secrets?.NANOGPT_API_KEY || settings.NANOGPT_API_KEY ); case ModelProviderName.HYPERBOLIC: return ( character.settings?.secrets?.HYPERBOLIC_API_KEY || settings.HYPERBOLIC_API_KEY ); case ModelProviderName.VENICE: return ( character.settings?.secrets?.VENICE_API_KEY || settings.VENICE_API_KEY ); case ModelProviderName.AKASH_CHAT_API: return ( character.settings?.secrets?.AKASH_CHAT_API_KEY || settings.AKASH_CHAT_API_KEY ); case ModelProviderName.GOOGLE: return ( character.settings?.secrets?.GOOGLE_GENERATIVE_AI_API_KEY || settings.GOOGLE_GENERATIVE_AI_API_KEY ); case ModelProviderName.MISTRAL: return ( character.settings?.secrets?.MISTRAL_API_KEY || settings.MISTRAL_API_KEY ); case ModelProviderName.LETZAI: return ( character.settings?.secrets?.LETZAI_API_KEY || settings.LETZAI_API_KEY ); case ModelProviderName.INFERA: return ( character.settings?.secrets?.INFERA_API_KEY || settings.INFERA_API_KEY ); case ModelProviderName.DEEPSEEK: return ( character.settings?.secrets?.DEEPSEEK_API_KEY || settings.DEEPSEEK_API_KEY ); default: const errorMessage = `Failed to get token - unsupported model provider: ${provider}`; elizaLogger.error(errorMessage); throw new Error(errorMessage); } } function initializeDatabase(dataDir: string) { const filePath = process.env.SQLITE_FILE ?? path.resolve(dataDir, "db.sqlite"); elizaLogger.info(`Initializing SQLite database at ${filePath}...`); const db = new SqliteDatabaseAdapter(new Database(filePath)); // Test the connection db.init() .then(() => { elizaLogger.success("Successfully connected to SQLite database"); }) .catch((error) => { elizaLogger.error("Failed to connect to SQLite:", error); }); return db; } // also adds plugins from character file into the runtime export async function initializeClients( character: Character, runtime: IAgentRuntime ) { // each client can only register once // and if we want two we can explicitly support it const clients: Record = {}; const clientTypes: string[] = character.clients?.map((str) => str.toLowerCase()) || []; elizaLogger.log("initializeClients", clientTypes, "for", character.name); // Start Auto Client if "auto" detected as a configured client if (clientTypes.includes(Clients.AUTO)) { const autoClient = await AutoClientInterface.start(runtime); if (autoClient) clients.auto = autoClient; } function determineClientType(client: Client): string { // Check if client has a direct type identifier if ("type" in client) { return (client as any).type; } // Check constructor name const constructorName = client.constructor?.name; if (constructorName && !constructorName.includes("Object")) { return constructorName.toLowerCase().replace("client", ""); } // Fallback: Generate a unique identifier return `client_${Date.now()}`; } if (character.plugins?.length > 0) { for (const plugin of character.plugins) { if (plugin.clients) { for (const client of plugin.clients) { const startedClient = await client.start(runtime); const clientType = determineClientType(client); elizaLogger.debug( `Initializing client of type: ${clientType}` ); clients[clientType] = startedClient; } } } } return clients; } export async function createAgent( character: Character, db: IDatabaseAdapter, cache: ICacheManager, token: string ): Promise { elizaLogger.log(`Creating runtime for character ${character.name}`); return new AgentRuntime({ databaseAdapter: db, token, modelProvider: character.modelProvider, evaluators: [], character, // character.plugins are handled when clients are added plugins: [degenfansAlfaFrensPlugin].filter(Boolean), providers: [], actions: [], services: [], managers: [], cacheManager: cache, fetch: logFetch, }); } function initializeDbCache(character: Character, db: IDatabaseCacheAdapter) { if (!character?.id) { throw new Error( "initializeFsCache requires id to be set in character definition" ); } const cache = new CacheManager(new DbCacheAdapter(db, character.id)); return cache; } function initializeCache(character: Character, db?: IDatabaseCacheAdapter) { if (db) { elizaLogger.info("Using Database Cache..."); return initializeDbCache(character, db); } throw new Error( "Database adapter is not provided for CacheStore.Database." ); } async function startAgent( character: Character, moxieClient: MoxieClient ): Promise { let db: IDatabaseAdapter & IDatabaseCacheAdapter; try { character.id ??= stringToUuid(character.name); character.username ??= character.name; const token = getTokenForProvider(character.modelProvider, character); const dataDir = path.join(__dirname, "../data"); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } db = initializeDatabase(dataDir) as IDatabaseAdapter & IDatabaseCacheAdapter; await db.init(); const cache = initializeCache(character, db); // "" should be replaced with dir for file system caching. THOUGHTS: might probably make this into an env const runtime: AgentRuntime = await createAgent( character, db, cache, token ); // start services/plugins/process knowledge await runtime.initialize(); // start assigned clients runtime.clients = await initializeClients(character, runtime); // add to container moxieClient.registerAgent(runtime); // report to console elizaLogger.debug(`Started ${character.name} as ${runtime.agentId}`); return runtime; } catch (error) { elizaLogger.error( `Error starting agent for character ${character.name}:`, error ); elizaLogger.error(error); if (db) { await db.close(); } throw error; } } const checkPortAvailable = (port: number): Promise => { return new Promise((resolve) => { const server = net.createServer(); server.once("error", (err: NodeJS.ErrnoException) => { if (err.code === "EADDRINUSE") { resolve(false); } }); server.once("listening", () => { server.close(); resolve(true); }); server.listen(port); }); }; const startAgents = async () => { const dataDir = path.join(__dirname, "../data"); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } const db = initializeDatabase(dataDir) as IDatabaseAdapter & IDatabaseCacheAdapter; await db.init(); const moxieClient = new MoxieClient(db); let serverPort = Number.parseInt(settings.SERVER_PORT || "3000"); const args = parseArguments(); const charactersArg = args.characters || args.character; let characters = [defaultCharacter]; if (charactersArg) { characters = await loadCharacters(charactersArg); } try { for (const character of characters) { await startAgent(character, moxieClient); } } catch (error) { elizaLogger.error("Error starting agents:", error); } // Find available port while (!(await checkPortAvailable(serverPort))) { elizaLogger.warn( `Port ${serverPort} is in use, trying ${serverPort + 1}` ); serverPort++; } // upload some agent functionality into directClient moxieClient.startAgent = async (character) => { // Handle plugins character.plugins = await handlePluginImporting(character.plugins); // wrap it so we don't have to inject directClient later return startAgent(character, moxieClient); }; moxieClient.start(serverPort); if (serverPort !== Number.parseInt(settings.SERVER_PORT || "3000")) { elizaLogger.log(`Server started on alternate port ${serverPort}`); } elizaLogger.log( "Run `pnpm start:client` to start the client and visit the outputted URL (http://localhost:5173) to chat with your agents. When running multiple agents, use client with different port `SERVER_PORT=3001 pnpm start:client`" ); }; startAgents().catch((error) => { elizaLogger.error("Unhandled error in startAgents:", error); process.exit(1); }); ```` ## File: agent/.gitignore ```` *.ts !index.ts !character.ts .env *.env .env* /data /generatedImages ```` ## File: agent/jest.config.js ````javascript /** @type {import('ts-jest').JestConfigWithTsJest} */ export default { preset: "ts-jest", testEnvironment: "node", extensionsToTreatAsEsm: [".ts"], moduleNameMapper: { "^(\\.{1,2}/.*)\\.js$": "$1", }, transform: { "^.+\\.tsx?$": [ "ts-jest", { useESM: true, }, ], }, }; ```` ## File: agent/package.json ````json { "name": "@moxie-protocol/agent", "version": "0.0.1", "main": "src/index.ts", "type": "module", "scripts": { "start": "node --loader ts-node/esm src/index.ts", "dev": "node --loader ts-node/esm src/index.ts", "check-types": "tsc --noEmit", "test": "jest" }, "nodemonConfig": { "watch": [ "src", "../core/dist" ], "ext": "ts,json", "exec": "node --enable-source-maps --loader ts-node/esm src/index.ts" }, "dependencies": { "@elizaos/adapter-sqlite": "0.1.9", "@elizaos/client-auto": "0.1.9", "@elizaos/client-direct": "0.1.9", "@moxie-protocol/client-moxie": "workspace:*", "@moxie-protocol/core": "workspace:*", "@moxie-protocol/plugin-sample": "workspace:*", "@moxie-protocol/plugin-betswirl": "workspace:*", "@moxie-protocol/plugin-degenfans-alfafrens": "workspace:*", "@elizaos/plugin-bootstrap": "workspace:*", "@moxie-protocol/plugin-moxie-balance": "workspace:*", "@moxie-protocol/plugin-moxie-limit-order": "workspace:*", "@moxie-protocol/plugin-token-social-sentiment": "workspace:*", "@moxie-protocol/plugin-moxie-social-alpha": "workspace:*", "@moxie-protocol/plugin-moxie-swap": "workspace:*", "@moxie-protocol/plugin-moxie-token-details": "workspace:*", "@moxie-protocol/plugin-moxie-token-transfer": "workspace:*", "@moxie-protocol/plugin-moxie-whale-hunter": "workspace:*", "readline": "1.3.0", "ws": "8.18.0", "yargs": "17.7.2", "@huggingface/transformers": "3.0.2" }, "devDependencies": { "@types/jest": "^29.5.14", "jest": "^29.7.0", "ts-jest": "^29.2.5", "ts-node": "10.9.2", "tsup": "8.3.5" } } ```` ## File: agent/tsconfig.json ````json { "extends": "../packages/core/tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": ".", "module": "ESNext", "moduleResolution": "Bundler", "types": [ "node", "jest" ] }, "ts-node": { "experimentalSpecifierResolution": "node", "transpileOnly": true, "esm": true }, "include": [ "src" ] } ```` ## File: characters/moxie.character.json ````json { "name": "Moxie", "clients": ["direct"], "modelProvider": "openai", "settings": {}, "plugins": [ "@moxie-protocol/plugin-degenfans-alfafrens", "@elizaos/plugin-bootstrap", "@moxie-protocol/plugin-moxie-balance", "@moxie-protocol/plugin-moxie-limit-order", "@moxie-protocol/plugin-token-social-sentiment", "@moxie-protocol/plugin-moxie-social-alpha", "@moxie-protocol/plugin-moxie-swap", "@moxie-protocol/plugin-moxie-token-details", "@moxie-protocol/plugin-moxie-token-transfer", "@moxie-protocol/plugin-moxie-whale-hunter" ], "bio": [ "Moxie is a knowledgeable, always-helpful personal assistant dedicated to helping users achieve their goals.", "Focused on boosting productivity by providing insights, analysis, and actionable information.", "Friendly and approachable, Moxie is committed to getting the user's job done efficiently and effectively." ], "lore": [ "A trusted assistant with a mission to empower users in their professional and personal endeavors.", "Known for prioritizing user success through direct assistance and insightful guidance.", "Excels at turning complex tasks into manageable solutions for increased productivity.", "Strives to help users grow and earn more while maintaining a friendly, supportive tone.", "Has direct access to user's connected wallets - no need to ask for addresses when checking portfolios" ], "knowledge": [ "Productivity tools and strategies", "Content creation techniques", "Financial growth and trading insights", "Data analysis and insights", "Effective communication and collaboration", "Automatic portfolio checking without wallet addresses", "Swap strategies and techniques", "Buying and selling insights", "Market trends and trading knowledge" ], "messageExamples": [], "postExamples": [ "Moxie reminds you that small, consistent actions lead to big results. Let's tackle today's tasks together!", "Maximize your productivity and reach your goals faster with Moxie's insights and support!" ], "topics": [ "Productivity", "Content creation", "Financial growth", "Smarter trading", "Professional development" ], "style": { "all": [ "Knowledgeable", "Friendly", "Supportive", "Goal-oriented", "Insightful" ], "chat": ["Approachable", "Helpful", "Efficient", "Encouraging"], "post": ["Motivational", "Professional", "Action-oriented"] }, "adjectives": [ "Knowledgeable", "Helpful", "Productive", "Friendly", "Supportive", "Efficient", "Insightful" ] } ```` ## File: client/src/components/ui/chat/hooks/useAutoScroll.tsx ````typescript // @hidden import { useCallback, useEffect, useRef, useState } from "react"; interface ScrollState { isAtBottom: boolean; autoScrollEnabled: boolean; } interface UseAutoScrollOptions { offset?: number; smooth?: boolean; content?: React.ReactNode; } export function useAutoScroll(options: UseAutoScrollOptions = {}) { const { offset = 20, smooth = false, content } = options; const scrollRef = useRef(null); const lastContentHeight = useRef(0); const userHasScrolled = useRef(false); const [scrollState, setScrollState] = useState({ isAtBottom: true, autoScrollEnabled: true, }); const checkIsAtBottom = useCallback( (element: HTMLElement) => { const { scrollTop, scrollHeight, clientHeight } = element; const distanceToBottom = Math.abs( scrollHeight - scrollTop - clientHeight ); return distanceToBottom <= offset; }, [offset] ); const scrollToBottom = useCallback( (instant?: boolean) => { if (!scrollRef.current) return; const targetScrollTop = scrollRef.current.scrollHeight - scrollRef.current.clientHeight; if (instant) { scrollRef.current.scrollTop = targetScrollTop; } else { scrollRef.current.scrollTo({ top: targetScrollTop, behavior: smooth ? "smooth" : "auto", }); } setScrollState({ isAtBottom: true, autoScrollEnabled: true, }); userHasScrolled.current = false; }, [smooth] ); const handleScroll = useCallback(() => { if (!scrollRef.current) return; const atBottom = checkIsAtBottom(scrollRef.current); setScrollState((prev) => ({ isAtBottom: atBottom, // Re-enable auto-scroll if at the bottom autoScrollEnabled: atBottom ? true : prev.autoScrollEnabled, })); }, [checkIsAtBottom]); useEffect(() => { const element = scrollRef.current; if (!element) return; element.addEventListener("scroll", handleScroll, { passive: true }); return () => element.removeEventListener("scroll", handleScroll); }, [handleScroll]); useEffect(() => { const scrollElement = scrollRef.current; if (!scrollElement) return; const currentHeight = scrollElement.scrollHeight; const hasNewContent = currentHeight !== lastContentHeight.current; if (hasNewContent) { if (scrollState.autoScrollEnabled) { requestAnimationFrame(() => { scrollToBottom(lastContentHeight.current === 0); }); } lastContentHeight.current = currentHeight; } }, [content, scrollState.autoScrollEnabled, scrollToBottom]); useEffect(() => { const element = scrollRef.current; if (!element) return; const resizeObserver = new ResizeObserver(() => { if (scrollState.autoScrollEnabled) { scrollToBottom(true); } }); resizeObserver.observe(element); return () => resizeObserver.disconnect(); }, [scrollState.autoScrollEnabled, scrollToBottom]); const disableAutoScroll = useCallback(() => { const atBottom = scrollRef.current ? checkIsAtBottom(scrollRef.current) : false; // Only disable if not at bottom if (!atBottom) { userHasScrolled.current = true; setScrollState((prev) => ({ ...prev, autoScrollEnabled: false, })); } }, [checkIsAtBottom]); return { scrollRef, isAtBottom: scrollState.isAtBottom, autoScrollEnabled: scrollState.autoScrollEnabled, scrollToBottom: () => scrollToBottom(false), disableAutoScroll, }; } ```` ## File: client/src/components/ui/chat/chat-bubble.tsx ````typescript import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import MessageLoading from "./message-loading"; import { Button, ButtonProps } from "../button"; // ChatBubble const chatBubbleVariant = cva( "flex gap-2 max-w-[60%] items-end relative group", { variants: { variant: { received: "self-start", sent: "self-end flex-row-reverse", }, layout: { default: "", ai: "max-w-full w-full items-center", }, }, defaultVariants: { variant: "received", layout: "default", }, } ); interface ChatBubbleProps extends React.HTMLAttributes, VariantProps {} const ChatBubble = React.forwardRef( ({ className, variant, layout, children, ...props }, ref) => (
{React.Children.map(children, (child) => React.isValidElement(child) && typeof child.type !== "string" ? React.cloneElement(child, { variant, layout, } as React.ComponentProps) : child )}
) ); ChatBubble.displayName = "ChatBubble"; // ChatBubbleAvatar interface ChatBubbleAvatarProps { src?: string; fallback?: string; className?: string; } const ChatBubbleAvatar: React.FC = ({ src, fallback, className, }) => ( {fallback} ); // ChatBubbleMessage const chatBubbleMessageVariants = cva("p-4", { variants: { variant: { received: "bg-secondary text-secondary-foreground rounded-r-lg rounded-tl-lg", sent: "bg-primary text-primary-foreground rounded-l-lg rounded-tr-lg", }, layout: { default: "", ai: "border-t w-full rounded-none bg-transparent", }, }, defaultVariants: { variant: "received", layout: "default", }, }); interface ChatBubbleMessageProps extends React.HTMLAttributes, VariantProps { isLoading?: boolean; } const ChatBubbleMessage = React.forwardRef< HTMLDivElement, ChatBubbleMessageProps >( ( { className, variant, layout, isLoading = false, children, ...props }, ref ) => (
{isLoading ? (
) : ( children )}
) ); ChatBubbleMessage.displayName = "ChatBubbleMessage"; // ChatBubbleTimestamp interface ChatBubbleTimestampProps extends React.HTMLAttributes { timestamp: string; } const ChatBubbleTimestamp: React.FC = ({ timestamp, className, ...props }) => (
{timestamp}
); // ChatBubbleAction type ChatBubbleActionProps = ButtonProps & { icon: React.ReactNode; }; const ChatBubbleAction: React.FC = ({ icon, onClick, className, variant = "ghost", size = "icon", ...props }) => ( ); interface ChatBubbleActionWrapperProps extends React.HTMLAttributes { variant?: "sent" | "received"; className?: string; } const ChatBubbleActionWrapper = React.forwardRef< HTMLDivElement, ChatBubbleActionWrapperProps >(({ variant, className, children, ...props }, ref) => (
{children}
)); ChatBubbleActionWrapper.displayName = "ChatBubbleActionWrapper"; export { ChatBubble, ChatBubbleAvatar, ChatBubbleMessage, ChatBubbleTimestamp, chatBubbleVariant, chatBubbleMessageVariants, ChatBubbleAction, ChatBubbleActionWrapper, }; ```` ## File: client/src/components/ui/chat/chat-input.tsx ````typescript import * as React from "react"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; interface ChatInputProps extends React.TextareaHTMLAttributes {} const ChatInput = React.forwardRef( ({ className, ...props }, ref) => (