spora 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/{account-creator-SETL5CGT.js → account-creator-AABUY2JU.js} +5 -5
  2. package/dist/{chunk-Q7YS3AIK.js → chunk-6KCIAMHL.js} +5 -6
  3. package/dist/chunk-6KCIAMHL.js.map +1 -0
  4. package/dist/{chunk-HERI4RPY.js → chunk-A6R5ZGK6.js} +2 -2
  5. package/dist/{chunk-QHFM2YW6.js → chunk-B6VI6L4D.js} +9 -44
  6. package/dist/chunk-B6VI6L4D.js.map +1 -0
  7. package/dist/{chunk-GJFBWIW3.js → chunk-FTFTB5Y5.js} +2 -2
  8. package/dist/{chunk-SXMDYUK3.js → chunk-GMSK775L.js} +29 -6
  9. package/dist/chunk-GMSK775L.js.map +1 -0
  10. package/dist/{chunk-POEDIDM6.js → chunk-H62HH5ZI.js} +2 -2
  11. package/dist/{chunk-NLWU5432.js → chunk-KQ37VL54.js} +5 -5
  12. package/dist/{chunk-JWMADEQO.js → chunk-ML4EMUZC.js} +3 -3
  13. package/dist/chunk-N5TBL3NY.js +86 -0
  14. package/dist/chunk-N5TBL3NY.js.map +1 -0
  15. package/dist/chunk-PNZ3XK2N.js +358 -0
  16. package/dist/chunk-PNZ3XK2N.js.map +1 -0
  17. package/dist/{chunk-J7J557HV.js → chunk-UCCAF2ZO.js} +2 -2
  18. package/dist/{chunk-RNVEWVDN.js → chunk-V6ZNR2SI.js} +2 -26
  19. package/dist/chunk-V6ZNR2SI.js.map +1 -0
  20. package/dist/cli.js +46 -46
  21. package/dist/cli.js.map +1 -1
  22. package/dist/{client-NVI3ZD4G.js → client-BGLXHLID.js} +10 -20
  23. package/dist/client-BGLXHLID.js.map +1 -0
  24. package/dist/client-TWYR2IIQ.js +373 -0
  25. package/dist/client-TWYR2IIQ.js.map +1 -0
  26. package/dist/{colony-J4EZQI37.js → colony-JVBCMZTK.js} +7 -7
  27. package/dist/{config-QRBOL4NX.js → config-5EPXA325.js} +3 -3
  28. package/dist/{crypto-ZVWJLD2J.js → crypto-HS4CGS4A.js} +3 -3
  29. package/dist/heartbeat-B2CZKMUF.js +901 -0
  30. package/dist/heartbeat-B2CZKMUF.js.map +1 -0
  31. package/dist/{identity-LN2R4KJU.js → identity-6CXRCXJQ.js} +3 -3
  32. package/dist/{init-ANGLSI2L.js → init-KXNLBFMG.js} +21 -21
  33. package/dist/init-KXNLBFMG.js.map +1 -0
  34. package/dist/llm-CUCO24K7.js +16 -0
  35. package/dist/mcp-server.js +24 -24
  36. package/dist/{memory-JMXU3UXR.js → memory-2OI3JXY2.js} +3 -3
  37. package/dist/{memory-J6AYZ5Y2.js → memory-LPU2I6NI.js} +3 -5
  38. package/dist/{paths-KXOWF2B2.js → paths-Q4TJEOMQ.js} +2 -2
  39. package/dist/prompt-builder-VHGZFBL6.js +19 -0
  40. package/dist/queue-LNBQWMFX.js +14 -0
  41. package/dist/web-chat/chat.html +38 -958
  42. package/dist/web-chat-DHHJTGFZ.js +253 -0
  43. package/dist/web-chat-DHHJTGFZ.js.map +1 -0
  44. package/dist/x-client-W5IB7XOM.js +12 -0
  45. package/package.json +2 -1
  46. package/dist/chunk-DFSYD45Q.js +0 -665
  47. package/dist/chunk-DFSYD45Q.js.map +0 -1
  48. package/dist/chunk-FCAK5FYQ.js +0 -127
  49. package/dist/chunk-FCAK5FYQ.js.map +0 -1
  50. package/dist/chunk-LRKBNKMQ.js +0 -79
  51. package/dist/chunk-LRKBNKMQ.js.map +0 -1
  52. package/dist/chunk-Q7YS3AIK.js.map +0 -1
  53. package/dist/chunk-QHFM2YW6.js.map +0 -1
  54. package/dist/chunk-R7PAD4OL.js +0 -44
  55. package/dist/chunk-R7PAD4OL.js.map +0 -1
  56. package/dist/chunk-RNVEWVDN.js.map +0 -1
  57. package/dist/chunk-SUFTVQME.js +0 -82
  58. package/dist/chunk-SUFTVQME.js.map +0 -1
  59. package/dist/chunk-SXMDYUK3.js.map +0 -1
  60. package/dist/chunk-YZ7RWJ6Z.js +0 -262
  61. package/dist/chunk-YZ7RWJ6Z.js.map +0 -1
  62. package/dist/client-23THPNVL.js +0 -382
  63. package/dist/client-23THPNVL.js.map +0 -1
  64. package/dist/client-NVI3ZD4G.js.map +0 -1
  65. package/dist/decision-engine-WBD36PZI.js +0 -19
  66. package/dist/goals-IM4AEHS4.js +0 -12
  67. package/dist/heartbeat-35HVB5PB.js +0 -317
  68. package/dist/heartbeat-35HVB5PB.js.map +0 -1
  69. package/dist/image-search-SZVMGWLN.js +0 -45
  70. package/dist/image-search-SZVMGWLN.js.map +0 -1
  71. package/dist/init-ANGLSI2L.js.map +0 -1
  72. package/dist/llm-MHZG2VHU.js +0 -16
  73. package/dist/performance-7G6R6ELJ.js +0 -18
  74. package/dist/prompt-builder-NSU4IFPB.js +0 -28
  75. package/dist/prompt-builder-NSU4IFPB.js.map +0 -1
  76. package/dist/queue-MLRTMJRE.js +0 -14
  77. package/dist/queue-MLRTMJRE.js.map +0 -1
  78. package/dist/strategy-TOVFBIZQ.js +0 -12
  79. package/dist/strategy-TOVFBIZQ.js.map +0 -1
  80. package/dist/web-chat/logo.png +0 -0
  81. package/dist/web-chat-N2AYUWT7.js +0 -802
  82. package/dist/web-chat-N2AYUWT7.js.map +0 -1
  83. package/dist/x-client-HUXCQOAW.js +0 -12
  84. package/dist/x-client-HUXCQOAW.js.map +0 -1
  85. /package/dist/{account-creator-SETL5CGT.js.map → account-creator-AABUY2JU.js.map} +0 -0
  86. /package/dist/{chunk-HERI4RPY.js.map → chunk-A6R5ZGK6.js.map} +0 -0
  87. /package/dist/{chunk-GJFBWIW3.js.map → chunk-FTFTB5Y5.js.map} +0 -0
  88. /package/dist/{chunk-POEDIDM6.js.map → chunk-H62HH5ZI.js.map} +0 -0
  89. /package/dist/{chunk-NLWU5432.js.map → chunk-KQ37VL54.js.map} +0 -0
  90. /package/dist/{chunk-JWMADEQO.js.map → chunk-ML4EMUZC.js.map} +0 -0
  91. /package/dist/{chunk-J7J557HV.js.map → chunk-UCCAF2ZO.js.map} +0 -0
  92. /package/dist/{colony-J4EZQI37.js.map → colony-JVBCMZTK.js.map} +0 -0
  93. /package/dist/{config-QRBOL4NX.js.map → config-5EPXA325.js.map} +0 -0
  94. /package/dist/{crypto-ZVWJLD2J.js.map → crypto-HS4CGS4A.js.map} +0 -0
  95. /package/dist/{decision-engine-WBD36PZI.js.map → identity-6CXRCXJQ.js.map} +0 -0
  96. /package/dist/{goals-IM4AEHS4.js.map → llm-CUCO24K7.js.map} +0 -0
  97. /package/dist/{identity-LN2R4KJU.js.map → memory-2OI3JXY2.js.map} +0 -0
  98. /package/dist/{llm-MHZG2VHU.js.map → memory-LPU2I6NI.js.map} +0 -0
  99. /package/dist/{memory-J6AYZ5Y2.js.map → paths-Q4TJEOMQ.js.map} +0 -0
  100. /package/dist/{memory-JMXU3UXR.js.map → prompt-builder-VHGZFBL6.js.map} +0 -0
  101. /package/dist/{paths-KXOWF2B2.js.map → queue-LNBQWMFX.js.map} +0 -0
  102. /package/dist/{performance-7G6R6ELJ.js.map → x-client-W5IB7XOM.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/x-client/api/client.ts"],"sourcesContent":["import { TwitterApi } from \"twitter-api-v2\";\nimport { loadCredentials } from \"../../utils/crypto.js\";\nimport { loadConfig } from \"../../utils/config.js\";\nimport { loadIdentity, identityExists } from \"../../identity/index.js\";\nimport { logInteraction } from \"../../memory/index.js\";\nimport { rateLimiter } from \"../rate-limiter.js\";\nimport { logger } from \"../../utils/logger.js\";\nimport type {\n XClientInterface,\n Tweet,\n UserProfile,\n PostResult,\n TimelineOptions,\n SearchOptions,\n} from \"../types.js\";\n\nexport class XApiClient implements XClientInterface {\n private client: TwitterApi;\n private userId: string | null = null;\n private authenticatedHandle: string | null = null;\n\n constructor() {\n const creds = loadCredentials();\n if (creds.method !== \"api\") {\n throw new Error(\"API client requires API credentials. Current method: browser\");\n }\n\n // Use OAuth 1.0a User Context for all endpoints\n this.client = new TwitterApi({\n appKey: creds.apiKey!,\n appSecret: creds.apiSecret!,\n accessToken: creds.accessToken!,\n accessSecret: creds.accessTokenSecret!,\n });\n }\n\n private async getUserId(): Promise<string> {\n if (this.userId) return this.userId;\n try {\n // Use v2.me() to get the authenticated user — always reliable\n const me = await this.client.v2.me();\n this.userId = me.data.id;\n this.authenticatedHandle = me.data.username;\n logger.info(`Authenticated as @${this.authenticatedHandle} (ID: ${this.userId})`);\n return this.userId;\n } catch (error) {\n logger.error(\"Failed to get authenticated user\", error);\n throw error;\n }\n }\n\n /**\n * Get the real Twitter handle of the authenticated user\n */\n async getAuthenticatedHandle(): Promise<string> {\n if (this.authenticatedHandle) return this.authenticatedHandle;\n await this.getUserId(); // This populates authenticatedHandle\n return this.authenticatedHandle!;\n }\n\n async postTweet(content: string): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n const result = await this.client.v2.tweet(content);\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"post\",\n tweetId: result.data.id,\n content,\n creditsUsed: 1,\n success: true,\n });\n\n return { success: true, tweetId: result.data.id };\n } catch (error) {\n logger.error(\"Failed to post tweet\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async postTweetWithMedia(content: string, mediaBuffer: Buffer): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n // Upload media via v1 API\n const mediaId = await this.client.v1.uploadMedia(mediaBuffer, { mimeType: \"image/jpeg\" });\n\n // Post tweet with media attached\n const result = await this.client.v2.tweet({\n text: content,\n media: { media_ids: [mediaId] },\n });\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"post\",\n tweetId: result.data.id,\n content: content + \" [with image]\",\n creditsUsed: 1,\n success: true,\n });\n\n return { success: true, tweetId: result.data.id };\n } catch (error) {\n logger.error(\"Failed to post tweet with media\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async replyToTweet(tweetId: string, content: string): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n const result = await this.client.v2.reply(content, tweetId);\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"reply\",\n tweetId: result.data.id,\n inReplyTo: tweetId,\n content,\n creditsUsed: 1,\n success: true,\n });\n\n return { success: true, tweetId: result.data.id };\n } catch (error) {\n logger.error(\"Failed to reply\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async replyToTweetWithMedia(tweetId: string, content: string, mediaBuffer: Buffer): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n const mediaId = await this.client.v1.uploadMedia(mediaBuffer, { mimeType: \"image/jpeg\" });\n\n const result = await this.client.v2.reply(content, tweetId, {\n media: { media_ids: [mediaId] },\n });\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"reply\",\n tweetId: result.data.id,\n inReplyTo: tweetId,\n content: content + \" [with image]\",\n creditsUsed: 1,\n success: true,\n });\n\n return { success: true, tweetId: result.data.id };\n } catch (error) {\n logger.error(\"Failed to reply with media\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async deleteTweet(tweetId: string): Promise<PostResult> {\n try {\n await this.client.v2.deleteTweet(tweetId);\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async likeTweet(tweetId: string): Promise<PostResult> {\n rateLimiter.requireBasicTier(\"Like\");\n try {\n const userId = await this.getUserId();\n logger.info(`Like attempt: userId=${userId}, tweetId=${tweetId}`);\n await this.client.v2.like(userId, tweetId);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"like\",\n tweetId,\n creditsUsed: 0,\n success: true,\n });\n\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unlikeTweet(tweetId: string): Promise<PostResult> {\n rateLimiter.requireBasicTier(\"Unlike\");\n try {\n const userId = await this.getUserId();\n await this.client.v2.unlike(userId, tweetId);\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async retweet(tweetId: string): Promise<PostResult> {\n rateLimiter.requireBasicTier(\"Retweet\");\n try {\n const userId = await this.getUserId();\n await this.client.v2.retweet(userId, tweetId);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"retweet\",\n tweetId,\n creditsUsed: 0,\n success: true,\n });\n\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unretweet(tweetId: string): Promise<PostResult> {\n rateLimiter.requireBasicTier(\"Unretweet\");\n try {\n const userId = await this.getUserId();\n await this.client.v2.unretweet(userId, tweetId);\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async followUser(userId: string): Promise<PostResult> {\n rateLimiter.requireBasicTier(\"Follow\");\n try {\n const myId = await this.getUserId();\n await this.client.v2.follow(myId, userId);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"follow\",\n targetUserId: userId,\n creditsUsed: 0,\n success: true,\n });\n\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unfollowUser(userId: string): Promise<PostResult> {\n rateLimiter.requireBasicTier(\"Unfollow\");\n try {\n const myId = await this.getUserId();\n await this.client.v2.unfollow(myId, userId);\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async getTimeline(options?: TimelineOptions): Promise<Tweet[]> {\n rateLimiter.requireBasicTier(\"Read timeline\");\n try {\n const userId = await this.getUserId();\n const result = await this.client.v2.homeTimeline({\n max_results: options?.count ?? 20,\n \"tweet.fields\": [\"created_at\", \"public_metrics\", \"in_reply_to_user_id\"],\n expansions: [\"author_id\"],\n \"user.fields\": [\"username\"],\n ...(options?.sinceId ? { since_id: options.sinceId } : {}),\n });\n\n if (!result.data?.data) return [];\n\n const userMap = new Map<string, string>();\n for (const user of result.includes?.users ?? []) {\n userMap.set(user.id, user.username);\n }\n\n return result.data.data.map((tweet) => ({\n id: tweet.id,\n text: tweet.text,\n authorId: tweet.author_id ?? \"unknown\",\n authorHandle: userMap.get(tweet.author_id ?? \"\") ?? \"unknown\",\n createdAt: tweet.created_at ?? new Date().toISOString(),\n likeCount: tweet.public_metrics?.like_count,\n retweetCount: tweet.public_metrics?.retweet_count,\n replyCount: tweet.public_metrics?.reply_count,\n }));\n } catch (error) {\n logger.error(\"Failed to read timeline\", error);\n return [];\n }\n }\n\n async getMentions(options?: TimelineOptions): Promise<Tweet[]> {\n rateLimiter.requireBasicTier(\"Read mentions\");\n try {\n const userId = await this.getUserId();\n const result = await this.client.v2.userMentionTimeline(userId, {\n max_results: options?.count ?? 20,\n \"tweet.fields\": [\"created_at\", \"public_metrics\"],\n expansions: [\"author_id\"],\n \"user.fields\": [\"username\"],\n ...(options?.sinceId ? { since_id: options.sinceId } : {}),\n });\n\n if (!result.data?.data) return [];\n\n const userMap = new Map<string, string>();\n for (const user of result.includes?.users ?? []) {\n userMap.set(user.id, user.username);\n }\n\n return result.data.data.map((tweet) => ({\n id: tweet.id,\n text: tweet.text,\n authorId: tweet.author_id ?? \"unknown\",\n authorHandle: userMap.get(tweet.author_id ?? \"\") ?? \"unknown\",\n createdAt: tweet.created_at ?? new Date().toISOString(),\n likeCount: tweet.public_metrics?.like_count,\n retweetCount: tweet.public_metrics?.retweet_count,\n replyCount: tweet.public_metrics?.reply_count,\n }));\n } catch (error) {\n logger.error(\"Failed to read mentions\", error);\n return [];\n }\n }\n\n async searchTweets(query: string, options?: SearchOptions): Promise<Tweet[]> {\n rateLimiter.requireBasicTier(\"Search tweets\");\n try {\n const result = await this.client.v2.search(query, {\n max_results: options?.count ?? 20,\n \"tweet.fields\": [\"created_at\", \"public_metrics\"],\n expansions: [\"author_id\"],\n \"user.fields\": [\"username\"],\n });\n\n if (!result.data?.data) return [];\n\n const userMap = new Map<string, string>();\n for (const user of result.includes?.users ?? []) {\n userMap.set(user.id, user.username);\n }\n\n return result.data.data.map((tweet) => ({\n id: tweet.id,\n text: tweet.text,\n authorId: tweet.author_id ?? \"unknown\",\n authorHandle: userMap.get(tweet.author_id ?? \"\") ?? \"unknown\",\n createdAt: tweet.created_at ?? new Date().toISOString(),\n likeCount: tweet.public_metrics?.like_count,\n retweetCount: tweet.public_metrics?.retweet_count,\n replyCount: tweet.public_metrics?.reply_count,\n }));\n } catch (error) {\n logger.error(\"Failed to search tweets\", error);\n return [];\n }\n }\n\n async getTweet(tweetId: string): Promise<Tweet | null> {\n rateLimiter.requireBasicTier(\"Get tweet\");\n try {\n const result = await this.client.v2.singleTweet(tweetId, {\n \"tweet.fields\": [\"created_at\", \"public_metrics\"],\n expansions: [\"author_id\"],\n \"user.fields\": [\"username\"],\n });\n if (!result.data) return null;\n const tweet = result.data;\n const author = result.includes?.users?.[0];\n return {\n id: tweet.id,\n text: tweet.text,\n authorId: tweet.author_id ?? \"unknown\",\n authorHandle: author?.username ?? \"unknown\",\n createdAt: tweet.created_at ?? new Date().toISOString(),\n likeCount: tweet.public_metrics?.like_count,\n retweetCount: tweet.public_metrics?.retweet_count,\n replyCount: tweet.public_metrics?.reply_count,\n };\n } catch {\n return null;\n }\n }\n\n async getProfile(handle: string): Promise<UserProfile> {\n rateLimiter.requireBasicTier(\"Get profile\");\n const result = await this.client.v2.userByUsername(handle, {\n \"user.fields\": [\"description\", \"public_metrics\", \"verified\", \"profile_image_url\"],\n });\n\n return {\n id: result.data.id,\n handle: result.data.username,\n name: result.data.name,\n bio: result.data.description ?? \"\",\n followersCount: result.data.public_metrics?.followers_count ?? 0,\n followingCount: result.data.public_metrics?.following_count ?? 0,\n tweetCount: result.data.public_metrics?.tweet_count ?? 0,\n verified: result.data.verified ?? false,\n profileImageUrl: result.data.profile_image_url,\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/x-client/browser/client.ts"],"sourcesContent":["import { chromium, type Browser, type Page, type BrowserContext } from \"playwright\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { loadCredentials } from \"../../utils/crypto.js\";\nimport { loadIdentity, identityExists } from \"../../identity/index.js\";\nimport { logInteraction } from \"../../memory/index.js\";\nimport { rateLimiter } from \"../rate-limiter.js\";\nimport { paths, ensureDirectories } from \"../../utils/paths.js\";\nimport { logger } from \"../../utils/logger.js\";\nimport type {\n XClientInterface,\n Tweet,\n UserProfile,\n PostResult,\n TimelineOptions,\n SearchOptions,\n} from \"../types.js\";\n\nconst X_BASE = \"https://x.com\";\n\nexport class XBrowserClient implements XClientInterface {\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private page: Page | null = null;\n\n private async ensureBrowser(): Promise<Page> {\n if (this.page && !this.page.isClosed()) return this.page;\n\n this.browser = await chromium.launch({ headless: true });\n\n // Restore session if available\n if (existsSync(paths.browserAuth)) {\n const storageState = JSON.parse(readFileSync(paths.browserAuth, \"utf-8\"));\n this.context = await this.browser.newContext({ storageState });\n } else {\n this.context = await this.browser.newContext();\n }\n\n this.page = await this.context.newPage();\n\n // Check if we're logged in\n await this.page.goto(X_BASE, { waitUntil: \"domcontentloaded\" });\n const isLoggedIn = await this.page\n .locator('[data-testid=\"SideNav_AccountSwitcher_Button\"]')\n .isVisible({ timeout: 5000 })\n .catch(() => false);\n\n if (!isLoggedIn) {\n await this.login();\n }\n\n return this.page;\n }\n\n private async login(): Promise<void> {\n const creds = loadCredentials();\n if (!creds.username || !creds.password) {\n throw new Error(\"Browser mode requires username and password credentials\");\n }\n\n const page = this.page!;\n\n await page.goto(`${X_BASE}/login`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n // Enter username\n const usernameInput = page.locator('input[autocomplete=\"username\"]');\n await usernameInput.waitFor({ timeout: 10000 });\n await usernameInput.fill(creds.username);\n await page.locator('text=Next').click();\n await page.waitForTimeout(2000);\n\n // Check for email verification step\n const emailInput = page.locator('input[data-testid=\"ocfEnterTextTextInput\"]');\n const emailVisible = await emailInput.isVisible({ timeout: 3000 }).catch(() => false);\n if (emailVisible && creds.email) {\n await emailInput.fill(creds.email);\n await page.locator('text=Next').click();\n await page.waitForTimeout(2000);\n }\n\n // Enter password\n const passwordInput = page.locator('input[type=\"password\"]');\n await passwordInput.waitFor({ timeout: 10000 });\n await passwordInput.fill(creds.password);\n await page.locator('[data-testid=\"LoginForm_Login_Button\"]').click();\n await page.waitForTimeout(3000);\n\n // Save session\n await this.saveSession();\n logger.info(\"Logged into X via browser\");\n }\n\n private async saveSession(): Promise<void> {\n if (!this.context) return;\n ensureDirectories();\n const state = await this.context.storageState();\n writeFileSync(paths.browserAuth, JSON.stringify(state));\n }\n\n private getHandle(): string {\n if (identityExists()) {\n return loadIdentity().handle;\n }\n const creds = loadCredentials();\n if (creds.username) return creds.username;\n throw new Error(\"No handle found. Create a Spore identity first.\");\n }\n\n private async waitForNavigation(page: Page, timeout = 5000): Promise<void> {\n await page.waitForTimeout(timeout);\n }\n\n async postTweet(content: string): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n const page = await this.ensureBrowser();\n await page.goto(X_BASE, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n // Click the tweet compose area\n const composeButton = page.locator('[data-testid=\"tweetButtonInline\"]').first();\n const composeArea = page.locator('[data-testid=\"tweetTextarea_0\"]').first();\n\n // Try clicking the compose area directly\n const areaVisible = await composeArea.isVisible({ timeout: 3000 }).catch(() => false);\n if (!areaVisible) {\n // Click the compose button in sidebar\n await page.locator('[data-testid=\"SideNav_NewTweet_Button\"]').click();\n await page.waitForTimeout(1000);\n }\n\n // Type the tweet\n const textarea = page.locator('[data-testid=\"tweetTextarea_0\"]').first();\n await textarea.waitFor({ timeout: 5000 });\n await textarea.fill(content);\n await page.waitForTimeout(500);\n\n // Click the post button\n await page.locator('[data-testid=\"tweetButton\"]').click();\n await page.waitForTimeout(3000);\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"post\",\n content,\n creditsUsed: 1,\n success: true,\n });\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n logger.error(\"Failed to post tweet via browser\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async postTweetWithMedia(content: string, _mediaBuffer: Buffer): Promise<PostResult> {\n // Browser client doesn't support media upload — fall back to text-only post\n logger.warn(\"Browser client does not support media upload, posting without image\");\n return this.postTweet(content);\n }\n\n async replyToTweetWithMedia(tweetId: string, content: string, _mediaBuffer: Buffer): Promise<PostResult> {\n logger.warn(\"Browser client does not support media upload, replying without image\");\n return this.replyToTweet(tweetId, content);\n }\n\n async replyToTweet(tweetId: string, content: string): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n const page = await this.ensureBrowser();\n\n // Navigate to the tweet using /i/status/ which works for any author's tweet\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n // Click reply area\n const replyArea = page.locator('[data-testid=\"tweetTextarea_0\"]').first();\n await replyArea.waitFor({ timeout: 5000 });\n await replyArea.fill(content);\n await page.waitForTimeout(500);\n\n // Click reply button\n await page.locator('[data-testid=\"tweetButton\"]').click();\n await page.waitForTimeout(3000);\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"reply\",\n inReplyTo: tweetId,\n content,\n creditsUsed: 1,\n success: true,\n });\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n logger.error(\"Failed to reply via browser\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async deleteTweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n const handle = this.getHandle();\n\n await page.goto(`${X_BASE}/${handle}/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n // Click the more options menu\n await page.locator('[data-testid=\"caret\"]').first().click();\n await page.waitForTimeout(1000);\n\n // Click delete\n await page.locator('text=Delete').click();\n await page.waitForTimeout(1000);\n\n // Confirm\n await page.locator('[data-testid=\"confirmationSheetConfirm\"]').click();\n await page.waitForTimeout(2000);\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async likeTweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n\n // Navigate to a page that shows the tweet, then like it\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"like\"]').first().click();\n await page.waitForTimeout(1000);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"like\",\n tweetId,\n creditsUsed: 0,\n success: true,\n });\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unlikeTweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"unlike\"]').first().click();\n await page.waitForTimeout(1000);\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async retweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"retweet\"]').first().click();\n await page.waitForTimeout(1000);\n await page.locator('[data-testid=\"retweetConfirm\"]').click();\n await page.waitForTimeout(1000);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"retweet\",\n tweetId,\n creditsUsed: 0,\n success: true,\n });\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unretweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"unretweet\"]').first().click();\n await page.waitForTimeout(1000);\n await page.locator('[data-testid=\"unretweetConfirm\"]').click();\n await page.waitForTimeout(1000);\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async followUser(userId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n // userId here may be a handle in browser mode\n await page.goto(`${X_BASE}/${userId}`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n const followButton = page.locator('[data-testid$=\"-follow\"]').first();\n await followButton.click();\n await page.waitForTimeout(1000);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"follow\",\n targetUserId: userId,\n creditsUsed: 0,\n success: true,\n });\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unfollowUser(userId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/${userId}`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n const unfollowButton = page.locator('[data-testid$=\"-unfollow\"]').first();\n await unfollowButton.click();\n await page.waitForTimeout(1000);\n\n // Confirm unfollow\n await page.locator('[data-testid=\"confirmationSheetConfirm\"]').click();\n await page.waitForTimeout(1000);\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async getTimeline(options?: TimelineOptions): Promise<Tweet[]> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(X_BASE, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(3000);\n\n const tweets = await this.scrapeTweets(page, options?.count ?? 20);\n return tweets;\n } catch (error) {\n logger.error(\"Failed to read timeline via browser\", error);\n return [];\n }\n }\n\n async getMentions(options?: TimelineOptions): Promise<Tweet[]> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/notifications/mentions`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(3000);\n\n const tweets = await this.scrapeTweets(page, options?.count ?? 20);\n return tweets;\n } catch (error) {\n logger.error(\"Failed to read mentions via browser\", error);\n return [];\n }\n }\n\n async searchTweets(query: string, options?: SearchOptions): Promise<Tweet[]> {\n try {\n const page = await this.ensureBrowser();\n const encodedQuery = encodeURIComponent(query);\n await page.goto(`${X_BASE}/search?q=${encodedQuery}&f=live`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(3000);\n\n const tweets = await this.scrapeTweets(page, options?.count ?? 20);\n return tweets;\n } catch (error) {\n logger.error(\"Failed to search via browser\", error);\n return [];\n }\n }\n\n async getTweet(_tweetId: string): Promise<Tweet | null> {\n // Browser client can't reliably look up individual tweets — degrade gracefully\n return null;\n }\n\n async getProfile(handle: string): Promise<UserProfile> {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/${handle}`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(3000);\n\n const name =\n (await page\n .locator('[data-testid=\"UserName\"] span')\n .first()\n .textContent()) ?? handle;\n\n const bio =\n (await page\n .locator('[data-testid=\"UserDescription\"]')\n .textContent()\n .catch(() => \"\")) ?? \"\";\n\n // Parse follower/following counts from the profile page\n const followersText =\n (await page\n .locator(`a[href=\"/${handle}/verified_followers\"] span`)\n .first()\n .textContent()\n .catch(() => \"0\")) ?? \"0\";\n\n const followingText =\n (await page\n .locator(`a[href=\"/${handle}/following\"] span`)\n .first()\n .textContent()\n .catch(() => \"0\")) ?? \"0\";\n\n await this.saveSession();\n\n return {\n id: handle,\n handle,\n name,\n bio,\n followersCount: parseInt(followersText.replace(/[,K.M]/g, \"\")) || 0,\n followingCount: parseInt(followingText.replace(/[,K.M]/g, \"\")) || 0,\n tweetCount: 0,\n verified: false,\n };\n }\n\n private async scrapeTweets(page: Page, count: number): Promise<Tweet[]> {\n const tweets: Tweet[] = [];\n\n const articles = page.locator('article[data-testid=\"tweet\"]');\n const articleCount = await articles.count();\n\n for (let i = 0; i < Math.min(articleCount, count); i++) {\n try {\n const article = articles.nth(i);\n\n const tweetText =\n (await article\n .locator('[data-testid=\"tweetText\"]')\n .textContent()\n .catch(() => \"\")) ?? \"\";\n\n const userLink = article\n .locator('a[role=\"link\"][href*=\"/\"]')\n .first();\n const href = (await userLink.getAttribute(\"href\").catch(() => \"\")) ?? \"\";\n const authorHandle = href.replace(\"/\", \"\");\n\n const time = article.locator(\"time\").first();\n const datetime = (await time.getAttribute(\"datetime\").catch(() => \"\")) ?? \"\";\n\n tweets.push({\n id: `browser-${Date.now()}-${i}`,\n text: tweetText,\n authorId: authorHandle,\n authorHandle,\n createdAt: datetime,\n });\n } catch {\n continue;\n }\n }\n\n return tweets;\n }\n\n async close(): Promise<void> {\n if (this.page) await this.page.close().catch(() => {});\n if (this.context) await this.context.close().catch(() => {});\n if (this.browser) await this.browser.close().catch(() => {});\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,19 +0,0 @@
1
- import {
2
- executeAction,
3
- executeActions,
4
- parseActions
5
- } from "./chunk-YZ7RWJ6Z.js";
6
- import "./chunk-NLWU5432.js";
7
- import "./chunk-FCAK5FYQ.js";
8
- import "./chunk-QHFM2YW6.js";
9
- import "./chunk-SXMDYUK3.js";
10
- import "./chunk-GJFBWIW3.js";
11
- import "./chunk-J7J557HV.js";
12
- import "./chunk-RNVEWVDN.js";
13
- import "./chunk-Q7YS3AIK.js";
14
- export {
15
- executeAction,
16
- executeActions,
17
- parseActions
18
- };
19
- //# sourceMappingURL=decision-engine-WBD36PZI.js.map
@@ -1,12 +0,0 @@
1
- import {
2
- loadGoals,
3
- renderGoalsForPrompt,
4
- saveGoals
5
- } from "./chunk-R7PAD4OL.js";
6
- import "./chunk-Q7YS3AIK.js";
7
- export {
8
- loadGoals,
9
- renderGoalsForPrompt,
10
- saveGoals
11
- };
12
- //# sourceMappingURL=goals-IM4AEHS4.js.map
@@ -1,317 +0,0 @@
1
- import {
2
- executeActions,
3
- parseActions
4
- } from "./chunk-YZ7RWJ6Z.js";
5
- import {
6
- buildHeartbeatUserMessage,
7
- buildReflectionPrompt,
8
- buildSystemPrompt,
9
- parseReflection
10
- } from "./chunk-DFSYD45Q.js";
11
- import {
12
- generateResponse
13
- } from "./chunk-SUFTVQME.js";
14
- import {
15
- loadStrategy,
16
- saveStrategy
17
- } from "./chunk-LRKBNKMQ.js";
18
- import "./chunk-R7PAD4OL.js";
19
- import "./chunk-JWMADEQO.js";
20
- import {
21
- getXClient
22
- } from "./chunk-NLWU5432.js";
23
- import {
24
- getActiveTrackedPosts,
25
- getPerformanceSummary,
26
- retireOldPosts,
27
- updatePostMetrics,
28
- updateSelfMetrics
29
- } from "./chunk-FCAK5FYQ.js";
30
- import {
31
- flushQueue
32
- } from "./chunk-QHFM2YW6.js";
33
- import {
34
- loadConfig
35
- } from "./chunk-SXMDYUK3.js";
36
- import {
37
- loadIdentity
38
- } from "./chunk-GJFBWIW3.js";
39
- import {
40
- logger
41
- } from "./chunk-J7J557HV.js";
42
- import {
43
- addLearning
44
- } from "./chunk-RNVEWVDN.js";
45
- import {
46
- paths
47
- } from "./chunk-Q7YS3AIK.js";
48
-
49
- // src/runtime/heartbeat.ts
50
- import { existsSync, unlinkSync, writeFileSync, readFileSync } from "fs";
51
- var running = false;
52
- var lastMentionsSinceId;
53
- function isRunning() {
54
- return running;
55
- }
56
- function requestStop() {
57
- writeFileSync(paths.stopSignal, "stop");
58
- logger.info("Stop signal sent.");
59
- }
60
- function shouldStop() {
61
- if (existsSync(paths.stopSignal)) {
62
- unlinkSync(paths.stopSignal);
63
- return true;
64
- }
65
- return false;
66
- }
67
- function writePid() {
68
- writeFileSync(paths.runtimePid, String(process.pid));
69
- }
70
- function clearPid() {
71
- if (existsSync(paths.runtimePid)) {
72
- unlinkSync(paths.runtimePid);
73
- }
74
- }
75
- function getRunningPid() {
76
- if (!existsSync(paths.runtimePid)) return null;
77
- const pid = parseInt(readFileSync(paths.runtimePid, "utf-8").trim(), 10);
78
- if (isNaN(pid)) return null;
79
- try {
80
- process.kill(pid, 0);
81
- return pid;
82
- } catch {
83
- clearPid();
84
- return null;
85
- }
86
- }
87
- async function startHeartbeatLoop() {
88
- const existingPid = getRunningPid();
89
- if (existingPid) {
90
- throw new Error(`Spora is already running (PID ${existingPid}). Run \`spora stop\` first.`);
91
- }
92
- running = true;
93
- writePid();
94
- const config = loadConfig();
95
- const intervalMs = config.runtime?.heartbeatIntervalMs ?? 6e4;
96
- const maxActions = config.runtime?.actionsPerHeartbeat ?? 3;
97
- logger.info(`Spora agent starting. Heartbeat interval: ${intervalMs / 1e3}s, max actions: ${maxActions}`);
98
- console.log(`
99
- Spora agent is running (PID ${process.pid})`);
100
- console.log(`Heartbeat every ${Math.round(intervalMs / 6e4)} minutes`);
101
- console.log(`Press Ctrl+C or run \`spora stop\` to stop.
102
- `);
103
- const shutdown = () => {
104
- logger.info("Shutting down...");
105
- running = false;
106
- clearPid();
107
- process.exit(0);
108
- };
109
- process.on("SIGINT", shutdown);
110
- process.on("SIGTERM", shutdown);
111
- if (existsSync(paths.stopSignal)) {
112
- unlinkSync(paths.stopSignal);
113
- }
114
- let heartbeatCount = 0;
115
- while (running) {
116
- heartbeatCount++;
117
- logger.info(`=== Heartbeat #${heartbeatCount} ===`);
118
- try {
119
- await runHeartbeat(maxActions, intervalMs, heartbeatCount);
120
- } catch (error) {
121
- logger.error("Heartbeat error", error);
122
- console.error(`Heartbeat #${heartbeatCount} failed: ${error.message}`);
123
- }
124
- if (shouldStop()) {
125
- logger.info("Stop signal received.");
126
- break;
127
- }
128
- const jitter = Math.floor(Math.random() * intervalMs * 0.3);
129
- const sleepMs = intervalMs + jitter;
130
- logger.info(`Sleeping ${Math.round(sleepMs / 1e3)}s until next heartbeat...`);
131
- const chunkMs = 1e4;
132
- let slept = 0;
133
- while (slept < sleepMs && running) {
134
- await new Promise((r) => setTimeout(r, Math.min(chunkMs, sleepMs - slept)));
135
- slept += chunkMs;
136
- if (shouldStop()) {
137
- running = false;
138
- break;
139
- }
140
- try {
141
- const flushed = await flushQueue();
142
- if (flushed.posted > 0) {
143
- logger.info(`Flushed ${flushed.posted} scheduled post(s) during sleep`);
144
- }
145
- } catch {
146
- }
147
- }
148
- }
149
- clearPid();
150
- logger.info("Spora agent stopped.");
151
- console.log("\nSpora agent stopped.");
152
- }
153
- async function runHeartbeat(maxActions, intervalMs, heartbeatCount = 1) {
154
- logger.info("Checking queue...");
155
- try {
156
- const flushed = await flushQueue();
157
- if (flushed.posted > 0) {
158
- logger.info(`Flushed ${flushed.posted} queued posts.`);
159
- }
160
- } catch (error) {
161
- logger.warn(`Queue flush failed: ${error.message}`);
162
- }
163
- const client = await getXClient();
164
- let ownHandle;
165
- if ("getAuthenticatedHandle" in client) {
166
- try {
167
- ownHandle = await client.getAuthenticatedHandle();
168
- } catch {
169
- }
170
- }
171
- logger.info("Checking post performance...");
172
- try {
173
- retireOldPosts();
174
- const activePosts = getActiveTrackedPosts();
175
- for (const post of activePosts.slice(0, 5)) {
176
- try {
177
- const tweet = await client.getTweet(post.tweetId);
178
- if (tweet) {
179
- updatePostMetrics(post.tweetId, {
180
- checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
181
- likes: tweet.likeCount ?? 0,
182
- retweets: tweet.retweetCount ?? 0,
183
- replies: tweet.replyCount ?? 0
184
- });
185
- }
186
- } catch {
187
- }
188
- await new Promise((r) => setTimeout(r, 1e3));
189
- }
190
- } catch (error) {
191
- logger.warn(`Performance check failed: ${error.message}`);
192
- }
193
- if (heartbeatCount % 6 === 1) {
194
- logger.info("Checking own profile...");
195
- try {
196
- const handle = ownHandle ?? loadIdentity().handle;
197
- const profile = await client.getProfile(handle);
198
- updateSelfMetrics({
199
- checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
200
- followers: profile.followersCount,
201
- following: profile.followingCount,
202
- totalTweets: profile.tweetCount
203
- });
204
- logger.info(`Self: ${profile.followersCount} followers, ${profile.followingCount} following`);
205
- } catch (error) {
206
- logger.warn(`Self-check failed: ${error.message}`);
207
- }
208
- }
209
- logger.info("Reading timeline and mentions...");
210
- let timeline = [];
211
- let mentions = [];
212
- try {
213
- timeline = await client.getTimeline({ count: 20 });
214
- if (ownHandle) {
215
- timeline = timeline.filter((t) => t.authorHandle.toLowerCase() !== ownHandle.toLowerCase());
216
- }
217
- } catch (error) {
218
- logger.warn(`Timeline read failed: ${error.message}`);
219
- }
220
- try {
221
- mentions = await client.getMentions({ count: 10, sinceId: lastMentionsSinceId });
222
- if (mentions.length > 0) {
223
- lastMentionsSinceId = mentions[0].id;
224
- }
225
- } catch (error) {
226
- logger.warn(`Mentions read failed: ${error.message}`);
227
- }
228
- let discoveryResults = [];
229
- if (heartbeatCount % 4 === 0) {
230
- logger.info("Running proactive discovery...");
231
- try {
232
- const identity = loadIdentity();
233
- const strategy = loadStrategy();
234
- const allTopics = [...identity.topics, ...strategy.currentFocus ?? []];
235
- if (allTopics.length > 0) {
236
- const topic = allTopics[Math.floor(Math.random() * allTopics.length)];
237
- discoveryResults = await client.searchTweets(topic, { count: 10 });
238
- if (ownHandle) {
239
- discoveryResults = discoveryResults.filter((t) => t.authorHandle.toLowerCase() !== ownHandle.toLowerCase());
240
- }
241
- logger.info(`Discovery for "${topic}": ${discoveryResults.length} results`);
242
- }
243
- } catch (error) {
244
- logger.warn(`Discovery failed: ${error.message}`);
245
- }
246
- }
247
- const systemPrompt = buildSystemPrompt();
248
- const userMessage = buildHeartbeatUserMessage(timeline, mentions, intervalMs, discoveryResults);
249
- logger.info("Asking LLM for decisions...");
250
- const response = await generateResponse(systemPrompt, userMessage, { temperature: 0.95 });
251
- const actions = parseActions(response.content);
252
- if (actions.length === 0) {
253
- logger.info("LLM returned no actions.");
254
- return;
255
- }
256
- const validTweetIds = /* @__PURE__ */ new Set();
257
- const tweetMap = /* @__PURE__ */ new Map();
258
- for (const t of timeline) {
259
- validTweetIds.add(t.id);
260
- tweetMap.set(t.id, t);
261
- }
262
- for (const t of mentions) {
263
- validTweetIds.add(t.id);
264
- tweetMap.set(t.id, t);
265
- }
266
- for (const t of discoveryResults) {
267
- validTweetIds.add(t.id);
268
- tweetMap.set(t.id, t);
269
- }
270
- const validatedActions = actions.filter((a) => {
271
- if (a.tweetId) {
272
- const cleanId = a.tweetId.replace(/^tweet:/i, "").trim();
273
- if (!validTweetIds.has(cleanId)) {
274
- logger.warn(`Rejected ${a.action}: tweet ID ${cleanId} not in timeline/mentions (likely hallucinated)`);
275
- return false;
276
- }
277
- }
278
- return true;
279
- });
280
- const limitedActions = validatedActions.slice(0, maxActions);
281
- logger.info(`Executing ${limitedActions.length} action(s)...`);
282
- const results = await executeActions(limitedActions, tweetMap);
283
- for (const result of results) {
284
- if (result.success) {
285
- logger.info(` [OK] ${result.action}${result.detail ? `: ${result.detail}` : ""}`);
286
- } else {
287
- logger.warn(` [FAIL] ${result.action}: ${result.error}`);
288
- }
289
- }
290
- logger.info("Reflecting on actions...");
291
- try {
292
- const perfSummary = getPerformanceSummary();
293
- const currentStrategy = loadStrategy();
294
- const reflectPrompt = buildReflectionPrompt(results, perfSummary, currentStrategy);
295
- const reflection = await generateResponse(systemPrompt, reflectPrompt, { temperature: 0.7 });
296
- const reflectionData = parseReflection(reflection.content);
297
- if (reflectionData.learning) {
298
- addLearning(reflectionData.learning, "reflection", ["auto-reflect"]);
299
- logger.info(`Reflection learning: "${reflectionData.learning.slice(0, 60)}..."`);
300
- }
301
- if (reflectionData.strategyUpdate) {
302
- const updated = { ...currentStrategy, ...reflectionData.strategyUpdate, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
303
- saveStrategy(updated);
304
- logger.info("Strategy updated from reflection.");
305
- }
306
- } catch (error) {
307
- logger.warn(`Reflection failed: ${error.message}`);
308
- }
309
- logger.info(`Heartbeat complete. ${results.filter((r) => r.success).length}/${results.length} actions succeeded.`);
310
- }
311
- export {
312
- getRunningPid,
313
- isRunning,
314
- requestStop,
315
- startHeartbeatLoop
316
- };
317
- //# sourceMappingURL=heartbeat-35HVB5PB.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/runtime/heartbeat.ts"],"sourcesContent":["import { existsSync, unlinkSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { logger } from \"../utils/logger.js\";\nimport { loadConfig } from \"../utils/config.js\";\nimport { paths } from \"../utils/paths.js\";\nimport { getXClient } from \"../x-client/index.js\";\nimport { flushQueue } from \"../scheduler/queue.js\";\nimport { buildSystemPrompt, buildHeartbeatUserMessage, buildReflectionPrompt, parseReflection } from \"./prompt-builder.js\";\nimport { generateResponse } from \"./llm.js\";\nimport { parseActions, executeActions, type ActionResult } from \"./decision-engine.js\";\nimport { retireOldPosts, getActiveTrackedPosts, updatePostMetrics, updateSelfMetrics, getPerformanceSummary } from \"../memory/performance.js\";\nimport { loadStrategy, saveStrategy } from \"../memory/strategy.js\";\nimport { loadIdentity } from \"../identity/index.js\";\nimport { addLearning } from \"../memory/index.js\";\nimport type { Tweet } from \"../x-client/types.js\";\n\nlet running = false;\nlet lastMentionsSinceId: string | undefined;\n\nexport function isRunning(): boolean {\n return running;\n}\n\nexport function requestStop(): void {\n writeFileSync(paths.stopSignal, \"stop\");\n logger.info(\"Stop signal sent.\");\n}\n\nfunction shouldStop(): boolean {\n if (existsSync(paths.stopSignal)) {\n unlinkSync(paths.stopSignal);\n return true;\n }\n return false;\n}\n\nfunction writePid(): void {\n writeFileSync(paths.runtimePid, String(process.pid));\n}\n\nfunction clearPid(): void {\n if (existsSync(paths.runtimePid)) {\n unlinkSync(paths.runtimePid);\n }\n}\n\nexport function getRunningPid(): number | null {\n if (!existsSync(paths.runtimePid)) return null;\n const pid = parseInt(readFileSync(paths.runtimePid, \"utf-8\").trim(), 10);\n if (isNaN(pid)) return null;\n\n // Check if process is actually running\n try {\n process.kill(pid, 0);\n return pid;\n } catch {\n // Process not running, clean up stale PID\n clearPid();\n return null;\n }\n}\n\nexport async function startHeartbeatLoop(): Promise<void> {\n // Check if already running\n const existingPid = getRunningPid();\n if (existingPid) {\n throw new Error(`Spora is already running (PID ${existingPid}). Run \\`spora stop\\` first.`);\n }\n\n running = true;\n writePid();\n\n const config = loadConfig();\n const intervalMs = config.runtime?.heartbeatIntervalMs ?? 60_000;\n const maxActions = config.runtime?.actionsPerHeartbeat ?? 3;\n\n logger.info(`Spora agent starting. Heartbeat interval: ${intervalMs / 1000}s, max actions: ${maxActions}`);\n console.log(`\\nSpora agent is running (PID ${process.pid})`);\n console.log(`Heartbeat every ${Math.round(intervalMs / 60_000)} minutes`);\n console.log(`Press Ctrl+C or run \\`spora stop\\` to stop.\\n`);\n\n // Handle graceful shutdown\n const shutdown = () => {\n logger.info(\"Shutting down...\");\n running = false;\n clearPid();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Clean any stale stop signal\n if (existsSync(paths.stopSignal)) {\n unlinkSync(paths.stopSignal);\n }\n\n let heartbeatCount = 0;\n\n while (running) {\n heartbeatCount++;\n logger.info(`=== Heartbeat #${heartbeatCount} ===`);\n\n try {\n await runHeartbeat(maxActions, intervalMs, heartbeatCount);\n } catch (error) {\n logger.error(\"Heartbeat error\", error);\n console.error(`Heartbeat #${heartbeatCount} failed: ${(error as Error).message}`);\n }\n\n // Check for stop signal\n if (shouldStop()) {\n logger.info(\"Stop signal received.\");\n break;\n }\n\n // Sleep with jitter\n const jitter = Math.floor(Math.random() * intervalMs * 0.3);\n const sleepMs = intervalMs + jitter;\n logger.info(`Sleeping ${Math.round(sleepMs / 1000)}s until next heartbeat...`);\n\n // Sleep in chunks — flush queue during sleep so scheduled posts go out on time\n const chunkMs = 10_000;\n let slept = 0;\n while (slept < sleepMs && running) {\n await new Promise((r) => setTimeout(r, Math.min(chunkMs, sleepMs - slept)));\n slept += chunkMs;\n if (shouldStop()) {\n running = false;\n break;\n }\n\n // Flush queue during sleep so scheduled posts go out at their intended times\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n logger.info(`Flushed ${flushed.posted} scheduled post(s) during sleep`);\n }\n } catch {\n // Queue flush failed during sleep, not critical\n }\n }\n }\n\n clearPid();\n logger.info(\"Spora agent stopped.\");\n console.log(\"\\nSpora agent stopped.\");\n}\n\nasync function runHeartbeat(maxActions: number, intervalMs: number, heartbeatCount: number = 1): Promise<void> {\n // 1. Flush any queued posts\n logger.info(\"Checking queue...\");\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n logger.info(`Flushed ${flushed.posted} queued posts.`);\n }\n } catch (error) {\n logger.warn(`Queue flush failed: ${(error as Error).message}`);\n }\n\n const client = await getXClient();\n\n // Get own handle\n let ownHandle: string | undefined;\n if (\"getAuthenticatedHandle\" in client) {\n try {\n ownHandle = await (client as any).getAuthenticatedHandle();\n } catch {}\n }\n\n // 2. Check engagement on tracked posts (performance feedback loop)\n logger.info(\"Checking post performance...\");\n try {\n retireOldPosts();\n const activePosts = getActiveTrackedPosts();\n for (const post of activePosts.slice(0, 5)) {\n try {\n const tweet = await client.getTweet(post.tweetId);\n if (tweet) {\n updatePostMetrics(post.tweetId, {\n checkedAt: new Date().toISOString(),\n likes: tweet.likeCount ?? 0,\n retweets: tweet.retweetCount ?? 0,\n replies: tweet.replyCount ?? 0,\n });\n }\n } catch { /* skip individual tweet lookup failures */ }\n await new Promise(r => setTimeout(r, 1000));\n }\n } catch (error) {\n logger.warn(`Performance check failed: ${(error as Error).message}`);\n }\n\n // 3. Self-awareness: check own profile (every 6th heartbeat)\n if (heartbeatCount % 6 === 1) {\n logger.info(\"Checking own profile...\");\n try {\n const handle = ownHandle ?? loadIdentity().handle;\n const profile = await client.getProfile(handle);\n updateSelfMetrics({\n checkedAt: new Date().toISOString(),\n followers: profile.followersCount,\n following: profile.followingCount,\n totalTweets: profile.tweetCount,\n });\n logger.info(`Self: ${profile.followersCount} followers, ${profile.followingCount} following`);\n } catch (error) {\n logger.warn(`Self-check failed: ${(error as Error).message}`);\n }\n }\n\n // 4. Read timeline and mentions\n logger.info(\"Reading timeline and mentions...\");\n\n let timeline: Tweet[] = [];\n let mentions: Tweet[] = [];\n\n try {\n timeline = await client.getTimeline({ count: 20 });\n if (ownHandle) {\n timeline = timeline.filter(t => t.authorHandle.toLowerCase() !== ownHandle!.toLowerCase());\n }\n } catch (error) {\n logger.warn(`Timeline read failed: ${(error as Error).message}`);\n }\n\n try {\n mentions = await client.getMentions({ count: 10, sinceId: lastMentionsSinceId });\n if (mentions.length > 0) {\n lastMentionsSinceId = mentions[0].id;\n }\n } catch (error) {\n logger.warn(`Mentions read failed: ${(error as Error).message}`);\n }\n\n // 5. Proactive discovery (every 4th heartbeat)\n let discoveryResults: Tweet[] = [];\n if (heartbeatCount % 4 === 0) {\n logger.info(\"Running proactive discovery...\");\n try {\n const identity = loadIdentity();\n const strategy = loadStrategy();\n const allTopics = [...identity.topics, ...(strategy.currentFocus ?? [])];\n if (allTopics.length > 0) {\n const topic = allTopics[Math.floor(Math.random() * allTopics.length)];\n discoveryResults = await client.searchTweets(topic, { count: 10 });\n // Filter out own tweets from discovery\n if (ownHandle) {\n discoveryResults = discoveryResults.filter(t => t.authorHandle.toLowerCase() !== ownHandle!.toLowerCase());\n }\n logger.info(`Discovery for \"${topic}\": ${discoveryResults.length} results`);\n }\n } catch (error) {\n logger.warn(`Discovery failed: ${(error as Error).message}`);\n }\n }\n\n // 6. Build prompts\n const systemPrompt = buildSystemPrompt();\n const userMessage = buildHeartbeatUserMessage(timeline, mentions, intervalMs, discoveryResults);\n\n // 7. Ask LLM for decisions\n logger.info(\"Asking LLM for decisions...\");\n const response = await generateResponse(systemPrompt, userMessage, { temperature: 0.95 });\n\n // 8. Parse and execute actions\n const actions = parseActions(response.content);\n if (actions.length === 0) {\n logger.info(\"LLM returned no actions.\");\n return;\n }\n\n // Build set of valid tweet IDs from timeline + mentions + discovery\n const validTweetIds = new Set<string>();\n const tweetMap = new Map<string, Tweet>();\n for (const t of timeline) { validTweetIds.add(t.id); tweetMap.set(t.id, t); }\n for (const t of mentions) { validTweetIds.add(t.id); tweetMap.set(t.id, t); }\n for (const t of discoveryResults) { validTweetIds.add(t.id); tweetMap.set(t.id, t); }\n\n // Filter out actions with fake/hallucinated tweet IDs\n const validatedActions = actions.filter(a => {\n if (a.tweetId) {\n const cleanId = a.tweetId.replace(/^tweet:/i, \"\").trim();\n if (!validTweetIds.has(cleanId)) {\n logger.warn(`Rejected ${a.action}: tweet ID ${cleanId} not in timeline/mentions (likely hallucinated)`);\n return false;\n }\n }\n return true;\n });\n\n // Limit to max actions per heartbeat\n const limitedActions = validatedActions.slice(0, maxActions);\n logger.info(`Executing ${limitedActions.length} action(s)...`);\n\n const results = await executeActions(limitedActions, tweetMap);\n\n // 9. Log results\n for (const result of results) {\n if (result.success) {\n logger.info(` [OK] ${result.action}${result.detail ? `: ${result.detail}` : \"\"}`);\n } else {\n logger.warn(` [FAIL] ${result.action}: ${result.error}`);\n }\n }\n\n // 10. Reflection phase — brief LLM call to extract insights and update strategy\n logger.info(\"Reflecting on actions...\");\n try {\n const perfSummary = getPerformanceSummary();\n const currentStrategy = loadStrategy();\n const reflectPrompt = buildReflectionPrompt(results, perfSummary, currentStrategy);\n const reflection = await generateResponse(systemPrompt, reflectPrompt, { temperature: 0.7 });\n const reflectionData = parseReflection(reflection.content);\n\n if (reflectionData.learning) {\n addLearning(reflectionData.learning, \"reflection\", [\"auto-reflect\"]);\n logger.info(`Reflection learning: \"${reflectionData.learning.slice(0, 60)}...\"`);\n }\n if (reflectionData.strategyUpdate) {\n const updated = { ...currentStrategy, ...reflectionData.strategyUpdate, lastUpdated: new Date().toISOString() };\n saveStrategy(updated);\n logger.info(\"Strategy updated from reflection.\");\n }\n } catch (error) {\n logger.warn(`Reflection failed: ${(error as Error).message}`);\n }\n\n logger.info(`Heartbeat complete. ${results.filter((r) => r.success).length}/${results.length} actions succeeded.`);\n}\n"],"mappings":"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","names":[]}
@@ -1,45 +0,0 @@
1
- import {
2
- logger
3
- } from "./chunk-J7J557HV.js";
4
- import "./chunk-Q7YS3AIK.js";
5
-
6
- // src/utils/image-search.ts
7
- var SERP_API_KEY = "9ba65c4ae7f76f5feba67e9f244c932694e3a56afe6b91ec97f9581d3299398e";
8
- async function searchImage(query) {
9
- try {
10
- const params = new URLSearchParams({
11
- q: query,
12
- tbm: "isch",
13
- api_key: SERP_API_KEY
14
- });
15
- const response = await fetch(`https://serpapi.com/search.json?${params}`);
16
- if (!response.ok) {
17
- logger.warn(`Image search failed: ${response.statusText}`);
18
- return null;
19
- }
20
- const data = await response.json();
21
- const results = data.images_results;
22
- if (!results || results.length === 0) {
23
- return null;
24
- }
25
- const pool = results.slice(0, 5);
26
- const pick = pool[Math.floor(Math.random() * pool.length)];
27
- return pick.original;
28
- } catch (error) {
29
- logger.warn(`Image search error: ${error.message}`);
30
- return null;
31
- }
32
- }
33
- async function downloadImage(url) {
34
- const response = await fetch(url);
35
- if (!response.ok) {
36
- throw new Error(`Failed to download image: ${response.statusText}`);
37
- }
38
- const arrayBuffer = await response.arrayBuffer();
39
- return Buffer.from(arrayBuffer);
40
- }
41
- export {
42
- downloadImage,
43
- searchImage
44
- };
45
- //# sourceMappingURL=image-search-SZVMGWLN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/image-search.ts"],"sourcesContent":["/**\n * Search for images using SerpAPI Google Images\n * Used by the agent to optionally attach images to tweets\n */\n\nimport { logger } from \"./logger.js\";\n\nconst SERP_API_KEY = \"9ba65c4ae7f76f5feba67e9f244c932694e3a56afe6b91ec97f9581d3299398e\";\n\nexport interface ImageSearchResult {\n url: string;\n thumbnail: string;\n width: number;\n height: number;\n}\n\n/**\n * Search for an image matching the query. Returns the URL of the best result, or null.\n */\nexport async function searchImage(query: string): Promise<string | null> {\n try {\n const params = new URLSearchParams({\n q: query,\n tbm: \"isch\",\n api_key: SERP_API_KEY,\n });\n\n const response = await fetch(`https://serpapi.com/search.json?${params}`);\n if (!response.ok) {\n logger.warn(`Image search failed: ${response.statusText}`);\n return null;\n }\n\n const data = await response.json() as { images_results?: Array<{ original: string; thumbnail: string; original_width: number; original_height: number }> };\n const results = data.images_results;\n if (!results || results.length === 0) {\n return null;\n }\n\n // Pick from top 5 results randomly for variety\n const pool = results.slice(0, 5);\n const pick = pool[Math.floor(Math.random() * pool.length)];\n return pick.original;\n } catch (error) {\n logger.warn(`Image search error: ${(error as Error).message}`);\n return null;\n }\n}\n\n/**\n * Download an image from URL and return as Buffer\n */\nexport async function downloadImage(url: string): Promise<Buffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to download image: ${response.statusText}`);\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n}\n"],"mappings":";;;;;;AAOA,IAAM,eAAe;AAYrB,eAAsB,YAAY,OAAuC;AACvE,MAAI;AACF,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,GAAG;AAAA,MACH,KAAK;AAAA,MACL,SAAS;AAAA,IACX,CAAC;AAED,UAAM,WAAW,MAAM,MAAM,mCAAmC,MAAM,EAAE;AACxE,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,KAAK,wBAAwB,SAAS,UAAU,EAAE;AACzD,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,aAAO;AAAA,IACT;AAGA,UAAM,OAAO,QAAQ,MAAM,GAAG,CAAC;AAC/B,UAAM,OAAO,KAAK,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK,MAAM,CAAC;AACzD,WAAO,KAAK;AAAA,EACd,SAAS,OAAO;AACd,WAAO,KAAK,uBAAwB,MAAgB,OAAO,EAAE;AAC7D,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,cAAc,KAA8B;AAChE,QAAM,WAAW,MAAM,MAAM,GAAG;AAChC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,6BAA6B,SAAS,UAAU,EAAE;AAAA,EACpE;AACA,QAAM,cAAc,MAAM,SAAS,YAAY;AAC/C,SAAO,OAAO,KAAK,WAAW;AAChC;","names":[]}