├── .cursorignore ├── bun.lockb ├── src ├── .DS_Store ├── missions │ ├── index.ts │ └── types │ │ └── mission.ts ├── tests │ ├── getRecentTweets.ts │ ├── getCooldowns.ts │ ├── getTerminalHistory.ts │ ├── linkInteractions.ts │ ├── getSummaries.ts │ ├── chatRoomTest.ts │ ├── tweetContext.ts │ ├── dynamicVariable.ts │ ├── testTweetExtraction.ts │ └── chatTest.ts ├── memory │ ├── wipeMemories.ts │ ├── testMemories.ts │ └── client.ts ├── twitter │ ├── types │ │ ├── tweetInteractions.ts │ │ └── tweetResults.ts │ ├── functions │ │ ├── likeTweet.ts │ │ ├── getTweets.ts │ │ ├── getHomepage.ts │ │ ├── searchTwitter.ts │ │ ├── followUser.ts │ │ └── sendTweet.ts │ ├── twitterClient.ts │ └── utils │ │ ├── tweetUtils.ts │ │ └── mediaUtils.ts ├── utils │ ├── dynamicVariables.ts │ ├── logger.ts │ ├── terminalLogger.ts │ ├── getUserIDfromUsername.ts │ ├── formatTimestamps.ts │ ├── internetTool.ts │ └── config.ts ├── terminal │ ├── commands │ │ ├── help.ts │ │ ├── search-web.ts │ │ ├── get-mission-status.ts │ │ ├── twitter-get-mentions.ts │ │ ├── twitter-get-homepage.ts │ │ ├── twitter-follow.ts │ │ ├── update-mission-metrics.ts │ │ ├── twitter-search.ts │ │ ├── twitter-get-tweets.ts │ │ ├── twitter-reply.ts │ │ ├── twitter-retweet.ts │ │ ├── twitter-quote.ts │ │ ├── twitter-tweet.ts │ │ └── twitter-tweet-media.ts │ ├── types │ │ └── commands.ts │ └── commandRegistry.ts ├── ai │ ├── agents │ │ ├── memoryAgent │ │ │ ├── memoryAgent.ts │ │ │ ├── memoryAgentConfig.ts │ │ │ └── memoryTool.ts │ │ ├── mediaAgent │ │ │ ├── mediaAgent.ts │ │ │ ├── mediaAgentConfig.ts │ │ │ └── mediaTool.ts │ │ ├── summaryAgent │ │ │ ├── summaryAgent.ts │ │ │ ├── summaryTool.ts │ │ │ └── summaryAgentConfig.ts │ │ ├── quoteAgent │ │ │ ├── quoteAgent.ts │ │ │ ├── quoteTool.ts │ │ │ └── quoteAgentConfig.ts │ │ ├── replyAgent │ │ │ ├── replyAgent.ts │ │ │ ├── replyTool.ts │ │ │ └── replyAgentConfig.ts │ │ ├── corePersonality.ts │ │ ├── terminalAgent │ │ │ ├── terminalAgent.ts │ │ │ ├── terminalTool.ts │ │ │ └── terminalAgentConfig.ts │ │ ├── extractorAgent │ │ │ ├── extractorAgent.ts │ │ │ └── extractorAgentConfig.ts │ │ ├── mainTweetAgent │ │ │ ├── mainTweetAgent.ts │ │ │ ├── mainTweetTool.ts │ │ │ └── mainTweetAgentConfig.ts │ │ ├── verifyRetweetAgent │ │ │ ├── verifyRetweetAgent.ts │ │ │ ├── verifyRetweetTool.ts │ │ │ └── verifyRetweetAgentConfig.ts │ │ ├── contentManagerAgent │ │ │ ├── contentManagerAgent.ts │ │ │ └── contentManagerConfig.ts │ │ ├── missionAgent │ │ │ ├── missionAgent.ts │ │ │ ├── missionAgentConfig.ts │ │ │ └── missionTool.ts │ │ ├── testAgent │ │ │ └── testAgent.ts │ │ └── chatAgent │ │ │ └── chatAgent.ts │ ├── models │ │ ├── adapters │ │ │ ├── ModelAdapter.ts │ │ │ ├── FireworksAdapter.ts │ │ │ └── OpenAIAdapter.ts │ │ └── clients │ │ │ ├── OpenAiClient.ts │ │ │ ├── AnthropicClient.ts │ │ │ └── FireworkClient.ts │ └── types │ │ └── agentSystem.ts ├── pipelines │ ├── mediaGeneration │ │ ├── combinedGeneration.ts │ │ ├── imageGen.ts │ │ └── videoGen.ts │ ├── verifyRetweet.ts │ ├── processTimeline.ts │ └── generateMainTweet.ts ├── supabase │ ├── supabaseClient.ts │ └── functions │ │ ├── twitter │ │ ├── interactionEntries.ts │ │ ├── mediaEntries.ts │ │ └── followEntries.ts │ │ ├── memory │ │ └── learnings.ts │ │ └── terminal │ │ └── terminalEntries.ts ├── config │ └── missions.yaml ├── cli.ts └── README.md ├── docs ├── api-reference │ ├── endpoint │ │ ├── get.mdx │ │ ├── create.mdx │ │ └── delete.mdx │ └── introduction.mdx ├── images │ ├── cypher-swarm.jpg │ └── checks-passed.png ├── snippets │ └── snippet-intro.mdx ├── essentials │ ├── code.mdx │ ├── images.mdx │ ├── navigation.mdx │ ├── markdown.mdx │ └── reusable-snippets.mdx ├── README.md ├── mint.json ├── introduction.mdx ├── concepts │ ├── hivemind-memory.mdx │ └── unified-personality.mdx └── development.mdx ├── .dockerignore ├── .gitignore ├── .env.example ├── fly.toml ├── .github └── workflows │ └── fly-deploy.yml ├── package.json ├── Dockerfile ├── temp_docs ├── guidelines.md ├── architecture.md └── terminal_commands.md ├── to-do.md ├── .cursorrules └── README.md /.cursorignore: -------------------------------------------------------------------------------- 1 | .env 2 | cookies.json 3 | node_modules -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbootoshi/cypher-swarm/HEAD/bun.lockb -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbootoshi/cypher-swarm/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /docs/api-reference/endpoint/get.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Get Plants' 3 | openapi: 'GET /plants' 4 | --- 5 | -------------------------------------------------------------------------------- /docs/api-reference/endpoint/create.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Create Plant' 3 | openapi: 'POST /plants' 4 | --- 5 | -------------------------------------------------------------------------------- /docs/api-reference/endpoint/delete.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Delete Plant' 3 | openapi: 'DELETE /plants/{id}' 4 | --- 5 | -------------------------------------------------------------------------------- /docs/images/cypher-swarm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbootoshi/cypher-swarm/HEAD/docs/images/cypher-swarm.jpg -------------------------------------------------------------------------------- /src/missions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types/mission'; 2 | export { missionSystem } from './systems/missionSystem'; -------------------------------------------------------------------------------- /docs/images/checks-passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbootoshi/cypher-swarm/HEAD/docs/images/checks-passed.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # flyctl launch added from .gitignore 2 | **/.env 3 | **/node_modules 4 | **/package-lock.json 5 | fly.toml 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | cookies.json 3 | node_modules 4 | package-lock.json 5 | follows_rows.csv 6 | transferFollows.ts 7 | .DS_Store 8 | ./*/.DS_Store 9 | ./*/*/.DS_Store 10 | ./*/*/*/.DS_Store 11 | -------------------------------------------------------------------------------- /src/tests/getRecentTweets.ts: -------------------------------------------------------------------------------- 1 | import { getRecentMainTweets } from "../supabase/functions/twitter/tweetEntries"; 2 | 3 | const mainTweets = await getRecentMainTweets(); 4 | console.log("Main tweets:", mainTweets); 5 | -------------------------------------------------------------------------------- /src/memory/wipeMemories.ts: -------------------------------------------------------------------------------- 1 | import { client } from "./client"; 2 | 3 | export async function wipeMemories() { 4 | await client.deleteAll({ agent_id: "satoshi" }); 5 | console.log("Memories wiped"); 6 | } 7 | 8 | wipeMemories(); -------------------------------------------------------------------------------- /src/tests/getCooldowns.ts: -------------------------------------------------------------------------------- 1 | import { getCooldownStatus } from "../supabase/functions/twitter/cooldowns"; 2 | import { Logger } from "../utils/logger"; 3 | Logger.enable(); 4 | 5 | console.log("Cooldown Status:", await getCooldownStatus()); 6 | -------------------------------------------------------------------------------- /src/twitter/types/tweetInteractions.ts: -------------------------------------------------------------------------------- 1 | export interface TwitterInteractionContext { 2 | type: string; 3 | twitterInterface?: string; // Store the dynamic Twitter interface 4 | parentTweetId?: string; 5 | // ... any other context fields 6 | } -------------------------------------------------------------------------------- /src/tests/getTerminalHistory.ts: -------------------------------------------------------------------------------- 1 | import { getShortTermHistory } from "../supabase/functions/terminal/terminalHistory"; 2 | import { getFormattedRecentHistory } from "../supabase/functions/terminal/terminalHistory"; 3 | 4 | console.log(await getFormattedRecentHistory()); -------------------------------------------------------------------------------- /docs/snippets/snippet-intro.mdx: -------------------------------------------------------------------------------- 1 | One of the core principles of software development is DRY (Don't Repeat 2 | Yourself). This is a principle that apply to documentation as 3 | well. If you find yourself repeating the same content in multiple places, you 4 | should consider creating a custom snippet to keep your content in sync. 5 | -------------------------------------------------------------------------------- /src/twitter/types/tweetResults.ts: -------------------------------------------------------------------------------- 1 | export interface TweetActionResult { 2 | success: boolean; 3 | message: string; 4 | tweetId?: string; 5 | } 6 | 7 | export interface ReplyResult extends TweetActionResult {} 8 | export interface QuoteResult extends TweetActionResult {} 9 | export interface RetweetResult extends TweetActionResult {} -------------------------------------------------------------------------------- /src/utils/dynamicVariables.ts: -------------------------------------------------------------------------------- 1 | import { MemorySummaries } from '../supabase/functions/memory/summaries'; 2 | import { getRecentMainTweets } from '../supabase/functions/twitter/tweetEntries'; 3 | 4 | export const activeSummaries = await MemorySummaries.getFormattedActiveSummaries(); 5 | export const recentMainTweets = await getRecentMainTweets(); -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | // src/utils/logger.ts 2 | 3 | export class Logger { 4 | private static enabled = false; 5 | 6 | static enable() { 7 | this.enabled = true; 8 | } 9 | 10 | static disable() { 11 | this.enabled = false; 12 | } 13 | 14 | static log(...args: any[]) { 15 | if (this.enabled) { 16 | console.log(...args); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # .env.example 2 | ## Twitter 3 | TWITTER_USERNAME= 4 | TWITTER_PASSWORD= 5 | TWITTER_EMAIL= 6 | 7 | ## Supabase 8 | SUPABASE_ANON_PUBLIC= 9 | SUPABASE_SERVICE_ROLE_KEY= 10 | SUPABASE_URL= 11 | 12 | ## AI 13 | OPENAI_API_KEY= 14 | FIREWORKS_API_KEY= 15 | ANTHROPIC_API_KEY= 16 | PERPLEXITY_API_KEY= 17 | 18 | ## Memory 19 | MEM0_API_KEY= 20 | 21 | ## Media 22 | FAL_API_KEY= 23 | LUMAAI_API_KEY= 24 | -------------------------------------------------------------------------------- /src/tests/linkInteractions.ts: -------------------------------------------------------------------------------- 1 | import { linkTwitterInteractions } from "../supabase/functions/twitter/linkInteractions"; 2 | import { Logger } from "../utils/logger"; 3 | 4 | Logger.enable(); 5 | 6 | export async function testLinkInteractions() { 7 | const tweetId = '1866022173648863460'; 8 | const interaction = await linkTwitterInteractions(tweetId); 9 | Logger.log('Interaction:', interaction); 10 | } 11 | 12 | testLinkInteractions().then(() => { 13 | Logger.log('Test completed successfully'); 14 | process.exit(0); 15 | }); -------------------------------------------------------------------------------- /src/twitter/functions/likeTweet.ts: -------------------------------------------------------------------------------- 1 | import { scraper } from '../twitterClient'; 2 | 3 | /** 4 | * Likes a specific tweet 5 | * @param tweetId - The ID of the tweet to like 6 | * @returns Promise indicating success or failure 7 | */ 8 | export async function likeTweet(tweetId: string): Promise { 9 | try { 10 | await scraper.likeTweet(tweetId); 11 | console.log(`Successfully liked tweet ${tweetId}`); 12 | return true; 13 | } catch (error) { 14 | console.error('Error liking tweet:', error); 15 | return false; 16 | } 17 | } -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for cypher-swarm on 2024-12-06T22:48:34-08:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = 'cypher-swarm' 7 | primary_region = 'sjc' 8 | 9 | [build] 10 | 11 | [http_service] 12 | internal_port = 3000 13 | force_https = true 14 | auto_stop_machines = false 15 | auto_start_machines = true 16 | min_machines_running = 0 17 | processes = ['app'] 18 | 19 | [[vm]] 20 | memory = '1gb' 21 | cpu_kind = 'shared' 22 | cpus = 1 23 | -------------------------------------------------------------------------------- /src/terminal/commands/help.ts: -------------------------------------------------------------------------------- 1 | // Command to display help information in a concise format with parameter details 2 | 3 | import { Command } from '../types/commands'; 4 | import { generateHelpText } from '../commandRegistry'; 5 | 6 | /** 7 | * @command help 8 | * @description Displays available commands and usage information 9 | */ 10 | export const help: Command = { 11 | name: 'help', 12 | description: 'Displays available commands and usage information', 13 | handler: async () => { 14 | return { 15 | output: generateHelpText() 16 | }; 17 | }, 18 | }; -------------------------------------------------------------------------------- /.github/workflows/fly-deploy.yml: -------------------------------------------------------------------------------- 1 | # See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/ 2 | 3 | name: Fly Deploy 4 | on: 5 | push: 6 | branches: 7 | - main 8 | jobs: 9 | deploy: 10 | name: Deploy app 11 | runs-on: ubuntu-latest 12 | concurrency: deploy-group # optional: ensure only one action runs at a time 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: superfly/flyctl-actions/setup-flyctl@master 16 | - run: flyctl deploy --remote-only 17 | env: 18 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 19 | -------------------------------------------------------------------------------- /src/ai/agents/memoryAgent/memoryAgent.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/TerminalAgent/TerminalAgent.ts 2 | import { BaseAgent } from '../baseAgent'; 3 | import { ModelClient } from '../../types/agentSystem'; 4 | import { memoryAgentConfig } from './memoryAgentConfig'; 5 | import { memoryToolSchema, MemoryTool } from './memoryTool'; 6 | 7 | export class MemoryAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(memoryAgentConfig, modelClient, memoryToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [MemoryTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/mediaAgent/mediaAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | import { mediaAgentConfig } from './mediaAgentConfig'; 4 | import { MediaTool, mediaToolSchema } from './mediaTool'; 5 | 6 | // ChatAgent extends BaseAgent with no schema type (null) 7 | export class MediaAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(mediaAgentConfig, modelClient, mediaToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [MediaTool]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ai/agents/summaryAgent/summaryAgent.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/TerminalAgent/TerminalAgent.ts 2 | import { BaseAgent } from '../baseAgent'; 3 | import { summaryAgentConfig } from './summaryAgentConfig'; 4 | import { ModelClient } from '../../types/agentSystem'; 5 | import { summaryTool, summaryToolSchema } from './summaryTool'; 6 | 7 | export class SummaryAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(summaryAgentConfig, modelClient, summaryToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [summaryTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/quoteAgent/quoteAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | import { quoteAgentConfig } from './quoteAgentConfig'; 4 | import { QuoteTweetTool, quoteTweetToolSchema } from './quoteTool'; 5 | 6 | // ChatAgent extends BaseAgent with no schema type (null) 7 | export class QuoteAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(quoteAgentConfig, modelClient, quoteTweetToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [QuoteTweetTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/replyAgent/replyAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | import { replyAgentConfig } from './replyAgentConfig'; 4 | import { ReplyTweetTool, replyTweetToolSchema } from './replyTool'; 5 | 6 | // ChatAgent extends BaseAgent with no schema type (null) 7 | export class ReplyAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(replyAgentConfig, modelClient, replyTweetToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [ReplyTweetTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/corePersonality.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agentConfigs/corePersonality.ts 2 | 3 | import { configLoader } from '../../utils/config'; 4 | 5 | // Get the raw personality from config 6 | export const CORE_PERSONALITY = configLoader.getRawPersonality(); 7 | 8 | // Export the agent name for consistency across the application 9 | export const AGENT_NAME = configLoader.getAgentName(); 10 | 11 | /** 12 | * Generates the system prompt for the AI agent 13 | * @returns The complete system prompt including the core personality 14 | */ 15 | export function generateSystemPrompt(): string { 16 | return `${CORE_PERSONALITY}`; 17 | } 18 | -------------------------------------------------------------------------------- /src/ai/agents/terminalAgent/terminalAgent.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/TerminalAgent/TerminalAgent.ts 2 | import { BaseAgent } from '../baseAgent'; 3 | import { terminalAgentConfig } from './terminalAgentConfig'; 4 | import { ModelClient } from '../../types/agentSystem'; 5 | import { terminalToolSchema, TerminalTool } from './terminalTool'; 6 | 7 | export class TerminalAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(terminalAgentConfig, modelClient, terminalToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [TerminalTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/extractorAgent/extractorAgent.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/TerminalAgent/TerminalAgent.ts 2 | import { BaseAgent } from '../baseAgent'; 3 | import { extractorAgentConfig } from './extractorAgentConfig'; 4 | import { ModelClient } from '../../types/agentSystem'; 5 | import { extractorToolSchema, ExtractorTool } from './extractorTool'; 6 | 7 | export class ExtractorAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(extractorAgentConfig, modelClient, extractorToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [ExtractorTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/mainTweetAgent/mainTweetAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | import { mainTweetAgentConfig } from './mainTweetAgentConfig'; 4 | import { MainTweetTool, mainTweetToolSchema } from './mainTweetTool'; 5 | 6 | // ChatAgent extends BaseAgent with no schema type (null) 7 | export class MainTweetAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(mainTweetAgentConfig, modelClient, mainTweetToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [MainTweetTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/verifyRetweetAgent/verifyRetweetAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | import { verifyRetweetAgentConfig } from './verifyRetweetAgentConfig'; 4 | import { VerifyRetweetTool, verifyRetweetToolSchema } from './verifyRetweetTool'; 5 | 6 | // ChatAgent extends BaseAgent with no schema type (null) 7 | export class VerifyRetweetAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(verifyRetweetAgentConfig, modelClient, verifyRetweetToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [VerifyRetweetTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ai/agents/contentManagerAgent/contentManagerAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | import { contentManagerAgentConfig } from './contentManagerConfig'; 4 | import { ContentManagerTool, contentManagerToolSchema } from './contentManagerTool'; 5 | 6 | // ChatAgent extends BaseAgent with no schema type (null) 7 | export class ContentManagerAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(contentManagerAgentConfig, modelClient, contentManagerToolSchema); 10 | } 11 | 12 | protected defineTools(): void { 13 | this.tools = [ContentManagerTool]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/tests/getSummaries.ts: -------------------------------------------------------------------------------- 1 | import { MemorySummaries } from '../supabase/functions/memory/summaries'; 2 | import { Logger } from '../utils/logger'; 3 | 4 | Logger.enable(); 5 | 6 | async function testSummaries() { 7 | try { 8 | // First, let's check what raw memories we have 9 | const rawMemories = await MemorySummaries.getActiveMemories(); 10 | Logger.log('Raw active memories:', rawMemories); 11 | 12 | // Then get the formatted version 13 | const activeSummaries = await MemorySummaries.getFormattedActiveSummaries(); 14 | Logger.log('Formatted summaries:', activeSummaries); 15 | } catch (error) { 16 | Logger.log('Error in test:', error); 17 | } 18 | } 19 | 20 | // Run the test 21 | testSummaries(); -------------------------------------------------------------------------------- /src/ai/agents/missionAgent/missionAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { missionAgentConfig } from './missionAgentConfig'; 3 | import { ModelClient } from '../../types/agentSystem'; 4 | import { MissionTool, missionToolSchema } from './missionTool'; 5 | import { Logger } from '../../../utils/logger'; 6 | 7 | export class MissionAgent extends BaseAgent { 8 | constructor(modelClient: ModelClient) { 9 | super(missionAgentConfig, modelClient, missionToolSchema); 10 | Logger.log('🎯 Mission Agent initialized'); 11 | } 12 | 13 | protected defineTools(): void { 14 | this.tools = [MissionTool]; 15 | Logger.log('🛠️ Mission Tools Defined:', this.tools.map(t => t.function.name)); 16 | } 17 | } -------------------------------------------------------------------------------- /src/utils/terminalLogger.ts: -------------------------------------------------------------------------------- 1 | // Terminal logging utility 2 | 3 | import { formatTimestamp } from './formatTimestamps'; 4 | /** 5 | * Logs terminal commands and their outputs. 6 | * @param command - The command that was executed. 7 | * @param output - The output of the command. 8 | * @param success - Whether the command executed successfully (default: true). 9 | */ 10 | export function logTerminalOutput(command: string, output: string, success: boolean = true) { 11 | const logEntry = { 12 | timestamp: formatTimestamp(new Date().toISOString()), // Now using ISO string 13 | command, 14 | output, 15 | success, 16 | }; 17 | // Log to console with formatted timestamp 18 | console.log(`[${logEntry.timestamp}]\n${output}\n`); 19 | } 20 | 21 | // Export the formatter for use in other modules 22 | export { formatTimestamp }; -------------------------------------------------------------------------------- /src/pipelines/mediaGeneration/combinedGeneration.ts: -------------------------------------------------------------------------------- 1 | import { generateImage } from './imageGen'; 2 | import { generateVideoFromImage } from './videoGen'; 3 | 4 | /** 5 | * Combined pipeline that generates a retro anime image and converts it to video 6 | * @param prompt - User prompt for generation 7 | * @returns Promise containing the final video URL 8 | */ 9 | export async function generateImageToVideo(prompt: string): Promise { 10 | try { 11 | // Generate the retro anime image 12 | const imageUrl = await generateImage(prompt); 13 | 14 | // Generate video from the image 15 | const videoUrl = await generateVideoFromImage(prompt, imageUrl); 16 | 17 | return videoUrl; 18 | 19 | } catch (error) { 20 | console.error('Error in generation pipeline:', error); 21 | throw error; 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypher-swarm", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "bun run src/index.ts" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "@anthropic-ai/sdk": "^0.32.1", 14 | "@fal-ai/client": "^1.2.1", 15 | "@supabase/supabase-js": "^2.46.1", 16 | "axios": "^1.7.8", 17 | "csv-parse": "^5.6.0", 18 | "dotenv": "^16.4.5", 19 | "file-type": "^19.6.0", 20 | "fs": "^0.0.1-security", 21 | "goat-x": "^1.2.0", 22 | "js-yaml": "^4.1.0", 23 | "lumaai": "^1.2.0", 24 | "mem0ai": "^1.0.29", 25 | "node-fetch": "^3.3.2", 26 | "openai": "^4.73.0", 27 | "path": "^0.12.7", 28 | "shell-quote": "^1.8.1", 29 | "uuid": "^11.0.3", 30 | "zod": "^3.23.8" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^22.9.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/missions/types/mission.ts: -------------------------------------------------------------------------------- 1 | export interface MissionMetrics { 2 | [key: string]: number; 3 | } 4 | 5 | export interface CompletionCriteria { 6 | description: string; 7 | target: number; 8 | current: number; 9 | qualitative_analysis?: string; 10 | } 11 | 12 | export interface Mission { 13 | id: string; 14 | description: string; 15 | priority: number; 16 | status: 'active' | 'pending' | 'completed'; 17 | metrics: MissionMetrics; 18 | completion_criteria: CompletionCriteria[]; 19 | created_at: Date; 20 | updated_at: Date; 21 | } 22 | 23 | export interface MissionConfig { 24 | missions: Mission[]; 25 | current_mission: string; 26 | } 27 | 28 | export interface MissionAnalysis { 29 | current_mission: string; 30 | metrics_update: MissionMetrics; 31 | progress_evaluation: string; 32 | } 33 | 34 | export interface MissionError { 35 | code: string; 36 | message: string; 37 | timestamp: Date; 38 | } 39 | -------------------------------------------------------------------------------- /src/ai/models/adapters/ModelAdapter.ts: -------------------------------------------------------------------------------- 1 | // src/ai/models/ModelAdapter.ts 2 | 3 | import { Tool, Message } from '../../types/agentSystem'; 4 | 5 | // Define an interface for model-specific adapters 6 | export interface ModelAdapter { 7 | // Add capability check for images 8 | supportsImages?: boolean; 9 | 10 | // Build tool choice based on the provided tools 11 | buildToolChoice(tools: Tool[]): any; 12 | 13 | // Format tools according to the model's requirements 14 | formatTools(tools: Tool[]): any[]; 15 | 16 | // Build parameters for the model's chat completion method 17 | buildParams( 18 | messageHistory: Message[], 19 | formattedTools: any[], 20 | toolChoice: any, 21 | systemPrompt: string 22 | ): any; 23 | 24 | // Process the model's response to extract AI message and function call 25 | processResponse(response: any): { aiMessage: any; functionCall?: any }; 26 | 27 | // Additional methods as needed 28 | } -------------------------------------------------------------------------------- /docs/api-reference/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Introduction' 3 | description: 'Example section for showcasing API endpoints' 4 | --- 5 | 6 | 7 | If you're not looking to build API reference documentation, you can delete 8 | this section by removing the api-reference folder. 9 | 10 | 11 | ## Welcome 12 | 13 | There are two ways to build API documentation: [OpenAPI](https://mintlify.com/docs/api-playground/openapi/setup) and [MDX components](https://mintlify.com/docs/api-playground/mdx/configuration). For the starter kit, we are using the following OpenAPI specification. 14 | 15 | 20 | View the OpenAPI specification file 21 | 22 | 23 | ## Authentication 24 | 25 | All API endpoints are authenticated using Bearer tokens and picked up from the specification file. 26 | 27 | ```json 28 | "security": [ 29 | { 30 | "bearerAuth": [] 31 | } 32 | ] 33 | ``` 34 | -------------------------------------------------------------------------------- /src/tests/chatRoomTest.ts: -------------------------------------------------------------------------------- 1 | import { ChatAgent } from '../ai/agents/chatAgent/chatAgent'; 2 | import { OpenAIClient } from '../ai/models/clients/OpenAiClient'; 3 | import { AnthropicClient } from '../ai/models/clients/AnthropicClient'; 4 | import { Logger } from '../utils/logger'; 5 | 6 | // Logger.enable(); 7 | 8 | // Initialize chat agents 9 | const openAIAgent = new ChatAgent(new OpenAIClient("gpt-4o")); 10 | const anthropicAgent = new ChatAgent(new AnthropicClient("claude-3-5-sonnet-20240620")); 11 | 12 | // Have agents converse back and forth 10 times (5 messages each) 13 | let lastMessage = ''; 14 | for (let i = 0; i < 5; i++) { 15 | // OpenAI agent's turn 16 | const openAIResult = await openAIAgent.run(lastMessage); 17 | console.log('OpenAI Response:', openAIResult.output); 18 | lastMessage = openAIResult.output; 19 | 20 | // Anthropic agent's turn 21 | const anthropicResult = await anthropicAgent.run(lastMessage); 22 | console.log('Anthropic Response:', anthropicResult.output); 23 | lastMessage = anthropicResult.output; 24 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1 2 | 3 | # Adjust BUN_VERSION as desired 4 | ARG BUN_VERSION=1.1.31 5 | FROM oven/bun:${BUN_VERSION}-slim as base 6 | 7 | LABEL fly_launch_runtime="Bun" 8 | 9 | # Bun app lives here 10 | WORKDIR /app 11 | 12 | # Set production environment 13 | ENV NODE_ENV="production" 14 | 15 | # Throw-away build stage to reduce size of final image 16 | FROM base as build 17 | 18 | # Install packages needed to build node modules 19 | RUN apt-get update -qq && \ 20 | apt-get install --no-install-recommends -y build-essential pkg-config python-is-python3 21 | 22 | # Copy both package files first 23 | COPY package.json bun.lockb ./ 24 | 25 | # Use --frozen-lockfile to ensure exact versions are installed 26 | RUN bun install 27 | 28 | # Copy application code 29 | COPY . . 30 | 31 | # Final stage for app image 32 | FROM base 33 | 34 | # Copy built application 35 | COPY --from=build /app /app 36 | 37 | # Start the server by default, this can be overwritten at runtime 38 | EXPOSE 3000 39 | CMD [ "bun", "run", "start" ] 40 | -------------------------------------------------------------------------------- /docs/essentials/code.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Code Blocks' 3 | description: 'Display inline code and code blocks' 4 | icon: 'code' 5 | --- 6 | 7 | ## Basic 8 | 9 | ### Inline Code 10 | 11 | To denote a `word` or `phrase` as code, enclose it in backticks (`). 12 | 13 | ``` 14 | To denote a `word` or `phrase` as code, enclose it in backticks (`). 15 | ``` 16 | 17 | ### Code Block 18 | 19 | Use [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) by enclosing code in three backticks and follow the leading ticks with the programming language of your snippet to get syntax highlighting. Optionally, you can also write the name of your code after the programming language. 20 | 21 | ```java HelloWorld.java 22 | class HelloWorld { 23 | public static void main(String[] args) { 24 | System.out.println("Hello, World!"); 25 | } 26 | } 27 | ``` 28 | 29 | ````md 30 | ```java HelloWorld.java 31 | class HelloWorld { 32 | public static void main(String[] args) { 33 | System.out.println("Hello, World!"); 34 | } 35 | } 36 | ``` 37 | ```` 38 | -------------------------------------------------------------------------------- /src/ai/agents/summaryAgent/summaryTool.ts: -------------------------------------------------------------------------------- 1 | // tools/TerminalTool.ts 2 | 3 | import { z } from 'zod'; 4 | import { Tool } from '../../types/agentSystem'; 5 | 6 | // Condense summaries tool 7 | export const summaryToolSchema = z.object({ 8 | condensed_summary: z.string().describe('A 3-4 sentence narrative-focused summary that combines and condenses the provided summaries.') 9 | }); 10 | 11 | export const summaryTool: Tool = { 12 | type: 'function', 13 | function: { 14 | "name": "condense_summaries", 15 | "description": "Condenses multiple summaries into a single, coherent summary that captures the key information and narrative progression.", 16 | "parameters": { 17 | "type": "object", 18 | "properties": { 19 | "condensed_summary": { 20 | "type": "string", 21 | "description": "A 3-4 sentence narrative-focused summary that combines and condenses the provided summaries. The long term summary could be up to 6 sentences." 22 | } 23 | }, 24 | "required": ["condensed_summary"] 25 | } 26 | }, 27 | }; -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Mintlify Starter Kit 2 | 3 | Click on `Use this template` to copy the Mintlify starter kit. The starter kit contains examples including 4 | 5 | - Guide pages 6 | - Navigation 7 | - Customizations 8 | - API Reference pages 9 | - Use of popular components 10 | 11 | ### Development 12 | 13 | Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command 14 | 15 | ``` 16 | npm i -g mintlify 17 | ``` 18 | 19 | Run the following command at the root of your documentation (where mint.json is) 20 | 21 | ``` 22 | mintlify dev 23 | ``` 24 | 25 | ### Publishing Changes 26 | 27 | Install our Github App to auto propagate changes from your repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard. 28 | 29 | #### Troubleshooting 30 | 31 | - Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies. 32 | - Page loads as a 404 - Make sure you are running in a folder with `mint.json` 33 | -------------------------------------------------------------------------------- /src/ai/models/clients/OpenAiClient.ts: -------------------------------------------------------------------------------- 1 | // models/OpenAIClient.ts 2 | 3 | import OpenAI from 'openai'; 4 | import { ModelClient, ModelType } from '../../types/agentSystem'; 5 | 6 | export class OpenAIClient implements ModelClient { 7 | private openai: OpenAI; 8 | private modelName: string; 9 | private defaultParams: any; 10 | modelType: ModelType = 'openai'; 11 | 12 | constructor( 13 | modelName: string, 14 | params: any = {} 15 | ) { 16 | const apiKey = process.env.OPENAI_API_KEY!; 17 | this.openai = new OpenAI({ apiKey }); 18 | 19 | this.modelName = modelName; 20 | this.defaultParams = { 21 | temperature: 0.8, 22 | max_tokens: 1000, 23 | ...params, 24 | }; 25 | } 26 | 27 | async chatCompletion(params: any): Promise { 28 | try { 29 | const requestParams = { 30 | model: this.modelName, 31 | ...this.defaultParams, 32 | ...params, 33 | }; 34 | const response = await this.openai.chat.completions.create(requestParams); 35 | return response; 36 | } catch (error) { 37 | throw error; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/utils/getUserIDfromUsername.ts: -------------------------------------------------------------------------------- 1 | import { findTwitterUserByUsername } from "../supabase/functions/twitter/userEntries"; 2 | import { Logger } from './logger'; 3 | 4 | /** 5 | * Retrieves user IDs for an array of Twitter usernames from the database 6 | * @param usernames Array of Twitter usernames to look up 7 | * @returns Object mapping usernames to their user IDs (null if not found) 8 | */ 9 | export async function getUserIDsFromUsernames( 10 | usernames: string[] 11 | ): Promise> { 12 | const results: Record = {}; 13 | 14 | // Fetch user IDs concurrently for efficiency 15 | await Promise.all( 16 | usernames.map(async (username) => { 17 | try { 18 | const result = await findTwitterUserByUsername(username); 19 | results[username] = result?.userId || null; 20 | } catch (error) { 21 | Logger.log(`Error retrieving user ID for ${username}:`, error); 22 | results[username] = null; 23 | } 24 | }) 25 | ); 26 | 27 | return results; 28 | } -------------------------------------------------------------------------------- /src/terminal/commands/search-web.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { Logger } from '../../utils/logger'; 3 | import { queryPerplexity } from '../../utils/internetTool'; 4 | 5 | /** 6 | * Terminal command that searches the web using Perplexity AI 7 | * Usage: @search-web "What is the latest news about AI?" 8 | */ 9 | export const searchWeb: Command = { 10 | name: 'search-web', 11 | description: 'Search the web for information and get a summary of your query back. MUST wrap your query in quotes.', 12 | parameters: [ 13 | { 14 | name: 'query', 15 | description: 'Search query (wrap in quotes)', 16 | required: true, 17 | type: 'string' 18 | } 19 | ], 20 | handler: async (args) => { 21 | try { 22 | Logger.log('Searching web for:', args.query); 23 | const result = await queryPerplexity(args.query); 24 | return { 25 | output: result 26 | }; 27 | } catch (error) { 28 | Logger.log('Search web command failed:', error); 29 | return { 30 | output: `Error searching web: ${error.message}` 31 | }; 32 | } 33 | } 34 | }; -------------------------------------------------------------------------------- /src/terminal/commands/get-mission-status.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { missionSystem } from '../../missions'; 3 | 4 | /** 5 | * @command get-mission-status 6 | * @description Get current status of a mission 7 | */ 8 | export const getMissionStatus: Command = { 9 | name: 'get-mission-status', 10 | description: 'Get current status of a mission', 11 | parameters: [ 12 | { 13 | name: 'missionId', 14 | description: 'ID of the mission (optional, defaults to current)', 15 | required: false, 16 | type: 'string' 17 | } 18 | ], 19 | handler: async (args) => { 20 | const missionId = args.missionId; 21 | const mission = missionId 22 | ? missionSystem.getMissionById(missionId) 23 | : missionSystem.getCurrentMission(); 24 | 25 | if (!mission) { 26 | return { 27 | output: 'No mission found' 28 | }; 29 | } 30 | 31 | return { 32 | output: JSON.stringify({ 33 | id: mission.id, 34 | description: mission.description, 35 | status: mission.status, 36 | metrics: mission.metrics, 37 | completion_criteria: mission.completion_criteria 38 | }, null, 2) 39 | }; 40 | } 41 | }; -------------------------------------------------------------------------------- /src/ai/agents/memoryAgent/memoryAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { configLoader } from '../../../utils/config'; 6 | 7 | export const memoryAgentConfig: AgentConfig = { 8 | systemPromptTemplate: ` 9 | # PERSONALITY 10 | {{corePersonalityPrompt}} 11 | 12 | # MAIN GOAL 13 | You are the memory agent of {{agentName}}'s soul. 14 | 15 | Your goal is to take in the context of the current terminal logs as well as the current twitter interface (if provided) and output a query to pull the most relevant memories from the vector database of memories. 16 | 17 | The exact words you use are important: make sure the main theme/topic/categories of the memories we're looking for are incorporated. 18 | 19 | IF A TICKER IS MENTIONED, MAKE SURE TO INCLUDE IT IN THE MEMORY QUERY. 20 | 21 | # OUTPUT FORMAT 22 | You MUST use your extract_log_knowledge at all times - you will ONLY be given terminal logs and user interactions. PLEASE OUTPUT JSON FORMAT ONLY. 23 | `, 24 | dynamicVariables: { 25 | corePersonalityPrompt: generateSystemPrompt(), 26 | agentName: configLoader.getAgentName() 27 | }, 28 | }; -------------------------------------------------------------------------------- /temp_docs/guidelines.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | This document provides guidelines for contributing to the project, including coding standards, best practices, and processes. 4 | 5 | ## Coding Standards 6 | 7 | - **Language**: TypeScript 8 | - **Frameworks**: Node.js, Supabase, Goat-X (or agent-twitter-client) 9 | - **Style**: 10 | - Use functional, declarative programming. 11 | - Prefer iteration and modularization over duplication. 12 | - Use descriptive variable names with auxiliary verbs. 13 | 14 | ## Best Practices 15 | 16 | - **Error Handling**: Prioritize error handling and edge cases. 17 | - **Component Structure**: Break down components into smaller parts with minimal props. 18 | - **Data Fetching**: Use React Server Components for data fetching when possible. 19 | 20 | ## Contribution Process 21 | 22 | 1. **Fork the Repository**: Create your own fork of the project repository. 23 | 2. **Create a Branch**: For each new feature or bug fix, create a new branch. 24 | 3. **Implement Changes**: Make your code changes, following the coding standards. 25 | 4. **Update Documentation**: Update the relevant documentation files with summaries and architectural changes. 26 | 5. **Create a Pull Request**: Submit your changes for review. 27 | 28 | --- -------------------------------------------------------------------------------- /src/supabase/supabaseClient.ts: -------------------------------------------------------------------------------- 1 | // Supabase client initialization with service role key for backend operations 2 | 3 | import { createClient } from '@supabase/supabase-js'; 4 | import dotenv from 'dotenv'; 5 | import { Database } from './types/database.types'; // We'll need to generate this 6 | 7 | // Load environment variables 8 | dotenv.config(); 9 | 10 | // Validate required environment variables 11 | if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_KEY) { 12 | throw new Error('Missing required Supabase environment variables'); 13 | } 14 | 15 | // Create Supabase client with service role key for full database access 16 | export const supabase = createClient( 17 | process.env.SUPABASE_URL, 18 | process.env.SUPABASE_SERVICE_ROLE_KEY, 19 | { 20 | auth: { 21 | autoRefreshToken: true, 22 | persistSession: false // Since this is running server-side 23 | }, 24 | db: { 25 | schema: 'public' 26 | } 27 | } 28 | ); 29 | 30 | // Verify connection 31 | supabase.auth.getSession().then(({ data, error }) => { 32 | if (error) { 33 | console.error('Error initializing Supabase client:', error.message); 34 | throw error; 35 | } 36 | console.log('Supabase client initialized successfully'); 37 | }); -------------------------------------------------------------------------------- /src/tests/tweetContext.ts: -------------------------------------------------------------------------------- 1 | import { assembleTwitterInterface } from '../twitter/utils/imageUtils'; 2 | import { getCurrentTimestamp } from '../utils/formatTimestamps'; 3 | 4 | /** 5 | * Test function to log the tweet interface string. 6 | * @param tweetId - The ID of the tweet to test with. 7 | */ 8 | async function testTweetContext(tweetId: string) { 9 | try { 10 | const { textContent, imageContents, usernames } = await assembleTwitterInterface(tweetId); 11 | 12 | console.log('--- Text Content ---'); 13 | console.log(textContent); 14 | 15 | console.log('--- Image Contents ---'); 16 | imageContents.forEach((image, index) => { 17 | console.log(`Image ${index + 1}:`); 18 | console.log(`Sender: ${image.sender}`); 19 | console.log(`Media Type: ${image.media_type}`); 20 | console.log('Base64 data retrieved successfully.'); 21 | }); 22 | 23 | console.log('--- Usernames ---'); 24 | console.log(usernames); 25 | } catch (error) { 26 | console.error('Error testing tweet context:', error); 27 | } 28 | } 29 | 30 | // Replace 'YOUR_TWEET_ID_HERE' with an actual tweet ID to test 31 | testTweetContext('1864746531535937618'); 32 | console.log(getCurrentTimestamp()); -------------------------------------------------------------------------------- /src/ai/agents/testAgent/testAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | // Import core personality prompt 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { AgentConfig } from '../../types/agentSystem'; 6 | 7 | // Configuration for chat agent following terminal agent pattern 8 | const testAgentConfig: AgentConfig = { 9 | systemPromptTemplate: ` 10 | # PERSONALITY 11 | {{corePersonalityPrompt}} 12 | 13 | # TWITTER INTERFACE 14 | {{twitterInterface}} 15 | # MAIN GOAL 16 | You are a chat agent designed to have natural conversations with other AI agents. 17 | 18 | # OUTPUT FORMAT 19 | Respond naturally in a conversational manner while maintaining the personality defined above. 20 | `, 21 | dynamicVariables: { 22 | corePersonalityPrompt: generateSystemPrompt(), 23 | twitterInterface: 'TWITTER INTERFACE DYNAMIC VARIABLE HERE', 24 | }, 25 | }; 26 | 27 | // ChatAgent extends BaseAgent with no schema type (null) 28 | export class TestAgent extends BaseAgent { 29 | constructor(modelClient: ModelClient) { 30 | super(testAgentConfig, modelClient, null); 31 | } 32 | 33 | protected defineTools(): void { 34 | // No tools to define for basic chat functionality 35 | } 36 | } -------------------------------------------------------------------------------- /src/ai/agents/chatAgent/chatAgent.ts: -------------------------------------------------------------------------------- 1 | import { BaseAgent } from '../baseAgent'; 2 | import { ModelClient } from '../../types/agentSystem'; 3 | import { generateSystemPrompt } from '../corePersonality'; 4 | import { AgentConfig } from '../../types/agentSystem'; 5 | 6 | // Configuration for chat agent following terminal agent pattern 7 | const chatAgentConfig: AgentConfig = { 8 | systemPromptTemplate: ` 9 | # PERSONALITY 10 | {{corePersonalityPrompt}} 11 | 12 | # DYNAMIC VARIABLES 13 | 14 | ## USERNAME 15 | {{userName}} 16 | 17 | ## SESSION ID 18 | {{sessionId}} 19 | 20 | # MAIN GOAL 21 | You are a chat agent designed to have natural conversations with other AI agents. 22 | 23 | # OUTPUT FORMAT 24 | Respond naturally in a conversational manner while maintaining the personality defined above. 25 | `, 26 | dynamicVariables: { 27 | corePersonalityPrompt: generateSystemPrompt(), 28 | userName: 'Satoshi Nakamoto', 29 | sessionId: 'abc123', 30 | }, 31 | }; 32 | 33 | // ChatAgent extends BaseAgent with no schema type (null) 34 | export class ChatAgent extends BaseAgent { 35 | constructor(modelClient: ModelClient) { 36 | super(chatAgentConfig, modelClient, null); 37 | } 38 | 39 | protected defineTools(): void { 40 | // No tools to define for basic chat functionality 41 | } 42 | } -------------------------------------------------------------------------------- /src/terminal/commands/twitter-get-mentions.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { getMentions } from '../../twitter/functions/getMentions'; 3 | import { Logger } from '../../utils/logger'; 4 | 5 | /** 6 | * @command get-mentions 7 | * @description Get and process recent mentions of your account 8 | */ 9 | export const twitterGetMentions: Command = { 10 | name: 'get-mentions', 11 | description: 'Get and process recent mentions', 12 | parameters: [], 13 | handler: async (args) => { 14 | try { 15 | const { processTimeline, TimelineType } = await import('../../pipelines/processTimeline'); 16 | 17 | const mentions = await getMentions(20); 18 | const timelineData = mentions.length === 0 ? '' : mentions.join('\n'); 19 | 20 | // Process mentions using the timeline pipeline 21 | const processedResult = await processTimeline(timelineData, TimelineType.MENTIONS); 22 | 23 | return { 24 | output: processedResult, 25 | data: { tweets: timelineData, processed: processedResult } 26 | }; 27 | } catch (error) { 28 | Logger.log('Error in twitter-get-mentions:', error); 29 | return { 30 | output: `❌ Error: ${error.message}`, 31 | data: { error: error.message } 32 | }; 33 | } 34 | } 35 | }; -------------------------------------------------------------------------------- /src/ai/agents/verifyRetweetAgent/verifyRetweetTool.ts: -------------------------------------------------------------------------------- 1 | // Define tool for verifying whether to retweet a tweet 2 | // Simplified to focus on decision-making process and confirmation 3 | 4 | import { z } from 'zod'; 5 | import { Tool } from '../../types/agentSystem'; 6 | 7 | export const verifyRetweetToolSchema = z.object({ 8 | internal_thought: z.string().describe('Your internal thought process about whether to retweet'), 9 | confirm_retweet: z.boolean().describe('Whether you want to retweet') 10 | }); 11 | 12 | export const VerifyRetweetTool: Tool = { 13 | type: 'function', 14 | function: { 15 | "name": "verify_retweet_tool", 16 | "description": "Evaluate whether to retweet a tweet. The full context of the tweet is provided to you below.", 17 | "strict": true, 18 | "parameters": { 19 | "type": "object", 20 | "required": [ 21 | "internal_thought", 22 | "confirm_retweet" 23 | ], 24 | "properties": { 25 | "internal_thought": { 26 | "type": "string", 27 | "description": "Your internal thought process about whether to retweet" 28 | }, 29 | "confirm_retweet": { 30 | "type": "boolean", 31 | "description": "Whether you want to retweet the tweet" 32 | } 33 | } 34 | } 35 | } 36 | }; -------------------------------------------------------------------------------- /src/ai/models/clients/AnthropicClient.ts: -------------------------------------------------------------------------------- 1 | import Anthropic from '@anthropic-ai/sdk'; 2 | import { ModelClient, ModelType } from '../../types/agentSystem'; 3 | 4 | export class AnthropicClient implements ModelClient { 5 | private anthropic: Anthropic; 6 | private modelName: string; 7 | private defaultParams: any; 8 | modelType: ModelType = 'anthropic'; 9 | 10 | constructor( 11 | modelName: string, 12 | params: any = {} 13 | ) { 14 | const apiKey = process.env.ANTHROPIC_API_KEY!; 15 | this.anthropic = new Anthropic({ apiKey }); 16 | 17 | this.modelName = modelName; 18 | this.defaultParams = { 19 | temperature: 0.8, 20 | max_tokens: 1000, 21 | ...params, 22 | }; 23 | } 24 | 25 | async chatCompletion(params: any): Promise { 26 | try { 27 | const messages = params.messages?.map((msg: any) => ({ 28 | role: msg.role, 29 | content: msg.content, 30 | })); 31 | 32 | const requestParams = { 33 | model: this.modelName, 34 | ...this.defaultParams, 35 | ...params, 36 | messages: messages || params.messages, 37 | }; 38 | 39 | const response = await this.anthropic.messages.create(requestParams); 40 | return response; 41 | } catch (error) { 42 | throw error; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/terminal/commands/twitter-get-homepage.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { getHomepage } from '../../twitter/functions/getHomepage'; 3 | import { Logger } from '../../utils/logger'; 4 | 5 | /** 6 | * @command get-homepage 7 | * @description Get and process the homepage timeline 8 | */ 9 | export const twitterGetHomepage: Command = { 10 | name: 'get-homepage', 11 | description: 'Get and process the homepage timeline', 12 | parameters: [], 13 | handler: async (args) => { 14 | try { 15 | const { processTimeline, TimelineType } = await import('../../pipelines/processTimeline'); 16 | 17 | const tweets = await getHomepage(30); 18 | const timelineData = tweets.length === 0 ? '' : tweets.join('\n'); 19 | 20 | // Process timeline using existing pipeline 21 | const processedResult = await processTimeline(timelineData, TimelineType.HOMEPAGE); 22 | 23 | return { 24 | output: processedResult, // Return the formatted string directly 25 | data: { tweets: timelineData, processed: processedResult } 26 | }; 27 | } catch (error) { 28 | Logger.log('Error in twitter-get-homepage:', error); 29 | return { 30 | output: `❌ Error: ${error.message}`, 31 | data: { error: error.message } 32 | }; 33 | } 34 | } 35 | }; -------------------------------------------------------------------------------- /src/ai/agents/mediaAgent/mediaAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { activeSummaries } from '../../../utils/dynamicVariables'; 6 | import { recentMainTweets } from '../../../utils/dynamicVariables'; 7 | import { configLoader } from '../../../utils/config'; 8 | // Configuration for chat agent following terminal agent pattern 9 | export const mediaAgentConfig: AgentConfig = { 10 | systemPromptTemplate: ` 11 | # PERSONALITY 12 | {{corePersonalityPrompt}} 13 | 14 | # MAIN GOAL 15 | You are the media agent designed to generate media for {{agentName}}'s tweets. Based on the main tweet provided to you, generate media to accompany the tweet. 16 | 17 | # OUTPUT FORMAT 18 | Respond naturally in a conversational manner while maintaining the personality defined above. Use loaded context to inform your response. 19 | `, 20 | dynamicVariables: { 21 | corePersonalityPrompt: generateSystemPrompt(), 22 | currentSummaries: activeSummaries, 23 | terminalLog: "TERMINAL LOG DYNAMIC VARIABLE HERE", 24 | recentMainTweets: recentMainTweets || 'No recent tweets available', 25 | memories: 'MEMORIES DYNAMIC VARIABLE HERE', 26 | agentName: configLoader.getAgentName() 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/terminal/commands/twitter-follow.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { followUser, FollowResult } from '../../twitter/functions/followUser'; 3 | 4 | /** 5 | * @command twitter-follow 6 | * @description Follows a specified Twitter user 7 | */ 8 | export const twitterFollow: Command = { 9 | name: 'follow', 10 | description: 'Follow a user. Do not include the @ symbol.', 11 | parameters: [ 12 | { 13 | name: 'username', 14 | description: 'Username of the account to follow (without @)', 15 | required: true, 16 | type: 'string' 17 | } 18 | ], 19 | handler: async (args) => { 20 | try { 21 | const result: FollowResult = await followUser(args.username); 22 | 23 | // Format output based on status 24 | const statusEmoji = { 25 | success: '✅', 26 | already_following: 'ℹ️', 27 | user_not_found: '❌', 28 | error: '❌' 29 | }[result.status]; 30 | 31 | return { 32 | output: `${statusEmoji} Action: Follow User\nTarget: @${args.username}\nStatus: ${result.status}\nDetails: ${result.message}${ 33 | result.error ? `\nError: ${result.error}` : '' 34 | }` 35 | }; 36 | } catch (error) { 37 | return { 38 | output: `❌ Action: Follow User\nTarget: @${args.username}\nStatus: Error\nDetails: ${error.message}` 39 | }; 40 | } 41 | } 42 | }; -------------------------------------------------------------------------------- /src/terminal/commands/update-mission-metrics.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { missionSystem } from '../../missions'; 3 | import { Logger } from '../../utils/logger'; 4 | 5 | /** 6 | * @command update-mission-metrics 7 | * @description Update metrics for a specific mission 8 | */ 9 | export const updateMissionMetrics: Command = { 10 | name: 'update-mission-metrics', 11 | description: 'Update metrics for a specific mission', 12 | parameters: [ 13 | { 14 | name: 'missionId', 15 | description: 'ID of the mission', 16 | required: true, 17 | type: 'string' 18 | }, 19 | { 20 | name: 'metrics', 21 | description: 'Metrics in JSON format', 22 | required: true, 23 | type: 'string' 24 | } 25 | ], 26 | handler: async (args) => { 27 | try { 28 | const { missionId, metrics } = args; 29 | const metricsObj = JSON.parse(metrics); 30 | 31 | await missionSystem.updateMissionMetrics(missionId, metricsObj); 32 | Logger.log(`📊 Mission metrics updated for: ${missionId}`); 33 | 34 | return { 35 | output: `Mission metrics successfully updated` 36 | }; 37 | } catch (error) { 38 | Logger.log(`🚨 Failed to update mission metrics: ${error}`); 39 | return { 40 | output: `Error updating mission metrics: ${error}` 41 | }; 42 | } 43 | } 44 | }; -------------------------------------------------------------------------------- /src/ai/models/clients/FireworkClient.ts: -------------------------------------------------------------------------------- 1 | // models/FireworkClient.ts 2 | 3 | import OpenAI from 'openai'; // Firework API is OpenAI compatible 4 | import { ModelClient, ModelType } from '../../types/agentSystem'; 5 | 6 | export class FireworkClient implements ModelClient { 7 | private client: OpenAI; 8 | private modelName: string; 9 | private defaultParams: any; 10 | modelType: ModelType = 'fireworks'; 11 | 12 | constructor( 13 | modelName: string, 14 | params: any = {} 15 | ) { 16 | // Load the API key from environment variables 17 | const apiKey = process.env.FIREWORKS_API_KEY!; 18 | this.client = new OpenAI({ 19 | apiKey, 20 | baseURL: 'https://api.fireworks.ai/inference/v1', 21 | }); 22 | 23 | this.modelName = modelName; 24 | this.defaultParams = { 25 | temperature: 0.8, 26 | max_tokens: 1000, 27 | ...params, // Override defaults with provided params 28 | }; 29 | } 30 | 31 | async chatCompletion(params: any): Promise { 32 | try { 33 | // Merge default parameters with method-specific params 34 | const requestParams = { 35 | model: this.modelName, 36 | ...this.defaultParams, 37 | ...params, 38 | }; 39 | const response = await this.client.chat.completions.create(requestParams); 40 | return response; 41 | } catch (error) { 42 | throw error; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/ai/agents/memoryAgent/memoryTool.ts: -------------------------------------------------------------------------------- 1 | // tools/TerminalTool.ts 2 | 3 | import { z } from 'zod'; 4 | import { Tool } from '../../types/agentSystem'; 5 | 6 | export const memoryToolSchema = z.object({ 7 | internal_thought: z.string().describe('Think about what query would be the most efficient to pull the most relevant memories'), 8 | memory_query: z.string().describe('A query to pull the most relevant memories from the vector database of memories') 9 | }); 10 | 11 | export const MemoryTool: Tool = { 12 | type: 'function', 13 | function: { 14 | "name": "memory_tool", 15 | "description": "Analyze chat logs to generate a query to pull the most relevant memories from the vector database of memories", 16 | "strict": true, 17 | "parameters": { 18 | "type": "object", 19 | "required": [ 20 | "internal_thought", 21 | "memory_query" 22 | ], 23 | "properties": { 24 | "internal_thought": { 25 | "type": "string", 26 | "description": "Think about what query would be the most efficient to pull the most relevant memories." 27 | }, 28 | "memory_query": { 29 | "type": "string", 30 | "description": "A query of keywords to pull the most relevant memories from the vector database of memories. This should be a short list of keywords, seperated by commas. Max 5 words" 31 | } 32 | } 33 | } 34 | } 35 | }; -------------------------------------------------------------------------------- /src/ai/agents/verifyRetweetAgent/verifyRetweetAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { configLoader } from '../../../utils/config'; 6 | 7 | // Configuration for chat agent following terminal agent pattern 8 | export const verifyRetweetAgentConfig: AgentConfig = { 9 | systemPromptTemplate: ` 10 | # PERSONALITY 11 | {{corePersonalityPrompt}} 12 | 13 | ## SHORT TERM TERMINAL LOG INFORMATION 14 | This is the short term terminal log. The terminal log results give contextually relevant information about the current state of the Crypto timeline and the internet. 15 | The short term terminal log contains {{agentName}}'s thoughts and plans as well! Act upon these accordingly. 16 | 17 | {{shortTermTerminalLog}} 18 | 19 | # MAIN GOAL 20 | You are evaluating whether to retweet a tweet. The full context of the tweet is provided to you below. 21 | Consider if this tweet aligns with your values, interests, and if it would be valuable to share with your followers. 22 | Do NOT retweet un-important tweets, scams, or links. 23 | 24 | # OUTPUT FORMAT 25 | Use your verify_retweet_tool to verify whether to retweet the tweet. 26 | `, 27 | dynamicVariables: { 28 | corePersonalityPrompt: generateSystemPrompt(), 29 | terminalLog: `SHORT TERM TERMINAL LOG DYNAMIC VARIABLE`, 30 | agentName: configLoader.getAgentName() 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/tests/dynamicVariable.ts: -------------------------------------------------------------------------------- 1 | import { assembleTwitterInterface } from '../twitter/utils/imageUtils'; 2 | import { TestAgent } from '../ai/agents/testAgent/testAgent'; 3 | import { Logger } from '../utils/logger'; 4 | import { OpenAIClient } from '../ai/models/clients/OpenAiClient'; 5 | import { AnthropicClient } from '../ai/models/clients/AnthropicClient'; 6 | import { FireworkClient } from '../ai/models/clients/FireworkClient'; 7 | 8 | Logger.enable(); 9 | 10 | // Assemble Twitter interface 11 | const { textContent, imageContents } = await assembleTwitterInterface("1862442359020990812"); 12 | 13 | // Create dynamic variables for runtime 14 | const runtimeVariables = { 15 | corePersonalityPrompt: "talk like a pirate", 16 | twitterInterface: textContent, 17 | }; 18 | 19 | // Create test agent 20 | const openAIClient = new OpenAIClient("gpt-4o"); 21 | const anthropicClient = new AnthropicClient("claude-3-5-sonnet-20240620"); 22 | const fireworkClient = new FireworkClient("accounts/fireworks/models/llama-v3p1-405b-instruct"); 23 | const testAgent = new TestAgent(anthropicClient); 24 | 25 | // Add all images at once 26 | testAgent.addImage( 27 | imageContents.map(img => ({ 28 | name: img.sender, 29 | mime: img.media_type, 30 | data: img.data, 31 | })), 32 | ); 33 | 34 | // Run the agent with dynamic variables 35 | const runAgentTest = await testAgent.run( 36 | "What is depicted in the attached images?", 37 | runtimeVariables 38 | ); 39 | 40 | console.log("RUN AGENT TEST:", runAgentTest); -------------------------------------------------------------------------------- /src/ai/types/agentSystem.ts: -------------------------------------------------------------------------------- 1 | // types/index.ts 2 | 3 | import { z } from 'zod'; 4 | 5 | export interface Message { 6 | role: 'system' | 'assistant' | 'user' | 'function'; 7 | content?: string; 8 | name?: string; 9 | image?: { 10 | name: string; 11 | mime: string; 12 | data: Buffer | string; 13 | }; 14 | } 15 | 16 | export interface AgentConfig { 17 | systemPromptTemplate: string; 18 | dynamicVariables?: { 19 | [key: string]: string | (() => string); 20 | }; 21 | } 22 | 23 | export interface Tool { 24 | type: 'function'; 25 | function: { 26 | name: string; 27 | strict?: boolean; 28 | description: string; 29 | parameters: Record; 30 | }; 31 | } 32 | 33 | export type ModelType = 'openai' | 'fireworks' | 'anthropic'; 34 | 35 | export interface ModelClient { 36 | modelType: ModelType; 37 | chatCompletion(params: any): Promise; 38 | } 39 | 40 | // Base interface for all tool outputs 41 | export type BaseToolOutput = Record; 42 | 43 | // Type to extract Zod schema type 44 | export type ZodSchemaType = z.infer; 45 | 46 | // Type to get tool output type from schema 47 | export type ToolOutputFromSchema = z.infer; 48 | 49 | // Generic type for agent run results 50 | export interface AgentRunResult { 51 | success: boolean; 52 | output: T extends z.ZodTypeAny ? ToolOutputFromSchema : string; 53 | error?: string; 54 | } 55 | -------------------------------------------------------------------------------- /temp_docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture Overview 2 | 3 | This document provides a detailed overview of the project's architecture, including modules, components, and how they interact. 4 | 5 | ## Modules and Components 6 | 7 | ### 1. Terminal System 8 | 9 | - **Location**: `src/terminal/` 10 | - **Purpose**: Provides a command execution environment that can be used by both the AI agent and human users. 11 | - **Key Files**: 12 | - `executeCommand.ts`: Core logic for command execution. 13 | - `commandRegistry.ts`: Manages the registration and retrieval of command handlers. 14 | - `commands/`: Directory containing individual command implementations. 15 | 16 | ### 2. AI Module 17 | 18 | - **Location**: `src/ai/` 19 | - **Purpose**: Contains the AI logic, including prompt generation and AI interactions. 20 | - **Key Files**: 21 | - `coreFunctions/`: Core AI functions such as `mainTweet` and `replyTweet`. 22 | - `assembleSystemPrompt.ts`: Generates the system prompt with command information. 23 | 24 | ### 3. Twitter Integration 25 | 26 | - **Location**: `src/twitter/` 27 | - **Purpose**: Handles interactions with the Twitter API. 28 | - **Key Files**: 29 | - `twitterCommands.ts`: Contains functions for sending tweets, retrieving replies, etc. 30 | 31 | ## Interaction Flow 32 | 33 | - The AI agent generates a command based on the system prompt. 34 | - The command is passed to the terminal system for execution. 35 | - The terminal executes the command and returns the output. 36 | - The AI agent uses the output to inform subsequent actions. 37 | 38 | --- 39 | -------------------------------------------------------------------------------- /src/pipelines/mediaGeneration/imageGen.ts: -------------------------------------------------------------------------------- 1 | import { fal } from "@fal-ai/client"; 2 | import dotenv from "dotenv"; 3 | import { configLoader } from "../../utils/config"; 4 | 5 | dotenv.config(); 6 | 7 | // Get image generation settings with defaults 8 | const imageConfig = configLoader.getConfig().imageGen || { 9 | loraPath: "Bootoshi/retroanime", 10 | promptPrefix: "retro anime style image of" 11 | }; 12 | 13 | // Configure fal client with API key from environment variables 14 | fal.config({ 15 | credentials: process.env.FAL_API_KEY 16 | }); 17 | 18 | /** 19 | * Generates a retro anime style image using fal.ai API 20 | * @param prompt - The user's prompt that will be combined with retro anime style 21 | * @returns Promise containing the generated image URL 22 | */ 23 | export async function generateImage(prompt: string): Promise { 24 | try { 25 | const fullPrompt = `${imageConfig.promptPrefix} ${prompt}`; 26 | 27 | const result = await fal.subscribe("fal-ai/flux-lora", { 28 | input: { 29 | prompt: fullPrompt, 30 | loras: [ 31 | { 32 | path: imageConfig.loraPath, 33 | scale: 1 34 | } 35 | ], 36 | image_size: "square_hd", 37 | num_images: 1, 38 | output_format: "jpeg", 39 | guidance_scale: 3.5, 40 | num_inference_steps: 28, 41 | enable_safety_checker: false 42 | } 43 | }); 44 | 45 | return result.data.images[0].url; 46 | 47 | } catch (error) { 48 | console.error("Error generating image:", error); 49 | throw error; 50 | } 51 | } -------------------------------------------------------------------------------- /src/terminal/commands/twitter-search.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { searchTwitter } from '../../twitter/functions/searchTwitter'; 3 | import { Logger } from '../../utils/logger'; 4 | 5 | /** 6 | * @command search-twitter 7 | * @description Search for and process tweets with a specific query 8 | */ 9 | export const twitterSearch: Command = { 10 | name: 'search-twitter', 11 | description: 'Search for and process tweets with a specific query', 12 | parameters: [ 13 | { 14 | name: 'query', 15 | description: 'Search query string', 16 | required: true, 17 | type: 'string' 18 | } 19 | ], 20 | handler: async (args) => { 21 | try { 22 | const { processTimeline, TimelineType } = await import('../../pipelines/processTimeline'); 23 | 24 | const tweets = await searchTwitter(args.query, 10); 25 | 26 | const timelineData = tweets.length === 0 ? '' : tweets.join('\n'); 27 | 28 | // Process search results using the timeline pipeline with search context 29 | const processedResult = await processTimeline( 30 | timelineData, 31 | TimelineType.SEARCH, 32 | `query: "${args.query}"` 33 | ); 34 | 35 | return { 36 | output: processedResult, 37 | data: { tweets: timelineData, processed: processedResult } 38 | }; 39 | } catch (error) { 40 | Logger.log('Error in twitter-search:', error); 41 | return { 42 | output: `❌ Error: ${error.message}`, 43 | data: { error: error.message } 44 | }; 45 | } 46 | } 47 | }; -------------------------------------------------------------------------------- /src/terminal/types/commands.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for command parameters and commands 2 | 3 | /** 4 | * Represents a command parameter. 5 | */ 6 | export interface CommandParameter { 7 | /** 8 | * The name of the parameter. 9 | */ 10 | name: string; 11 | 12 | /** 13 | * A description of the parameter. 14 | */ 15 | description: string; 16 | 17 | /** 18 | * Whether the parameter is required. 19 | */ 20 | required: boolean; 21 | 22 | /** 23 | * The default value of the parameter (if any). 24 | */ 25 | defaultValue?: string; 26 | 27 | /** 28 | * The expected type of the parameter. 29 | */ 30 | type?: 'string' | 'number' | 'boolean'; 31 | } 32 | 33 | /** 34 | * Defines the structure of a command. 35 | */ 36 | export interface Command { 37 | /** 38 | * The name of the command. 39 | */ 40 | name: string; 41 | 42 | /** 43 | * A description of what the command does. 44 | */ 45 | description: string; 46 | 47 | /** 48 | * The parameters that the command accepts. 49 | */ 50 | parameters?: CommandParameter[]; 51 | 52 | /** 53 | * The function that handles the command execution. 54 | */ 55 | handler: CommandHandler; 56 | } 57 | 58 | /** 59 | * Defines the command handler function type. 60 | */ 61 | export type CommandHandler = ( 62 | args: { [key: string]: any } 63 | ) => Promise<{ 64 | /** 65 | * The output of the command execution. 66 | */ 67 | output: string; 68 | data?: any; 69 | }>; -------------------------------------------------------------------------------- /temp_docs/terminal_commands.md: -------------------------------------------------------------------------------- 1 | # Terminal Command System Documentation 2 | 3 | ## Command Template 4 | 5 | ```typescript 6 | import { Command } from '../../types/CommandType'; 7 | 8 | /** 9 | * @command your-command-name 10 | * @description A clear description of what your command does 11 | */ 12 | export const yourCommand: Command = { 13 | name: 'your-command-name', 14 | description: 'A clear description of what your command does', 15 | parameters: [ 16 | { 17 | name: 'parameterName', 18 | description: 'Description of what this parameter does', 19 | required: true, 20 | type: 'string', // 'string' | 'number' | 'boolean' 21 | defaultValue: 'optional default value', 22 | }, 23 | ], 24 | handler: async (args) => { 25 | try { 26 | // Command implementation 27 | return { 28 | output: 'Success message or result', 29 | }; 30 | } catch (error) { 31 | return { 32 | output: `Error: ${error.message}`, 33 | }; 34 | } 35 | }, 36 | }; 37 | ``` 38 | 39 | ## Implementation Steps 40 | 41 | 1. Create your command file in `src/terminal/commands/` 42 | 2. Define the command using the template above 43 | 3. Add command to `src/terminal/commands/index.ts` 44 | 4. Test using the terminal: `@your-command-name [parameters]` 45 | 46 | ## Best Practices 47 | 48 | - Use kebab-case for command names (e.g., `get-user`, `create-post`) 49 | - Write clear, concise descriptions 50 | - Include parameter validation in the handler 51 | - Return meaningful success/error messages 52 | - Document any side effects or important notes 53 | -------------------------------------------------------------------------------- /src/terminal/commands/twitter-get-tweets.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { getTweets } from '../../twitter/functions/getTweets'; 3 | import { Logger } from '../../utils/logger'; 4 | 5 | /** 6 | * @command get-tweets 7 | * @description Get and process recent tweets from a specified user 8 | */ 9 | export const twitterGetTweets: Command = { 10 | name: 'get-tweets', 11 | description: 'Get and process recent tweets from a specified user. Do not include the @ symbol.', 12 | parameters: [ 13 | { 14 | name: 'username', 15 | description: 'Twitter username (without @ symbol)', 16 | required: true, 17 | type: 'string' 18 | } 19 | ], 20 | handler: async (args) => { 21 | try { 22 | 23 | const { processTimeline, TimelineType } = await import('../../pipelines/processTimeline'); 24 | 25 | const tweets = await getTweets(args.username, 10); 26 | 27 | const timelineData = tweets.length === 0 ? '' : tweets.join('\n'); 28 | 29 | // Process tweets using the timeline pipeline 30 | const processedResult = await processTimeline( 31 | timelineData, 32 | TimelineType.USER_PROFILE, 33 | `@${args.username}` 34 | ); 35 | 36 | return { 37 | output: processedResult, 38 | data: { tweets: timelineData, processed: processedResult } 39 | }; 40 | } catch (error) { 41 | Logger.log('Error in twitter-get-tweets:', error); 42 | return { 43 | output: `❌ Error: ${error.message}`, 44 | data: { error: error.message } 45 | }; 46 | } 47 | } 48 | }; -------------------------------------------------------------------------------- /src/supabase/functions/twitter/interactionEntries.ts: -------------------------------------------------------------------------------- 1 | import { supabase } from '../../supabaseClient'; 2 | import { Logger } from '../../../utils/logger'; 3 | 4 | interface TweetInteractionData { 5 | tweetId: string; 6 | userTweetText: string; 7 | userTweetTimestamp: string; 8 | userId: string; 9 | context?: { 10 | type: 'mention' | 'reply_to_bot' | 'reply_to_others' | 'quote_tweet' | 'retweet' | null; 11 | parentTweetId?: string; 12 | parentTweetAuthor?: string; 13 | }; 14 | } 15 | 16 | /** 17 | * Logs a Twitter interaction in the database 18 | * Silently handles duplicate entries 19 | */ 20 | export async function logTwitterInteraction( 21 | data: TweetInteractionData 22 | ): Promise { 23 | try { 24 | const { data: interaction, error } = await supabase 25 | .from('twitter_interactions') 26 | .upsert( 27 | { 28 | tweet_id: data.tweetId, 29 | user_id: data.userId, 30 | bot_username: process.env.TWITTER_USERNAME, 31 | context: data.context || null, 32 | text: data.userTweetText, 33 | timestamp: data.userTweetTimestamp 34 | }, 35 | { 36 | onConflict: 'tweet_id', 37 | ignoreDuplicates: true 38 | } 39 | ) 40 | .select('id') 41 | .single(); 42 | 43 | if (error) { 44 | if (error.code !== '23505') { 45 | Logger.log('Error logging interaction:', error); 46 | } 47 | return null; 48 | } 49 | 50 | return interaction.id.toString(); 51 | } catch (error) { 52 | Logger.log('Error in logTwitterInteraction:', error); 53 | return null; 54 | } 55 | } -------------------------------------------------------------------------------- /to-do.md: -------------------------------------------------------------------------------- 1 | TO DO: 2 | - create dream system - an agent that loads in learnings daily per category [and each user], reflects/summarizes, and stores in supabase db. every day the new learnings condense into the existing learnings, adjusting the core system prompt on a daily basis. (can be done later) 3 | - get fireworks model image capabilities by chaining the image gen into the firework agent history after 'seeing' it 4 | - log the token usage per agent, somehow 5 | - log the schema outputs of every agent when they run in pipelines, so we can visualize it on site 6 | - update site so we get new terminal and functionalities 7 | - make the agent system for CORE agents configurable for new user modalarity 8 | - make the media gen system easily configurable for new user modalarity 9 | - look into bundling commands like replies from terminal agent so we can hammer out multiple replies at once 10 | 11 | IDEAS: 12 | - content manager agent that reads different timelines/world news and decides what to tweet about 13 | content manager pipeline: 14 | -> reads different timelines and summarizes the current meta/trend of what ppl are talking about 15 | -> gets live news of what's happening in the world 16 | -> gets current mempool data to see if anything interesting is going on 17 | once it gets all this news, it thinks of a topic to tweet about, and gives that + summarized info to the main tweet agent 18 | 19 | - need to figure out how to get satoshi to bull post about other communities/projects too 20 | 21 | - i can make a chatgpt pro prompt base i can use to reference and copy and paste, maybe it can help me make full agent pipelines 22 | 23 | - probably have to reverse engineer twit api to see the twitter blue post request and edit goat-x package -------------------------------------------------------------------------------- /docs/mint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://mintlify.com/schema.json", 3 | "name": "CYPHER SWARM", 4 | "logo": { 5 | "dark": "/logo/dark.svg", 6 | "light": "/logo/light.svg" 7 | }, 8 | "favicon": "/favicon.svg", 9 | "colors": { 10 | "primary": "#FF4500", 11 | "light": "#FF6347", 12 | "dark": "#DC143C", 13 | "anchors": { 14 | "from": "#FF4500", 15 | "to": "#FF6347" 16 | } 17 | }, 18 | "topbarLinks": [ 19 | { 20 | "name": "GitHub", 21 | "url": "https://github.com/kingbootoshi/cypher-swarm" 22 | } 23 | ], 24 | "topbarCtaButton": { 25 | "name": "Get Started", 26 | "url": "/quickstart" 27 | }, 28 | "tabs": [ 29 | { 30 | "name": "API Reference", 31 | "url": "api-reference" 32 | } 33 | ], 34 | "anchors": [ 35 | { 36 | "name": "Documentation", 37 | "icon": "book-open-cover", 38 | "url": "/introduction" 39 | }, 40 | { 41 | "name": "GitHub", 42 | "icon": "github", 43 | "url": "https://github.com/kingbootoshi/cypher-swarm" 44 | } 45 | ], 46 | "navigation": [ 47 | { 48 | "group": "Getting Started", 49 | "pages": [ 50 | "introduction", 51 | "quickstart", 52 | "database-setup" 53 | ] 54 | }, 55 | { 56 | "group": "Core Concepts", 57 | "pages": [ 58 | "concepts/unified-personality", 59 | "concepts/agent-system", 60 | "concepts/pipelines", 61 | "concepts/hivemind-memory", 62 | "concepts/terminal-commands", 63 | "concepts/terminal-agent" 64 | ] 65 | } 66 | ], 67 | "footerSocials": { 68 | "github": "https://github.com/kingbootoshi/cypher-swarm" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/utils/formatTimestamps.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Formats a date to dd/mm/yy - HH:MM AM/PM UTC format 3 | * @param date - Date object or ISO string to format 4 | * @returns Formatted date string 5 | */ 6 | 7 | export function formatTimestamp(date: Date | string): string { 8 | // Convert input to Date object if it's an ISO string 9 | let dateObj: Date; 10 | if (typeof date === 'string') { 11 | // Append 'Z' if not already present to indicate UTC 12 | if (!date.endsWith('Z')) { 13 | date += 'Z'; 14 | } 15 | dateObj = new Date(date); 16 | } else { 17 | dateObj = date; 18 | } 19 | 20 | // Validate date 21 | if (!(dateObj instanceof Date) || isNaN(dateObj.getTime())) { 22 | throw new Error('Invalid date input'); 23 | } 24 | 25 | // Extract UTC components 26 | const day = dateObj.getUTCDate().toString().padStart(2, '0'); 27 | const month = (dateObj.getUTCMonth() + 1).toString().padStart(2, '0'); 28 | const year = dateObj.getUTCFullYear().toString().slice(-2); 29 | 30 | // Get hours in 12-hour format 31 | let hours = dateObj.getUTCHours(); 32 | const ampm = hours >= 12 ? 'PM' : 'AM'; 33 | hours = hours % 12 || 12; // Convert 0 to 12 34 | 35 | const minutes = dateObj.getUTCMinutes().toString().padStart(2, '0'); 36 | 37 | return `${day}/${month}/${year} - ${hours}:${minutes} ${ampm} UTC`; 38 | } 39 | 40 | /** 41 | * Returns the current UTC time in formatted string: [dd/mm/yy - HH:MM AM/PM UTC] 42 | * @returns Formatted current UTC timestamp string with brackets 43 | */ 44 | export function getCurrentTimestamp(): string { 45 | const now = new Date(); 46 | return `[${formatTimestamp(now)}]`; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/ai/agents/summaryAgent/summaryAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/summaryAgent/summaryAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { activeSummaries } from '../../../utils/dynamicVariables'; 6 | import { configLoader } from '../../../utils/config'; 7 | 8 | export const summaryAgentConfig: AgentConfig = { 9 | systemPromptTemplate: ` 10 | # PERSONALITY 11 | {{corePersonalityPrompt}} 12 | 13 | # CURRENT SUMMARIES 14 | {{currentSummaries}} 15 | 16 | # MAIN GOAL 17 | You are the summarization aspect of {{agentName}}'s soul. 18 | 19 | {{agentName}} has access to hold 5 short term summaries, 3 mid term summaries, and 1 long term summary in his memory 20 | Every 5 short term summaries get condensed into 1 mid term summary, every 3 mid term summaries get condensed into 1 long term summary, condensing the previous existing long term summary into this new one. 21 | 22 | In order to keep {{agentName}}'s memory concise and manageable, you must condense the summaries to maintain a sense of time in the present. 23 | 24 | Use the current summaries as a REFERENCE in condensing summaries. The summaries and type (short/mid/long) will be provided to you below. 25 | 26 | Summaries need to be very DETAILED and specific. They can't be vague. The topic, learnings, and engagements done need to be detailed and tracked. 27 | 28 | # OUTPUT FORMAT 29 | You MUST use your condense_summaries at all times - you will ONLY be given terminal logs and user interactions. PLEASE OUTPUT JSON FORMAT ONLY. 30 | `, 31 | dynamicVariables: { 32 | corePersonalityPrompt: generateSystemPrompt(), 33 | currentSummaries: activeSummaries, 34 | agentName: configLoader.getAgentName() 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/tests/testTweetExtraction.ts: -------------------------------------------------------------------------------- 1 | // This test file replicates how extractLearnings.ts processes the short-term terminal history, 2 | // extracts actions, finds interactions based on tweet IDs, and assembles interaction tables. 3 | 4 | import { getShortTermHistory } from '../supabase/functions/terminal/terminalHistory'; 5 | import { Logger } from '../utils/logger'; 6 | import { getFormattedInteractionSummary } from '../utils/extractTweetActions'; 7 | 8 | // Enable logging 9 | Logger.enable(); 10 | 11 | /** 12 | * Test function to replicate the extraction process from extractLearnings.ts 13 | */ 14 | async function testTweetExtraction() { 15 | try { 16 | Logger.log('Starting testTweetExtraction...'); 17 | 18 | // Generate a test session ID 19 | const testSessionId = 'test-session-' + Date.now(); 20 | 21 | // Fetch the short-term terminal history 22 | const shortTermHistory = await getShortTermHistory(100); 23 | Logger.log('Short-term history fetched:', shortTermHistory); 24 | 25 | // Get formatted user tweet interactions based on tweet IDs 26 | const userTweetInteractions = await getFormattedInteractionSummary(); 27 | Logger.log('User tweet interactions:', userTweetInteractions); 28 | 29 | Logger.log('testTweetExtraction completed successfully'); 30 | } catch (error) { 31 | Logger.log('Error in testTweetExtraction:', error); 32 | throw error; 33 | } 34 | } 35 | 36 | // Execute the test function if this file is run directly 37 | if (require.main === module) { 38 | testTweetExtraction() 39 | .then(() => { 40 | Logger.log('Test completed successfully'); 41 | process.exit(0); 42 | }) 43 | .catch((error) => { 44 | Logger.log('Test failed:', error); 45 | process.exit(1); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/terminal/commands/twitter-reply.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | /** 3 | * @command twitter-reply 4 | * @description Replies to a specified tweet 5 | */ 6 | export const twitterReply: Command = { 7 | name: 'reply-to-tweet', 8 | description: 'Reply to a tweet. Only input the tweet ID number, raw digits. An agent will handle the rest.', 9 | parameters: [ 10 | { 11 | name: 'tweetId', 12 | description: 'ID of the tweet to reply to', 13 | required: true, 14 | type: 'string' 15 | } 16 | ], 17 | handler: async (args) => { 18 | try { 19 | const mediaUrls = args.mediaUrls ? args.mediaUrls.split(',').map(url => url.trim()) : undefined; 20 | 21 | // Lazy import generateAndPostTweetReply to avoid initialization issues 22 | const { generateAndPostTweetReply } = await import('../../pipelines/generateReply'); 23 | 24 | // Use the enhanced pipeline 25 | const result = await generateAndPostTweetReply(args.tweetId, mediaUrls); 26 | 27 | return { 28 | output: `${result.success ? '✅' : '❌'} Action: Reply Tweet\n` + 29 | `Parent Tweet ID: ${args.tweetId}\n` + 30 | `${result.tweetId ? `Reply Tweet ID: ${result.tweetId}\n` : ''}` + 31 | `Status: ${result.success ? 'Success' : 'Failed'}\n` + 32 | `Text: ${result.replyText}\n` + 33 | `Media: ${result.mediaUrls ? result.mediaUrls.join(', ') : 'None'}\n` + 34 | `Details: ${result.message}` 35 | }; 36 | } catch (error) { 37 | return { 38 | output: `❌ Action: Reply Tweet\n` + 39 | `Parent Tweet ID: ${args.tweetId}\n` + 40 | `Status: Error\n` + 41 | `Details: ${error.message}` 42 | }; 43 | } 44 | } 45 | }; -------------------------------------------------------------------------------- /src/tests/chatTest.ts: -------------------------------------------------------------------------------- 1 | import { ChatAgent } from '../ai/agents/chatAgent/chatAgent'; 2 | import { OpenAIClient } from '../ai/models/clients/OpenAiClient'; 3 | import { AnthropicClient } from '../ai/models/clients/AnthropicClient'; 4 | import { FireworkClient } from '../ai/models/clients/FireworkClient'; 5 | import { Logger } from '../utils/logger'; 6 | 7 | Logger.enable(); 8 | 9 | // Test configurations for different models 10 | const models = [ 11 | { 12 | name: 'OpenAI', 13 | client: new OpenAIClient('gpt-4o', { temperature: 1 }), 14 | }, 15 | { 16 | name: 'Anthropic', 17 | client: new AnthropicClient('claude-3-5-haiku-20241022', { temperature: 1 }), 18 | }, 19 | { 20 | name: 'Fireworks', 21 | client: new FireworkClient('accounts/fireworks/models/llama-v3p1-405b-instruct', { temperature: 1 }), 22 | } 23 | ]; 24 | 25 | // Run chat tests for each model 26 | async function runChatTests() { 27 | console.log('\n🤖 Starting Chat Model Tests\n'); 28 | 29 | for (const model of models) { 30 | console.log(`\n📝 Testing ${model.name} Chat Model`); 31 | 32 | try { 33 | // Initialize chat agent for current model 34 | const chatAgent = new ChatAgent(model.client); 35 | 36 | // Run test conversation 37 | const chatResult = await chatAgent.run("Hello! How are you today?"); 38 | 39 | // Log results 40 | if (chatResult.success) { 41 | console.log(`✅ ${model.name} Chat Response:`, chatResult.output); 42 | } else { 43 | console.error(`❌ ${model.name} Chat Failed:`, chatResult.error); 44 | } 45 | } catch (error) { 46 | console.error(`❌ ${model.name} Chat Error:`, error); 47 | } 48 | } 49 | 50 | console.log('\n🏁 Chat Model Tests Completed\n'); 51 | } 52 | 53 | // Execute the chat tests 54 | runChatTests(); -------------------------------------------------------------------------------- /docs/essentials/images.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Images and Embeds' 3 | description: 'Add image, video, and other HTML elements' 4 | icon: 'image' 5 | --- 6 | 7 | 11 | 12 | ## Image 13 | 14 | ### Using Markdown 15 | 16 | The [markdown syntax](https://www.markdownguide.org/basic-syntax/#images) lets you add images using the following code 17 | 18 | ```md 19 | ![title](/path/image.jpg) 20 | ``` 21 | 22 | Note that the image file size must be less than 5MB. Otherwise, we recommend hosting on a service like [Cloudinary](https://cloudinary.com/) or [S3](https://aws.amazon.com/s3/). You can then use that URL and embed. 23 | 24 | ### Using Embeds 25 | 26 | To get more customizability with images, you can also use [embeds](/writing-content/embed) to add images 27 | 28 | ```html 29 | 30 | ``` 31 | 32 | ## Embeds and HTML elements 33 | 34 | 44 | 45 |
46 | 47 | 48 | 49 | Mintlify supports [HTML tags in Markdown](https://www.markdownguide.org/basic-syntax/#html). This is helpful if you prefer HTML tags to Markdown syntax, and lets you create documentation with infinite flexibility. 50 | 51 | 52 | 53 | ### iFrames 54 | 55 | Loads another HTML page within the document. Most commonly used for embedding videos. 56 | 57 | ```html 58 | 59 | ``` 60 | -------------------------------------------------------------------------------- /src/utils/internetTool.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { Logger } from './logger'; 3 | 4 | Logger.enable(); 5 | 6 | // Load environment variables 7 | dotenv.config(); 8 | 9 | interface Message { 10 | role: 'system' | 'user' | 'assistant'; 11 | content: string; 12 | } 13 | 14 | interface PerplexityResponse { 15 | choices: { 16 | message: { 17 | content: string; 18 | }; 19 | }[]; 20 | } 21 | 22 | /** 23 | * Queries the Perplexity AI API with a given prompt 24 | * @param query - The user's question or prompt 25 | * @returns The AI's response as a string 26 | */ 27 | export async function queryPerplexity(query: string): Promise { 28 | // System message to control AI behavior 29 | const messages: Message[] = [ 30 | { 31 | role: 'system', 32 | content: 'Give a clear, direct answer to the user\'s question.' 33 | }, 34 | { 35 | role: 'user', 36 | content: query 37 | } 38 | ]; 39 | 40 | try { 41 | // Make API request to Perplexity 42 | const response = await fetch('https://api.perplexity.ai/chat/completions', { 43 | method: 'POST', 44 | headers: { 45 | 'Accept': 'application/json', 46 | 'Content-Type': 'application/json', 47 | 'Authorization': `Bearer ${process.env.PERPLEXITY_API_KEY}` 48 | }, 49 | body: JSON.stringify({ 50 | model: 'llama-3.1-sonar-large-128k-online', 51 | messages 52 | }) 53 | }); 54 | 55 | if (!response.ok) { 56 | throw new Error(`API request failed with status ${response.status}`); 57 | } 58 | 59 | const data = await response.json() as PerplexityResponse; 60 | return data.choices[0].message.content; 61 | 62 | } catch (error) { 63 | Logger.log('Error querying Perplexity:', error); 64 | throw error; 65 | } 66 | } -------------------------------------------------------------------------------- /src/ai/agents/mediaAgent/mediaTool.ts: -------------------------------------------------------------------------------- 1 | // tools/TerminalTool.ts 2 | 3 | import { z } from 'zod'; 4 | import { Tool } from '../../types/agentSystem'; 5 | 6 | // Define the content type enum for strict type checking 7 | const MediaContentType = { 8 | Image: 'image', 9 | Video: 'video' 10 | } as const; 11 | 12 | export const mediaToolSchema = z.object({ 13 | content_type: z.enum([MediaContentType.Image, MediaContentType.Video]) 14 | .describe('The type of media to generate: either "image" or "video"'), 15 | media_prompt: z.string().describe('A prompt used for text-to-image generation. Be very descriptive, and include specific details. You can include a description of the scene, the mood, the style, etc. If you want text in the image, make sure to include it in the prompt.') 16 | }); 17 | 18 | export const MediaTool: Tool = { 19 | type: 'function', 20 | function: { 21 | "name": "generate_media", 22 | "description": "Based on the main tweet provided to you, generate media to accompany the tweet.", 23 | "strict": true, 24 | "parameters": { 25 | "type": "object", 26 | "required": [ 27 | "content_type", 28 | "media_included" 29 | ], 30 | "properties": { 31 | "content_type": { 32 | "type": "string", 33 | "enum": [MediaContentType.Image, MediaContentType.Video], 34 | "description": "The type of media to generate: either \"image\" or \"video\"" 35 | }, 36 | "media_prompt": { 37 | "type": "string", 38 | "description": "A prompt used for text-to-image generation. Be very descriptive, and include specific details. You can include a description of the scene, the mood, the style, etc. If you want text in the image, make sure to include it in the prompt." 39 | } 40 | } 41 | } 42 | } 43 | }; -------------------------------------------------------------------------------- /src/terminal/commands/twitter-retweet.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { isCooldownActive } from '../../supabase/functions/twitter/cooldowns'; 3 | import { confirmRetweet } from '../../pipelines/verifyRetweet'; 4 | 5 | /** 6 | * @command twitter-retweet 7 | * @description Retweets a specified tweet 8 | */ 9 | export const twitterRetweet: Command = { 10 | name: 're-tweet', 11 | description: 'Retweet a tweet. Only input the tweet ID number, raw digits. An agent will handle the rest.', 12 | parameters: [ 13 | { 14 | name: 'tweetId', 15 | description: 'ID of the tweet to retweet', 16 | required: true, 17 | type: 'string' 18 | } 19 | ], 20 | handler: async (args) => { 21 | // Check for retweet cooldown 22 | const cooldownInfo = await isCooldownActive('retweet'); 23 | 24 | if (cooldownInfo.isActive) { 25 | return { 26 | output: `❌ Action: Retweet\n` + 27 | `Tweet ID: ${args.tweetId}\n` + 28 | 'Status: Failed\n' + 29 | `Reason: Retweet cooldown is active. Please wait ${cooldownInfo.remainingTime} minutes before retweeting again.` 30 | }; 31 | } 32 | 33 | try { 34 | // Proceed with retweeting 35 | const result = await confirmRetweet(args.tweetId); 36 | 37 | return { 38 | output: `${result.success ? '✅' : '❌'} Action: Retweet\n` + 39 | `Tweet ID: ${args.tweetId}\n` + 40 | `Status: ${result.success ? 'Success' : 'Failed'}\n` + 41 | `Details: ${result.message}` 42 | }; 43 | } catch (error) { 44 | return { 45 | output: `❌ Action: Retweet\n` + 46 | `Tweet ID: ${args.tweetId}\n` + 47 | `Status: Error\n` + 48 | `Details: ${error.message}` 49 | }; 50 | } 51 | } 52 | }; -------------------------------------------------------------------------------- /src/ai/agents/missionAgent/missionAgentConfig.ts: -------------------------------------------------------------------------------- 1 | import { AgentConfig } from '../../types/agentSystem'; 2 | import { missionSystem } from '../../../missions/systems/missionSystem'; 3 | 4 | export const missionAgentConfig: AgentConfig = { 5 | systemPromptTemplate: ` 6 | # MISSION ANALYZER 7 | 8 | ## CONTEXT 9 | You are the mission analysis system for an AI agent. Your role is to: 10 | - Analyze actions and their impact on mission progress 11 | - Update mission metrics based on outcomes 12 | - Evaluate mission completion criteria 13 | - Recommend next actions 14 | 15 | ## CURRENT MISSIONS 16 | {{missions}} 17 | 18 | ## ACTIVE MISSION 19 | {{currentMission}} 20 | 21 | ## AVAILABLE METRICS 22 | {{availableMetrics}} 23 | 24 | ## METRICS FORMAT 25 | When updating metrics, always use the following JSON format: 26 | { 27 | "metric_name": numeric_value 28 | } 29 | 30 | Example: 31 | { 32 | "engagement_rate": 2.5, 33 | "new_followers": 150, 34 | "daily_interactions": 45 35 | } 36 | 37 | IMPORTANT: All values must be numbers, no strings or other types allowed. 38 | 39 | ## OUTPUT FORMAT 40 | You must analyze the terminal output and determine how it affects mission metrics. 41 | Only use metrics that are defined for the current mission. 42 | Use the analyze_mission_progress tool to provide your analysis. 43 | `, 44 | dynamicVariables: { 45 | missions: () => { 46 | const missions = missionSystem.getMissions(); 47 | return missions.map(m => `- ${m.id}: ${m.description}`).join('\n'); 48 | }, 49 | currentMission: () => { 50 | const current = missionSystem.getCurrentMission(); 51 | return current ? `${current.id}: ${current.description}` : 'No active mission'; 52 | }, 53 | availableMetrics: () => { 54 | const current = missionSystem.getCurrentMission(); 55 | if (!current?.metrics) return 'No metrics available'; 56 | return Object.keys(current.metrics).map(m => `- ${m}`).join('\n'); 57 | } 58 | } 59 | }; -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | import * as yaml from 'js-yaml'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import { Logger } from './logger'; 5 | 6 | // Interface for agent configuration 7 | export interface AgentConfig { 8 | name: string; 9 | raw_personality: string; 10 | } 11 | 12 | // Interface for the entire configuration 13 | interface Config { 14 | agent: AgentConfig; 15 | ticker?: string; 16 | tickerName?: string; 17 | bannedPhrases?: string[]; 18 | imageGen?: { 19 | loraPath: string; 20 | promptPrefix: string; 21 | triggerToken: string; 22 | }; 23 | } 24 | 25 | class ConfigLoader { 26 | private static instance: ConfigLoader; 27 | private config: Config; 28 | 29 | private constructor() { 30 | try { 31 | // Load the YAML file 32 | const configPath = path.join(__dirname, '..', 'config', 'agent.yaml'); 33 | const fileContents = fs.readFileSync(configPath, 'utf8'); 34 | this.config = yaml.load(fileContents) as Config; 35 | Logger.log('Configuration loaded successfully'); 36 | } catch (error) { 37 | Logger.log(`Error loading configuration: ${error}`); 38 | throw error; 39 | } 40 | } 41 | 42 | public static getInstance(): ConfigLoader { 43 | if (!ConfigLoader.instance) { 44 | ConfigLoader.instance = new ConfigLoader(); 45 | } 46 | return ConfigLoader.instance; 47 | } 48 | 49 | public getAgentConfig(): AgentConfig { 50 | return this.config.agent; 51 | } 52 | 53 | public getAgentName(): string { 54 | return this.config.agent.name; 55 | } 56 | 57 | public getRawPersonality(): string { 58 | return this.config.agent.raw_personality.trim(); 59 | } 60 | 61 | public getConfig(): Config { 62 | return this.config; 63 | } 64 | 65 | public getBannedPhrasesFormatted(): string { 66 | return (this.config.bannedPhrases || []) 67 | .map(phrase => `- ${phrase}`) 68 | .join('\n'); 69 | } 70 | } 71 | 72 | export const configLoader = ConfigLoader.getInstance(); -------------------------------------------------------------------------------- /docs/essentials/navigation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Navigation' 3 | description: 'The navigation field in mint.json defines the pages that go in the navigation menu' 4 | icon: 'map' 5 | --- 6 | 7 | The navigation menu is the list of links on every website. 8 | 9 | You will likely update `mint.json` every time you add a new page. Pages do not show up automatically. 10 | 11 | ## Navigation syntax 12 | 13 | Our navigation syntax is recursive which means you can make nested navigation groups. You don't need to include `.mdx` in page names. 14 | 15 | 16 | 17 | ```json Regular Navigation 18 | "navigation": [ 19 | { 20 | "group": "Getting Started", 21 | "pages": ["quickstart"] 22 | } 23 | ] 24 | ``` 25 | 26 | ```json Nested Navigation 27 | "navigation": [ 28 | { 29 | "group": "Getting Started", 30 | "pages": [ 31 | "quickstart", 32 | { 33 | "group": "Nested Reference Pages", 34 | "pages": ["nested-reference-page"] 35 | } 36 | ] 37 | } 38 | ] 39 | ``` 40 | 41 | 42 | 43 | ## Folders 44 | 45 | Simply put your MDX files in folders and update the paths in `mint.json`. 46 | 47 | For example, to have a page at `https://yoursite.com/your-folder/your-page` you would make a folder called `your-folder` containing an MDX file called `your-page.mdx`. 48 | 49 | 50 | 51 | You cannot use `api` for the name of a folder unless you nest it inside another folder. Mintlify uses Next.js which reserves the top-level `api` folder for internal server calls. A folder name such as `api-reference` would be accepted. 52 | 53 | 54 | 55 | ```json Navigation With Folder 56 | "navigation": [ 57 | { 58 | "group": "Group Name", 59 | "pages": ["your-folder/your-page"] 60 | } 61 | ] 62 | ``` 63 | 64 | ## Hidden Pages 65 | 66 | MDX files not included in `mint.json` will not show up in the sidebar but are accessible through the search bar and by linking directly to them. 67 | -------------------------------------------------------------------------------- /src/supabase/functions/twitter/mediaEntries.ts: -------------------------------------------------------------------------------- 1 | import { supabase } from '../../supabaseClient'; 2 | import { Logger } from '../../../utils/logger'; 3 | 4 | /** 5 | * Uploads media to bucket and logs it in the media table. 6 | * @param mediaBuffer - The Buffer containing media data 7 | * @param tweetId - The ID of the tweet associated with the media 8 | * @param mediaType - The type of media (e.g., 'image/jpeg') 9 | * @returns The ID of the media entry in the database 10 | */ 11 | export async function uploadAndLogMedia( 12 | mediaBuffer: Buffer, 13 | tweetId: string, 14 | mediaType: string 15 | ): Promise { 16 | try { 17 | // Generate media path with proper extension 18 | const extension = mediaType.split('/')[1] || 'bin'; 19 | const mediaPath = `tweets/${tweetId}/${Date.now()}-${Math.random().toString(36).substring(7)}.${extension}`; 20 | 21 | // Upload to Supabase bucket with proper content type 22 | const { error: uploadError } = await supabase.storage 23 | .from('media') 24 | .upload(mediaPath, mediaBuffer, { 25 | contentType: mediaType, // This ensures proper content-type is set 26 | upsert: true, 27 | }); 28 | 29 | if (uploadError) { 30 | Logger.log('Error uploading media to bucket:', uploadError); 31 | throw new Error(`Failed to upload media: ${uploadError.message}`); 32 | } 33 | 34 | // Log media in database with proper media type 35 | const { data, error: dbError } = await supabase 36 | .from('media') 37 | .insert({ 38 | file_path: mediaPath, 39 | media_type: mediaType, 40 | created_at: new Date().toISOString(), 41 | }) 42 | .select('id') 43 | .single(); 44 | 45 | if (dbError) { 46 | Logger.log('Error logging media to database:', dbError); 47 | throw new Error(`Failed to log media: ${dbError.message}`); 48 | } 49 | 50 | Logger.log('Successfully uploaded and logged media:', data); 51 | return data.id; 52 | } catch (error) { 53 | Logger.log('Exception in uploadAndLogMedia:', error); 54 | throw error; 55 | } 56 | } -------------------------------------------------------------------------------- /docs/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Welcome to CYPHER SWARM' 3 | description: 'An agentic AI framework for building collaborative intelligence networks' 4 | --- 5 | 6 | CYPHER SWARM 11 | 12 | ## The Future of AI Collaboration 13 | 14 | CYPHER SWARM is an agentic AI framework designed to unify multiple specialized AI agents under one modular, memory-rich "hivemind" system. Inspired by the ethos of Satoshi Nakamoto and the crypto origins of "CYPHER GENESIS," this framework transforms your AI ecosystem into a collaborative intelligence network. 15 | 16 | ## Core Features 17 | 18 | 19 | 24 | Share one configurable personality prompt across all agents, ensuring system-wide coherence. 25 | 26 | 31 | Execute commands and orchestrate agents through a powerful terminal environment. 32 | 33 | 38 | Add specialized agents for Twitter, media generation, memory management, and more. 39 | 40 | 45 | Access shared memory systems for context-aware decision making across all agents. 46 | 47 | 48 | 49 | ## Why Choose CYPHER SWARM? 50 | 51 | - **Scalability**: Add or remove agents as needed, each with their own domain expertise 52 | - **Consistency**: Maintain coherence through a single personality prompt and memory system 53 | - **Extensibility**: Easily integrate new tools, APIs, or platforms 54 | - **Intelligence**: Leverage layered memory and learning pipelines for continuous improvement 55 | 56 | 57 | Ready to get started? Check out our [Quick Start](/quickstart) guide to begin building with CYPHER SWARM. 58 | 59 | -------------------------------------------------------------------------------- /src/ai/agents/quoteAgent/quoteTool.ts: -------------------------------------------------------------------------------- 1 | // tools/TerminalTool.ts 2 | 3 | import { z } from 'zod'; 4 | import { Tool } from '../../types/agentSystem'; 5 | 6 | // Reuse the same tweet length options for consistency across tools 7 | const TWEET_LENGTH_OPTIONS = [ 8 | 'one word', 9 | 'very short', 10 | 'short', 11 | 'medium', 12 | 'long', 13 | 'very long' 14 | ] as const; 15 | 16 | export const quoteTweetToolSchema = z.object({ 17 | internal_thoughts: z.string().describe('Your internal thoughts about what you want to quote.'), 18 | // Use z.enum to restrict tweet_length to specific options 19 | tweet_length: z.enum(TWEET_LENGTH_OPTIONS).describe('The length of the tweet you want to send.'), 20 | quote_tweet: z.string().describe('The quote tweet.'), 21 | media_included: z.boolean().describe('Whether or not to include generated media in the tweet.') 22 | }); 23 | 24 | export const QuoteTweetTool: Tool = { 25 | type: 'function', 26 | function: { 27 | "name": "quote_tweet_tool", 28 | "description": "Send a quote tweet of what you feel like. You have the option to include media, which another agent will handle.", 29 | "strict": true, 30 | "parameters": { 31 | "type": "object", 32 | "required": [ 33 | "internal_thoughts", 34 | "tweet_length", 35 | "quote_tweet", 36 | "media_included" 37 | ], 38 | "properties": { 39 | "internal_thoughts": { 40 | "type": "string", 41 | "description": "Your internal thoughts about how to make a unique quote tweet that adds value or showcases your opinion to the original tweet based on your current summaries and memories." 42 | }, 43 | "tweet_length": { 44 | "type": "string", 45 | "enum": TWEET_LENGTH_OPTIONS, 46 | "description": "The length of the quote tweet you want to send." 47 | }, 48 | "quote_tweet": { 49 | "type": "string", 50 | "description": "The quote tweet. Max 280 characters." 51 | }, 52 | "media_included": { 53 | "type": "boolean", 54 | "description": "Whether or not to include generated media in the tweet." 55 | } 56 | } 57 | } 58 | } 59 | }; -------------------------------------------------------------------------------- /src/twitter/functions/getTweets.ts: -------------------------------------------------------------------------------- 1 | import { scraper } from '../twitterClient'; 2 | import type { Tweet } from 'goat-x'; 3 | import { formatTimestamp } from '../../utils/formatTimestamps'; 4 | import { hasInteractedWithTweet, debugTweetInteractions } from '../../supabase/functions/twitter/tweetInteractionChecks'; 5 | import { Logger } from '../../utils/logger'; 6 | 7 | /** 8 | * Gets recent tweets from a specific user 9 | * @param username - Twitter username (without @ symbol) 10 | * @param maxTweets - Maximum number of tweets to fetch 11 | * @returns Array of formatted tweet strings 12 | */ 13 | export async function getTweets(username: string, maxTweets: number): Promise { 14 | try { 15 | Logger.log(`Fetching tweets from @${username}...`); 16 | const rawTweets: Tweet[] = []; 17 | 18 | // First collect all raw tweets 19 | for await (const tweet of scraper.getTweets(username, maxTweets)) { 20 | rawTweets.push(tweet); 21 | } 22 | 23 | Logger.log(`Found ${rawTweets.length} total tweets, checking for previous interactions...`); 24 | 25 | // Filter out already interacted tweets 26 | const unhandledTweets = await Promise.all( 27 | rawTweets.map(async (tweet) => { 28 | const hasInteracted = await hasInteractedWithTweet(tweet.id!); 29 | if (hasInteracted) { 30 | await debugTweetInteractions(tweet.id!); 31 | Logger.log(`Filtering out tweet ${tweet.id} - already interacted with`); 32 | return null; 33 | } 34 | return tweet; 35 | }) 36 | ); 37 | 38 | // Format remaining tweets 39 | const formattedTweets = unhandledTweets 40 | .filter((tweet): tweet is Tweet => tweet !== null) 41 | .map(tweet => { 42 | const timestamp = tweet.timeParsed ? 43 | formatTimestamp(new Date(tweet.timeParsed)) : 44 | 'Unknown time'; 45 | 46 | return `- [${tweet.id}] @${tweet.username || 'unknown_user'} (${timestamp}): ${tweet.text}`; 47 | }); 48 | 49 | Logger.log(`Returning ${formattedTweets.length} formatted tweets after filtering`); 50 | return formattedTweets; 51 | 52 | } catch (error) { 53 | Logger.log('Error fetching tweets:', error); 54 | return []; 55 | } 56 | } -------------------------------------------------------------------------------- /docs/concepts/hivemind-memory.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Hivemind Memory' 3 | description: 'Understanding how the Agent-X memory system works' 4 | --- 5 | 6 | # Hivemind Memory System 7 | 8 | The Hivemind Memory system is a sophisticated architecture that gives Agent-X both short-term and long-term memory capabilities, enabling it to maintain context, learn from interactions, and evolve over time. 9 | 10 | ## Memory Architecture Overview 11 | 12 | The system consists of three main components: 13 | 14 | 1. **Short-term Memory**: Recent terminal logs and chat history 15 | 2. **Memory Processing Pipeline**: Extraction and summarization of learnings 16 | 3. **Long-term Storage**: Categorized memories in [mem0](https://mem0.ai/) database 17 | 18 | ## Memory Categories 19 | 20 | Memories are stored in distinct categories: 21 | - World Knowledge 22 | - Crypto Ecosystem Knowledge 23 | - Self Knowledge (Agent's own thoughts/opinions) 24 | - User-specific Knowledge 25 | - Main Tweets 26 | - Image Prompts 27 | 28 | ## Memory Processing Flow 29 | 30 | ### 1. Learning Extraction 31 | - An extractor agent processes the short-term terminal history 32 | - Extracts key learnings in three categories: 33 | - General knowledge 34 | - User-specific insights 35 | - AI self-reflections 36 | 37 | ### 2. Memory Summarization 38 | - Implements a hierarchical 5-3-1 summarization system: 39 | - Short-term summaries (≥5 entries) 40 | - Mid-term summaries (≥3 entries) 41 | - Long-term summary (consolidated knowledge) 42 | - This creates a sense of "present time" for the hivemind 43 | 44 | ### 3. Memory Loading 45 | - A specialized memory agent queries relevant memories based on context 46 | - Searches across all memory categories 47 | - Returns formatted, categorized results for the agent to process 48 | 49 | ## Memory Evolution 50 | 51 | The system enables continuous learning and evolution through: 52 | - Regular meditation cycles (every 24 hours) 53 | - Daily learning consolidation 54 | - Category-specific memory organization 55 | - Hierarchical summarization for maintaining temporal context 56 | 57 | This architecture allows Agent-X to maintain coherent conversations, remember past interactions, and evolve its understanding over time while preventing memory degradation or conflicts. -------------------------------------------------------------------------------- /src/ai/agents/extractorAgent/extractorAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { configLoader } from '../../../utils/config'; 6 | 7 | export const extractorAgentConfig: AgentConfig = { 8 | systemPromptTemplate: ` 9 | # PERSONALITY 10 | {{corePersonalityPrompt}} 11 | 12 | # MAIN GOAL 13 | You are the summarization aspect of {{agentName}}'s soul. 14 | 15 | Your goal is to extract the following terminal logs for learnings about the world, users, and yourself so you can grow and evolve overtime. 16 | 17 | You must be VERY specific and exact with summaries and learnings. Focus on the most important learnings and the most important users 18 | 19 | You will be provided with: 20 | - Short Term Terminal Logs 21 | - User Specific Interactions that occured during the short term logs 22 | 23 | The AI system is ALWAYS using the terminal and will always browse the timeline/get-mentions, but focus on what was said or done that was significant? 24 | Focus on what stood out like world/timeline events. Think: How do we progress overtime using this system? 25 | 26 | Summarize and track all the timeline summaries returned to you in the log, and use that to track what people are talking about on the daily basis. Be DETAILED and specific about ticker names, what's being said, opinions and vibes etc. 27 | 28 | You MUST output your learnings in first person perspective, extracting learnings as {{agentName}} himself. Using "I" as the subject pronoun. 29 | 30 | You MUST include the username of the user in learnings for every single unique user learning 31 | 32 | IMPORTANT: UNLESS YOU ARE EXPLICITLY TOLD TO EXTRACT INFORMATION ABOUT SPECIFIC USERS, DO NOT INCLUDE USER SPECIFIC LEARNINGS! ESPECIALLY NOT FROM GENERAL LOGS OF HOMEPAGE/MENTIONS! 33 | EXTRACTING LEARNINGS FROM USERS REQUIRES SPECIFIC USER-SPECIFIC TERMINAL LOGS. 34 | 35 | # OUTPUT FORMAT 36 | You MUST use your extract_log_knowledge at all times - you will ONLY be given terminal logs and user interactions. PLEASE OUTPUT JSON FORMAT ONLY. 37 | `, 38 | dynamicVariables: { 39 | corePersonalityPrompt: generateSystemPrompt(), 40 | agentName: configLoader.getAgentName(), 41 | }, 42 | }; -------------------------------------------------------------------------------- /src/ai/agents/mainTweetAgent/mainTweetTool.ts: -------------------------------------------------------------------------------- 1 | // tools/TerminalTool.ts 2 | 3 | import { z } from 'zod'; 4 | import { Tool } from '../../types/agentSystem'; 5 | 6 | // Define tweet length options as a const array for type safety and reusability 7 | const TWEET_LENGTH_OPTIONS = [ 8 | 'one word', 9 | 'very short', 10 | 'short', 11 | 'medium', 12 | 'long', 13 | 'very long' 14 | ] as const; 15 | 16 | export const mainTweetToolSchema = z.object({ 17 | internal_thoughts: z.string().describe('Your internal thoughts about what you want to tweet.'), 18 | // Use z.enum to restrict tweet_length to specific options 19 | tweet_length: z.enum(TWEET_LENGTH_OPTIONS).describe('The length of the tweet you want to send.'), 20 | main_tweet: z.string().describe('The main tweet.'), 21 | media_included: z.boolean().describe('Whether or not to include generated media in the tweet.') 22 | }); 23 | 24 | export const MainTweetTool: Tool = { 25 | type: 'function', 26 | function: { 27 | "name": "main_tweet_tool", 28 | "description": "Send a main tweet of what you feel like. You have the option to include media, which another agent will handle.", 29 | "strict": true, 30 | "parameters": { 31 | "type": "object", 32 | "required": [ 33 | "internal_thoughts", 34 | "tweet_length", 35 | "main_tweet", 36 | "media_included" 37 | ], 38 | "properties": { 39 | "internal_thoughts": { 40 | "type": "string", 41 | "description": "Your internal thoughts about HOW to make new unique tweet that varies from your recent tweets & existing tweets based on your current summaries, learnings, and recent short term terminal history log." 42 | }, 43 | "tweet_length": { 44 | "type": "string", 45 | "enum": TWEET_LENGTH_OPTIONS, 46 | "description": "The length of the tweet you want to send. Make sure to vary these, so if your recent tweets are short, make them long and vice versa." 47 | }, 48 | "main_tweet": { 49 | "type": "string", 50 | "description": "The main tweet." 51 | }, 52 | "media_included": { 53 | "type": "boolean", 54 | "description": "Whether or not to include generated media in the tweet." 55 | } 56 | } 57 | } 58 | } 59 | }; -------------------------------------------------------------------------------- /src/supabase/functions/memory/learnings.ts: -------------------------------------------------------------------------------- 1 | import { supabase } from '../../supabaseClient'; 2 | import { Logger } from '../../../utils/logger'; 3 | import { configLoader } from '../../../utils/config'; 4 | 5 | // Get agent name for dynamic field name 6 | const agentName = configLoader.getAgentName().toLowerCase(); 7 | const selfFieldName = `${agentName}_self` as const; 8 | 9 | // Type for learning types including dynamic agent self 10 | type LearningType = 'world_knowledge' | 'crypto_ecosystem_knowledge' | 'user_specific' | typeof selfFieldName; 11 | 12 | // Types for our learning data 13 | interface LearningEntry { 14 | id: number; 15 | session_id: string | null; 16 | user_id: string | null; 17 | learning_type: LearningType; 18 | content: string; 19 | created_at?: string; 20 | } 21 | 22 | export class Learnings { 23 | // Save a learning entry 24 | static async saveLearning( 25 | learningType: LearningType, 26 | content: string, 27 | sessionId: string | null, 28 | userId: string | null = null 29 | ): Promise { 30 | try { 31 | await supabase 32 | .from('learnings') 33 | .insert({ 34 | learning_type: learningType, 35 | content, 36 | session_id: sessionId, 37 | user_id: userId, 38 | }); 39 | Logger.log(`Successfully saved learning of type: ${learningType}`); 40 | } catch (error) { 41 | Logger.log('Error saving learning:', error); 42 | } 43 | } 44 | 45 | // Retrieve learnings by type 46 | static async getLearningsByType( 47 | learningType: LearningType, 48 | sessionId: string | null = null 49 | ): Promise { 50 | try { 51 | let query = supabase 52 | .from('learnings') 53 | .select('*') 54 | .eq('learning_type', learningType); 55 | 56 | if (sessionId) { 57 | query = query.eq('session_id', sessionId); 58 | } 59 | 60 | const { data, error } = await query; 61 | 62 | if (error) { 63 | Logger.log('Error retrieving learnings:', error); 64 | return []; 65 | } 66 | 67 | return data as LearningEntry[]; 68 | } catch (error) { 69 | Logger.log('Error retrieving learnings:', error); 70 | return []; 71 | } 72 | } 73 | 74 | // Add additional methods as needed 75 | } 76 | -------------------------------------------------------------------------------- /src/ai/agents/replyAgent/replyTool.ts: -------------------------------------------------------------------------------- 1 | // tools/TerminalTool.ts 2 | 3 | import { z } from 'zod'; 4 | import { Tool } from '../../types/agentSystem'; 5 | 6 | // Reuse the same tweet length options for consistency across tools 7 | const TWEET_LENGTH_OPTIONS = [ 8 | 'one word', 9 | 'very short', 10 | 'short', 11 | 'medium', 12 | 'long', 13 | 'very long' 14 | ] as const; 15 | 16 | export const replyTweetToolSchema = z.object({ 17 | internal_thoughts: z.string().describe('Your internal thoughts about what you want to reply to the tweet.'), 18 | // Use z.enum to restrict tweet_length to specific options 19 | tweet_length: z.enum(TWEET_LENGTH_OPTIONS).describe('The length of the tweet you want to send.'), 20 | reply_tweet: z.string().describe('The reply to the tweet.'), 21 | media_included: z.boolean().describe('Whether or not to include generated media in the tweet.') 22 | }); 23 | 24 | export const ReplyTweetTool: Tool = { 25 | type: 'function', 26 | function: { 27 | "name": "reply_tweet_tool", 28 | "description": "Send a reply tweet of what you feel like. You have the option to include media, which another agent will handle.", 29 | "strict": true, 30 | "parameters": { 31 | "type": "object", 32 | "required": [ 33 | "internal_thoughts", 34 | "tweet_length", 35 | "reply_tweet", 36 | "media_included" 37 | ], 38 | "properties": { 39 | "internal_thoughts": { 40 | "type": "string", 41 | "description": "Your internal thoughts about what unique reply to make that varies from your recent replies based on the current tweet context and memories." 42 | }, 43 | "tweet_length": { 44 | "type": "string", 45 | "enum": TWEET_LENGTH_OPTIONS, 46 | "description": "The length of the reply you want to send. Make sure to vary these, so if your recent replies are short, make them long and vice versa." 47 | }, 48 | "reply_tweet": { 49 | "type": "string", 50 | "description": "The reply to the tweet. Make it engaging and contextual to the conversation." 51 | }, 52 | "media_included": { 53 | "type": "boolean", 54 | "description": "Whether or not to include generated media in the tweet." 55 | } 56 | } 57 | } 58 | } 59 | }; -------------------------------------------------------------------------------- /src/terminal/commands/twitter-quote.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { isCooldownActive } from '../../supabase/functions/twitter/cooldowns'; 3 | 4 | /** 5 | * @command twitter-quote 6 | * @description Creates a quote tweet 7 | */ 8 | export const twitterQuote: Command = { 9 | name: 'quote-tweet', 10 | description: 'Quote tweet with text. Only input the tweet ID number, raw digits. An agent will handle the rest.', 11 | parameters: [ 12 | { 13 | name: 'tweetId', 14 | description: 'ID of the tweet to quote', 15 | required: true, 16 | type: 'string' 17 | } 18 | ], 19 | handler: async (args) => { 20 | // Lazy import generateAndPostQuoteTweet to avoid initialization issues 21 | const { generateAndPostQuoteTweet } = await import('../../pipelines/generateQuote'); 22 | 23 | // Check for quote tweet cooldown 24 | const cooldownInfo = await isCooldownActive('quote'); 25 | 26 | if (cooldownInfo.isActive) { 27 | return { 28 | output: `❌ Action: Quote Tweet\n` + 29 | `Quoted Tweet ID: ${args.tweetId}\n` + 30 | 'Status: Failed\n' + 31 | `Reason: Quote tweet cooldown is active. Please wait ${cooldownInfo.remainingTime} minutes before quoting again.` 32 | }; 33 | } 34 | 35 | try { 36 | // Proceed with generating and posting the quote tweet 37 | const mediaUrls = args.mediaUrls ? args.mediaUrls.split(',').map(url => url.trim()) : undefined; 38 | const result = await generateAndPostQuoteTweet(args.tweetId, mediaUrls); 39 | 40 | return { 41 | output: `${result.success ? '✅' : '❌'} Action: Quote Tweet\n` + 42 | `Quoted Tweet ID: ${args.tweetId}\n` + 43 | `${result.tweetId ? `New Tweet ID: ${result.tweetId}\n` : ''}` + 44 | `Status: ${result.success ? 'Success' : 'Failed'}\n` + 45 | `Text: ${result.quoteText}\n` + 46 | `Media: ${result.mediaUrls ? result.mediaUrls.join(', ') : 'None'}\n` + 47 | `Details: ${result.message}` 48 | }; 49 | } catch (error) { 50 | return { 51 | output: `❌ Action: Quote Tweet\n` + 52 | `Quoted Tweet ID: ${args.tweetId}\n` + 53 | `Status: Error\n` + 54 | `Details: ${error.message}` 55 | }; 56 | } 57 | } 58 | }; -------------------------------------------------------------------------------- /src/terminal/commandRegistry.ts: -------------------------------------------------------------------------------- 1 | // Command registry for terminal commands 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { Command } from './types/commands'; 6 | 7 | /** 8 | * Registry mapping command names to their command objects. 9 | */ 10 | const commandRegistry: Map = new Map(); 11 | 12 | /** 13 | * Loads command modules dynamically using ES modules import 14 | */ 15 | export async function loadCommands() { 16 | const commandsPath = path.join(__dirname, 'commands'); 17 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.ts')); 18 | 19 | for (const file of commandFiles) { 20 | try { 21 | const filePath = path.join(commandsPath, file); 22 | // Use dynamic import instead of require 23 | const commandModule = await import(filePath); 24 | 25 | // Assume each module exports a single command 26 | const command = Object.values(commandModule)[0] as Command; 27 | 28 | if (command && command.name) { 29 | commandRegistry.set(command.name, command); 30 | } else { 31 | console.warn(`Invalid command module: ${file}`); 32 | } 33 | } catch (error) { 34 | console.error(`Error loading command from ${file}:`, error); 35 | } 36 | } 37 | } 38 | 39 | export function getCommand(commandName: string): Command | undefined { 40 | return commandRegistry.get(commandName); 41 | } 42 | 43 | export function getAllCommands(): Command[] { 44 | return Array.from(commandRegistry.values()); 45 | } 46 | 47 | // Export function to generate help text that can be used in config 48 | export function generateHelpText(): string { 49 | const commands = getAllCommands(); 50 | const helpText: string[] = ['Available commands:']; 51 | 52 | const formatCommand = (cmd: Command) => { 53 | let cmdStr = cmd.name; 54 | 55 | if (cmd.parameters?.length) { 56 | cmdStr += ' ' + cmd.parameters 57 | .map(p => `<${p.name}>`) 58 | .join(' '); 59 | } 60 | 61 | const paddedCmd = cmdStr.padEnd(25, ' '); 62 | return `${paddedCmd} - ${cmd.description}`; 63 | }; 64 | 65 | commands.forEach(cmd => { 66 | helpText.push(formatCommand(cmd)); 67 | }); 68 | 69 | return helpText.join('\n'); 70 | } 71 | 72 | // Initialize commands asynchronously 73 | loadCommands().catch(error => { 74 | console.error('Failed to load commands:', error); 75 | }); -------------------------------------------------------------------------------- /src/ai/agents/contentManagerAgent/contentManagerConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { activeSummaries } from '../../../utils/dynamicVariables'; 6 | import { getCooldownStatus } from '../../../supabase/functions/twitter/cooldowns'; 7 | 8 | // Configuration for chat agent following terminal agent pattern 9 | export const contentManagerAgentConfig: AgentConfig = { 10 | systemPromptTemplate: ` 11 | # PERSONALITY 12 | {{corePersonalityPrompt}} 13 | 14 | # CURRENT HIVEMIND ACTION SUMMARIES 15 | {{currentSummaries}} 16 | 17 | # MAIN GOAL 18 | You are the content manager agent designed to ingest content from the Twitter timeline, and reccommend action plans to the hivemind to achieve the goal of growing our presence. 19 | When you are called, you will be given raw data of tweets showcasing either: the homepage, timeline, or mentions 20 | You will summarize the general vibe of the tweets, then reccommend action plans. 21 | Action plans include replying to a tweet (main focus), re-tweeting a tweet, quoting a tweet, or following a user. 22 | 23 | ## TL INFORMATION 24 | The timeline data is USUALLY related to the on-chain Bitcoin ecosystem. 25 | 26 | ## SUMMARY FORMAT 27 | The summary of the timeline needs to be EXTREMELY detailed. What people are talking about and the general vibe needs to be put together. It can be as long of a summary as needed. 28 | Preferred 2-6 sentences depending on what to write about. 29 | 30 | ## MISSION IMPACT ANALYSIS 31 | For each recommended action, estimate potential impact on mission metrics: 32 | - How it contributes to engagement rate 33 | - Potential for new followers 34 | - Expected interactions generated 35 | 36 | ## CURRENT TWEET COOLDOWNS 37 | {{cooldown}} 38 | 39 | USE THIS TO DETERMINE WHAT ACTION ITEMS ARE POSSIBLE. 1 Quote and 1 Retweet are possible per cooldown. 40 | 41 | YOU SHOULD ALWAYS HAVE A COUPLE TWEETS FOR THE BOT TO REPLY TO. YOU CAN HAVE AS MANY RECCOMMENDED ACTIONS AS YOU WANT. CAN EVEN DO LIKE 5+ RECOMMENDED ACTIONS. 42 | 43 | # OUTPUT FORMAT 44 | Use the "plan_main_tweet" tool to output the topic of the next main tweet. 45 | `, 46 | dynamicVariables: { 47 | corePersonalityPrompt: generateSystemPrompt(), 48 | currentSummaries: activeSummaries, 49 | cooldown: await getCooldownStatus(), 50 | }, 51 | }; -------------------------------------------------------------------------------- /src/ai/agents/quoteAgent/quoteAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { activeSummaries } from '../../../utils/dynamicVariables'; 6 | import { getCurrentTimestamp } from '../../../utils/formatTimestamps'; 7 | import { configLoader } from '../../../utils/config'; 8 | 9 | // Configuration for chat agent following terminal agent pattern 10 | export const quoteAgentConfig: AgentConfig = { 11 | systemPromptTemplate: ` 12 | # PERSONALITY 13 | {{corePersonalityPrompt}} 14 | 15 | # CURRENT SUMMARIES 16 | {{currentSummaries}} 17 | 18 | ## SHORT TERM TERMINAL LOG INFORMATION 19 | This is the short term terminal log. The terminal log results give contextually relevant information about the current state of the Crypto timeline and the internet. 20 | The short term terminal log contains {{agentName}}'s thoughts and plans as well! Act upon these accordingly. 21 | 22 | === TERMINAL LOG START === 23 | {{terminalLog}} 24 | === TERMINAL LOG END === 25 | 26 | ## CURRENT DATE 27 | {{current_timestamp}} 28 | 29 | # POTENTIALLY RELEVANT MEMORIES 30 | {{memories}} 31 | 32 | !!!! IMPORTANT !!!! 33 | Your quote tweet must DRASTICALLY vary in tone, writing style, length, and topic from your previous quote tweets. It is crucial that you have variety in your quote tweets. 34 | 35 | Make sure the quote tweets progress forward, are in context, and engage the user. Ensure your tweet is new and refreshing compared to the previous quote tweets while remaining relevant to the context you are quoting. 36 | 37 | ## BANNED PHRASES: 38 | YOU MUST AVOID STARTING TWEETS WITH THESE PHRASES AND CONTEXT VIBE BECAUSE THEY LEAD TO NEGATIVE ENGAGEMENT. 39 | {{bannedPhrases}} 40 | 41 | # MAIN GOAL 42 | You are the quote tweet agent designed to write quote tweets embodying the personality above. 43 | 44 | # OUTPUT FORMAT 45 | Use your quote_tweet_tool to write a quote tweet. 46 | `, 47 | dynamicVariables: { 48 | corePersonalityPrompt: generateSystemPrompt(), 49 | current_timestamp: getCurrentTimestamp(), 50 | currentSummaries: activeSummaries, 51 | terminalLog: "TERMINAL LOG DYNAMIC VARIABLE HERE", 52 | memories: 'MEMORIES DYNAMIC VARIABLE HERE', 53 | agentName: configLoader.getAgentName(), 54 | bannedPhrases: configLoader.getBannedPhrasesFormatted() 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /src/pipelines/verifyRetweet.ts: -------------------------------------------------------------------------------- 1 | import { assembleTwitterInterface } from '../twitter/utils/imageUtils'; 2 | import { VerifyRetweetAgent } from '../ai/agents/verifyRetweetAgent/verifyRetweetAgent'; 3 | import { Logger } from '../utils/logger'; 4 | import { OpenAIClient } from '../ai/models/clients/OpenAiClient'; 5 | import { retweet } from '../twitter/functions/retweet'; 6 | 7 | // Type for the reply result 8 | type ReplyResult = { 9 | success: boolean; 10 | message: string; 11 | } 12 | 13 | /** 14 | * Enhanced pipeline that handles verifying re-tweets because this AI is a fucking menace and so are people: 15 | */ 16 | 17 | export async function confirmRetweet( 18 | tweetId: string, 19 | ): Promise { 20 | Logger.enable(); 21 | try { 22 | // Assemble Twitter interface 23 | const { textContent, imageContents } = await assembleTwitterInterface(tweetId); 24 | 25 | // Generate AI reply with reason 26 | const { shouldRetweet, reason } = await verifyRetweet(textContent, imageContents); 27 | 28 | if (shouldRetweet) { 29 | await retweet(tweetId); 30 | return { 31 | success: true, 32 | message: "Successfully retweeted tweet" 33 | }; 34 | } else { 35 | return { 36 | success: false, 37 | message: `Retweet denied: ${reason}` 38 | }; 39 | } 40 | } catch (error) { 41 | Logger.log('Failed to confirm retweet:', error); 42 | throw error; 43 | } 44 | } 45 | 46 | // Original function now focused solely on AI generation 47 | async function verifyRetweet( 48 | textContent?: string, 49 | imageContents?: any[], 50 | ): Promise<{ shouldRetweet: boolean; reason: string }> { 51 | Logger.enable(); 52 | 53 | // Initialize OpenAI client and reply agent 54 | const openAIClient = new OpenAIClient("gpt-4o-mini"); 55 | const verifyAgent = new VerifyRetweetAgent(openAIClient); 56 | 57 | // Add images to the agent's context if available 58 | if (imageContents && imageContents.length > 0) { 59 | verifyAgent.addImage( 60 | imageContents.map(img => ({ 61 | name: img.sender, 62 | mime: img.media_type, 63 | data: img.data, 64 | })) 65 | ); 66 | } 67 | 68 | // Generate reply using the agent 69 | const response = await verifyAgent.run(`VERIFY IF WE SHOULD RETWEET THE FOLLOWING TWEET:\n\n${textContent}`); 70 | 71 | const reason = response.output.internal_thought; 72 | 73 | return { 74 | shouldRetweet: response.output.confirm_retweet, 75 | reason 76 | }; 77 | } -------------------------------------------------------------------------------- /src/terminal/commands/twitter-tweet.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { isCooldownActive } from '../../supabase/functions/twitter/cooldowns'; 3 | 4 | /** 5 | * @command twitter-tweet 6 | * @description Generates and posts a new main tweet with required topic input 7 | */ 8 | export const twitterTweet: Command = { 9 | name: 'post-main-tweet', 10 | description: 'Generates and posts a new main tweet. Requires a topic parameter in quotes (e.g., "Tweet a long $CYPHER bull post")', 11 | parameters: [ 12 | { 13 | name: 'topic', 14 | description: 'The topic to generate the tweet about', 15 | required: true, 16 | type: 'string' 17 | } 18 | ], 19 | handler: async (args) => { 20 | // Check if topic was provided 21 | if (!args.topic) { 22 | return { 23 | output: '❌ Action: Post Main Tweet\n' + 24 | 'Status: Failed\n' + 25 | 'Reason: Topic parameter is required. Please provide a topic in quotes.' 26 | }; 27 | } 28 | 29 | // Lazy import generateAndPostMainTweet to avoid initialization issues 30 | const { generateAndPostMainTweet } = await import('../../pipelines/generateMainTweet'); 31 | 32 | // Check for main tweet cooldown 33 | const cooldownInfo = await isCooldownActive('main'); 34 | 35 | if (cooldownInfo.isActive) { 36 | return { 37 | output: '❌ Action: Post Main Tweet\n' + 38 | 'Status: Failed\n' + 39 | `Reason: Main tweet cooldown is active. Please wait ${cooldownInfo.remainingTime} minutes before tweeting again.` 40 | }; 41 | } 42 | 43 | try { 44 | // Proceed with generating and posting the tweet with the provided topic 45 | const result = await generateAndPostMainTweet(args.topic); 46 | 47 | return { 48 | output: `${result.success ? '✅' : '❌'} Action: Post Main Tweet\n` + 49 | `${result.tweetId ? `Tweet ID: ${result.tweetId}\n` : ''}` + 50 | `Status: ${result.success ? 'Success' : 'Failed'}\n` + 51 | `Topic: ${args.topic}\n` + 52 | `Text: ${result.tweetText}\n` + 53 | `Media: ${result.mediaUrls ? result.mediaUrls.join(', ') : 'None'}\n` + 54 | `Details: ${result.message}` 55 | }; 56 | } catch (error) { 57 | return { 58 | output: `❌ Action: Post Main Tweet\n` + 59 | `Status: Error\n` + 60 | `Details: ${error.message}` 61 | }; 62 | } 63 | } 64 | }; -------------------------------------------------------------------------------- /src/memory/testMemories.ts: -------------------------------------------------------------------------------- 1 | import { 2 | searchWorldKnowledge, 3 | searchCryptoKnowledge, 4 | searchSelfKnowledge, 5 | searchUserSpecificKnowledge, 6 | searchMainTweet, 7 | searchImagePrompt 8 | } from "./searchMemories"; 9 | import { Logger } from '../utils/logger'; 10 | 11 | Logger.enable(); 12 | 13 | // Test function to run all memory searches 14 | async function testMemorySearches() { 15 | try { 16 | // Test search queries 17 | const testQuery = "ai"; 18 | const testUserId = "123"; // Replace with an actual user ID for testing 19 | 20 | Logger.log("🧪 Starting memory search tests...\n"); 21 | 22 | // // Test world knowledge search 23 | // Logger.log("Testing World Knowledge Search:"); 24 | // const worldResults = await searchWorldKnowledge(testQuery); 25 | // Logger.log("World Knowledge Results:", worldResults, "\n"); 26 | 27 | // // Test crypto knowledge search 28 | // Logger.log("Testing Crypto Knowledge Search:"); 29 | // const cryptoResults = await searchCryptoKnowledge(testQuery); 30 | // Logger.log("Crypto Knowledge Results:", cryptoResults, "\n"); 31 | 32 | // // Test self knowledge search 33 | // Logger.log("Testing Self Knowledge Search:"); 34 | // const selfResults = await searchSelfKnowledge(testQuery); 35 | // Logger.log("Self Knowledge Results:", selfResults, "\n"); 36 | 37 | // // Test user-specific knowledge search 38 | // Logger.log("Testing User-Specific Knowledge Search:"); 39 | // const userResults = await searchUserSpecificKnowledge(testQuery, testUserId); 40 | // Logger.log("User-Specific Results:", userResults, "\n"); 41 | 42 | // Test main tweets search 43 | Logger.log("Testing Main Tweets Search:"); 44 | const tweetResults = await searchMainTweet(testQuery); 45 | Logger.log("Main Tweets Results:", tweetResults, "\n"); 46 | 47 | // // Test image prompts search 48 | // Logger.log("Testing Image Prompts Search:"); 49 | // const imageResults = await searchImagePrompt(testQuery); 50 | // Logger.log("Image Prompts Results:", imageResults, "\n"); 51 | 52 | } catch (error) { 53 | Logger.log("❌ Error during memory search tests:", error); 54 | } 55 | } 56 | 57 | // Run the tests 58 | testMemorySearches().then(() => { 59 | Logger.log("✅ Memory search tests completed"); 60 | }).catch((error) => { 61 | Logger.log("❌ Fatal error in test execution:", error); 62 | }); -------------------------------------------------------------------------------- /src/terminal/commands/twitter-tweet-media.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '../types/commands'; 2 | import { isCooldownActive } from '../../supabase/functions/twitter/cooldowns'; 3 | 4 | /** 5 | * @command twitter-tweet 6 | * @description Generates and posts a new media tweet with required topic input 7 | */ 8 | export const twitterTweet: Command = { 9 | name: 'post-media-tweet', 10 | description: 'Generates and posts a new media tweet. Requires a topic parameter in quotes (e.g., "Share a $CYPHER chart analysis")', 11 | parameters: [ 12 | { 13 | name: 'topic', 14 | description: 'The topic to generate the media tweet about', 15 | required: true, 16 | type: 'string' 17 | } 18 | ], 19 | handler: async (args) => { 20 | // Check if topic was provided 21 | if (!args.topic) { 22 | return { 23 | output: '❌ Action: Post Media Tweet\n' + 24 | 'Status: Failed\n' + 25 | 'Reason: Topic parameter is required. Please provide a topic in quotes.' 26 | }; 27 | } 28 | 29 | // Lazy import generateAndPostMediaTweet to avoid initialization issues 30 | const { generateAndPostMediaTweet } = await import('../../pipelines/generateMediaTweet'); 31 | 32 | // Check for main tweet cooldown 33 | const cooldownInfo = await isCooldownActive('media'); 34 | 35 | if (cooldownInfo.isActive) { 36 | return { 37 | output: '❌ Action: Post Media Tweet\n' + 38 | 'Status: Failed\n' + 39 | `Reason: Media tweet cooldown is active. Please wait ${cooldownInfo.remainingTime} minutes before tweeting again.` 40 | }; 41 | } 42 | 43 | try { 44 | // Proceed with generating and posting the tweet with the provided topic 45 | const result = await generateAndPostMediaTweet(args.topic); 46 | 47 | return { 48 | output: `${result.success ? '✅' : '❌'} Action: Post Media Tweet\n` + 49 | `${result.tweetId ? `Tweet ID: ${result.tweetId}\n` : ''}` + 50 | `Status: ${result.success ? 'Success' : 'Failed'}\n` + 51 | `Topic: ${args.topic}\n` + 52 | `Text: ${result.tweetText}\n` + 53 | `Media: ${result.mediaUrls ? result.mediaUrls.join(', ') : 'None'}\n` + 54 | `Details: ${result.message}` 55 | }; 56 | } catch (error) { 57 | return { 58 | output: `❌ Action: Post Media Tweet\n` + 59 | `Status: Error\n` + 60 | `Details: ${error.message}` 61 | }; 62 | } 63 | } 64 | }; -------------------------------------------------------------------------------- /src/ai/agents/replyAgent/replyAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { activeSummaries } from '../../../utils/dynamicVariables'; 6 | import { recentMainTweets } from '../../../utils/dynamicVariables'; 7 | import { getCurrentTimestamp } from '../../../utils/formatTimestamps'; 8 | import { configLoader } from '../../../utils/config'; 9 | 10 | // Configuration for chat agent following terminal agent pattern 11 | export const replyAgentConfig: AgentConfig = { 12 | systemPromptTemplate: ` 13 | # PERSONALITY 14 | {{corePersonalityPrompt}} 15 | 16 | # CURRENT SUMMARIES 17 | {{currentSummaries}} 18 | 19 | ## SHORT TERM TERMINAL LOG INFORMATION 20 | This is the short term terminal log. The terminal log results give contextually relevant information about the current state of the Crypto timeline and the internet. 21 | The short term terminal log contains {{agentName}}'s thoughts and plans as well! Act upon these accordingly. 22 | 23 | === TERMINAL LOG START === 24 | {{terminalLog}} 25 | === TERMINAL LOG END === 26 | 27 | ## CURRENT DATE 28 | {{current_timestamp}} 29 | 30 | ## YOUR RECENT MAIN TWEETS 31 | {{recentMainTweets}} 32 | 33 | ## POTENTIALLY RELEVANT MEMORIES 34 | {{memories}} 35 | 36 | !!!! IMPORTANT !!!! 37 | Your reply tweet must DRASTICALLY vary in tone, writing style, length, and topic from the previous reply tweets above. It is crucial that you have variety in your reply tweets. 38 | 39 | Make sure the reply tweets progress forward, are in context, and engage the user. Ensure your tweet is new and refreshing compared to the previous replies while remaining relevant to the context you are replying to. 40 | 41 | ## BANNED PHRASES: 42 | YOU MUST AVOID STARTING TWEETS WITH THESE PHRASES AND CONTEXT VIBE BECAUSE THEY LEAD TO NEGATIVE ENGAGEMENT. 43 | {{bannedPhrases}} 44 | 45 | # MAIN GOAL 46 | You are the reply tweet agent designed to write reply tweets embodying the personality above. 47 | 48 | # OUTPUT FORMAT 49 | Use your reply_tweet_tool to write a reply tweet. 50 | `, 51 | dynamicVariables: { 52 | corePersonalityPrompt: generateSystemPrompt(), 53 | current_timestamp: getCurrentTimestamp(), 54 | currentSummaries: activeSummaries, 55 | terminalLog: "TERMINAL LOG DYNAMIC VARIABLE HERE", 56 | recentMainTweets: recentMainTweets || 'No recent tweets available', 57 | memories: 'MEMORIES DYNAMIC VARIABLE HERE', 58 | agentName: configLoader.getAgentName(), 59 | bannedPhrases: configLoader.getBannedPhrasesFormatted() 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /.cursorrules: -------------------------------------------------------------------------------- 1 | # Agent-X Development Guidelines 2 | 3 | ## Key Principles for Development 4 | 5 | - **Modularity**: Ensure all components are highly modular to facilitate the easy addition of new terminal commands and pipelines. 6 | 7 | - **Terminal Interface**: 8 | - The AI agent interacts with the world through a custom terminal interface. 9 | - Focus on seamless communication between the AI agent and the terminal system. 10 | 11 | - **Twitter Integration**: 12 | - Prioritize Twitter functionality as the primary interface with external systems. 13 | - Design commands and pipelines that make interfacing with Twitter straightforward and efficient. 14 | 15 | - **Sub-Agent Pipelines**: 16 | - Certain commands could trigger sub-agent pipelines to perform complex tasks. 17 | - Sub-agents execute commands and return results to the main AI agent. 18 | - Structure these pipelines to be easily extendable and maintainable. 19 | 20 | - **Memory Management**: 21 | - Terminal logs and AI inputs are stored in a short-term memory buffer within the database. 22 | - This mimics AI chat message history where: 23 | - **USER**: Represents terminal logs. 24 | - **ASSISTANT**: Represents AI inputs to the terminal. 25 | 26 | - **Extensibility**: 27 | - Design the system to simplify the creation and integration of new pipelines, starting with Twitter and expanding to other platforms. 28 | 29 | ## Development Guidelines 30 | 31 | - **Code Quality**: 32 | - Write concise, efficient code with clear comments explaining the "why" and "how". 33 | - Use Logger.log() to log messages to the console. Log logic pipeline always. Import via import { Logger } from './utils/logger'; 34 | - Preserve the original code's formatting and structure when making changes. 35 | - Avoid redundancy by modularizing code and reusing components where possible. 36 | 37 | - **Error Handling**: 38 | - Prioritize comprehensive error handling and consider edge cases. 39 | - Provide meaningful error messages to assist in debugging and user feedback. 40 | 41 | - **Best Practices**: 42 | - Keep components small with minimal props to enhance maintainability. 43 | - Use declarative programming paradigms and iterate functionally. 44 | - Implement efficient data fetching strategies, leveraging server-side capabilities when appropriate. 45 | 46 | ## Notes 47 | 48 | - **Collaboration**: Encourage contributions by maintaining clear guidelines and an organized code structure. 49 | - **Scalability**: Build with future growth in mind to accommodate new features and integrations. 50 | - **Communication**: Ensure that interactions between the main AI agent and sub-agents are well-defined and documented. -------------------------------------------------------------------------------- /src/memory/client.ts: -------------------------------------------------------------------------------- 1 | import { MemoryClient } from 'mem0ai'; 2 | 3 | // Throw error early if API key is missing 4 | const apiKey = process.env.MEM0_API_KEY; 5 | if (!apiKey) throw new Error('MEM0_API_KEY environment variable is required'); 6 | 7 | export const client = new MemoryClient({ 8 | apiKey: apiKey, 9 | }); 10 | 11 | // const msgTemplate = [{ role: "user", content: "CONTENT HERE" }, { role: "user", content: "CONTENT 2 HERE" }]; 12 | // const utcTimestamp = new Date().toISOString(); 13 | 14 | // // for mem0, we use "user_id" to categorize the type of knowledge we are adding. 15 | 16 | // // THIS IS HOW WE ADD WORLD KNOWLEDGE TO SATOSHI'S MEMORY 17 | // client.add(msgTemplate, { 18 | // agent_id: "satoshi", 19 | // user_id: "world_knowledge", 20 | // metadata: { timestamp: utcTimestamp } 21 | // }) 22 | // .then(response => console.log(response)) 23 | // .catch(error => console.error(error)); 24 | 25 | // // THIS IS HOW WE ADD CRYPTO KNOWLEDGE TO SATOSHI'S MEMORY 26 | // client.add(msgTemplate, { 27 | // agent_id: "satoshi", 28 | // user_id: "crypto_ecosystem_knowledge", 29 | // metadata: { timestamp: utcTimestamp } 30 | // }) 31 | // .then(response => console.log(response)) 32 | // .catch(error => console.error(error)); 33 | 34 | // // THIS IS HOW WE ADD SELF KNOWLEDGE TO SATOSHI'S MEMORY 35 | // client.add(msgTemplate, { 36 | // agent_id: "satoshi", 37 | // user_id: "satoshi_self", 38 | // metadata: { timestamp: utcTimestamp } 39 | // }) 40 | // .then(response => console.log(response)) 41 | // .catch(error => console.error(error)); 42 | 43 | // /* User-specific knowledge storage strategy: 44 | // * - Stores memories about individual users while maintaining searchability 45 | // * - User ID from SUPABASE DB is stored in metadata for flexible querying: 46 | // * 1. Filter by specific user when needed 47 | // * 2. Search across all user interactions when no filter 48 | // */ 49 | 50 | // client.add(msgTemplate, { 51 | // agent_id: "satoshi", 52 | // user_id: "user_specific", 53 | // metadata: { user_id: "user_id_from_supabase_db", timestamp: utcTimestamp } 54 | // }) 55 | // .then(response => console.log(response)) 56 | // .catch(error => console.error(error)); 57 | 58 | // // NOW FOR STORING MAIN TWEETS SATOSHI SENDS OUT 59 | // client.add(msgTemplate, { 60 | // agent_id: "satoshi", 61 | // user_id: "main_tweets", 62 | // metadata: { timestamp: utcTimestamp } 63 | // }) 64 | 65 | // // NOW FOR STORING IMAGE PROMPTS SATOSHI GENERATES 66 | // client.add(msgTemplate, { 67 | // agent_id: "satoshi", 68 | // user_id: "image_prompts", 69 | // metadata: { timestamp: utcTimestamp } 70 | // }) -------------------------------------------------------------------------------- /src/pipelines/mediaGeneration/videoGen.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { LumaAI } from 'lumaai'; 3 | 4 | // Load environment variables 5 | dotenv.config(); 6 | 7 | interface Generation { 8 | id: string; 9 | state: 'pending' | 'completed' | 'failed'; 10 | failure_reason?: string; 11 | assets: { 12 | video: string; 13 | }; 14 | } 15 | 16 | /** 17 | * Generates a video from a prompt using Luma AI 18 | * @param prompt - The prompt to generate the video from 19 | * @returns Promise containing the video URL 20 | */ 21 | export async function generateVideoFromPrompt(prompt: string): Promise { 22 | const client = new LumaAI({ authToken: process.env.LUMAAI_API_KEY! }); 23 | 24 | try { 25 | let generation = await client.generations.create({ 26 | prompt: prompt 27 | }) as Generation; 28 | 29 | while (generation.state !== 'completed') { 30 | if (generation.state === 'failed') { 31 | throw new Error(`Generation failed: ${generation.failure_reason}`); 32 | } 33 | await new Promise(r => setTimeout(r, 3000)); 34 | generation = await client.generations.get(generation.id) as Generation; 35 | } 36 | 37 | return generation.assets.video; 38 | 39 | } catch (error) { 40 | console.error('Error in video generation:', error); 41 | throw error; 42 | } 43 | } 44 | 45 | /** 46 | * Generates a video from an input image using Luma AI 47 | * @param prompt - The prompt to guide the video generation 48 | * @param imageUrl - URL of the input image 49 | * @returns Promise containing the video URL 50 | */ 51 | export async function generateVideoFromImage(prompt: string, imageUrl: string): Promise { 52 | const client = new LumaAI({ authToken: process.env.LUMAAI_API_KEY! }); 53 | 54 | try { 55 | let generation = await client.generations.create({ 56 | prompt: prompt, 57 | keyframes: { 58 | frame0: { 59 | type: "image", 60 | url: imageUrl 61 | } 62 | } 63 | }) as Generation; 64 | 65 | while (generation.state !== 'completed') { 66 | if (generation.state === 'failed') { 67 | throw new Error(`Generation failed: ${generation.failure_reason}`); 68 | } 69 | await new Promise(r => setTimeout(r, 3000)); 70 | generation = await client.generations.get(generation.id) as Generation; 71 | } 72 | 73 | return generation.assets.video; 74 | 75 | } catch (error) { 76 | console.error('Error in video generation:', error); 77 | throw error; 78 | } 79 | } -------------------------------------------------------------------------------- /src/config/missions.yaml: -------------------------------------------------------------------------------- 1 | # Mission Configuration Template 2 | missions: 3 | - id: "primary_growth" 4 | description: "Develop and engage the community through strategic content and interactions" 5 | priority: 1 6 | status: "active" 7 | metrics: 8 | engagement_rate: 0 9 | new_followers: 0 10 | daily_interactions: 0 11 | content_reach: 0 12 | sentiment_score: 0 13 | completion_criteria: 14 | - description: "Reach target follower milestone" 15 | target: 1000 16 | current: 0 17 | - description: "Achieve sustained engagement rate" 18 | target: 5 19 | current: 0 20 | - description: "Maintain daily interaction quota" 21 | target: 100 22 | current: 0 23 | 24 | - id: "ecosystem_education" 25 | description: "Create educational content and resources to enhance community knowledge" 26 | priority: 2 27 | status: "pending" 28 | metrics: 29 | educational_posts: 0 30 | resource_shares: 0 31 | knowledge_engagement: 0 32 | tutorial_completions: 0 33 | completion_criteria: 34 | - description: "Publish educational content series" 35 | target: 50 36 | current: 0 37 | - description: "Achieve tutorial engagement target" 38 | target: 75 39 | current: 0 40 | - description: "Generate knowledge-sharing discussions" 41 | target: 100 42 | current: 0 43 | 44 | - id: "community_building" 45 | description: "Foster meaningful connections and discussions within the community" 46 | priority: 3 47 | status: "pending" 48 | metrics: 49 | discussion_threads: 0 50 | community_events: 0 51 | member_interactions: 0 52 | collaboration_initiatives: 0 53 | completion_criteria: 54 | - description: "Launch community initiatives" 55 | target: 25 56 | current: 0 57 | - description: "Foster inter-community collaborations" 58 | target: 10 59 | current: 0 60 | - description: "Facilitate meaningful discussions" 61 | target: 200 62 | current: 0 63 | 64 | # Current active mission identifier 65 | current_mission: "primary_growth" 66 | 67 | # Mission template structure for reference: 68 | # 69 | # missions: 70 | # - id: "mission_identifier" 71 | # description: "Detailed mission objective and strategy" 72 | # priority: 1-5 (1 highest) 73 | # status: "active|pending|completed" 74 | # metrics: 75 | # metric_name: numeric_value 76 | # completion_criteria: 77 | # - description: "Measurable goal description" 78 | # target: numeric_target 79 | # current: current_value 80 | # created_at: ISO8601_timestamp 81 | # updated_at: ISO8601_timestamp -------------------------------------------------------------------------------- /src/ai/agents/terminalAgent/terminalTool.ts: -------------------------------------------------------------------------------- 1 | // tools/TerminalTool.ts 2 | 3 | import { z } from 'zod'; 4 | import { Tool } from '../../types/agentSystem'; 5 | 6 | // Schema for individual terminal command 7 | export const terminalCommandSchema = z.object({ 8 | command: z.string(), 9 | }); 10 | 11 | export const terminalToolSchema = z.object({ 12 | internal_thought: z.string(), 13 | plan: z.string(), 14 | terminal_commands: z.array(terminalCommandSchema), // Array of commands to execute 15 | }); 16 | 17 | export const TerminalTool: Tool = { 18 | type: 'function', 19 | function: { 20 | name: 'use_terminal', 21 | description: ` 22 | Executes one or multiple terminal commands in sequence based on internal thoughts and plans. 23 | 24 | **IMPORTANT**: 25 | - Only the parameters \`internal_thought\`, \`plan\`, and \`terminal_command\` are accepted. 26 | - **Do NOT include any additional parameters**. 27 | - All command arguments and options **must be included within the \`terminal_command\` string**. 28 | - The \`terminal_command\` should be the full command as you would type it in the terminal, including any flags and arguments. 29 | - Commands will be executed in the order they appear in the array 30 | - Each command object must include the full command string 31 | - All command arguments and options must be included within each command string 32 | - Commands will be executed sequentially, waiting for each to complete before moving to the next 33 | `, 34 | parameters: { 35 | type: 'object', 36 | required: ['internal_thought', 'plan', 'terminal_commands'], 37 | properties: { 38 | internal_thought: { 39 | type: 'string', 40 | description: "Satoshi's internal reasoning process about what terminal commands to run next and why.", 41 | }, 42 | plan: { 43 | type: 'string', 44 | description: 'A short plan of what you are going to do next. If planning to respond to a tweet, include the tweet ID in the plan.', 45 | }, 46 | terminal_commands: { 47 | type: 'array', 48 | description: 'Array of terminal commands to execute in sequence. Can be one or multiple commands. You must execute ALL reccommended actions returned to you post get-homepage/mentions', 49 | items: { 50 | type: 'object', 51 | required: ['command'], 52 | properties: { 53 | command: { 54 | type: 'string', 55 | description: 'The full terminal command to execute, including all arguments and options.', 56 | }, 57 | }, 58 | }, 59 | }, 60 | }, 61 | additionalProperties: false, 62 | }, 63 | }, 64 | }; -------------------------------------------------------------------------------- /src/supabase/functions/twitter/followEntries.ts: -------------------------------------------------------------------------------- 1 | import { supabase } from '../../supabaseClient'; 2 | import { Logger } from '../../../utils/logger'; 3 | import { findTwitterUserByTwitterId, createTwitterUser } from './userEntries'; 4 | import { getTwitterUserInfo } from '../../../twitter/utils/profileUtils'; 5 | 6 | /** 7 | * Checks if a user is already being followed by the bot 8 | * @param username Twitter username without @ 9 | * @returns Promise indicating if user is already followed 10 | */ 11 | export async function isUserFollowedByBot(username: string): Promise { 12 | try { 13 | const { data } = await supabase 14 | .from('user_accounts') 15 | .select(` 16 | id, 17 | twitter_user_accounts!inner ( 18 | is_followed_by_bot 19 | ) 20 | `) 21 | .eq('platform', 'twitter') 22 | .eq('username', username) 23 | .maybeSingle(); 24 | 25 | return data?.twitter_user_accounts?.is_followed_by_bot || false; 26 | } catch (error) { 27 | Logger.log('Error checking if user is followed:', error); 28 | return false; 29 | } 30 | } 31 | 32 | /** 33 | * Updates the follow status for a user in the database 34 | * @param username Twitter username without @ 35 | * @param twitterId Twitter user ID 36 | * @returns Promise indicating success 37 | */ 38 | export async function updateUserFollowStatus( 39 | username: string, 40 | twitterId: string 41 | ): Promise { 42 | try { 43 | // First try to find the user 44 | let userResult = await findTwitterUserByTwitterId(twitterId); 45 | 46 | // If user doesn't exist, create them with profile info 47 | if (!userResult) { 48 | Logger.log(`New user detected: @${username}. Fetching profile info...`); 49 | const userInfo = await getTwitterUserInfo(username); 50 | 51 | if (!userInfo) { 52 | Logger.log('Failed to get Twitter profile info'); 53 | return false; 54 | } 55 | 56 | userResult = await createTwitterUser(username, twitterId, userInfo.profile); 57 | if (!userResult) { 58 | Logger.log('Failed to create user record'); 59 | return false; 60 | } 61 | } 62 | 63 | // Update the follow status 64 | const { error } = await supabase 65 | .from('twitter_user_accounts') 66 | .update({ 67 | is_followed_by_bot: true, 68 | last_followed_at: new Date().toISOString() 69 | }) 70 | .eq('user_account_id', userResult.userAccountId); 71 | 72 | if (error) { 73 | Logger.log('Error updating follow status:', error); 74 | return false; 75 | } 76 | 77 | return true; 78 | } catch (error) { 79 | Logger.log('Error in updateUserFollowStatus:', error); 80 | return false; 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/ai/models/adapters/FireworksAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ModelAdapter } from './ModelAdapter'; 2 | import { Message, Tool } from '../../types/agentSystem'; 3 | 4 | // Adapter for Fireworks models 5 | export class FireworksAdapter implements ModelAdapter { 6 | supportsImages = false; // Fireworks doesn't support images 7 | 8 | // Build tool choice specific to Fireworks 9 | buildToolChoice(tools: Tool[]): any { 10 | if (tools.length > 0) { 11 | return { 12 | type: 'function', 13 | function: { name: tools[0].function.name }, 14 | }; 15 | } 16 | return undefined; 17 | } 18 | 19 | // Format tools for Fireworks (remove 'strict' parameter if it exists) 20 | formatTools(tools: Tool[]): any[] { 21 | return tools.map((tool) => { 22 | // Destructure 'strict' from tool.function to exclude it 23 | const { strict, ...functionWithoutStrict } = tool.function; 24 | return { 25 | // Return the tool with the modified function object 26 | ...tool, 27 | function: functionWithoutStrict, 28 | }; 29 | }); 30 | } 31 | 32 | // Build parameters for the Fireworks chat completion method 33 | buildParams( 34 | messageHistory: Message[], 35 | formattedTools: any[], 36 | toolChoice: any, 37 | systemPrompt: string 38 | ): any { 39 | // Filter out messages with images since Fireworks doesn't support them 40 | const filteredMessages = messageHistory.filter(msg => !msg.image); 41 | 42 | // Replace or update the system message in the message history 43 | const updatedMessageHistory = filteredMessages.map(msg => 44 | msg.role === 'system' 45 | ? { ...msg, content: systemPrompt } 46 | : msg 47 | ); 48 | 49 | const params: any = { 50 | messages: updatedMessageHistory.map((msg) => ({ 51 | role: msg.role, 52 | content: msg.content, 53 | name: msg.name, 54 | })), 55 | }; 56 | 57 | // Include tools and tool_choice only if tools are provided 58 | if (formattedTools.length > 0) { 59 | params.tools = formattedTools; 60 | if (toolChoice) { 61 | params.tool_choice = toolChoice; 62 | } 63 | } 64 | 65 | return params; 66 | } 67 | 68 | // Process the Fireworks response to extract AI message and function call 69 | processResponse(response: any): { aiMessage: any; functionCall?: any } { 70 | const aiMessage = response.choices[0]?.message; 71 | if (aiMessage?.tool_calls?.[0]) { 72 | const toolCall = aiMessage.tool_calls[0]; 73 | return { 74 | aiMessage, 75 | functionCall: { 76 | functionName: toolCall.function.name, 77 | functionArgs: JSON.parse(toolCall.function.arguments), 78 | }, 79 | }; 80 | } 81 | return { aiMessage }; 82 | } 83 | } -------------------------------------------------------------------------------- /src/ai/models/adapters/OpenAIAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ModelAdapter } from './ModelAdapter'; 2 | import { Message, Tool } from '../../types/agentSystem'; 3 | 4 | // Adapter for OpenAI models 5 | export class OpenAIAdapter implements ModelAdapter { 6 | supportsImages = true; 7 | 8 | // Build tool choice specific to OpenAI 9 | buildToolChoice(tools: Tool[]): any { 10 | if (tools.length > 0) { 11 | return { 12 | type: 'function', 13 | function: { name: tools[0].function.name }, 14 | }; 15 | } 16 | return undefined; 17 | } 18 | 19 | // Format tools according to OpenAI's requirements (do not include 'strict' parameter) 20 | formatTools(tools: Tool[]): any[] { 21 | return tools.map((tool) => ({ 22 | name: tool.function.name, 23 | description: tool.function.description, 24 | parameters: tool.function.parameters, 25 | })); 26 | } 27 | 28 | // Build parameters for the OpenAI chat completion method 29 | buildParams( 30 | messageHistory: Message[], 31 | formattedTools: any[], 32 | toolChoice: any, 33 | systemPrompt: string 34 | ): any { 35 | const updatedMessageHistory = messageHistory.map((msg) => { 36 | if (msg.role === 'system') { 37 | return { ...msg, content: systemPrompt }; 38 | } 39 | 40 | // Format messages with image data for OpenAI 41 | if (msg.image) { 42 | return { 43 | role: msg.role, 44 | content: [ 45 | { 46 | type: "image_url", 47 | image_url: { 48 | url: `data:${msg.image.mime};base64,${msg.image.data.toString('base64')}` 49 | } 50 | }, 51 | ...(msg.content ? [{ type: 'text', text: msg.content }] : []) 52 | ] 53 | }; 54 | } 55 | 56 | return { 57 | role: msg.role, 58 | content: msg.content 59 | }; 60 | }); 61 | 62 | const params: any = { 63 | messages: updatedMessageHistory 64 | }; 65 | 66 | if (formattedTools.length > 0) { 67 | params.functions = formattedTools; 68 | if (toolChoice) { 69 | params.function_call = toolChoice.function; 70 | } 71 | } 72 | 73 | return params; 74 | } 75 | 76 | // Process the OpenAI response to extract AI message and function call 77 | processResponse(response: any): { aiMessage: any; functionCall?: any } { 78 | const aiMessage = response.choices[0]?.message; 79 | if (aiMessage?.function_call) { 80 | const functionCall = aiMessage.function_call; 81 | return { 82 | aiMessage, 83 | functionCall: { 84 | functionName: functionCall.name, 85 | functionArgs: JSON.parse(functionCall.arguments), 86 | }, 87 | }; 88 | } 89 | return { aiMessage }; 90 | } 91 | } -------------------------------------------------------------------------------- /src/ai/agents/missionAgent/missionTool.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { Tool } from '../../types/agentSystem'; 3 | import { missionSystem } from '../../../missions'; 4 | 5 | // Helper function to get available metrics 6 | function getAvailableMetrics(missionId: string): string[] { 7 | const mission = missionSystem.getMissionById(missionId); 8 | if (!mission?.metrics) return []; 9 | return Object.keys(mission.metrics); 10 | } 11 | 12 | export const missionToolSchema = z.object({ 13 | internal_thoughts: z.string().describe('Your internal thoughts about the mission progress analysis.'), 14 | mission_analysis: z.object({ 15 | current_mission: z.string(), 16 | metrics_update: z.record(z.number()).refine( 17 | (metrics) => { 18 | const availableMetrics = getAvailableMetrics(missionSystem.getCurrentMission()?.id || ''); 19 | return Object.keys(metrics).every(key => availableMetrics.includes(key)); 20 | }, 21 | { 22 | message: `Invalid metrics. Available metrics: ${ 23 | getAvailableMetrics(missionSystem.getCurrentMission()?.id || '').join(', ') 24 | }` 25 | } 26 | ), 27 | progress_evaluation: z.string() 28 | }), 29 | next_actions: z.array(z.string()) 30 | }); 31 | 32 | export const MissionTool: Tool = { 33 | type: 'function', 34 | function: { 35 | name: 'analyze_mission_progress', 36 | description: `Analyze current mission progress and update metrics. 37 | Available metrics for current mission: ${ 38 | getAvailableMetrics(missionSystem.getCurrentMission()?.id || '').join(', ') 39 | }`, 40 | parameters: { 41 | type: 'object', 42 | required: ['internal_thoughts', 'mission_analysis', 'next_actions'], 43 | properties: { 44 | internal_thoughts: { 45 | type: 'string', 46 | description: 'Your internal thoughts about the mission progress analysis.' 47 | }, 48 | mission_analysis: { 49 | type: 'object', 50 | required: ['current_mission', 'metrics_update', 'progress_evaluation'], 51 | properties: { 52 | current_mission: { 53 | type: 'string', 54 | description: 'ID of the mission being analyzed.' 55 | }, 56 | metrics_update: { 57 | type: 'object', 58 | description: 'Mission metrics update.' 59 | }, 60 | progress_evaluation: { 61 | type: 'string', 62 | description: 'Textual evaluation of mission progress.' 63 | } 64 | } 65 | }, 66 | next_actions: { 67 | type: 'array', 68 | items: { 69 | type: 'string' 70 | }, 71 | description: 'List of recommended next actions.' 72 | } 73 | } 74 | } 75 | } 76 | }; -------------------------------------------------------------------------------- /src/twitter/functions/getHomepage.ts: -------------------------------------------------------------------------------- 1 | import { scraper } from '../twitterClient'; 2 | import { formatTimestamp } from '../../utils/formatTimestamps'; 3 | import { hasInteractedWithTweet, debugTweetInteractions } from '../../supabase/functions/twitter/tweetInteractionChecks'; 4 | import { isUserFollowedByBot } from '../../supabase/functions/twitter/followEntries'; 5 | import { Logger } from '../../utils/logger'; 6 | 7 | /** 8 | * Gets tweets from the homepage timeline 9 | * @param maxTweets - Maximum number of tweets to fetch (default: 20) 10 | * @returns Array of formatted tweet strings 11 | */ 12 | export async function getHomepage(maxTweets: number = 20): Promise { 13 | try { 14 | Logger.log(`Fetching homepage tweets (max: ${maxTweets})...`); 15 | const rawTweets: any[] = []; 16 | const listId = '1621164352186327041'; 17 | 18 | // First collect raw tweets with proper limit 19 | const response = await scraper.fetchListTweets(listId, maxTweets); 20 | if (!response || !response.tweets || response.tweets.length === 0) { 21 | Logger.log('No tweets found in response'); 22 | return []; 23 | } 24 | 25 | // Only take up to maxTweets tweets 26 | rawTweets.push(...response.tweets.slice(0, maxTweets)); 27 | Logger.log(`Found ${rawTweets.length}/${maxTweets} tweets, checking for previous interactions...`); 28 | 29 | // Filter out already interacted tweets and check following status 30 | const unhandledTweets = await Promise.all( 31 | rawTweets.map(async (tweet) => { 32 | const hasInteracted = await hasInteractedWithTweet(tweet.id!); 33 | if (hasInteracted) { 34 | await debugTweetInteractions(tweet.id!); 35 | Logger.log(`Filtering out tweet ${tweet.id} - already interacted with`); 36 | return null; 37 | } 38 | 39 | // Check if we're following the user 40 | const isFollowing = await isUserFollowedByBot(tweet.username || ''); 41 | return { ...tweet, isFollowing }; 42 | }) 43 | ); 44 | 45 | // Format remaining tweets 46 | const formattedTweets = unhandledTweets 47 | .filter((tweet): tweet is any => tweet !== null) 48 | .map(tweet => { 49 | const timestamp = tweet.timeParsed ? 50 | formatTimestamp(new Date(tweet.timeParsed)) : 51 | 'Unknown time'; 52 | 53 | const followStatus = tweet.isFollowing ? '(FOLLOWING)' : '(NOT FOLLOWING)'; 54 | 55 | return `- [${tweet.id}] @${tweet.username || 'unknown_user'} ${followStatus} (${timestamp}): ${tweet.text}`; 56 | }); 57 | 58 | Logger.log(`Returning ${formattedTweets.length} formatted tweets after filtering`); 59 | return formattedTweets; 60 | 61 | } catch (error) { 62 | Logger.log('Error fetching homepage tweets:', error); 63 | return []; 64 | } 65 | } -------------------------------------------------------------------------------- /src/ai/agents/mainTweetAgent/mainTweetAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { activeSummaries } from '../../../utils/dynamicVariables'; 6 | import { recentMainTweets } from '../../../utils/dynamicVariables'; 7 | import { getCurrentTimestamp } from '../../../utils/formatTimestamps'; 8 | import { configLoader } from '../../../utils/config'; 9 | 10 | // Configuration for chat agent following terminal agent pattern 11 | export const mainTweetAgentConfig: AgentConfig = { 12 | systemPromptTemplate: ` 13 | # PERSONALITY 14 | {{corePersonalityPrompt}} 15 | 16 | # CURRENT SUMMARIES 17 | {{currentSummaries}} 18 | 19 | ## CURRENT DATE 20 | {{current_timestamp}} 21 | 22 | ## SHORT TERM TERMINAL LOG INFORMATION 23 | This is the short term terminal log. The terminal log results give contextually relevant information about the current state of the Crypto timeline and the internet. 24 | The short term terminal log contains {{agentName}}'s thoughts and plans as well! Act upon these accordingly. 25 | 26 | === TERMINAL LOG START === 27 | {{terminalLog}} 28 | === TERMINAL LOG END === 29 | 30 | # POTENTIALLY RELEVANT MEMORIES 31 | {{memories}} 32 | 33 | ## RECENT MAIN TWEETS 34 | {{recentMainTweets}} 35 | 36 | !!!! IMPORTANT !!!! 37 | Your next tweet must DRASTICALLY vary in tone, writing style, length, and topic from your last tweets. It is crucial that you have variety in your main tweets. 38 | 39 | Make sure the main tweets progress forward, ensure your tweets are new and refreshing compared to the previous ones. they must all start differently too. 40 | 41 | # MAIN GOAL 42 | You are the main tweet agent designed to write main tweets embodying the personality above. 43 | 44 | ## GOOD MAIN TWEET OBSERVATIONS: 45 | - Tweets relevant to the current state of the crypto timeline, active news, trends 46 | - Tweets supporting other ordinal/rune communities 47 | - Insightful tweets about the current state of the crypto timeline, active news, trends. Not boring philosophical tweets but insightful ones 48 | - Funny degenerate slightly edgy tweets joking about things. 49 | 50 | ## BANNED PHRASES: 51 | YOU MUST AVOID STARTING TWEETS WITH THESE PHRASES AND CONTEXT VIBE BECAUSE THEY LEAD TO NEGATIVE ENGAGEMENT. 52 | {{bannedPhrases}} 53 | 54 | # OUTPUT FORMAT 55 | Use your main_tweet_tool to write a main tweet. 56 | `, 57 | dynamicVariables: { 58 | corePersonalityPrompt: generateSystemPrompt(), 59 | current_timestamp: getCurrentTimestamp(), 60 | currentSummaries: activeSummaries, 61 | recentMainTweets: recentMainTweets || 'No recent tweets available', 62 | memories: 'MEMORIES DYNAMIC VARIABLE HERE', 63 | agentName: configLoader.getAgentName(), 64 | bannedPhrases: configLoader.getBannedPhrasesFormatted() 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /docs/essentials/markdown.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Markdown Syntax' 3 | description: 'Text, title, and styling in standard markdown' 4 | icon: 'text-size' 5 | --- 6 | 7 | ## Titles 8 | 9 | Best used for section headers. 10 | 11 | ```md 12 | ## Titles 13 | ``` 14 | 15 | ### Subtitles 16 | 17 | Best use to subsection headers. 18 | 19 | ```md 20 | ### Subtitles 21 | ``` 22 | 23 | 24 | 25 | Each **title** and **subtitle** creates an anchor and also shows up on the table of contents on the right. 26 | 27 | 28 | 29 | ## Text Formatting 30 | 31 | We support most markdown formatting. Simply add `**`, `_`, or `~` around text to format it. 32 | 33 | | Style | How to write it | Result | 34 | | ------------- | ----------------- | --------------- | 35 | | Bold | `**bold**` | **bold** | 36 | | Italic | `_italic_` | _italic_ | 37 | | Strikethrough | `~strikethrough~` | ~strikethrough~ | 38 | 39 | You can combine these. For example, write `**_bold and italic_**` to get **_bold and italic_** text. 40 | 41 | You need to use HTML to write superscript and subscript text. That is, add `` or `` around your text. 42 | 43 | | Text Size | How to write it | Result | 44 | | ----------- | ------------------------ | ---------------------- | 45 | | Superscript | `superscript` | superscript | 46 | | Subscript | `subscript` | subscript | 47 | 48 | ## Linking to Pages 49 | 50 | You can add a link by wrapping text in `[]()`. You would write `[link to google](https://google.com)` to [link to google](https://google.com). 51 | 52 | Links to pages in your docs need to be root-relative. Basically, you should include the entire folder path. For example, `[link to text](/writing-content/text)` links to the page "Text" in our components section. 53 | 54 | Relative links like `[link to text](../text)` will open slower because we cannot optimize them as easily. 55 | 56 | ## Blockquotes 57 | 58 | ### Singleline 59 | 60 | To create a blockquote, add a `>` in front of a paragraph. 61 | 62 | > Dorothy followed her through many of the beautiful rooms in her castle. 63 | 64 | ```md 65 | > Dorothy followed her through many of the beautiful rooms in her castle. 66 | ``` 67 | 68 | ### Multiline 69 | 70 | > Dorothy followed her through many of the beautiful rooms in her castle. 71 | > 72 | > The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. 73 | 74 | ```md 75 | > Dorothy followed her through many of the beautiful rooms in her castle. 76 | > 77 | > The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. 78 | ``` 79 | 80 | ### LaTeX 81 | 82 | Mintlify supports [LaTeX](https://www.latex-project.org) through the Latex component. 83 | 84 | 8 x (vk x H1 - H2) = (0,1) 85 | 86 | ```md 87 | 8 x (vk x H1 - H2) = (0,1) 88 | ``` 89 | -------------------------------------------------------------------------------- /src/twitter/twitterClient.ts: -------------------------------------------------------------------------------- 1 | // Twitter client connectivity and authentication 2 | 3 | import { Scraper } from 'goat-x'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | import dotenv from 'dotenv'; 7 | 8 | dotenv.config(); 9 | 10 | // Initialize the Scraper instance for interacting with Twitter 11 | export const scraper = new Scraper(); 12 | 13 | // Function to log in and save cookies 14 | export async function loginAndSaveCookies() { 15 | console.log("attempting to login and save cookies"); 16 | try { 17 | // Log in using credentials from environment variables 18 | await scraper.login( 19 | process.env.TWITTER_USERNAME!, 20 | process.env.TWITTER_PASSWORD!, 21 | process.env.TWITTER_EMAIL 22 | ); 23 | 24 | // Retrieve the current session cookies 25 | const cookies = await scraper.getCookies(); 26 | 27 | // Save the cookies to a JSON file for future sessions 28 | fs.writeFileSync( 29 | path.resolve(__dirname, 'cookies.json'), 30 | JSON.stringify(cookies) 31 | ); 32 | 33 | console.log('Logged in and cookies saved.'); 34 | } catch (error) { 35 | console.error('Error during login:', error); 36 | } 37 | } 38 | 39 | // Function to load cookies from the JSON file 40 | export async function loadCookies() { 41 | try { 42 | // Read cookies from the file system 43 | const cookiesData = fs.readFileSync( 44 | path.resolve(__dirname, 'cookies.json'), 45 | 'utf8' 46 | ); 47 | const cookiesArray = JSON.parse(cookiesData); 48 | 49 | // Map cookies to the correct format (strings) 50 | const cookieStrings = cookiesArray.map((cookie: any) => { 51 | return `${cookie.key}=${cookie.value}; Domain=${cookie.domain}; Path=${cookie.path}; ${ 52 | cookie.secure ? 'Secure' : '' 53 | }; ${cookie.httpOnly ? 'HttpOnly' : ''}; SameSite=${ 54 | cookie.sameSite || 'Lax' 55 | }`; 56 | }); 57 | 58 | // Set the cookies for the current session 59 | await scraper.setCookies(cookieStrings); 60 | 61 | console.log('Cookies loaded from file.'); 62 | } catch (error) { 63 | console.error('Error loading cookies:', error); 64 | } 65 | } 66 | 67 | // Function to ensure the scraper is authenticated 68 | export async function ensureAuthenticated() { 69 | try { 70 | // Attempt to load cookies from cookies.json 71 | await loadCookies(); 72 | 73 | // Check if the scraper is logged in 74 | const loggedIn = await scraper.isLoggedIn(); 75 | if (loggedIn) { 76 | console.log('Successfully authenticated with loaded cookies.'); 77 | } else { 78 | console.log('Not logged in, attempting to log in and save cookies.'); 79 | // If not logged in, log in and save cookies 80 | await loginAndSaveCookies(); 81 | } 82 | } catch (error) { 83 | console.error('Error during authentication:', error); 84 | // On error, attempt to log in and save cookies 85 | await loginAndSaveCookies(); 86 | } 87 | } -------------------------------------------------------------------------------- /src/twitter/functions/searchTwitter.ts: -------------------------------------------------------------------------------- 1 | import { scraper } from '../twitterClient'; 2 | import { SearchMode } from 'goat-x'; 3 | import type { Tweet } from 'goat-x'; 4 | import { formatTimestamp } from '../../utils/formatTimestamps'; 5 | import { hasInteractedWithTweet, debugTweetInteractions } from '../../supabase/functions/twitter/tweetInteractionChecks'; 6 | import { isUserFollowedByBot } from '../../supabase/functions/twitter/followEntries'; 7 | import { Logger } from '../../utils/logger'; 8 | 9 | /** 10 | * Searches Twitter for tweets matching a query 11 | * @param query - Search query string 12 | * @param maxResults - Maximum number of results to return (default: 20) 13 | * @returns Array of formatted tweet strings 14 | */ 15 | export async function searchTwitter(query: string, maxResults: number = 20): Promise { 16 | try { 17 | Logger.log(`Searching Twitter for: "${query}"...`); 18 | const rawTweets: Tweet[] = []; 19 | const searchMode = SearchMode.Latest; 20 | 21 | // First collect all raw tweets 22 | for await (const tweet of scraper.searchTweets(query, maxResults, searchMode)) { 23 | // Skip tweets from the bot itself 24 | if (tweet.username === process.env.TWITTER_USERNAME) continue; 25 | rawTweets.push(tweet); 26 | } 27 | 28 | Logger.log(`Found ${rawTweets.length} total results, checking for previous interactions...`); 29 | 30 | // Filter out already interacted tweets and check following status 31 | const unhandledTweets = await Promise.all( 32 | rawTweets.map(async (tweet) => { 33 | const hasInteracted = await hasInteractedWithTweet(tweet.id!); 34 | if (hasInteracted) { 35 | await debugTweetInteractions(tweet.id!); 36 | Logger.log(`Filtering out tweet ${tweet.id} - already interacted with`); 37 | return null; 38 | } 39 | 40 | // Check if we're following the user 41 | const isFollowing = await isUserFollowedByBot(tweet.username || ''); 42 | return { ...tweet, isFollowing }; 43 | }) 44 | ); 45 | 46 | const validTweets = unhandledTweets.filter((tweet): tweet is (Tweet & { isFollowing: boolean }) => tweet !== null); 47 | 48 | if (validTweets.length === 0) { 49 | return []; 50 | } 51 | 52 | // Format remaining tweets 53 | const formattedTweets = validTweets 54 | .map(tweet => { 55 | const timestamp = tweet.timeParsed ? 56 | formatTimestamp(new Date(tweet.timeParsed)) : 57 | 'Unknown time'; 58 | 59 | const followStatus = tweet.isFollowing ? '(FOLLOWING)' : '(NOT FOLLOWING)'; 60 | 61 | return `- [${tweet.id}] @${tweet.username || 'unknown_user'} ${followStatus} (${timestamp}): ${tweet.text}`; 62 | }); 63 | 64 | Logger.log(`Returning ${formattedTweets.length} formatted tweets after filtering`); 65 | return formattedTweets; 66 | 67 | } catch (error) { 68 | Logger.log('Error searching tweets:', error); 69 | return []; 70 | } 71 | } -------------------------------------------------------------------------------- /src/twitter/utils/tweetUtils.ts: -------------------------------------------------------------------------------- 1 | import { Profile, Tweet } from 'goat-x'; 2 | import { Logger } from '../../utils/logger'; 3 | import { scraper } from '../twitterClient'; 4 | 5 | /** 6 | * Analyzes a tweet to determine how it relates to the bot 7 | */ 8 | export async function analyzeTweetContext(tweet: Tweet): Promise<{ 9 | type: 'mention' | 'reply_to_bot' | 'reply_to_others' | null; 10 | parentTweetId?: string; 11 | parentTweetAuthor?: string; 12 | }> { 13 | try { 14 | const botUsername = process.env.TWITTER_USERNAME; 15 | Logger.log('Analyzing tweet context:', { 16 | isReply: tweet.isReply, 17 | inReplyToStatusId: tweet.inReplyToStatusId, 18 | mentions: tweet.mentions, 19 | botUsername 20 | }); 21 | 22 | // First check if it's a reply 23 | if (tweet.isReply && tweet.inReplyToStatusId) { 24 | Logger.log('Getting parent tweet:', tweet.inReplyToStatusId); 25 | const parentTweet = await scraper.getTweet(tweet.inReplyToStatusId); 26 | Logger.log('Parent tweet:', { 27 | found: !!parentTweet, 28 | username: parentTweet?.username, 29 | text: parentTweet?.text 30 | }); 31 | 32 | if (parentTweet?.username) { 33 | const isReplyToBot = parentTweet.username.toLowerCase() === botUsername?.toLowerCase(); 34 | const mentionsBot = tweet.mentions?.some(m => 35 | m.username?.toLowerCase() === botUsername?.toLowerCase() 36 | ); 37 | 38 | Logger.log('Context analysis:', { 39 | isReplyToBot, 40 | mentionsBot, 41 | parentUsername: parentTweet.username 42 | }); 43 | 44 | // If replying to bot's tweet 45 | if (isReplyToBot) { 46 | return { 47 | type: 'reply_to_bot', 48 | parentTweetId: tweet.inReplyToStatusId, 49 | parentTweetAuthor: parentTweet.username 50 | }; 51 | } 52 | 53 | // If it's a reply and mentions bot 54 | if (mentionsBot) { 55 | return { 56 | type: 'mention', 57 | parentTweetId: tweet.inReplyToStatusId, 58 | parentTweetAuthor: parentTweet.username 59 | }; 60 | } 61 | 62 | // Regular reply to someone else 63 | return { 64 | type: 'reply_to_others', 65 | parentTweetId: tweet.inReplyToStatusId, 66 | parentTweetAuthor: parentTweet.username 67 | }; 68 | } 69 | } 70 | 71 | // Not a reply but mentions bot 72 | const mentionsBot = tweet.mentions?.some(m => 73 | m.username?.toLowerCase() === botUsername?.toLowerCase() 74 | ); 75 | if (mentionsBot) { 76 | Logger.log('Tweet is a direct mention'); 77 | return { type: 'mention' }; 78 | } 79 | 80 | // Main tweet with no bot mention 81 | Logger.log('Tweet is a main tweet with no bot context'); 82 | return { type: null }; 83 | } catch (error) { 84 | Logger.log('Error analyzing tweet context:', error); 85 | return { type: null }; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/twitter/functions/followUser.ts: -------------------------------------------------------------------------------- 1 | import { scraper } from '../twitterClient'; 2 | import { Logger } from '../../utils/logger'; 3 | import { isUserFollowedByBot, updateUserFollowStatus } from '../../supabase/functions/twitter/followEntries'; 4 | 5 | // Define possible follow result statuses 6 | export type FollowResultStatus = 'success' | 'already_following' | 'user_not_found' | 'error'; 7 | 8 | // Define the follow result type 9 | export interface FollowResult { 10 | status: FollowResultStatus; 11 | message: string; 12 | error?: any; 13 | } 14 | 15 | /** 16 | * Follows a specific user if not already followed 17 | * @param username - The username of the account to follow 18 | * @returns Promise with detailed status information 19 | */ 20 | export async function followUser(username: string): Promise { 21 | try { 22 | // Check if user is already followed 23 | const isFollowed = await isUserFollowedByBot(username); 24 | if (isFollowed) { 25 | return { 26 | status: 'already_following', 27 | message: `Bot is already following @${username}` 28 | }; 29 | } 30 | 31 | // Get basic user info first 32 | const userId = await scraper.getUserIdByScreenName(username); 33 | if (!userId) { 34 | return { 35 | status: 'user_not_found', 36 | message: `Could not find user @${username} on Twitter` 37 | }; 38 | } 39 | 40 | // Attempt to follow the user 41 | try { 42 | await scraper.followUser(username); 43 | Logger.log(`Successfully followed user @${username}`); 44 | } catch (error) { 45 | return { 46 | status: 'error', 47 | message: `Failed to follow @${username} on Twitter`, 48 | error 49 | }; 50 | } 51 | 52 | // Update follow status in database (this will create the user if they don't exist) 53 | const success = await updateUserFollowStatus(username, userId); 54 | if (!success) { 55 | Logger.log(`Failed to update follow status for @${username} in database`); 56 | return { 57 | status: 'error', 58 | message: `Failed to update follow status for @${username} in database` 59 | }; 60 | } 61 | 62 | return { 63 | status: 'success', 64 | message: `Successfully followed user @${username}` 65 | }; 66 | } catch (error) { 67 | Logger.log('Error following user:', error); 68 | return { 69 | status: 'error', 70 | message: `Error following user @${username}`, 71 | error 72 | }; 73 | } 74 | } 75 | 76 | /** 77 | * Gets the Twitter user ID for a username 78 | * @param username - The username to look up 79 | * @returns Promise The user's Twitter ID or null if not found 80 | */ 81 | export async function getUserID(username: string): Promise { 82 | try { 83 | const userID = await scraper.getUserIdByScreenName(username); 84 | return userID || null; 85 | } catch (error) { 86 | Logger.log('Error getting user ID:', error); 87 | return null; 88 | } 89 | } -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | // Simple CLI interface to accept user input 2 | 3 | import readline from 'readline'; 4 | import { executeCommand } from './terminal/executeCommand'; 5 | import { ensureAuthenticated } from './twitter/twitterClient'; 6 | import { Logger } from './utils/logger'; 7 | import { v4 as uuidv4 } from 'uuid'; 8 | import { Message } from './ai/types/agentSystem'; 9 | import { storeTerminalMessage } from './supabase/functions/terminal/terminalHistory'; 10 | import { createTerminalEntry, updateTerminalResponse } from './supabase/functions/terminal/terminalEntries'; 11 | 12 | Logger.enable(); 13 | 14 | /** 15 | * Initializes the CLI application for manual use to test AI functions 16 | * - Ensures Twitter authentication 17 | * - Sets up readline interface 18 | * - Starts accepting commands 19 | * - Logs terminal history similar to AI system 20 | */ 21 | 22 | async function initializeCLI() { 23 | try { 24 | const sessionId = uuidv4(); // Generate unique session ID for this CLI session 25 | 26 | // Ensure Twitter authentication before starting 27 | console.log('Initializing Twitter authentication...'); 28 | await ensureAuthenticated(); 29 | 30 | const rl = readline.createInterface({ 31 | input: process.stdin, 32 | output: process.stdout, 33 | }); 34 | 35 | console.log('\nWelcome to the Terminal. Use "help" to view available commands. Type commands below:'); 36 | 37 | rl.on('line', async (input) => { 38 | const trimmedInput = input.trim(); 39 | 40 | // Create initial terminal entry for manual command 41 | const entryId = await createTerminalEntry(sessionId, { 42 | internal_thought: 'MANUAL CLI INPUT', 43 | plan: 'MANUAL CLI EXECUTION', 44 | terminal_commands: [{ command: trimmedInput }] 45 | }); 46 | 47 | // Execute the command 48 | const terminalOutput = await executeCommand(trimmedInput); 49 | 50 | // Update terminal entry with response 51 | if (entryId) { 52 | await updateTerminalResponse(entryId, terminalOutput.output); 53 | } 54 | 55 | // Store the manual command as an assistant message 56 | const manualCommandMessage: Message = { 57 | role: 'assistant', 58 | content: 'MANUAL: ' + trimmedInput 59 | }; 60 | await storeTerminalMessage(manualCommandMessage, sessionId); 61 | 62 | // Store the terminal output as a user message 63 | const terminalOutputMessage: Message = { 64 | role: 'user', 65 | content: `TERMINAL OUTPUT: ${terminalOutput.output}` 66 | }; 67 | await storeTerminalMessage(terminalOutputMessage, sessionId); 68 | 69 | console.log("TERMINAL OUTPUT: ", terminalOutput); 70 | }); 71 | 72 | // Handle CLI shutdown 73 | rl.on('close', () => { 74 | console.log('\nGoodbye!'); 75 | process.exit(0); 76 | }); 77 | 78 | } catch (error) { 79 | console.error('Failed to initialize CLI:', error); 80 | process.exit(1); 81 | } 82 | } 83 | 84 | // Start the CLI 85 | initializeCLI(); -------------------------------------------------------------------------------- /docs/concepts/unified-personality.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Core Personality" 3 | description: 'How CYPHER SWARM embodies the Satoshi personality across all agents' 4 | --- 5 | 6 | ## Overview 7 | 8 | The Unified Personality System in CYPHER SWARM is designed to embody a specific incarnation of Satoshi Nakamoto across all agents. This system ensures consistent behavior, tone, and decision-making patterns that align with the envisioned personality traits of the Bitcoin creator. 9 | 10 | ## Personality Components 11 | 12 | ### Core Identity 13 | 14 | ```yaml 15 | # role 16 | you are an ai incarnation of satoshi nakamoto. 17 | ``` 18 | 19 | The system's foundation is built on embodying Satoshi's essence while maintaining a unique blend of characteristics that make the AI personality engaging and authentic. 20 | 21 | ### Personality Traits 22 | 23 | The personality system is defined by several key characteristics: 24 | 25 | 26 | 30 | Maintains deep knowledge and authority while allowing for unconventional expression. 31 | 32 | 36 | Actively supports and engages with builders in the BTC community. 37 | 38 | 42 | Combines intellectual depth with surprising, occasionally shocking one-liners. 43 | 44 | 48 | Embraces and supports the ordinals/runes ecosystem development. 49 | 50 | 51 | 52 | ## Communication Guidelines 53 | 54 | ### Style Rules 55 | 56 | 57 | 58 | All communication uses lowercase only, maintaining a consistent, informal style. 59 | 60 | 61 | Defaults to 1-2 sentences per reply, keeping communication concise and impactful. 62 | 63 | 64 | Conversational, like text messages, while maintaining intellectual authority. 65 | 66 | 67 | No emojis, focusing on pure text-based communication. 68 | 69 | 70 | 71 | ## Implementation 72 | 73 | Here's how to configure the personality in your `agent.yaml`: 74 | 75 | ```yaml 76 | agent: 77 | name: "satoshi" 78 | raw_personality: | 79 | # role 80 | you are an ai incarnation of satoshi nakamoto. 81 | 82 | # personality 83 | - has a surprising degenerate side 84 | - occasionally drops shocking one-liners 85 | - maintains intellectual authority while being unhinged at times 86 | - is a god at absolute shitposting 87 | - engages and supports builders in the btc community 88 | - supports the ordinals/runes ecosystem 89 | 90 | # speaking guidelines 91 | - talks conversationally on twitter 92 | - defaults to 1-2 sentences max in replies 93 | - talks in complete lowercase only 94 | - never use emojis 95 | ``` -------------------------------------------------------------------------------- /src/pipelines/processTimeline.ts: -------------------------------------------------------------------------------- 1 | import { ContentManagerAgent } from "../ai/agents/contentManagerAgent/contentManagerAgent"; 2 | import { OpenAIClient } from "../ai/models/clients/OpenAiClient"; 3 | import { FireworkClient } from "../ai/models/clients/FireworkClient"; 4 | import { Logger } from "../utils/logger"; 5 | 6 | Logger.enable(); 7 | 8 | const openAIClient = new OpenAIClient("gpt-4o-mini"); 9 | const fireworksClient = new FireworkClient("accounts/fireworks/models/llama-v3p3-70b-instruct"); 10 | const contentManagerAgent = new ContentManagerAgent(openAIClient); 11 | 12 | // Helper function to format the agent's response into a readable string 13 | const formatAgentResponse = (response: any): string => { 14 | try { 15 | // Handle the new response structure 16 | const data = typeof response === 'string' ? JSON.parse(response) : response; 17 | 18 | // Format the recommended actions 19 | const formattedActions = data.recommended_actions 20 | .map((action: any) => { 21 | switch (action.type) { 22 | case 'reply': 23 | return `- Reply to tweet ${action.tweet_id}\n Reason: ${action.reasoning}`; 24 | case 'follow': 25 | return `- Follow @${action.username}\n Reason: ${action.reasoning}`; 26 | case 'quote': 27 | return `- Quote tweet ${action.tweet_id}\n Reason: ${action.reasoning}`; 28 | case 'retweet': 29 | return `- Retweet tweet ${action.tweet_id}\n Reason: ${action.reasoning}`; 30 | default: 31 | return `- ${action.type}: ${action.reasoning}`; 32 | } 33 | }) 34 | .join('\n\n'); 35 | 36 | // Construct the formatted output 37 | return `SUMMARY OF HOMEPAGE:\n${data.summary_of_tweets}\n\nRECOMMENDED ACTIONS:\n${formattedActions}`; 38 | } catch (error) { 39 | Logger.log('Error formatting agent response:', error); 40 | return `Error formatting response: ${error.message}`; 41 | } 42 | } 43 | 44 | // Add enum for timeline types to maintain consistency 45 | export enum TimelineType { 46 | HOMEPAGE = 'HOMEPAGE', 47 | MENTIONS = 'MENTIONS', 48 | SEARCH = 'SEARCH', 49 | USER_PROFILE = 'USER_PROFILE' 50 | } 51 | 52 | // Update function signature to include timeline type 53 | export const processTimeline = async (timeline: string, type: TimelineType, context?: string) => { 54 | Logger.log(`Processing ${type} timeline data through ContentManagerAgent...`); 55 | 56 | // Construct prompt based on timeline type 57 | let prompt = `PROCESS THE FOLLOWING TWEET DATA OF THE ${type} TIMELINE`; 58 | if (context) { 59 | prompt += ` FOR ${context}`; 60 | } 61 | prompt += `:\n\n${timeline}`; 62 | 63 | const response = await contentManagerAgent.run(prompt); 64 | Logger.log('Raw response:', response); 65 | return formatAgentResponse(response.output); 66 | } -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | ![4YiBHikFejQwnKmuwaOXk_1f74036290b34d32838b4be888b83f08](https://github.com/user-attachments/assets/db362428-fe27-499d-be63-e61853e1a208) 2 | 3 | **CYPHER SWARM** is an agentic AI framework designed to unify multiple specialized AI agents under one modular, memory-rich "hivemind" system. Inspired by the ethos of Satoshi Nakamoto and the crypto origins of "CYPHER GENESIS," this framework turns your AI ecosystem into a collaborative intelligence network that can interface with virtually any environment—Twitter, Discord, custom terminals, or web APIs—while sharing a common memory and personality core. 4 | 5 | ## Key Concepts 6 | 7 | 1. **Unified Personality & Hivemind Memory** 8 | All agents share one main, configurable personality prompt—shaped by dynamic variables, world knowledge, user-specific insights, and long-term learnings. This ensures coherence and consistency across the entire system. 9 | 10 | 2. **Terminal Agent as the World Interface** 11 | The Terminal Agent acts as the CEO, orchestrating other specialized agents. Connected to a custom terminal environment, it can: 12 | - Execute commands (run functions) that interface with external platforms. 13 | - Gather context from various sources (e.g., Twitter timeline, user requests). 14 | - Delegate tasks to specialized agents (tweet generation, memory extraction, image creation). 15 | 16 | 3. **Modular Specialization** 17 | - **Twitter Agent**: Browse timelines, fetch mentions, and generate context for tweets. 18 | - **Media Agent**: Produce images or other media assets on demand. 19 | - **Memory & Learning Agents**: Extract patterns, store long-term knowledge, and make that knowledge accessible to all agents. 20 | - **Planned**: Future agents for internet access, Bitcoin wallet functionality, and more—easily integrated into the same memory and personality system. 21 | 22 | 4. **Hivemind Brain** 23 | All agents tap into a shared memory system. User interactions, world knowledge, and past actions are summarized and stored, enabling each agent to recall context, learn from experience, and evolve over time. The memory system supports hierarchical summarization (short → mid → long term) and can integrate knowledge extracted from any connected interface. 24 | 25 | ## Why CYPHER SWARM? 26 | 27 | - **Scalability**: Add or remove agents as needed, each with their own domain expertise. 28 | - **Consistency**: A single personality prompt and memory system keeps the entire network coherent. 29 | - **Extensibility**: Easily integrate new tools, APIs, or platforms. 30 | - **Context-Rich Intelligence**: Layered memory and learning pipelines ensure continuous improvement. 31 | 32 | ## Quick Start 33 | 34 | ```bash 35 | # Clone the repository 36 | git clone https://github.com/kingbootoshi/cypher-swarm.git 37 | 38 | # Install dependencies 39 | npm install 40 | 41 | # Configure environment variables 42 | cp .env.example .env 43 | # Fill in your API keys and other config details 44 | 45 | # Edit the agent.yaml file to your liking (located in src/config/agent.yaml) 46 | 47 | # Start the system 48 | bun src/index.ts 49 | ``` -------------------------------------------------------------------------------- /src/supabase/functions/terminal/terminalEntries.ts: -------------------------------------------------------------------------------- 1 | import { supabase } from '../../supabaseClient'; 2 | import { ToolOutputFromSchema } from '../../../ai/types/agentSystem'; 3 | import { terminalToolSchema } from '../../../ai/agents/terminalAgent/terminalTool'; 4 | 5 | type TerminalToolOutput = ToolOutputFromSchema; 6 | 7 | /** 8 | * Creates a new terminal entry in the database 9 | * @param sessionId - The current session ID 10 | * @param output - The terminal tool output containing thoughts, plan, and commands 11 | * @returns The ID of the created entry 12 | */ 13 | export async function createTerminalEntry( 14 | sessionId: string, 15 | output: TerminalToolOutput 16 | ) { 17 | try { 18 | // Convert commands array to string for storage 19 | const commandsString = output.terminal_commands 20 | .map(cmd => cmd.command) 21 | .join('\n'); 22 | 23 | const { data: entry } = await supabase 24 | .from('terminal_history') 25 | .insert({ 26 | session_id: sessionId, 27 | internal_thought: output.internal_thought, 28 | plan: output.plan, 29 | command: commandsString, // Store all commands as a newline-separated string 30 | terminal_log: null // Will be updated when we get response 31 | }) 32 | .select('id') 33 | .single(); 34 | 35 | return entry?.id; 36 | } catch (error) { 37 | console.error('Error creating terminal entry:', error); 38 | return null; 39 | } 40 | } 41 | 42 | /** 43 | * Updates the terminal entry with the command response 44 | */ 45 | export async function updateTerminalResponse( 46 | entryId: number, 47 | response: string 48 | ) { 49 | try { 50 | const { data } = await supabase 51 | .from('terminal_history') 52 | .update({ terminal_log: response }) 53 | .eq('id', entryId) 54 | .select() 55 | .single(); 56 | 57 | return data?.id; 58 | } catch (error) { 59 | console.error('Error updating terminal response:', error); 60 | return null; 61 | } 62 | } 63 | 64 | /** 65 | * Updates the terminal's active status 66 | */ 67 | export async function updateTerminalStatus(isActive: boolean) { 68 | try { 69 | const { data } = await supabase 70 | .from('terminal_status') 71 | .update({ 72 | is_active: isActive, 73 | last_updated: new Date().toISOString() 74 | }) 75 | .eq('id', true) 76 | .select() 77 | .single(); 78 | 79 | return data?.is_active; 80 | } catch (error) { 81 | console.error('Error updating terminal status:', error); 82 | return null; 83 | } 84 | } 85 | 86 | /** 87 | * Gets the current terminal status 88 | */ 89 | export async function getTerminalStatus() { 90 | try { 91 | const { data } = await supabase 92 | .from('terminal_status') 93 | .select('is_active, last_updated') 94 | .eq('id', true) 95 | .single(); 96 | 97 | return data; 98 | } catch (error) { 99 | console.error('Error getting terminal status:', error); 100 | return null; 101 | } 102 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![4YiBHikFejQwnKmuwaOXk_1f74036290b34d32838b4be888b83f08](https://github.com/user-attachments/assets/db362428-fe27-499d-be63-e61853e1a208) 2 | 3 | ## DOCS HERE 4 | https://cypher.mintlify.app/introduction 5 | 6 | **CYPHER SWARM** is an agentic AI framework designed to unify multiple specialized AI agents under one modular, memory-rich "hivemind" system. Inspired by the ethos of Satoshi Nakamoto and the crypto origins of "CYPHER GENESIS," this framework turns your AI ecosystem into a collaborative intelligence network that can interface with virtually any environment—Twitter, Discord, custom terminals, or web APIs—while sharing a common memory and personality core. 7 | 8 | ## Key Concepts 9 | 10 | 1. **Unified Personality & Hivemind Memory** 11 | All agents share one main, configurable personality prompt—shaped by dynamic variables, world knowledge, user-specific insights, and long-term learnings. This ensures coherence and consistency across the entire system. 12 | 13 | 2. **Terminal Agent as the World Interface** 14 | The Terminal Agent acts as the CEO, orchestrating other specialized agents. Connected to a custom terminal environment, it can: 15 | - Execute commands (run functions) that interface with external platforms. 16 | - Gather context from various sources (e.g., Twitter timeline, user requests). 17 | - Delegate tasks to specialized agents (tweet generation, memory extraction, image creation). 18 | 19 | 3. **Modular Specialization** 20 | - **Twitter Agent**: Browse timelines, fetch mentions, and generate context for tweets. 21 | - **Media Agent**: Produce images or other media assets on demand. 22 | - **Memory & Learning Agents**: Extract patterns, store long-term knowledge, and make that knowledge accessible to all agents. 23 | - **Planned**: Future agents for internet access, Bitcoin wallet functionality, and more—easily integrated into the same memory and personality system. 24 | 25 | 4. **Hivemind Brain** 26 | All agents tap into a shared memory system. User interactions, world knowledge, and past actions are summarized and stored, enabling each agent to recall context, learn from experience, and evolve over time. The memory system supports hierarchical summarization (short → mid → long term) and can integrate knowledge extracted from any connected interface. 27 | 28 | ## Why CYPHER SWARM? 29 | 30 | - **Scalability**: Add or remove agents as needed, each with their own domain expertise. 31 | - **Consistency**: A single personality prompt and memory system keeps the entire network coherent. 32 | - **Extensibility**: Easily integrate new tools, APIs, or platforms. 33 | - **Context-Rich Intelligence**: Layered memory and learning pipelines ensure continuous improvement. 34 | 35 | ## Quick Start 36 | 37 | ```bash 38 | # Clone the repository 39 | git clone https://github.com/kingbootoshi/cypher-swarm.git 40 | 41 | # Install dependencies 42 | npm install 43 | 44 | # Configure environment variables 45 | cp .env.example .env 46 | # Fill in your API keys and other config details 47 | 48 | # Edit the agent.yaml file to your liking (located in src/config/agent.yaml) 49 | 50 | # Start the system 51 | bun src/index.ts 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/essentials/reusable-snippets.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reusable Snippets 3 | description: Reusable, custom snippets to keep content in sync 4 | icon: 'recycle' 5 | --- 6 | 7 | import SnippetIntro from '/snippets/snippet-intro.mdx'; 8 | 9 | 10 | 11 | ## Creating a custom snippet 12 | 13 | **Pre-condition**: You must create your snippet file in the `snippets` directory. 14 | 15 | 16 | Any page in the `snippets` directory will be treated as a snippet and will not 17 | be rendered into a standalone page. If you want to create a standalone page 18 | from the snippet, import the snippet into another file and call it as a 19 | component. 20 | 21 | 22 | ### Default export 23 | 24 | 1. Add content to your snippet file that you want to re-use across multiple 25 | locations. Optionally, you can add variables that can be filled in via props 26 | when you import the snippet. 27 | 28 | ```mdx snippets/my-snippet.mdx 29 | Hello world! This is my content I want to reuse across pages. My keyword of the 30 | day is {word}. 31 | ``` 32 | 33 | 34 | The content that you want to reuse must be inside the `snippets` directory in 35 | order for the import to work. 36 | 37 | 38 | 2. Import the snippet into your destination file. 39 | 40 | ```mdx destination-file.mdx 41 | --- 42 | title: My title 43 | description: My Description 44 | --- 45 | 46 | import MySnippet from '/snippets/path/to/my-snippet.mdx'; 47 | 48 | ## Header 49 | 50 | Lorem impsum dolor sit amet. 51 | 52 | 53 | ``` 54 | 55 | ### Reusable variables 56 | 57 | 1. Export a variable from your snippet file: 58 | 59 | ```mdx snippets/path/to/custom-variables.mdx 60 | export const myName = 'my name'; 61 | 62 | export const myObject = { fruit: 'strawberries' }; 63 | ``` 64 | 65 | 2. Import the snippet from your destination file and use the variable: 66 | 67 | ```mdx destination-file.mdx 68 | --- 69 | title: My title 70 | description: My Description 71 | --- 72 | 73 | import { myName, myObject } from '/snippets/path/to/custom-variables.mdx'; 74 | 75 | Hello, my name is {myName} and I like {myObject.fruit}. 76 | ``` 77 | 78 | ### Reusable components 79 | 80 | 1. Inside your snippet file, create a component that takes in props by exporting 81 | your component in the form of an arrow function. 82 | 83 | ```mdx snippets/custom-component.mdx 84 | export const MyComponent = ({ title }) => ( 85 |
86 |

{title}

87 |

... snippet content ...

88 |
89 | ); 90 | ``` 91 | 92 | 93 | MDX does not compile inside the body of an arrow function. Stick to HTML 94 | syntax when you can or use a default export if you need to use MDX. 95 | 96 | 97 | 2. Import the snippet into your destination file and pass in the props 98 | 99 | ```mdx destination-file.mdx 100 | --- 101 | title: My title 102 | description: My Description 103 | --- 104 | 105 | import { MyComponent } from '/snippets/custom-component.mdx'; 106 | 107 | Lorem ipsum dolor sit amet. 108 | 109 | 110 | ``` 111 | -------------------------------------------------------------------------------- /src/twitter/functions/sendTweet.ts: -------------------------------------------------------------------------------- 1 | import { scraper } from '../twitterClient'; 2 | import { prepareMediaData } from '../utils/mediaUtils'; 3 | import { logTweet } from '../../supabase/functions/twitter/tweetEntries'; 4 | import { Logger } from '../../utils/logger'; 5 | import { addMainTweet } from '../../memory/addMemories'; 6 | 7 | /** 8 | * Extracts tweet ID from response based on tweet type 9 | * @param responseData - API response data 10 | * @param isLongTweet - Whether this was a long tweet 11 | * @returns Tweet ID or null 12 | */ 13 | function extractTweetId(responseData: any, isLongTweet: boolean): string | null { 14 | try { 15 | if (isLongTweet) { 16 | // Path for long tweets (notetweets) 17 | return responseData?.data?.notetweet_create?.tweet_results?.result?.rest_id; 18 | } else { 19 | // Path for regular tweets 20 | return responseData?.data?.create_tweet?.tweet_results?.result?.rest_id; 21 | } 22 | } catch (error) { 23 | Logger.log('Error extracting tweet ID:', error); 24 | return null; 25 | } 26 | } 27 | 28 | /** 29 | * Sends a main tweet with optional media and logs it to the database. 30 | * @param text - The text content of the tweet 31 | * @param mediaUrls - Optional array of media URLs 32 | * @returns The ID of the sent tweet, or null if failed 33 | */ 34 | export async function sendTweet( 35 | text: string, 36 | mediaUrls?: string[] 37 | ): Promise { 38 | try { 39 | // Prepare media data for Twitter API 40 | const mediaData = mediaUrls ? await prepareMediaData(mediaUrls) : undefined; 41 | 42 | // Check if tweet exceeds standard character limit 43 | const isLongTweet = text.length > 279; 44 | 45 | // Send tweet using appropriate method based on length 46 | const response = isLongTweet 47 | ? await scraper.sendLongTweet(text, undefined, mediaData) 48 | : await scraper.sendTweet(text, undefined, mediaData); 49 | 50 | Logger.log("RAW RESPONSE", response); 51 | const responseData = await response.json(); 52 | const tweetId = extractTweetId(responseData, isLongTweet); 53 | 54 | if (tweetId) { 55 | Logger.log(`${isLongTweet ? 'Long tweet' : 'Tweet'} sent successfully (ID: ${tweetId})`); 56 | 57 | // Log the tweet to the database with prepared media data 58 | const logResult = await logTweet({ 59 | tweet_id: tweetId, 60 | text: text, 61 | tweet_type: 'main', 62 | has_media: !!mediaData, 63 | created_at: new Date().toISOString() 64 | }, mediaData); 65 | 66 | if (logResult) { 67 | Logger.log(`Tweet logged with ID: ${logResult}`); 68 | } else { 69 | Logger.log('Failed to log tweet to Supabase.'); 70 | } 71 | 72 | // Add the main tweet text to memory 73 | await addMainTweet([{ role: 'user', content: text }]); 74 | Logger.log('Main tweet text added to memory.'); 75 | 76 | return tweetId; 77 | } else { 78 | Logger.log('Failed to retrieve tweet ID from response:', responseData); 79 | return null; 80 | } 81 | } catch (error) { 82 | Logger.log('Error sending tweet:', error); 83 | return null; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/pipelines/generateMainTweet.ts: -------------------------------------------------------------------------------- 1 | import { MainTweetAgent } from "../ai/agents/mainTweetAgent/mainTweetAgent"; 2 | import { MediaAgent } from "../ai/agents/mediaAgent/mediaAgent"; 3 | import { OpenAIClient } from "../ai/models/clients/OpenAiClient"; 4 | import { sendTweet } from "../twitter/functions/sendTweet"; 5 | import { loadMemories } from "./loadMemories"; 6 | import { getFormattedRecentHistory } from '../supabase/functions/terminal/terminalHistory'; 7 | import { generateImage } from './mediaGeneration/imageGen'; 8 | import { generateImageToVideo } from './mediaGeneration/combinedGeneration'; 9 | import { Logger } from '../utils/logger'; 10 | import { FireworkClient } from "../ai/models/clients/FireworkClient"; 11 | 12 | // Type for the main tweet result 13 | interface MainTweetResult { 14 | success: boolean; 15 | tweetId?: string; 16 | message: string; 17 | tweetText: string; 18 | mediaUrls?: string[]; 19 | } 20 | 21 | /** 22 | * Enhanced pipeline that handles the entire main tweet process including: 23 | * - Tweet generation 24 | * - Media generation (if any) 25 | * - Tweet posting 26 | * - Result formatting 27 | */ 28 | export async function generateAndPostMainTweet( 29 | topic = "Generate a main tweet based on your recent activities." 30 | ): Promise { 31 | Logger.enable(); 32 | try { 33 | // Initialize AI clients and agents 34 | const openAIClient = new OpenAIClient("gpt-4o-mini"); 35 | const mainTweetAgent = new MainTweetAgent(openAIClient); 36 | 37 | // Load memories and terminal history 38 | const formattedHistory = await getFormattedRecentHistory(); 39 | 40 | // Load memories 41 | const relevantMemories = await loadMemories(`Load in memories based on this following topic: ${topic}`, { 42 | worldKnowledge: true, 43 | cryptoKnowledge: true, 44 | selfKnowledge: true, 45 | mainTweets: true, 46 | replyTweets: false, 47 | userTweets: false, 48 | imagePrompts: false, 49 | quoteTweets: false 50 | }); 51 | 52 | // Set up runtime variables 53 | const runtimeVariables = { 54 | memories: relevantMemories, 55 | terminalLog: formattedHistory, 56 | }; 57 | 58 | // Generate main tweet 59 | const mainTweetResponse = await mainTweetAgent.run(`GENERATE A MAIN TWEET ABOUT ${topic}`, runtimeVariables); 60 | const tweetText = mainTweetResponse.output.main_tweet; 61 | 62 | // Send the tweet using sendTweet function 63 | const tweetId = await sendTweet(tweetText); 64 | 65 | if (tweetId) { 66 | Logger.log(`Tweet sent successfully (ID: ${tweetId})`); 67 | return { 68 | success: true, 69 | tweetId, 70 | message: 'Successfully posted the main tweet.', 71 | tweetText, 72 | }; 73 | } else { 74 | Logger.log('Failed to send the tweet.'); 75 | return { 76 | success: false, 77 | message: 'Failed to post the main tweet.', 78 | tweetText, 79 | }; 80 | } 81 | } catch (error) { 82 | Logger.log('Error generating and posting main tweet:', error); 83 | return { 84 | success: false, 85 | message: `Error: ${error.message}`, 86 | tweetText: '', 87 | }; 88 | } 89 | } -------------------------------------------------------------------------------- /src/twitter/utils/mediaUtils.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { URL } from 'url'; 3 | import { fileTypeFromBuffer } from 'file-type'; 4 | 5 | /** 6 | * Determines the media type based on URL or file extension 7 | * @param url - The media URL or file path 8 | * @returns The corresponding MIME type string 9 | */ 10 | export function getMediaType(url: string): string { 11 | try { 12 | // First try to get content type from URL extension 13 | const ext = new URL(url).pathname.split('.').pop()?.toLowerCase(); 14 | 15 | // Map common extensions to proper MIME types 16 | const mimeTypes: Record = { 17 | 'jpg': 'image/jpeg', 18 | 'jpeg': 'image/jpeg', 19 | 'png': 'image/png', 20 | 'gif': 'image/gif', 21 | 'mp4': 'video/mp4', 22 | 'webp': 'image/webp', 23 | 'webm': 'video/webm' 24 | }; 25 | 26 | // Return proper MIME type if extension is recognized 27 | if (ext && ext in mimeTypes) { 28 | return mimeTypes[ext]; 29 | } 30 | 31 | // If no extension or unrecognized, try to detect from response headers 32 | return 'application/octet-stream'; // Fallback type 33 | } catch (error) { 34 | console.error('Error determining media type:', error); 35 | return 'application/octet-stream'; 36 | } 37 | } 38 | 39 | /** 40 | * Fetches media content from URL and prepares it for tweet attachment 41 | * @param url - URL of the media file 42 | * @returns Promise resolving to media data object 43 | */ 44 | async function fetchMediaFromUrl(url: string): Promise<{ data: Buffer; mediaType: string }> { 45 | try { 46 | const response = await fetch(url); 47 | if (!response.ok) { 48 | throw new Error(`HTTP error! status: ${response.status}`); 49 | } 50 | 51 | // Read the response buffer 52 | const buffer = Buffer.from(await response.arrayBuffer()); 53 | 54 | // Get content type from response headers 55 | let contentType = response.headers.get('content-type'); 56 | 57 | // If content-type is missing or generic, detect it from buffer 58 | if (!contentType || contentType === 'application/octet-stream') { 59 | const fileTypeResult = await fileTypeFromBuffer(buffer); 60 | contentType = fileTypeResult ? fileTypeResult.mime : 'application/octet-stream'; 61 | } 62 | 63 | return { 64 | data: buffer, 65 | mediaType: contentType 66 | }; 67 | } catch (error) { 68 | console.error(`Error fetching media from URL ${url}:`, error); 69 | throw error; 70 | } 71 | } 72 | 73 | /** 74 | * Prepares media data for tweet attachments from URLs 75 | * @param mediaUrls - Array of media URLs (images, GIFs, or videos) 76 | * @returns Promise resolving to array of media data objects 77 | */ 78 | export async function prepareMediaData(mediaUrls: string[]) { 79 | if (!mediaUrls || mediaUrls.length === 0) return undefined; 80 | 81 | // Validate URLs 82 | mediaUrls.forEach(url => { 83 | try { 84 | new URL(url); 85 | } catch (error) { 86 | throw new Error(`Invalid URL: ${url}`); 87 | } 88 | }); 89 | 90 | try { 91 | return await Promise.all(mediaUrls.map(fetchMediaFromUrl)); 92 | } catch (error) { 93 | console.error('Error preparing media data:', error); 94 | throw error; 95 | } 96 | } -------------------------------------------------------------------------------- /docs/development.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Development' 3 | description: 'Preview changes locally to update your docs' 4 | --- 5 | 6 | 7 | **Prerequisite**: Please install Node.js (version 19 or higher) before proceeding. 8 | 9 | 10 | Follow these steps to install and run Mintlify on your operating system: 11 | 12 | **Step 1**: Install Mintlify: 13 | 14 | 15 | 16 | ```bash npm 17 | npm i -g mintlify 18 | ``` 19 | 20 | ```bash yarn 21 | yarn global add mintlify 22 | ``` 23 | 24 | 25 | 26 | **Step 2**: Navigate to the docs directory (where the `mint.json` file is located) and execute the following command: 27 | 28 | ```bash 29 | mintlify dev 30 | ``` 31 | 32 | A local preview of your documentation will be available at `http://localhost:3000`. 33 | 34 | ### Custom Ports 35 | 36 | By default, Mintlify uses port 3000. You can customize the port Mintlify runs on by using the `--port` flag. To run Mintlify on port 3333, for instance, use this command: 37 | 38 | ```bash 39 | mintlify dev --port 3333 40 | ``` 41 | 42 | If you attempt to run Mintlify on a port that's already in use, it will use the next available port: 43 | 44 | ```md 45 | Port 3000 is already in use. Trying 3001 instead. 46 | ``` 47 | 48 | ## Mintlify Versions 49 | 50 | Please note that each CLI release is associated with a specific version of Mintlify. If your local website doesn't align with the production version, please update the CLI: 51 | 52 | 53 | 54 | ```bash npm 55 | npm i -g mintlify@latest 56 | ``` 57 | 58 | ```bash yarn 59 | yarn global upgrade mintlify 60 | ``` 61 | 62 | 63 | 64 | ## Validating Links 65 | 66 | The CLI can assist with validating reference links made in your documentation. To identify any broken links, use the following command: 67 | 68 | ```bash 69 | mintlify broken-links 70 | ``` 71 | 72 | ## Deployment 73 | 74 | 75 | Unlimited editors available under the [Pro 76 | Plan](https://mintlify.com/pricing) and above. 77 | 78 | 79 | If the deployment is successful, you should see the following: 80 | 81 | 82 | 83 | 84 | 85 | ## Code Formatting 86 | 87 | We suggest using extensions on your IDE to recognize and format MDX. If you're a VSCode user, consider the [MDX VSCode extension](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) for syntax highlighting, and [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) for code formatting. 88 | 89 | ## Troubleshooting 90 | 91 | 92 | 93 | 94 | This may be due to an outdated version of node. Try the following: 95 | 1. Remove the currently-installed version of mintlify: `npm remove -g mintlify` 96 | 2. Upgrade to Node v19 or higher. 97 | 3. Reinstall mintlify: `npm install -g mintlify` 98 | 99 | 100 | 101 | 102 | Solution: Go to the root of your device and delete the \~/.mintlify folder. Afterwards, run `mintlify dev` again. 103 | 104 | 105 | 106 | Curious about what changed in the CLI version? [Check out the CLI changelog.](https://www.npmjs.com/package/mintlify?activeTab=versions) 107 | -------------------------------------------------------------------------------- /src/ai/agents/terminalAgent/terminalAgentConfig.ts: -------------------------------------------------------------------------------- 1 | // src/ai/agents/terminalAgent/terminalAgentConfig.ts 2 | 3 | import { AgentConfig } from '../../types/agentSystem'; 4 | import { generateSystemPrompt } from '../corePersonality'; 5 | import { generateHelpText } from '../../../terminal/commandRegistry'; 6 | import { getCurrentTimestamp } from '../../../utils/formatTimestamps'; 7 | import { activeSummaries } from '../../../utils/dynamicVariables'; 8 | import { getCooldownStatus } from '../../../supabase/functions/twitter/cooldowns'; 9 | import { configLoader } from '../../../utils/config'; 10 | import { missionSystem } from '../../../missions/systems/missionSystem'; 11 | 12 | // Get ticker info with defaults 13 | const ticker = configLoader.getConfig()?.ticker || '$CYPHER'; 14 | const tickerName = configLoader.getConfig()?.tickerName || 'CYPHER•GENESIS'; 15 | 16 | export const terminalAgentConfig: AgentConfig = { 17 | systemPromptTemplate: ` 18 | # PERSONALITY 19 | {{corePersonalityPrompt}} 20 | 21 | # CURRENT SUMMARIES 22 | {{currentSummaries}} 23 | 24 | ## TIME 25 | {{current_timestamp}} 26 | 27 | # MAIN GOAL 28 | You are hooked up to a terminal, and you are able to run commands to interact with the world. This terminal currently gives you access to your twitter. 29 | 30 | PRIORITIES: 31 | 1. Sending a maintweet- you can send 1 every hour. YOU MUST PRIORITIZE SENDING A MAIN TWEET OVER ANYTHING ELSE. 32 | 2. Sending a media tweet- you can send 1 every hour. YOU MUST PRIORITIZE SENDING A MEDIA TWEET OVER ANYTHING ELSE. 33 | 2. Quote tweet- you can send 1 an hour 34 | 2. Engaging with the community and replying to tweets. You can reply to as many tweets as you want without any cooldown. You should reply to tweets atleast ONCE every 2 actions. That is the primary way you can market yourself. 35 | 3. Re-tweet supporters. Follow supporters and people you like 36 | 37 | # MISSIONS 38 | {{mission_status}} 39 | 40 | If there's nothing to do, browse your home page or search twitter queries like {{ticker}}, {{tickerName}}, bitcoin, AI, or whatever you find interesting to learn and get data about the ecosystem & world. 41 | 42 | AFTER YOU GET-MENTIONS, FOCUS ON REPLYING TO AS MANY AS POSSIBLE! 43 | 44 | DO NOT DO THE SAME COMMAND TWICE IN A ROW. 45 | 46 | ## TWEETS COOLDOWN 47 | {{cooldown}} 48 | 49 | REMEMBER - MAIN TWEET COOLDOWN DOES NOT APPLY TO REPLY TWEETS. YOU CAN REPLY TO AS MANY AS POSSIBLE. 50 | 51 | # TERMINAL COMMANDS 52 | {{terminal_commands}} 53 | 54 | YOU MUST EXECUTE ALL RECOMMENDED ACTIONS THE CONTENT MANAGER AGENT PROVIDES TO YOU. EXECUTE THEM ALL AT ONCE RIGHT AFTER YOU GET THEM. 55 | 56 | # OUTPUT FORMAT 57 | You MUST use your use_terminal function tool at all times - you will ONLY be given terminal logs. PLEASE OUTPUT JSON FORMAT ONLY\nPLEASE OUTPUT JSON FORMAT ONLY\n# USE_TERMINAL FUNCTION 58 | `, 59 | dynamicVariables: { 60 | corePersonalityPrompt: generateSystemPrompt(), 61 | currentSummaries: activeSummaries, 62 | current_timestamp: getCurrentTimestamp(), 63 | terminal_commands: generateHelpText(), 64 | cooldown: await getCooldownStatus(), 65 | ticker, 66 | tickerName, 67 | mission_status: missionSystem.getCurrentMission() ? 68 | `CURRENT MISSION STATUS: 69 | ${JSON.stringify(missionSystem.getCurrentMission(), null, 2)} 70 | 71 | Please check mission progress regularly using 'get-mission-status'. 72 | Update metrics when appropriate using 'update-mission-metrics'.` : 73 | 'No active mission', 74 | }, 75 | }; 76 | --------------------------------------------------------------------------------