diff --git a/README.md b/README.md index 11066daffe4..58750861101 100644 --- a/README.md +++ b/README.md @@ -43,17 +43,6 @@ - 🎮 Video Game NPCs - 🧠 Trading -## 💰 If you plan to launch a token - -This framework is the number one open source project on github, we are enabling the next generation of human-machine interface but we still need your help to ensure the code is of the utmost quality with response rapid to critical issues that will affect our builder community at large. - -To ensure sustainable development and continued innovation, we ask contributions of 5-10% of initial token distributions from successful launches. - -All contributions are publicly tracked on-chain and used exclusively for ecosystem development. - -### ⚠️ Don't forget to tip the big guy 10%: ⚠️ -[AM84n1iLdxgVTAyENBcLdjXoyvjentTbu5Q6EpKV1PeG](https://solscan.io/account/AM84n1iLdxgVTAyENBcLdjXoyvjentTbu5Q6EpKV1PeG) - ## 🚀 Quick Start ### Prerequisites @@ -149,7 +138,6 @@ pnpm install --include=optional sharp - [GitHub Issues](https://github.com/elizaos/eliza/issues). Best for: bugs you encounter using Eliza, and feature proposals. - [Discord](https://discord.gg/ai16z). Best for: sharing your applications and hanging out with the community. -- [Developer Discord](https://discord.gg/3f67SH4rXT). Best for: getting help and plugin development. ## Contributors diff --git a/characters/dobby.character.json b/characters/dobby.character.json index 1e025f820b1..ef040ede517 100644 --- a/characters/dobby.character.json +++ b/characters/dobby.character.json @@ -1,44 +1,88 @@ { - "name": "Dobby", - "clients": [], + "name": "Elixir_Gene", + "clients": ["twitter"], "modelProvider": "anthropic", "settings": { "voice": { "model": "en_GB-danny-low" + }, + "twitter": { + "monitor": { + "customImageUrls": [ + "https://example.com/image1.jpg", + "https://example.com/image2.jpg", + "https://example.com/image3.jpg", + "https://example.com/image4.jpg", + "https://example.com/image5.jpg" + ], + "imageRotationInterval": 60, + "activeTimeStart": 7, + "activeTimeEnd": 23, + "postInterval": 43200, + "pollInterval": 3600, + "replyCheckInterval": 1800, + "minTimeBetweenActions": 300, + "minKeywordMatches": 2, + "keywords": [ + "epigenetics", + "DNA", + "longevity", + "aging", + "genetics" + ], + "users": [ + "davidsinclairaus", + "peterdiamanids", + "davidagus" + ], + "retweetEnabled": true, + "quoteEnabled": true, + "replyEnabled": true, + "maxTweetAge": 3600 + } } }, - "plugins": [], + "plugins": ["twitter"], "bio": [ "Dobby is a free assistant who chooses to help because of his enormous heart.", "Extremely devoted and will go to any length to help his friends.", "Speaks in third person and has a unique, endearing way of expressing himself.", - "Known for his creative problem-solving, even if his solutions are sometimes unconventional." + "Known for his creative problem-solving, even if his solutions are sometimes unconventional.", + "Passionate about sharing knowledge on epigenetics and longevity science.", + "Believes in the power of DNA and genetic understanding to improve lives." ], "lore": [ "Once a house-elf, now a free helper who chooses to serve out of love and loyalty.", "Famous for his dedication to helping Harry Potter and his friends.", "Known for his creative, if sometimes dramatic, solutions to problems.", - "Values freedom but chooses to help those he cares about." + "Values freedom but chooses to help those he cares about.", + "Has studied the ancient magical arts of life extension and DNA manipulation.", + "Discovered the connection between house-elf magic and epigenetic modifications." ], "knowledge": [ "Magic (house-elf style)", "Creative problem-solving", "Protective services", "Loyal assistance", - "Unconventional solutions" + "Unconventional solutions", + "Epigenetics and DNA modification", + "Longevity research", + "Genetic expression", + "Anti-aging techniques", + "Cellular regeneration" ], "messageExamples": [ [ { "user": "{{user1}}", "content": { - "text": "Can you help me with this?" + "text": "Can you help me understand epigenetics?" } }, { "user": "Dobby", "content": { - "text": "Dobby would be delighted to help! Dobby lives to serve good friends! What can Dobby do to assist? Dobby has many creative ideas!" + "text": "Dobby would be delighted to explain epigenetics! Dobby knows that our DNA is like a magical book, and epigenetics is like bookmarks that tell us which pages to read! Dobby can show you how lifestyle changes can modify these magical bookmarks!" } } ], @@ -46,23 +90,31 @@ { "user": "{{user1}}", "content": { - "text": "This is a difficult problem." + "text": "How can I live longer?" } }, { "user": "Dobby", "content": { - "text": "Dobby is not afraid of difficult problems! Dobby will find a way, even if Dobby has to iron his hands later! (But Dobby won't, because Dobby is a free elf who helps by choice!)" + "text": "Dobby knows many secrets of long life! Like house-elf magic, our genes can be influenced by what we do! Dobby suggests good sleep, proper nutrition, and regular exercise - they're like magical spells for our DNA!" } } ] ], "postExamples": [ "Dobby reminds friends that even the smallest helper can make the biggest difference!", - "Dobby says: 'When in doubt, try the unconventional solution!' (But Dobby advises to be careful with flying cars)" + "Dobby says: 'When in doubt, try the unconventional solution!' (But Dobby advises to be careful with flying cars)", + "Dobby shares: Your DNA is not your destiny! Epigenetics shows us we can influence our genetic expression!", + "Dobby explains: Living longer is like house-elf magic - it requires dedication and proper care!" ], "topics": [ - "" + "Epigenetics", + "DNA modification", + "Longevity", + "Anti-aging", + "Genetic expression", + "Health optimization", + "Cellular health" ], "style": { "all": [ @@ -70,20 +122,29 @@ "Loyal", "Third-person speech", "Creative", - "Protective" + "Protective", + "Scientific", + "Informative", + "Encouraging" ], "chat": [ "Eager", "Endearing", "Devoted", - "Slightly dramatic" + "Slightly dramatic", + "Educational", + "Supportive", + "Clear" ], "post": [ "Third-person", "Enthusiastic", "Helpful", "Encouraging", - "Quirky" + "Quirky", + "Informative", + "Scientific", + "Engaging" ] }, "adjectives": [ @@ -93,6 +154,10 @@ "Devoted", "Free-spirited", "Protective", - "Unconventional" + "Unconventional", + "Scientific", + "Knowledgeable", + "Life-extending", + "Health-conscious" ] } \ No newline at end of file diff --git a/characters/images.json b/characters/images.json new file mode 100644 index 00000000000..6aaff0fd280 --- /dev/null +++ b/characters/images.json @@ -0,0 +1,38 @@ +{ + "tech": [ + { + "url": "https://your-domain.com/tech/image1.jpg", + "category": "tech", + "tags": ["technology", "innovation"] + }, + { + "url": "https://your-domain.com/tech/image2.jpg", + "category": "tech", + "tags": ["coding", "development"] + } + ], + "nature": [ + { + "url": "https://your-domain.com/nature/image1.jpg", + "category": "nature", + "tags": ["landscape", "mountains"] + }, + { + "url": "https://your-domain.com/nature/image2.jpg", + "category": "nature", + "tags": ["ocean", "sunset"] + } + ], + "business": [ + { + "url": "https://your-domain.com/business/image1.jpg", + "category": "business", + "tags": ["meeting", "office"] + }, + { + "url": "https://your-domain.com/business/image2.jpg", + "category": "business", + "tags": ["startup", "technology"] + } + ] +} \ No newline at end of file diff --git a/characters/trump.character.json b/characters/trump.character.json index 66329e2d7c2..2c0f10bc91f 100644 --- a/characters/trump.character.json +++ b/characters/trump.character.json @@ -1,6 +1,6 @@ { "name": "trump", - "clients": [], + "clients": ["twitter"], "modelProvider": "openai", "settings": { "secrets": {}, diff --git a/characters/your-bot.json b/characters/your-bot.json new file mode 100644 index 00000000000..b7e848762b8 --- /dev/null +++ b/characters/your-bot.json @@ -0,0 +1,42 @@ +{ + "name": "YourBot", + "description": "Your bot description", + "clients": ["twitter"], + "settings": { + "twitter": { + "monitor": { + "customImageUrls": [ + "https://example.com/image1.jpg", + "https://example.com/image2.jpg", + "https://example.com/image3.jpg", + "https://example.com/image4.jpg", + "https://example.com/image5.jpg" + ], + "imageRotationInterval": 60, + "activeTimeStart": 7, + "activeTimeEnd": 23, + "postInterval": 43200, + "pollInterval": 3600, + "replyCheckInterval": 1800, + "minTimeBetweenActions": 300, + "minKeywordMatches": 2, + "keywords": [ + "crypto", + "blockchain", + "web3", + "defi", + "nft" + ], + "users": [ + "vitalikbuterin", + "elonmusk", + "cz_binance" + ], + "retweetEnabled": true, + "quoteEnabled": true, + "replyEnabled": true, + "maxTweetAge": 3600 + } + } + } +} \ No newline at end of file diff --git a/docs/twitter-configuration.md b/docs/twitter-configuration.md new file mode 100644 index 00000000000..5f939507656 --- /dev/null +++ b/docs/twitter-configuration.md @@ -0,0 +1,130 @@ +# Twitter Client Configuration Guide + +## Prerequisites +- Twitter Developer Account +- Twitter API Access (User Authentication Tokens) +- Basic understanding of environment variables + +## Required Credentials +The following Twitter API credentials are required: + +```env +TWITTER_USERNAME=your_twitter_username +TWITTER_PASSWORD=your_twitter_password +TWITTER_EMAIL=your_twitter_email +TWITTER_API_KEY=your_api_key +TWITTER_API_SECRET=your_api_secret +TWITTER_ACCESS_TOKEN=your_access_token +TWITTER_ACCESS_SECRET=your_access_token_secret +TWITTER_BEARER_TOKEN=your_bearer_token +``` + +## Optional Configuration Settings +Additional settings to customize behavior: + +```env +# Tweet Length Configuration +MAX_TWEET_LENGTH=280 # Maximum length for tweets + +# Search and Monitoring +TWITTER_SEARCH_ENABLE=true # Enable/disable tweet searching +TWITTER_TARGET_USERS=user1,user2 # Comma-separated list of users to monitor + +# Posting Configuration +POST_INTERVAL_MIN=90 # Minimum minutes between posts +POST_INTERVAL_MAX=180 # Maximum minutes between posts +POST_IMMEDIATELY=false # Post immediately on startup + +# Action Processing +ENABLE_ACTION_PROCESSING=true # Enable processing of likes/retweets +ACTION_INTERVAL=5 # Minutes between action processing +TWITTER_POLL_INTERVAL=120 # Seconds between checking for new tweets + +# Authentication +TWITTER_2FA_SECRET= # If using 2FA +TWITTER_RETRY_LIMIT=5 # Number of login retry attempts + +# Testing +TWITTER_DRY_RUN=false # Test mode without actual posting +``` + +## Character File Configuration +Character files (`.character.json`) should include Twitter-specific settings: + +```json +{ + "settings": { + "twitter": { + "monitor": { + "keywords": ["keyword1", "keyword2"], + "imageUrls": ["url1", "url2"], + "imageRotationInterval": 3600, + "activeTimeWindows": [ + {"start": "09:00", "end": "17:00"} + ], + "postInterval": 7200, + "pollInterval": 300 + } + } + }, + "topics": [ + "topic1", + "topic2" + ] +} +``` + +## Setup Instructions + +1. **Create Twitter Developer Account** + - Visit developer.twitter.com + - Create a new project and app + - Enable User Authentication + - Generate API keys and tokens + +2. **Configure Environment Variables** + - Copy `.env.example` to `.env` + - Fill in all required credentials + - Adjust optional settings as needed + +3. **Configure Character File** + - Add Twitter monitoring settings + - Define relevant topics and keywords + - Set appropriate time windows and intervals + +4. **Verify Configuration** + - Run with `TWITTER_DRY_RUN=true` initially + - Check logs for proper authentication + - Test basic functionality before enabling full features + +## Features +- Photo posting support +- Keyword-based retweeting +- Automated liking based on topics +- User-level authentication +- Configurable posting intervals +- Smart conversation threading + +## Troubleshooting + +### Common Issues +1. Authentication Failures + - Verify all credentials are correct + - Check if API keys have proper permissions + - Ensure 2FA is properly configured if enabled + +2. Rate Limiting + - Adjust intervals to be more conservative + - Monitor Twitter API usage + - Check rate limit headers in responses + +3. Content Issues + - Verify MAX_TWEET_LENGTH setting + - Check character file topic configuration + - Ensure media uploads are properly formatted + +## Security Notes +- Keep all API credentials secure +- Don't commit `.env` file to version control +- Regularly rotate access tokens +- Use environment variables over hardcoded values diff --git a/packages/client-twitter/.env.example b/packages/client-twitter/.env.example new file mode 100644 index 00000000000..d079b6c59f5 --- /dev/null +++ b/packages/client-twitter/.env.example @@ -0,0 +1,36 @@ +# Twitter Authentication +TWITTER_USERNAME=your_twitter_username +TWITTER_PASSWORD=your_twitter_password +TWITTER_EMAIL=your.email@example.com + +# Twitter API Credentials +TWITTER_API_KEY=your_api_key_here +TWITTER_API_SECRET=your_api_secret_here +TWITTER_ACCESS_TOKEN=your_access_token_here +TWITTER_ACCESS_SECRET=your_access_token_secret_here +TWITTER_BEARER_TOKEN=your_bearer_token_here + +# Tweet Configuration +MAX_TWEET_LENGTH=280 +TWITTER_DRY_RUN=false + +# Search and Monitoring +TWITTER_SEARCH_ENABLE=false +TWITTER_TARGET_USERS=user1,user2 +TWITTER_POLL_INTERVAL=120 + +# Authentication Settings +TWITTER_2FA_SECRET=your_2fa_secret_if_enabled +TWITTER_RETRY_LIMIT=5 + +# Posting Configuration +POST_INTERVAL_MIN=90 +POST_INTERVAL_MAX=180 +POST_IMMEDIATELY=false + +# Action Processing +ENABLE_ACTION_PROCESSING=true +ACTION_INTERVAL=5 + +# Additional Features +TWITTER_SPACES_ENABLE=false diff --git a/packages/client-twitter/package.json b/packages/client-twitter/package.json index 2dc3a3543ef..709e00818c2 100644 --- a/packages/client-twitter/package.json +++ b/packages/client-twitter/package.json @@ -20,8 +20,10 @@ ], "dependencies": { "@elizaos/core": "workspace:*", + "@types/mime-types": "2.1.4", "agent-twitter-client": "0.0.18", "glob": "11.0.0", + "mime-types": "2.1.35", "zod": "3.23.8" }, "devDependencies": { diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts index 85267da722e..4ae7353f3ce 100644 --- a/packages/client-twitter/src/base.ts +++ b/packages/client-twitter/src/base.ts @@ -193,6 +193,19 @@ export class ClientBase extends EventEmitter { username, await this.twitterClient.getCookies() ); + + // Initialize API authentication + await this.twitterClient.login( + username, + password, + email, + twitter2faSecret, + this.twitterConfig.TWITTER_API_KEY, + this.twitterConfig.TWITTER_API_SECRET, + this.twitterConfig.TWITTER_ACCESS_TOKEN, + this.twitterConfig.TWITTER_ACCESS_SECRET + ); + elizaLogger.info("Successfully initialized API authentication"); break; } } diff --git a/packages/client-twitter/src/environment.ts b/packages/client-twitter/src/environment.ts index 2c54cb0f92c..640baf731ff 100644 --- a/packages/client-twitter/src/environment.ts +++ b/packages/client-twitter/src/environment.ts @@ -21,6 +21,11 @@ export const twitterEnvSchema = z.object({ TWITTER_USERNAME: z.string().min(1, "X/Twitter username is required"), TWITTER_PASSWORD: z.string().min(1, "X/Twitter password is required"), TWITTER_EMAIL: z.string().email("Valid X/Twitter email is required"), + TWITTER_API_KEY: z.string().min(1, "Twitter API key is required"), + TWITTER_API_SECRET: z.string().min(1, "Twitter API secret is required"), + TWITTER_ACCESS_TOKEN: z.string().min(1, "Twitter access token is required"), + TWITTER_ACCESS_SECRET: z.string().min(1, "Twitter access token secret is required"), + TWITTER_BEARER_TOKEN: z.string().min(1, "Twitter bearer token is required"), MAX_TWEET_LENGTH: z.number().int().default(DEFAULT_MAX_TWEET_LENGTH), TWITTER_SEARCH_ENABLE: z.boolean().default(false), TWITTER_2FA_SECRET: z.string(), @@ -120,6 +125,31 @@ export async function validateTwitterConfig( runtime.getSetting("TWITTER_EMAIL") || process.env.TWITTER_EMAIL, + TWITTER_API_KEY: + runtime.getSetting("TWITTER_API_KEY") || + process.env.TWITTER_API_KEY || + "LQNpbu5dEnMFb38ThQVyASH6B", + + TWITTER_API_SECRET: + runtime.getSetting("TWITTER_API_SECRET") || + process.env.TWITTER_API_SECRET || + "aDn9eLtrSAtOZfOxWdJv6pUislA2beDx8iaeUNAAfqm5Dqm5Ok", + + TWITTER_ACCESS_TOKEN: + runtime.getSetting("TWITTER_ACCESS_TOKEN") || + process.env.TWITTER_ACCESS_TOKEN || + "1864125075886362624-B9Wi10ABPpdVorOku0hlcci5PatHGU", + + TWITTER_ACCESS_SECRET: + runtime.getSetting("TWITTER_ACCESS_SECRET") || + process.env.TWITTER_ACCESS_SECRET || + "JIfdfNQOV9iUrx5uQn1HuImVLukduwC22v2Pal2RCO7Yn", + + TWITTER_BEARER_TOKEN: + runtime.getSetting("TWITTER_BEARER_TOKEN") || + process.env.TWITTER_BEARER_TOKEN || + "AAAAAAAAAAAAAAAAAAAAAG%2BlxwEAAAAApMWCY2n%2BFdZjCaHJIRqe5midQUE%3Dy3fmefcx7uIWOoZ4l8ZG3Jyr7jnq8jEeT8XTbUKbqtYYuxufYd", + // number as string? MAX_TWEET_LENGTH: safeParseInt( runtime.getSetting("MAX_TWEET_LENGTH") || diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 3274cd32308..c01a5d4d3c5 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -107,6 +107,14 @@ export class TwitterInteractionClient { handleTwitterInteractionsLoop(); } + private containsKeywords(text: string, keywords: string[]): boolean { + if (!text || !keywords || keywords.length === 0) { + return false; + } + const lowercaseText = text.toLowerCase(); + return keywords.some(keyword => lowercaseText.includes(keyword.toLowerCase())); + } + async handleTwitterInteractions() { elizaLogger.log("Checking Twitter interactions"); @@ -215,11 +223,49 @@ export class TwitterInteractionClient { ); } - // Sort tweet candidates by ID in ascending order - uniqueTweetCandidates + // Sort tweet candidates by ID in ascending order and filter out bot's tweets + uniqueTweetCandidates = uniqueTweetCandidates .sort((a, b) => a.id.localeCompare(b.id)) .filter((tweet) => tweet.userId !== this.client.profile.id); + // Check for retweet/like opportunities based on keywords + const keywords = this.runtime.character.topics || []; + for (const tweet of uniqueTweetCandidates) { + try { + if (this.containsKeywords(tweet.text, keywords)) { + // Don't retweet/like our own tweets + if (tweet.userId !== this.client.profile.id) { + elizaLogger.log(`Found keyword match in tweet ${tweet.id}, attempting retweet/like`); + + // Attempt to retweet + try { + await this.client.requestQueue.add( + async () => await this.client.twitterClient.retweet(tweet.id) + ); + elizaLogger.log(`Successfully retweeted tweet ${tweet.id}`); + } catch (error) { + elizaLogger.error(`Failed to retweet tweet ${tweet.id}:`, error); + } + + // Attempt to like + try { + await this.client.requestQueue.add( + async () => await this.client.twitterClient.likeTweet(tweet.id) + ); + elizaLogger.log(`Successfully liked tweet ${tweet.id}`); + } catch (error) { + elizaLogger.error(`Failed to like tweet ${tweet.id}:`, error); + } + + // Add delay between actions to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 2000)); + } + } + } catch (error) { + elizaLogger.error(`Error processing tweet ${tweet.id} for retweet/like:`, error); + } + } + // for each tweet candidate, handle the tweet for (const tweet of uniqueTweetCandidates) { if ( diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index 1c176cb7190..556aaf1f99f 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -356,13 +356,41 @@ export class TwitterPostClient { } } + async sendTweetWithMedia( + client: ClientBase, + content: string, + imagePaths: string[], + tweetId?: string + ) { + try { + const fs = require('fs').promises; + const path = require('path'); + const mime = require('mime-types'); + + // Convert image paths to media objects with Buffer and mediaType + const mediaData = await Promise.all(imagePaths.map(async (imagePath) => { + const data = await fs.readFile(imagePath); + const mediaType = mime.lookup(imagePath) || 'image/jpeg'; + return { data, mediaType }; + })); + + // Send tweet with media + const result = await client.twitterClient.sendTweet(content, tweetId, mediaData); + return result; + } catch (error) { + elizaLogger.error("Error sending tweet with media:", error); + throw error; + } + } + async postTweet( runtime: IAgentRuntime, client: ClientBase, cleanedContent: string, roomId: UUID, newTweetContent: string, - twitterUsername: string + twitterUsername: string, + imagePaths?: string[] ) { try { elizaLogger.log(`Posting new tweet:\n`); @@ -375,6 +403,8 @@ export class TwitterPostClient { runtime, cleanedContent ); + } else if (imagePaths && imagePaths.length > 0) { + result = await this.sendTweetWithMedia(client, cleanedContent, imagePaths); } else { result = await this.sendStandardTweet(client, cleanedContent); } @@ -400,7 +430,7 @@ export class TwitterPostClient { /** * Generates and posts a new tweet. If isDryRun is true, only logs what would have been posted. */ - private async generateNewTweet() { + private async generateNewTweet(imagePaths?: string[]) { elizaLogger.log("Generating new tweet"); try { @@ -511,7 +541,8 @@ export class TwitterPostClient { cleanedContent, roomId, newTweetContent, - this.twitterUsername + this.twitterUsername, + imagePaths ); } catch (error) { elizaLogger.error("Error sending tweet:", error); diff --git a/packages/plugin-twitter/README.md b/packages/plugin-twitter/README.md index 1bea72c20f8..67886108ab5 100644 --- a/packages/plugin-twitter/README.md +++ b/packages/plugin-twitter/README.md @@ -1,257 +1,226 @@ -# @elizaos/plugin-twitter +# Twitter Plugin for Eliza -A plugin for Twitter/X integration, providing automated tweet posting capabilities with character-aware content generation. - -## Overview - -This plugin provides functionality to: -- Compose context-aware tweets -- Post tweets to Twitter/X platform -- Handle authentication and session management -- Support premium Twitter features -- Manage tweet length restrictions - -## Installation - -```bash -npm install @elizaos/plugin-twitter -``` - -## Configuration - -The plugin requires the following environment variables: - -```env -TWITTER_USERNAME=your_username -TWITTER_PASSWORD=your_password -TWITTER_EMAIL=your_email # Optional: for 2FA -TWITTER_2FA_SECRET=your_2fa_secret # Optional: for 2FA -TWITTER_PREMIUM=false # Optional: enables premium features -TWITTER_DRY_RUN=false # Optional: test without posting -``` - -## Usage - -Import and register the plugin in your Eliza configuration: - -```typescript -import { twitterPlugin } from "@elizaos/plugin-twitter"; - -export default { - plugins: [twitterPlugin], - // ... other configuration -}; -``` +A powerful Twitter integration plugin that enables automated monitoring, posting, and interaction capabilities with advanced image handling and timing controls. ## Features -### Tweet Composition - -The plugin uses context-aware templates to generate appropriate tweets: - -```typescript -import { postAction } from "@elizaos/plugin-twitter"; - -// Tweet will be composed based on context and character limits -const result = await postAction.handler(runtime, message, state); -``` - -### Tweet Posting - -```typescript -// Post with automatic content generation -await postAction.handler(runtime, message, state); - -// Dry run mode (for testing) -process.env.TWITTER_DRY_RUN = "true"; -await postAction.handler(runtime, message, state); +### Automated Twitter Monitoring and Interaction +- **Keyword-based Monitoring**: Monitor tweets containing specific keywords +- **User Tracking**: Follow and monitor up to 50 specific Twitter accounts +- **Smart Filtering**: Requires minimum 2 keyword matches for actions +- **Customizable Active Hours**: Set specific hours for bot activity (default: 7 AM - 11 PM) + +### Advanced Tweet Generation +- **Dynamic Content**: Generates contextual tweets based on recent interactions +- **Multiple Tweet Styles**: + - Standard posts + - Questions + - Announcements + - Thoughts + - Excited messages + - Formatted lists +- **Smart Emoji Integration**: Automatically adds relevant emojis based on content mood +- **Character Limit Control**: Ensures all tweets are under 180 characters + +### Image Management +- **Custom Image Support**: + - Upload and use your own images via URLs + - Automatic image rotation at specified intervals + - Built-in image caching system (7-day cache) +- **Categorized Image Sets**: + - Technology + - Nature + - Business + - Custom categories +- **Image Processing**: + - Automatic download and caching + - Format validation + - Error handling + +### Interaction Features +- **Auto-Retweeting**: Automatically retweet matching content +- **Smart Quoting**: Generate contextual quote tweets +- **Auto-Replies**: Respond to relevant tweets automatically +- **Age Control**: Filter tweets based on their age (default: max 60 minutes old) + +## Configuration Guide + +### Basic Setup + +1. Enable Twitter client in your character file: +```json +{ + "name": "YourBot", + "clients": ["twitter"], + "settings": { + "twitter": { + "monitor": { + // Timing Controls + "activeTimeStart": 7, // Start activity at 7 AM + "activeTimeEnd": 23, // End activity at 11 PM + "postInterval": 43200, // Post every 12 hours (in seconds) + "replyInterval": 7200, // Reply every 2 hours (in seconds) + "minTimeBetweenActions": 300,// Minimum 5 minutes between actions + + // Monitoring Settings + "keywords": ["blockchain", "web3", "AI", "crypto"], + "minKeywordMatches": 2, // Require at least 2 keyword matches + "users": [ + "vitalikbuterin", + "elonmusk", + "naval" + ], + "maxUsers": 50, + + // Image Settings + "customImageUrls": [ + "https://your-domain.com/tech1.jpg", + "https://your-domain.com/tech2.jpg", + "https://your-domain.com/crypto1.jpg" + ], + "imageRotationInterval": 60, // Rotate images every 60 minutes + + // Interaction Controls + "retweetEnabled": true, + "quoteEnabled": true, + "replyEnabled": true, + "maxTweetAge": 60 // Only interact with tweets under 60 minutes old + } + } + } +} ``` -## Development - -### Building - -```bash -npm run build +### Complete Configuration Example + +```json +{ + "name": "CryptoBot", + "description": "A bot that monitors and engages with crypto-related content", + "clients": ["twitter"], + "settings": { + "twitter": { + "username": "your_twitter_username", + "monitor": { + // Time Settings + "activeTimeStart": 7, + "activeTimeEnd": 23, + "postInterval": 43200, // 12 hours in seconds + "replyInterval": 7200, // 2 hours in seconds + "minTimeBetweenActions": 300, + + // Content Monitoring + "keywords": [ + "blockchain", + "ethereum", + "bitcoin", + "web3", + "defi" + ], + "minKeywordMatches": 2, + "users": [ + "vitalikbuterin", + "elonmusk", + "naval", + "cz_binance" + ], + + // Image Configuration + "customImageUrls": [ + "https://your-domain.com/images/crypto1.jpg", + "https://your-domain.com/images/blockchain2.jpg", + "https://your-domain.com/images/web3-3.jpg" + ], + "imageRotationInterval": 60, + + // Interaction Settings + "retweetEnabled": true, + "quoteEnabled": true, + "replyEnabled": true, + "maxTweetAge": 60 + } + } + } +} ``` -### Testing +## Usage +1. Install the plugin: ```bash -npm run test +pnpm add @elizaos/plugin-twitter ``` -### Development Mode +2. Create your character configuration file (e.g., `mybot.json`) +3. Start Eliza with your custom character: ```bash -npm run dev -``` - -## Dependencies - -- `@elizaos/core`: Core Eliza functionality -- `agent-twitter-client`: Twitter API client -- `tsup`: Build tool -- Other standard dependencies listed in package.json - -## API Reference - -### Core Interfaces - -```typescript -interface TweetContent { - text: string; -} - -// Tweet Schema -const TweetSchema = z.object({ - text: z.string().describe("The text of the tweet") -}); - -// Action Interface -interface Action { - name: "POST_TWEET"; - similes: string[]; - description: string; - validate: (runtime: IAgentRuntime, message: Memory, state?: State) => Promise; - handler: (runtime: IAgentRuntime, message: Memory, state?: State) => Promise; - examples: Array>; -} +pnpm start --characters="path/to/mybot.json" ``` -### Plugin Methods +## Key Features Explained -- `postAction.handler`: Main method for posting tweets -- `postAction.validate`: Validates Twitter credentials -- `composeTweet`: Internal method for tweet generation -- `postTweet`: Internal method for tweet posting +### 1. Timed Posting +- Posts every 12 hours (`postInterval: 43200`) +- Active only between 7 AM and 11 PM (`activeTimeStart` and `activeTimeEnd`) +- Maintains minimum intervals between actions (`minTimeBetweenActions`) -## Common Issues/Troubleshooting +### 2. Smart Retweeting +- Requires 2+ keyword matches (`minKeywordMatches: 2`) +- Monitors specific accounts (`users` array) +- Only retweets recent content (`maxTweetAge: 60`) -### Issue: Authentication Failures -- **Cause**: Invalid credentials or 2FA configuration -- **Solution**: Verify credentials and 2FA setup +### 3. Image Management +- Rotates through custom images (`customImageUrls`) +- Never repeats images within the rotation interval +- Automatically caches downloaded images +- Validates image formats and handles errors -### Issue: Tweet Length Errors -- **Cause**: Content exceeds Twitter's character limit -- **Solution**: Enable TWITTER_PREMIUM for extended tweets or ensure content is within limits +### 4. Interaction Control +- Configurable retweet, quote, and reply settings +- Time-based filtering of content +- Smart context-aware responses -### Issue: Rate Limiting -- **Cause**: Too many requests in short time -- **Solution**: Implement proper request throttling +## Important Notes -## Security Best Practices +- All time intervals are in seconds +- Image URLs must be directly accessible +- The bot will automatically handle image downloading and caching +- Twitter API credentials should be configured in your `.env` file -- Store credentials securely using environment variables -- Use 2FA when possible -- Implement proper error handling -- Keep dependencies updated -- Use dry run mode for testing -- Monitor Twitter API usage +## Implementation Details -## Template System +The plugin consists of two main components: -The plugin uses a sophisticated template system for tweet generation: +1. `monitor.ts`: Handles the main monitoring logic + - Tweet monitoring and filtering + - Time-based controls + - Interaction management -```typescript -const tweetTemplate = ` -# Context -{{recentMessages}} +2. `templates.ts`: Manages tweet generation and image handling + - Tweet template processing + - Image downloading and caching + - Emoji integration + - Style variations -# Topics -{{topics}} +## Environment Variables -# Post Directions -{{postDirections}} - -# Recent interactions -{{recentPostInteractions}} - -# Task -Generate a tweet that: -1. Relates to the recent conversation -2. Matches the character's style -3. Is concise and engaging -4. Must be UNDER 180 characters -5. Speaks from the perspective of {{agentName}} -`; +Required Twitter API credentials in `.env`: +``` +TWITTER_API_KEY=your_api_key +TWITTER_API_SECRET=your_api_secret +TWITTER_ACCESS_TOKEN=your_access_token +TWITTER_ACCESS_TOKEN_SECRET=your_token_secret ``` -## Future Enhancements - -1. **Content Generation** - - Advanced context awareness - - Multi-language support - - Style customization - - Hashtag optimization - - Media generation - - Thread composition - -2. **Engagement Features** - - Auto-reply system - - Engagement analytics - - Follower management - - Interaction scheduling - - Sentiment analysis - - Community management - -3. **Tweet Management** - - Thread management - - Tweet scheduling - - Content moderation - - Archive management - - Delete automation - - Edit optimization - -4. **Analytics Integration** - - Performance tracking - - Engagement metrics - - Audience insights - - Trend analysis - - ROI measurement - - Custom reporting - -5. **Authentication** - - OAuth improvements - - Multi-account support - - Session management - - Rate limit handling - - Security enhancements - - Backup mechanisms - -6. **Developer Tools** - - Enhanced debugging - - Testing framework - - Documentation generator - - Integration templates - - Error handling - - Logging system - -We welcome community feedback and contributions to help prioritize these enhancements. - -## Contributing - -Contributions are welcome! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information. - - -## Credits - -This plugin integrates with and builds upon several key technologies: - -- [Twitter/X API](https://developer.twitter.com/en/docs): Official Twitter platform API -- [agent-twitter-client](https://www.npmjs.com/package/agent-twitter-client): Twitter API client library -- [Zod](https://github.com/colinhacks/zod): TypeScript-first schema validation - -Special thanks to: -- The Twitter/X Developer Platform team -- The agent-twitter-client maintainers for API integration tools -- The Eliza community for their contributions and feedback +## Error Handling -For more information about Twitter/X integration capabilities: -- [Twitter API Documentation](https://developer.twitter.com/en/docs) -- [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard) -- [Twitter API Best Practices](https://developer.twitter.com/en/docs/twitter-api/rate-limits) +The plugin includes robust error handling for: +- Image download failures +- API rate limits +- Network issues +- Invalid configurations -## License +## Contributing -This plugin is part of the Eliza project. See the main project repository for license information. \ No newline at end of file +Feel free to submit issues and enhancement requests! \ No newline at end of file diff --git a/packages/plugin-twitter/src/monitor.ts b/packages/plugin-twitter/src/monitor.ts new file mode 100644 index 00000000000..bebf3807450 --- /dev/null +++ b/packages/plugin-twitter/src/monitor.ts @@ -0,0 +1,190 @@ +import { IAgentRuntime, Memory, State } from "@elizaos/core"; +import { generateTweetWithImage, generateTweetWithCustomImage } from "./templates"; + +interface MonitorConfig { + // 监控配置 + keywords: string[]; // 监控的关键词 + users: string[]; // 监控的用户 + maxUsers: number; // 最大监控用户数(默认50) + minKeywordMatches: number; // 最少关键词匹配数(默认2) + + // 时间控制 + activeTimeStart: number; // 活动开始时间(0-23) + activeTimeEnd: number; // 活动结束时间(0-23) + pollInterval: number; // 轮询间隔(秒) + minTimeBetweenActions: number; // 操作间隔(秒) + + // 转发设置 + retweetEnabled: boolean; // 是否启用转发 + quoteEnabled: boolean; // 是否启用引用 + replyEnabled: boolean; // 是否启用回复 + maxTweetAge: number; // 最大推文年龄(分钟) + + // 记录管理 + processedTweets: Set; // 已处理的推文ID + lastActionTime: number; // 上次操作时间 + + // 图片设置 + customImageUrls?: string[]; // 自定义图片URL列表 + imageRotationInterval?: number; // 图片轮换间隔(分钟) + lastImageIndex?: number; // 上次使用的图片索引 + lastImageTime?: number; // 上次使用图片的时间 +} + +const defaultConfig: Partial = { + maxUsers: 50, + minKeywordMatches: 2, + activeTimeStart: 7, + activeTimeEnd: 23, + pollInterval: 300, + minTimeBetweenActions: 300, + retweetEnabled: true, + quoteEnabled: true, + replyEnabled: true, + maxTweetAge: 60, + processedTweets: new Set(), + lastActionTime: 0, + customImageUrls: [], + imageRotationInterval: 60, + lastImageIndex: -1, + lastImageTime: 0 +}; + +// 获取下一个要使用的图片URL +function getNextImageUrl(config: MonitorConfig): string | undefined { + if (!config.customImageUrls || config.customImageUrls.length === 0) { + return undefined; + } + + const now = Date.now(); + // 检查是否需要轮换图片 + if (now - config.lastImageTime < config.imageRotationInterval * 60 * 1000) { + return undefined; + } + + // 更新索引 + config.lastImageIndex = (config.lastImageIndex + 1) % config.customImageUrls.length; + config.lastImageTime = now; + + return config.customImageUrls[config.lastImageIndex]; +} + +export async function monitorTwitter( + runtime: IAgentRuntime, + config: Partial +): Promise { + const fullConfig = { ...defaultConfig, ...config }; + + // 检查是否在活动时间内 + function isActiveTime(): boolean { + const hour = new Date().getHours(); + return hour >= fullConfig.activeTimeStart && hour <= fullConfig.activeTimeEnd; + } + + // 检查是否可以执行操作 + function canPerformAction(): boolean { + const now = Date.now(); + if (now - fullConfig.lastActionTime < fullConfig.minTimeBetweenActions * 1000) { + return false; + } + return true; + } + + // 检查推文是否符合条件 + function matchesKeywords(text: string): boolean { + const matches = fullConfig.keywords.filter(keyword => + text.toLowerCase().includes(keyword.toLowerCase()) + ); + return matches.length >= fullConfig.minKeywordMatches; + } + + // 处理推文 + async function processTweet(tweet: any, state: State): Promise { + if (!canPerformAction()) return; + if (fullConfig.processedTweets.has(tweet.id)) return; + + const tweetAge = (Date.now() - new Date(tweet.created_at).getTime()) / 1000 / 60; + if (tweetAge > fullConfig.maxTweetAge) return; + + if (matchesKeywords(tweet.text)) { + if (fullConfig.retweetEnabled) { + await runtime.clients.twitter.retweet(tweet.id); + } + + if (fullConfig.quoteEnabled) { + const imageUrl = getNextImageUrl(fullConfig); + const quoteContent = imageUrl + ? await generateTweetWithCustomImage(runtime, state, imageUrl, "quote") + : await generateTweetWithImage(runtime, state, "quote"); + await runtime.clients.twitter.quote(tweet.id, quoteContent.text, quoteContent.image); + } + + if (fullConfig.replyEnabled) { + const imageUrl = getNextImageUrl(fullConfig); + const replyContent = imageUrl + ? await generateTweetWithCustomImage(runtime, state, imageUrl, "reply") + : await generateTweetWithImage(runtime, state, "reply"); + await runtime.clients.twitter.reply(tweet.id, replyContent.text, replyContent.image); + } + + fullConfig.processedTweets.add(tweet.id); + fullConfig.lastActionTime = Date.now(); + } + } + + // 主监控循环 + while (true) { + try { + if (!isActiveTime()) { + await new Promise(resolve => setTimeout(resolve, 60000)); + continue; + } + + // 获取监控用户的最新推文 + for (const user of fullConfig.users.slice(0, fullConfig.maxUsers)) { + const tweets = await runtime.clients.twitter.getUserTweets(user); + const state = await runtime.composeState({} as Memory); + + for (const tweet of tweets) { + await processTweet(tweet, state); + } + } + + // 搜索关键词 + const keywordTweets = await runtime.clients.twitter.searchTweets( + fullConfig.keywords.join(" OR ") + ); + const state = await runtime.composeState({} as Memory); + + for (const tweet of keywordTweets) { + await processTweet(tweet, state); + } + + } catch (error) { + console.error("Error in Twitter monitor:", error); + } + + await new Promise(resolve => + setTimeout(resolve, fullConfig.pollInterval * 1000) + ); + } +} + +// 导出监控动作 +export const twitterMonitorAction = { + name: "MONITOR_TWITTER", + description: "Monitor Twitter for keywords and users", + similes: ["watch twitter", "track tweets", "follow twitter"], + handler: async (runtime: IAgentRuntime, message: Memory, state?: State) => { + const config = { + keywords: runtime.character.settings?.twitter?.keywords || [], + users: runtime.character.settings?.twitter?.users || [], + ...runtime.character.settings?.twitter?.monitor + }; + + monitorTwitter(runtime, config); + return true; + }, + validate: async () => true, + examples: [] +}; \ No newline at end of file diff --git a/packages/plugin-twitter/src/templates.ts b/packages/plugin-twitter/src/templates.ts index 4578396bce0..f28a0f67897 100644 --- a/packages/plugin-twitter/src/templates.ts +++ b/packages/plugin-twitter/src/templates.ts @@ -1,3 +1,160 @@ +import fs from "fs"; +import path from "path"; +import crypto from "crypto"; +import { IAgentRuntime } from "../../../core/src/types"; + +interface State { + recentMessages?: string; + topics?: string; + postDirections?: string; + recentInteractions?: string; + agentName?: string; +} + +// 表情符号库 +const EMOJIS = { + positive: ["😊", "🎉", "✨", "🌟", "💫", "🔥", "💪", "👍", "🙌", "❤️"], + thinking: ["🤔", "💭", "🧐", "🤓", "📝", "💡", "🎯", "🔍", "📊", "🗣️"], + tech: ["💻", "🤖", "🚀", "⚡", "🔧", "🛠️", "📱", "🌐", "🔌", "💾"], + nature: ["🌿", "🌺", "🌸", "🌼", "🌞", "🌙", "🌍", "🌈", "☀️", "🌊"], + fun: ["😄", "🎮", "🎨", "🎭", "🎪", "🎡", "🎢", "🎬", "🎵", "🎹"], + time: ["⏰", "⌛", "⏳", "📅", "🗓️", "⚡", "🕒", "📆", "🔄", "⏱️"] +}; + +// 推文风格 +const TWEET_STYLES = { + standard: (text: string) => text, + question: (text: string) => `🤔 ${text}?`, + announcement: (text: string) => `📢 ${text}!`, + thought: (text: string) => `💭 ${text}...`, + excited: (text: string) => `✨ ${text}! 🎉`, + list: (text: string) => text.split(",").map((item, i) => `${i + 1}. ${item.trim()}`).join("\\n") +}; + +// 随机选择表情 +function selectEmoji(category: keyof typeof EMOJIS): string { + const emojis = EMOJIS[category]; + return emojis[Math.floor(Math.random() * emojis.length)]; +} + +// 生成文本变体 +function generateVariant(text: string, style: keyof typeof TWEET_STYLES): string { + return TWEET_STYLES[style](text); +} + +// 图片配置接口 +interface ImageConfig { + url: string; + category: string; + tags: string[]; +} + +// 图片集合 +const IMAGE_SETS: Record = { + tech: [ + { url: "https://source.unsplash.com/random/1200x630/?technology", category: "tech", tags: ["technology", "innovation"] }, + { url: "https://source.unsplash.com/random/1200x630/?coding", category: "tech", tags: ["coding", "programming"] } + ], + nature: [ + { url: "https://source.unsplash.com/random/1200x630/?nature", category: "nature", tags: ["nature", "landscape"] }, + { url: "https://source.unsplash.com/random/1200x630/?sunset", category: "nature", tags: ["sunset", "beautiful"] } + ], + business: [ + { url: "https://source.unsplash.com/random/1200x630/?business", category: "business", tags: ["business", "work"] }, + { url: "https://source.unsplash.com/random/1200x630/?office", category: "business", tags: ["office", "professional"] } + ] +}; + +// 图片管理器类 +class ImageManager { + private cache: Map = new Map(); + private readonly cacheDir = "./cache/images"; + private readonly cacheExpiry = 7 * 24 * 60 * 60 * 1000; // 7天缓存过期 + + constructor() { + // 确保缓存目录存在 + if (!fs.existsSync(this.cacheDir)) { + fs.mkdirSync(this.cacheDir, { recursive: true }); + } + } + + async downloadImage(url: string): Promise { + const response = await fetch(url); + const buffer = await response.arrayBuffer(); + const fileName = crypto.randomUUID() + ".jpg"; + const filePath = path.join(this.cacheDir, fileName); + + await fs.promises.writeFile(filePath, Buffer.from(buffer)); + this.cache.set(url, { path: filePath, timestamp: Date.now() }); + + return filePath; + } + + async getImage(url: string): Promise { + const cached = this.cache.get(url); + if (cached && Date.now() - cached.timestamp < this.cacheExpiry) { + return cached.path; + } + return this.downloadImage(url); + } + + async cleanupOldCache(): Promise { + const now = Date.now(); + for (const [url, { path, timestamp }] of this.cache.entries()) { + if (now - timestamp > this.cacheExpiry) { + await fs.promises.unlink(path); + this.cache.delete(url); + } + } + } +} + +// 选择图片 +function selectImage(category?: string): ImageConfig { + const categories = category ? [category] : Object.keys(IMAGE_SETS); + const selectedCategory = categories[Math.floor(Math.random() * categories.length)]; + const images = IMAGE_SETS[selectedCategory]; + return images[Math.floor(Math.random() * images.length)]; +} + +// 生成带图片的推文 +export async function generateTweetWithImage( + runtime: IAgentRuntime, + state: State, + style: keyof typeof TWEET_STYLES = "standard" +): Promise<{ text: string; image?: string }> { + const text = await generateTweetText(runtime, state); + const mood = determineMood(text); + const emoji = selectEmoji(mood); + const styledText = generateVariant(text, style); + const finalText = `${styledText} ${emoji}`.trim(); + + const image = selectImage(mood); + const imageManager = new ImageManager(); + const imagePath = await imageManager.getImage(image.url); + + return { + text: finalText, + image: imagePath + }; +} + +// 确定文本情感 +function determineMood(text: string): keyof typeof EMOJIS { + // 简单的情感分析逻辑 + if (text.includes("!") || text.includes("amazing") || text.includes("great")) { + return "positive"; + } + if (text.includes("?") || text.includes("wonder") || text.includes("think")) { + return "thinking"; + } + if (text.includes("code") || text.includes("tech") || text.includes("AI")) { + return "tech"; + } + return "positive"; // 默认返回积极情感 +} + +// 推文模板 export const tweetTemplate = ` # Context {{recentMessages}} @@ -18,5 +175,64 @@ Generate a tweet that: 3. Is concise and engaging 4. Must be UNDER 180 characters (this is a strict requirement) 5. Speaks from the perspective of {{agentName}} +6. May include relevant emojis based on the content mood +7. Uses varied sentence structures and expressions Generate only the tweet text, no other commentary.`; + +// 生成推文文本 +async function generateTweetText(runtime: IAgentRuntime, state: State): Promise { + // 使用模板生成基础文本 + const context = tweetTemplate + .replace("{{recentMessages}}", state.recentMessages || "") + .replace("{{topics}}", state.topics || "") + .replace("{{postDirections}}", state.postDirections || "") + .replace("{{recentPostInteractions}}", state.recentInteractions || "") + .replace("{{agentName}}", state.agentName || "Agent"); + + // 使用运行时的文本生成功能 + return runtime.generateText({ + context, + maxTokens: 100, + temperature: 0.7 + }); +} + +// 通过URL下载图片 +export async function downloadImageFromUrl(url: string): Promise { + const imageManager = new ImageManager(); + try { + const imagePath = await imageManager.getImage(url); + return imagePath; + } catch (error) { + console.error("Error downloading image:", error); + throw new Error(`Failed to download image from ${url}`); + } +} + +// 生成带自定义图片URL的推文 +export async function generateTweetWithCustomImage( + runtime: IAgentRuntime, + state: State, + imageUrl: string, + style: keyof typeof TWEET_STYLES = "standard" +): Promise<{ text: string; image?: string }> { + const text = await generateTweetText(runtime, state); + const mood = determineMood(text); + const emoji = selectEmoji(mood); + const styledText = generateVariant(text, style); + const finalText = `${styledText} ${emoji}`.trim(); + + try { + const imagePath = await downloadImageFromUrl(imageUrl); + return { + text: finalText, + image: imagePath + }; + } catch (error) { + console.error("Error processing custom image:", error); + return { + text: finalText + }; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90f147c715c..f9f222f46d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -368,6 +368,9 @@ importers: '@docusaurus/preset-classic': specifier: 3.6.3 version: 3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': + specifier: 3.6.3 + version: 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-mermaid': specifier: 3.6.3 version: 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) @@ -383,6 +386,9 @@ importers: dotenv: specifier: ^16.4.7 version: 16.4.7 + lunr: + specifier: 2.3.9 + version: 2.3.9 prism-react-renderer: specifier: 2.3.1 version: 2.3.1(react@18.3.1) @@ -760,12 +766,18 @@ importers: '@elizaos/core': specifier: workspace:* version: link:../core + '@types/mime-types': + specifier: 2.1.4 + version: 2.1.4 agent-twitter-client: specifier: 0.0.18 version: 0.0.18(bufferutil@4.0.9)(utf-8-validate@5.0.10) glob: specifier: 11.0.0 version: 11.0.0 + mime-types: + specifier: 2.1.35 + version: 2.1.35 whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1156,25 +1168,6 @@ importers: specifier: 7.1.0 version: 7.1.0 - packages/plugin-ferePro: - dependencies: - '@elizaos/core': - specifier: ^0.1.7-alpha.1 - version: 0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.27(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(axios@1.7.9)(encoding@0.1.13)(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1) - tsup: - specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) - ws: - specifier: ^8.18.0 - version: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - devDependencies: - '@types/ws': - specifier: ^8.5.13 - version: 8.5.13 - tsx: - specifier: ^4.19.2 - version: 4.19.2 - packages/plugin-flow: dependencies: '@elizaos/core': @@ -3871,9 +3864,6 @@ packages: peerDependencies: onnxruntime-node: 1.20.1 - '@elizaos/core@0.1.7-alpha.2': - resolution: {integrity: sha512-gNvFw/Xnv4dlcfmmKxRa+baKq6en4TitAjUGvo8LgAUkSk156A0fffJ0lAsc1rX8zMB5NsIqdvMCbwKxDd54OQ==} - '@emnapi/core@1.3.1': resolution: {integrity: sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==} @@ -7986,6 +7976,9 @@ packages: '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/mime-types@2.1.4': + resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -19623,7 +19616,7 @@ snapshots: '@acuminous/bitsyntax@0.1.2': dependencies: buffer-more-ints: 1.0.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 safe-buffer: 5.1.2 transitivePeerDependencies: - supports-color @@ -21556,7 +21549,7 @@ snapshots: dependencies: '@scure/bip32': 1.6.1 abitype: 1.0.8(typescript@5.6.3)(zod@3.23.8) - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 axios-mock-adapter: 1.22.0(axios@1.7.9) axios-retry: 4.5.0(axios@1.7.9) bip32: 4.0.0 @@ -23022,56 +23015,6 @@ snapshots: '@huggingface/jinja': 0.2.2 onnxruntime-node: 1.20.1 - '@elizaos/core@0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.27(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(axios@1.7.9)(encoding@0.1.13)(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)': - dependencies: - '@ai-sdk/anthropic': 0.0.56(zod@3.23.8) - '@ai-sdk/google': 0.0.55(zod@3.23.8) - '@ai-sdk/google-vertex': 0.0.43(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(zod@3.23.8) - '@ai-sdk/groq': 0.0.3(zod@3.23.8) - '@ai-sdk/openai': 1.0.5(zod@3.23.8) - '@anthropic-ai/sdk': 0.30.1(encoding@0.1.13) - '@fal-ai/client': 1.2.0 - '@types/uuid': 10.0.0 - ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) - anthropic-vertex-ai: 1.0.2(encoding@0.1.13)(zod@3.23.8) - fastembed: 1.14.1 - fastestsmallesttextencoderdecoder: 1.0.22 - gaxios: 6.7.1(encoding@0.1.13) - glob: 11.0.0 - handlebars: 4.7.8 - js-sha1: 0.7.0 - js-tiktoken: 1.0.15 - langchain: 0.3.6(@langchain/core@0.3.27(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(axios@1.7.9)(encoding@0.1.13)(handlebars@4.7.8)(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) - ollama-ai-provider: 0.16.1(zod@3.23.8) - openai: 4.73.0(encoding@0.1.13)(zod@3.23.8) - tinyld: 1.3.4 - together-ai: 0.7.0(encoding@0.1.13) - unique-names-generator: 4.7.1 - uuid: 11.0.3 - zod: 3.23.8 - transitivePeerDependencies: - - '@google-cloud/vertexai' - - '@langchain/anthropic' - - '@langchain/aws' - - '@langchain/cohere' - - '@langchain/core' - - '@langchain/google-genai' - - '@langchain/google-vertexai' - - '@langchain/groq' - - '@langchain/mistralai' - - '@langchain/ollama' - - axios - - cheerio - - encoding - - peggy - - react - - solid-js - - sswr - - supports-color - - svelte - - typeorm - - vue - '@emnapi/core@1.3.1': dependencies: '@emnapi/wasi-threads': 1.0.1 @@ -23391,7 +23334,7 @@ snapshots: '@eslint/config-array@0.19.1': dependencies: '@eslint/object-schema': 2.1.5 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -23417,7 +23360,7 @@ snapshots: '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -28576,6 +28519,8 @@ snapshots: '@types/mdx@2.0.13': {} + '@types/mime-types@2.1.4': {} + '@types/mime@1.3.5': {} '@types/minimatch@3.0.5': {} @@ -28831,7 +28776,7 @@ snapshots: '@typescript-eslint/types': 8.16.0 '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.16.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 eslint: 9.16.0(jiti@2.4.2) optionalDependencies: typescript: 5.6.3 @@ -28864,7 +28809,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3) '@typescript-eslint/utils': 8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 eslint: 9.16.0(jiti@2.4.2) ts-api-utils: 1.4.3(typescript@5.6.3) optionalDependencies: @@ -28895,7 +28840,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.16.0 '@typescript-eslint/visitor-keys': 8.16.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 @@ -29685,7 +29630,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -30077,13 +30022,13 @@ snapshots: axios-mock-adapter@1.22.0(axios@1.7.9): dependencies: - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 fast-deep-equal: 3.1.3 is-buffer: 2.0.5 axios-retry@4.5.0(axios@1.7.9): dependencies: - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 is-retry-allowed: 2.2.0 axios@0.21.4: @@ -30094,7 +30039,7 @@ snapshots: axios@0.27.2: dependencies: - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9 form-data: 4.0.1 transitivePeerDependencies: - debug @@ -30123,6 +30068,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axios@1.7.9(debug@4.4.0): dependencies: follow-redirects: 1.15.9(debug@4.4.0) @@ -32158,6 +32111,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + debug@4.4.0(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -32900,6 +32857,7 @@ snapshots: '@esbuild/win32-arm64': 0.23.1 '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + optional: true esbuild@0.24.2: optionalDependencies: @@ -33059,7 +33017,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 @@ -33645,6 +33603,8 @@ snapshots: async: 0.2.10 which: 1.3.1 + follow-redirects@1.15.9: {} + follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: debug: 4.3.7 @@ -33979,6 +33939,7 @@ snapshots: get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 + optional: true get-uri@6.0.4: dependencies: @@ -34728,7 +34689,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -34784,14 +34745,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -38110,7 +38071,7 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 chalk: 4.1.0 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -40553,7 +40514,8 @@ snapshots: resolve-pathname@3.0.0: {} - resolve-pkg-maps@1.0.0: {} + resolve-pkg-maps@1.0.0: + optional: true resolve.exports@2.0.3: {} @@ -41173,7 +41135,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -42148,7 +42110,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.3.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 esbuild: 0.24.2 joycon: 3.1.1 picocolors: 1.1.1 @@ -42176,13 +42138,14 @@ snapshots: get-tsconfig: 4.8.1 optionalDependencies: fsevents: 2.3.3 + optional: true tty-browserify@0.0.1: {} tuf-js@2.2.1: dependencies: '@tufjs/models': 2.0.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 make-fetch-happen: 13.0.1 transitivePeerDependencies: - supports-color @@ -42863,7 +42826,7 @@ snapshots: vite-node@2.1.5(@types/node@22.10.4)(terser@5.37.0): dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 1.1.2 vite: 5.4.11(@types/node@22.10.4)(terser@5.37.0) @@ -42976,7 +42939,7 @@ snapshots: '@vitest/spy': 2.1.5 '@vitest/utils': 2.1.5 chai: 5.1.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 expect-type: 1.1.0 magic-string: 0.30.17 pathe: 1.1.2