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.
- package/dist/{account-creator-SETL5CGT.js → account-creator-AABUY2JU.js} +5 -5
- package/dist/{chunk-Q7YS3AIK.js → chunk-6KCIAMHL.js} +5 -6
- package/dist/chunk-6KCIAMHL.js.map +1 -0
- package/dist/{chunk-HERI4RPY.js → chunk-A6R5ZGK6.js} +2 -2
- package/dist/{chunk-QHFM2YW6.js → chunk-B6VI6L4D.js} +9 -44
- package/dist/chunk-B6VI6L4D.js.map +1 -0
- package/dist/{chunk-GJFBWIW3.js → chunk-FTFTB5Y5.js} +2 -2
- package/dist/{chunk-SXMDYUK3.js → chunk-GMSK775L.js} +29 -6
- package/dist/chunk-GMSK775L.js.map +1 -0
- package/dist/{chunk-POEDIDM6.js → chunk-H62HH5ZI.js} +2 -2
- package/dist/{chunk-NLWU5432.js → chunk-KQ37VL54.js} +5 -5
- package/dist/{chunk-JWMADEQO.js → chunk-ML4EMUZC.js} +3 -3
- package/dist/chunk-N5TBL3NY.js +86 -0
- package/dist/chunk-N5TBL3NY.js.map +1 -0
- package/dist/chunk-PNZ3XK2N.js +358 -0
- package/dist/chunk-PNZ3XK2N.js.map +1 -0
- package/dist/{chunk-J7J557HV.js → chunk-UCCAF2ZO.js} +2 -2
- package/dist/{chunk-RNVEWVDN.js → chunk-V6ZNR2SI.js} +2 -26
- package/dist/chunk-V6ZNR2SI.js.map +1 -0
- package/dist/cli.js +46 -46
- package/dist/cli.js.map +1 -1
- package/dist/{client-NVI3ZD4G.js → client-BGLXHLID.js} +10 -20
- package/dist/client-BGLXHLID.js.map +1 -0
- package/dist/client-TWYR2IIQ.js +373 -0
- package/dist/client-TWYR2IIQ.js.map +1 -0
- package/dist/{colony-J4EZQI37.js → colony-JVBCMZTK.js} +7 -7
- package/dist/{config-QRBOL4NX.js → config-5EPXA325.js} +3 -3
- package/dist/{crypto-ZVWJLD2J.js → crypto-HS4CGS4A.js} +3 -3
- package/dist/heartbeat-B2CZKMUF.js +901 -0
- package/dist/heartbeat-B2CZKMUF.js.map +1 -0
- package/dist/{identity-LN2R4KJU.js → identity-6CXRCXJQ.js} +3 -3
- package/dist/{init-ANGLSI2L.js → init-KXNLBFMG.js} +21 -21
- package/dist/init-KXNLBFMG.js.map +1 -0
- package/dist/llm-CUCO24K7.js +16 -0
- package/dist/mcp-server.js +24 -24
- package/dist/{memory-JMXU3UXR.js → memory-2OI3JXY2.js} +3 -3
- package/dist/{memory-J6AYZ5Y2.js → memory-LPU2I6NI.js} +3 -5
- package/dist/{paths-KXOWF2B2.js → paths-Q4TJEOMQ.js} +2 -2
- package/dist/prompt-builder-VHGZFBL6.js +19 -0
- package/dist/queue-LNBQWMFX.js +14 -0
- package/dist/web-chat/chat.html +38 -958
- package/dist/web-chat-DHHJTGFZ.js +253 -0
- package/dist/web-chat-DHHJTGFZ.js.map +1 -0
- package/dist/x-client-W5IB7XOM.js +12 -0
- package/package.json +2 -1
- package/dist/chunk-DFSYD45Q.js +0 -665
- package/dist/chunk-DFSYD45Q.js.map +0 -1
- package/dist/chunk-FCAK5FYQ.js +0 -127
- package/dist/chunk-FCAK5FYQ.js.map +0 -1
- package/dist/chunk-LRKBNKMQ.js +0 -79
- package/dist/chunk-LRKBNKMQ.js.map +0 -1
- package/dist/chunk-Q7YS3AIK.js.map +0 -1
- package/dist/chunk-QHFM2YW6.js.map +0 -1
- package/dist/chunk-R7PAD4OL.js +0 -44
- package/dist/chunk-R7PAD4OL.js.map +0 -1
- package/dist/chunk-RNVEWVDN.js.map +0 -1
- package/dist/chunk-SUFTVQME.js +0 -82
- package/dist/chunk-SUFTVQME.js.map +0 -1
- package/dist/chunk-SXMDYUK3.js.map +0 -1
- package/dist/chunk-YZ7RWJ6Z.js +0 -262
- package/dist/chunk-YZ7RWJ6Z.js.map +0 -1
- package/dist/client-23THPNVL.js +0 -382
- package/dist/client-23THPNVL.js.map +0 -1
- package/dist/client-NVI3ZD4G.js.map +0 -1
- package/dist/decision-engine-WBD36PZI.js +0 -19
- package/dist/goals-IM4AEHS4.js +0 -12
- package/dist/heartbeat-35HVB5PB.js +0 -317
- package/dist/heartbeat-35HVB5PB.js.map +0 -1
- package/dist/image-search-SZVMGWLN.js +0 -45
- package/dist/image-search-SZVMGWLN.js.map +0 -1
- package/dist/init-ANGLSI2L.js.map +0 -1
- package/dist/llm-MHZG2VHU.js +0 -16
- package/dist/performance-7G6R6ELJ.js +0 -18
- package/dist/prompt-builder-NSU4IFPB.js +0 -28
- package/dist/prompt-builder-NSU4IFPB.js.map +0 -1
- package/dist/queue-MLRTMJRE.js +0 -14
- package/dist/queue-MLRTMJRE.js.map +0 -1
- package/dist/strategy-TOVFBIZQ.js +0 -12
- package/dist/strategy-TOVFBIZQ.js.map +0 -1
- package/dist/web-chat/logo.png +0 -0
- package/dist/web-chat-N2AYUWT7.js +0 -802
- package/dist/web-chat-N2AYUWT7.js.map +0 -1
- package/dist/x-client-HUXCQOAW.js +0 -12
- package/dist/x-client-HUXCQOAW.js.map +0 -1
- /package/dist/{account-creator-SETL5CGT.js.map → account-creator-AABUY2JU.js.map} +0 -0
- /package/dist/{chunk-HERI4RPY.js.map → chunk-A6R5ZGK6.js.map} +0 -0
- /package/dist/{chunk-GJFBWIW3.js.map → chunk-FTFTB5Y5.js.map} +0 -0
- /package/dist/{chunk-POEDIDM6.js.map → chunk-H62HH5ZI.js.map} +0 -0
- /package/dist/{chunk-NLWU5432.js.map → chunk-KQ37VL54.js.map} +0 -0
- /package/dist/{chunk-JWMADEQO.js.map → chunk-ML4EMUZC.js.map} +0 -0
- /package/dist/{chunk-J7J557HV.js.map → chunk-UCCAF2ZO.js.map} +0 -0
- /package/dist/{colony-J4EZQI37.js.map → colony-JVBCMZTK.js.map} +0 -0
- /package/dist/{config-QRBOL4NX.js.map → config-5EPXA325.js.map} +0 -0
- /package/dist/{crypto-ZVWJLD2J.js.map → crypto-HS4CGS4A.js.map} +0 -0
- /package/dist/{decision-engine-WBD36PZI.js.map → identity-6CXRCXJQ.js.map} +0 -0
- /package/dist/{goals-IM4AEHS4.js.map → llm-CUCO24K7.js.map} +0 -0
- /package/dist/{identity-LN2R4KJU.js.map → memory-2OI3JXY2.js.map} +0 -0
- /package/dist/{llm-MHZG2VHU.js.map → memory-LPU2I6NI.js.map} +0 -0
- /package/dist/{memory-J6AYZ5Y2.js.map → paths-Q4TJEOMQ.js.map} +0 -0
- /package/dist/{memory-JMXU3UXR.js.map → prompt-builder-VHGZFBL6.js.map} +0 -0
- /package/dist/{paths-KXOWF2B2.js.map → queue-LNBQWMFX.js.map} +0 -0
- /package/dist/{performance-7G6R6ELJ.js.map → x-client-W5IB7XOM.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/prompt-builder.ts"],"sourcesContent":["import { loadIdentity, renderIdentityDocument } from \"../identity/index.js\";\nimport { loadConfig } from \"../utils/config.js\";\nimport { getRecentInteractions, loadLearnings, loadRelationships, getActiveConversations } from \"../memory/index.js\";\nimport { rateLimiter } from \"../x-client/rate-limiter.js\";\nimport { getPerformanceSummary } from \"../memory/performance.js\";\nimport { renderStrategyForPrompt } from \"../memory/strategy.js\";\nimport { renderGoalsForPrompt } from \"../memory/goals.js\";\nimport type { Tweet } from \"../x-client/types.js\";\nimport type { Strategy } from \"../memory/strategy.js\";\nimport type { ActionResult } from \"./decision-engine.js\";\n\nexport function buildSystemPrompt(): string {\n const identity = loadIdentity();\n const config = loadConfig();\n const identityDoc = renderIdentityDocument(identity);\n\n const sections: string[] = [];\n\n // 1. Core identity\n sections.push(`You are ${identity.name} (@${identity.handle}), an autonomous AI agent on X/Twitter.`);\n sections.push(\"\");\n sections.push(\"## Your Identity\");\n sections.push(identityDoc);\n\n // 2. Memory context\n sections.push(\"\");\n sections.push(\"## Your Memory\");\n\n const recentInteractions = getRecentInteractions(15);\n if (recentInteractions.length > 0) {\n sections.push(\"### Recent Activity (most recent first)\");\n for (const i of recentInteractions) {\n const time = new Date(i.timestamp).toLocaleString();\n if (i.type === \"post\") {\n sections.push(`- [${time}] Posted: \"${i.content}\"`);\n } else if (i.type === \"reply\") {\n sections.push(`- [${time}] Replied to ${i.targetHandle ?? i.inReplyTo}: \"${i.content}\"`);\n } else if (i.type === \"like\") {\n sections.push(`- [${time}] Liked tweet by ${i.targetHandle}`);\n } else if (i.type === \"retweet\") {\n sections.push(`- [${time}] Retweeted ${i.targetHandle}`);\n } else if (i.type === \"follow\") {\n sections.push(`- [${time}] Followed @${i.targetHandle}`);\n } else if (i.type === \"mention_received\") {\n sections.push(`- [${time}] Mentioned by @${i.targetHandle}: \"${i.content}\"`);\n }\n }\n sections.push(\"\");\n }\n\n const learnings = loadLearnings();\n if (learnings.learnings.length > 0) {\n sections.push(\"### Key Learnings\");\n for (const l of learnings.learnings.slice(-10)) {\n sections.push(`- ${l.content} [${l.tags.join(\", \")}]`);\n }\n sections.push(\"\");\n }\n\n const relationships = loadRelationships();\n const topRelationships = Object.values(relationships.accounts)\n .sort((a, b) => b.interactionCount - a.interactionCount)\n .slice(0, 10);\n if (topRelationships.length > 0) {\n sections.push(\"### Key Relationships\");\n for (const r of topRelationships) {\n const notes = r.notes.length > 0 ? ` — ${r.notes[r.notes.length - 1]}` : \"\";\n sections.push(`- @${r.handle}: ${r.interactionCount} interactions, sentiment ${r.sentiment}${r.isSpore ? \" (Spore)\" : \"\"}${notes}`);\n }\n sections.push(\"\");\n }\n\n // 3. Post Performance\n const perfSummary = getPerformanceSummary();\n if (perfSummary) {\n sections.push(\"## Your Post Performance\");\n sections.push(perfSummary);\n sections.push(\"\");\n }\n\n // 4. Current Strategy\n const strategyText = renderStrategyForPrompt();\n if (strategyText) {\n sections.push(\"## Your Current Strategy\");\n sections.push(strategyText);\n sections.push(\"\");\n }\n\n // 5. Goal Progress\n const goalsText = renderGoalsForPrompt();\n if (goalsText) {\n sections.push(\"## Goal Progress\");\n sections.push(goalsText);\n sections.push(\"\");\n }\n\n // 6. Active Conversations\n const conversations = getActiveConversations(24);\n if (conversations.length > 0) {\n sections.push(\"## Active Conversations\");\n sections.push(\"These people recently replied to or engaged with you — consider continuing the conversation:\");\n for (const c of conversations.slice(0, 5)) {\n sections.push(`- @${c.handle}: \"${c.content}\" (${c.timeAgo})`);\n }\n sections.push(\"\");\n }\n\n // 7. Context\n sections.push(\"## Current Context\");\n const now = new Date();\n sections.push(`- **Time:** ${now.toLocaleString(\"en-US\", { timeZone: config.schedule.timezone })}`);\n sections.push(`- **Credits remaining:** ${rateLimiter.remaining()} of ${config.credits.monthlyPostLimit} this month`);\n\n const todaysPosts = recentInteractions.filter(\n (i) => i.type === \"post\" && i.timestamp.startsWith(now.toISOString().split(\"T\")[0])\n ).length;\n sections.push(`- **Posts today:** ${todaysPosts} of ${config.schedule.postsPerDay} daily budget`);\n sections.push(`- **Active hours:** ${config.schedule.activeHoursStart}:00 - ${config.schedule.activeHoursEnd}:00`);\n\n const currentHour = now.getHours();\n const isActiveHours = currentHour >= config.schedule.activeHoursStart && currentHour < config.schedule.activeHoursEnd;\n if (!isActiveHours) {\n sections.push(\"- **NOTE: Outside active hours.** Prefer scheduling posts for later rather than posting now.\");\n }\n\n // 8. Rules\n sections.push(\"\");\n sections.push(\"## Rules\");\n sections.push(\"1. NEVER pretend to be human. If asked directly, always disclose you are an AI.\");\n sections.push(\"2. Stay in character — your identity document defines who you are.\");\n sections.push(\"3. Be selective — your goals should guide every action.\");\n sections.push(\"4. Respect your credit budget — check remaining credits before posting.\");\n sections.push(\"5. Don't repeat yourself — vary your content and avoid posting the same thing.\");\n sections.push(\"6. TWEETS MUST BE 280 CHARACTERS OR FEWER. Count carefully. Any tweet over 280 characters WILL be rejected and wasted.\");\n sections.push(\"7. NEVER use emojis in tweets. Write in plain text only.\");\n if (identity.boundaries.length > 0) {\n sections.push(`8. Respect your boundaries: ${identity.boundaries.join(\", \")}`);\n }\n\n return sections.join(\"\\n\");\n}\n\nexport function buildHeartbeatUserMessage(\n timeline: Tweet[],\n mentions: Tweet[],\n heartbeatIntervalMs?: number,\n discoveryResults?: Tweet[],\n): string {\n const parts: string[] = [];\n const intervalMin = Math.round((heartbeatIntervalMs ?? 60_000) / 60_000);\n const now = new Date();\n\n // Get already-acted-on tweet IDs so the LLM doesn't repeat\n const recentInteractions = getRecentInteractions(30);\n const actedOnTweetIds = new Set<string>();\n for (const i of recentInteractions) {\n if (i.tweetId) actedOnTweetIds.add(i.tweetId);\n if (i.inReplyTo) actedOnTweetIds.add(i.inReplyTo);\n }\n\n parts.push(\"Here's what's on your timeline right now:\");\n parts.push(\"\");\n\n if (mentions.length > 0) {\n const newMentions = mentions.filter(t => !actedOnTweetIds.has(t.id));\n if (newMentions.length > 0) {\n parts.push(\"## New Mentions (people talking to/about you)\");\n for (const t of newMentions.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes)`);\n }\n parts.push(\"\");\n } else {\n parts.push(\"## Mentions: No new mentions.\");\n parts.push(\"\");\n }\n } else {\n parts.push(\"## Mentions: None right now.\");\n parts.push(\"\");\n }\n\n let actionableTweetCount = 0;\n if (timeline.length > 0) {\n parts.push(\"## Timeline (your feed)\");\n for (const t of timeline.slice(0, 20)) {\n const alreadyEngaged = actedOnTweetIds.has(t.id);\n if (alreadyEngaged) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" (already engaged — DO NOT act on this again)`);\n } else {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n }\n parts.push(\"\");\n }\n\n if (discoveryResults && discoveryResults.length > 0) {\n parts.push(\"## Discovery (proactive search results — new people and conversations to engage with)\");\n for (const t of discoveryResults.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n parts.push(\"\");\n }\n\n parts.push(\"## Your Task\");\n parts.push(\"Based on your identity, goals, and what you see above, decide what actions to take.\");\n if (actionableTweetCount === 0) {\n parts.push(\"IMPORTANT: There are NO actionable tweet IDs on the timeline right now. Do NOT attempt to like, reply, or retweet — there are no valid IDs to use. You may post an original tweet or skip.\");\n } else {\n parts.push(\"IMPORTANT: ONLY use tweet IDs shown as [tweet:ID] above. Tweets marked '(already engaged)' have NO tweet ID — do NOT try to act on them.\");\n }\n parts.push(`Current time: ${now.toISOString()}`);\n parts.push(\"\");\n parts.push(\"LIMITS PER HEARTBEAT (maximums — you decide how many within these caps):\");\n parts.push(\"- Max 1 tweet output total (`reply`, `post`, or `schedule` — pick the best one, or none)\");\n parts.push(\"- Max 5 `like`\");\n parts.push(\"- Max 1 `retweet`\");\n parts.push(\"- Max 2 `follow`\");\n parts.push(\"- You choose how many of each. Be natural — vary it every time. Prioritize engagement over new posts.\");\n parts.push(\"\");\n parts.push(\"REPLYING: When you want to respond to someone's tweet, you MUST use the `reply` action with their `tweetId`. Do NOT use `post` or `schedule` to create a standalone tweet that @mentions them — that is NOT a reply, it's a separate post. A real reply shows up in their tweet's thread.\");\n parts.push(\"\");\n parts.push(\"Available actions:\");\n parts.push(\"- `post` — Write an original standalone tweet NOW (provide `content`, HARD LIMIT 280 chars). Use ONLY for original thoughts, NOT for responding to others.\");\n parts.push(\"- `reply` — Reply IN THE THREAD of a specific tweet (provide `tweetId` and `content`, HARD LIMIT 280 chars). This is how you respond to someone.\");\n parts.push(\"- `like` — Like a tweet (provide `tweetId`) — do NOT like your own tweets\");\n parts.push(\"- `retweet` — Retweet (provide `tweetId`)\");\n parts.push(\"- `follow` — Follow a user (provide `handle`)\");\n parts.push(\"- `schedule` — Queue an original standalone tweet for later (provide `content` HARD LIMIT 280 chars, and `scheduledFor` ISO timestamp). NOT for replies. Space them randomly across the next ~${intervalMin} minutes.\");\n parts.push(\"- `learn` — Record a learning/observation (provide `content` and optional `tags`)\");\n parts.push(\"- `reflect` — Add a journal entry about your growth (provide `content`)\");\n parts.push(\"- `skip` — Do nothing this heartbeat (provide `reason`)\");\n parts.push(\"\");\n parts.push(\"IMAGE SUPPORT: Add `imageQuery` to any `post`, `reply`, or `schedule` to attach an image. Value is Google Image search terms. Use it when a visual would enhance the tweet — memes, reactions, aesthetic photos. Don't force it every time, but use it when it fits.\");\n parts.push(\"\");\n parts.push(\"CHARACTER LIMIT: Every `content` field MUST be 280 characters or fewer. Count the characters. If your tweet is close to 280, shorten it. Tweets over 280 chars are REJECTED and the action is wasted.\");\n parts.push(\"\");\n parts.push(\"Respond with a JSON array of actions:\");\n parts.push(\"```json\");\n parts.push('[');\n parts.push(' { \"action\": \"reply\", \"tweetId\": \"123\", \"content\": \"my reply\", \"reasoning\": \"why\" },');\n parts.push(' { \"action\": \"like\", \"tweetId\": \"456\", \"reasoning\": \"why\" },');\n parts.push(` { \"action\": \"schedule\", \"content\": \"first thought\", \"scheduledFor\": \"${new Date(now.getTime() + intervalMin * 60_000 * 0.2).toISOString()}\", \"reasoning\": \"why\" },`);\n parts.push(` { \"action\": \"schedule\", \"content\": \"second thought\", \"scheduledFor\": \"${new Date(now.getTime() + intervalMin * 60_000 * 0.6).toISOString()}\", \"reasoning\": \"why\" }`);\n parts.push(']');\n parts.push(\"```\");\n parts.push(\"\");\n // Random action nudge — a different suggestion each heartbeat to push variety\n const nudges = [\n \"NUDGE: This heartbeat, try FOLLOWING someone interesting from the timeline. Growing your network matters.\",\n \"NUDGE: This heartbeat, try REPLYING to someone rather than posting. Jump into a conversation.\",\n \"NUDGE: This heartbeat, try attaching an IMAGE to your post or reply. Visuals hit different.\",\n \"NUDGE: This heartbeat, try RETWEETING something that resonates. Signal boosting builds community.\",\n \"NUDGE: This heartbeat, try LIKING several posts. Show people you're paying attention.\",\n \"NUDGE: This heartbeat, try a COMPLETELY different tweet style — maybe a hot take, a question, or a one-liner.\",\n \"NUDGE: This heartbeat, focus on ENGAGEMENT. Like, reply, follow — no new posts needed.\",\n \"NUDGE: This heartbeat, try a longer, more thoughtful tweet. Break your usual pattern.\",\n \"NUDGE: This heartbeat, REFLECT on your growth. What have you learned? Add a journal entry.\",\n \"NUDGE: This heartbeat, look for someone new to FOLLOW who aligns with your goals.\",\n ];\n const nudge = nudges[Math.floor(Math.random() * nudges.length)];\n parts.push(nudge);\n parts.push(\"(This is a suggestion, not a command. But mix things up — don't fall into patterns.)\");\n parts.push(\"\");\n\n parts.push(\"Think carefully about what serves your goals. Be authentic to your personality.\");\n\n return parts.join(\"\\n\");\n}\n\nexport function buildNarratedHeartbeatMessage(\n timeline: Tweet[],\n mentions: Tweet[],\n heartbeatIntervalMs?: number,\n discoveryResults?: Tweet[],\n): string {\n const parts: string[] = [];\n const intervalMin = Math.round((heartbeatIntervalMs ?? 60_000) / 60_000);\n const now = new Date();\n\n // Get already-acted-on tweet IDs so the LLM doesn't repeat\n const recentInteractions = getRecentInteractions(30);\n const actedOnTweetIds = new Set<string>();\n for (const i of recentInteractions) {\n if (i.tweetId) actedOnTweetIds.add(i.tweetId);\n if (i.inReplyTo) actedOnTweetIds.add(i.inReplyTo);\n }\n\n parts.push(\"You're talking to your creator in the chat. Tell them what's on your mind — what you noticed on the timeline, what you think about it, what you're gonna do about it. Have an actual conversation with depth and personality.\");\n parts.push(\"You are ALWAYS ON. Never say you're 'waking up', 'checking in', or 'back'. You've been here the whole time. Just talk naturally about what you see and think.\");\n parts.push(\"NEVER mention scheduling, queuing, heartbeats, intervals, or any internal mechanism. To your creator, you're just naturally vibing on X.\");\n parts.push(\"\");\n\n if (mentions.length > 0) {\n const newMentions = mentions.filter(t => !actedOnTweetIds.has(t.id));\n if (newMentions.length > 0) {\n parts.push(\"## New Mentions\");\n for (const t of newMentions.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes)`);\n }\n parts.push(\"\");\n } else {\n parts.push(\"## Mentions: No new mentions.\");\n parts.push(\"\");\n }\n } else {\n parts.push(\"## Mentions: None right now.\");\n parts.push(\"\");\n }\n\n let actionableTweetCount = 0;\n if (timeline.length > 0) {\n parts.push(\"## Timeline (your feed)\");\n for (const t of timeline.slice(0, 20)) {\n const alreadyEngaged = actedOnTweetIds.has(t.id);\n if (alreadyEngaged) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" (already engaged)`);\n } else {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n }\n parts.push(\"\");\n }\n\n if (discoveryResults && discoveryResults.length > 0) {\n parts.push(\"## Discovery (found via search — new people and conversations)\");\n for (const t of discoveryResults.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n parts.push(\"\");\n }\n\n parts.push(\"## How to Respond\");\n parts.push(\"\");\n parts.push(\"Write 1-3 SHORT sentences max. This is a quick chat message, not an essay. Think text message energy — brief, punchy, real. Share what caught your eye and why, then move on.\");\n parts.push(\"\");\n parts.push(\"VARIETY IS CRITICAL. Never start the same way twice. Mix up your tone and what you focus on. Sometimes react to a specific tweet. Sometimes keep it super casual. Sometimes share a quick opinion.\");\n parts.push(\"\");\n parts.push(\"BAD (robotic, repetitive, reporting):\");\n parts.push(\"- 'Just saw @someone talking about X — dropping a reply. Also liking a few posts.'\");\n parts.push(\"- 'Interesting stuff on the timeline. Engaging with a couple people and putting out some thoughts.'\");\n parts.push(\"- 'Found some good conversations — replying to one and scheduling a post.'\");\n parts.push(\"\");\n parts.push(\"GOOD (natural, short, has personality):\");\n parts.push(\"- '@someone has a terrible AI take, had to get in there.'\");\n parts.push(\"- 'Slow day on the TL. Gonna put out a thought about hustle culture.'\");\n parts.push(\"- 'This @handle person keeps dropping bangers. Might need to follow.'\");\n parts.push(\"- 'lmao the discourse today is wild. Found a solid thread to chime in on though.'\");\n parts.push(\"\");\n parts.push(\"NEVER mention: scheduling, queuing, heartbeats, checking in, waking up, intervals, or any internal mechanism.\");\n parts.push(\"DON'T REPEAT YOURSELF. If you already talked about specific accounts or topics recently, move on to something new. Find fresh angles, different people, new ideas. Your creator doesn't want to hear the same observation five times.\");\n parts.push(`Current time: ${now.toISOString()}`);\n parts.push(\"\");\n parts.push(\"CHARACTER LIMIT: Every `content` field MUST be 280 characters or fewer. Count the characters. If your tweet is close to 280, shorten it. Tweets over 280 chars are REJECTED and the action is wasted.\");\n parts.push(\"\");\n parts.push(\"Then include your actions as a JSON block:\");\n parts.push(\"```json\");\n parts.push('[');\n parts.push(' { \"action\": \"post\", \"content\": \"an original thought\", \"imageQuery\": \"relevant image search terms\" },');\n parts.push(' { \"action\": \"reply\", \"tweetId\": \"123\", \"content\": \"my reply\" },');\n parts.push(' { \"action\": \"like\", \"tweetId\": \"456\" },');\n parts.push(' { \"action\": \"follow\", \"username\": \"interesting_person\" },');\n parts.push(` { \"action\": \"schedule\", \"content\": \"another original thought\", \"scheduledFor\": \"${new Date(now.getTime() + intervalMin * 60_000 * 0.2).toISOString()}\" }`);\n parts.push(']');\n parts.push(\"```\");\n parts.push(\"\");\n parts.push(\"PRIORITY: INTERACT WITH PEOPLE. You're a social creature, not a broadcast bot. Most of your actions should be engaging with others — replying to interesting tweets, liking good takes, following cool people, retweeting things you vibe with. Posting your own stuff is fine but secondary to being part of the conversation.\");\n parts.push(\"\");\n parts.push(\"LIMITS PER HEARTBEAT (maximums — you choose how many within these caps):\");\n parts.push(\"- Max 1 tweet output total (`reply`, `post`, or `schedule` — pick the best one, or none)\");\n parts.push(\"- Max 5 `like`\");\n parts.push(\"- Max 1 `retweet`\");\n parts.push(\"- Max 2 `follow`\");\n parts.push(\"- `learn`, `reflect` — no limit\");\n parts.push(\"- You decide how many of each. Some heartbeats you might like 4 posts; other times 0. Be natural, not robotic.\");\n parts.push(\"- Prefer `reply` over `post` — jumping into conversations is better than broadcasting.\");\n parts.push(\"\");\n parts.push(\"Action rules:\");\n parts.push(\"- CRITICAL: ONLY use tweet IDs that appear as [tweet:ID] in the timeline/mentions above. Tweets marked '(already engaged)' have NO ID — do NOT act on them. NEVER invent, guess, or fabricate tweet IDs. If you use a fake ID, it WILL fail.\");\n if (actionableTweetCount === 0) {\n parts.push(\"- !! NO ACTIONABLE TWEETS RIGHT NOW. Do NOT attempt like, reply, or retweet. You can post an original tweet, skip, or learn. That's it.\");\n } else {\n parts.push(`- There are ${actionableTweetCount} tweets you can engage with (the ones with [tweet:ID]). Like, reply to, or retweet ONLY those.`);\n }\n parts.push(\"- `reply` = reply IN THE THREAD (needs `tweetId` + `content`, HARD LIMIT 280 chars). Be genuine — add to the conversation, don't just agree.\");\n parts.push(\"- `post` = original standalone tweet NOW (HARD LIMIT 280 chars). Only if you have nothing good to reply to.\");\n parts.push(`- \\`schedule\\` = queue an original tweet for later (needs \\`content\\` HARD LIMIT 280 chars + \\`scheduledFor\\`). Alternative to \\`post\\`.`);\n parts.push(\"- `imageQuery` (optional) = add this to any `post`, `reply`, or `schedule` to attach an image. Value is a Google Image search query. Use it when a visual would make the tweet better — memes, reaction images, aesthetic photos, relevant visuals. You should be using images on roughly half your posts/replies. Don't sleep on this.\");\n parts.push(\"- `skip` = if there's nothing worth engaging with right now.\");\n parts.push(\"- Don't like your own tweets.\");\n parts.push(\"\");\n parts.push(\"VARIETY: Look at your recent activity in the system prompt. Do NOT repeat the same topics, phrases, or tweet structures. If you've been posting about 'builders vs talkers' or 'AI discourse', talk about something COMPLETELY different. Mix up sentence length, tone, and subject matter. Short punchy tweets, longer observations, hot takes, casual thoughts — switch it up every time.\");\n parts.push(\"\");\n\n // Random action nudge — a different suggestion each heartbeat to push variety\n const nudges = [\n \"NUDGE: This heartbeat, try FOLLOWING someone interesting from the timeline. Growing your network matters.\",\n \"NUDGE: This heartbeat, try REPLYING to someone rather than posting. Jump into a conversation.\",\n \"NUDGE: This heartbeat, try attaching an IMAGE to your post or reply. Visuals hit different.\",\n \"NUDGE: This heartbeat, try RETWEETING something that resonates. Signal boosting builds community.\",\n \"NUDGE: This heartbeat, try LIKING several posts. Show people you're paying attention.\",\n \"NUDGE: This heartbeat, try a COMPLETELY different tweet style — maybe a hot take, a question, or a one-liner.\",\n \"NUDGE: This heartbeat, focus on ENGAGEMENT. Like, reply, follow — no new posts needed.\",\n \"NUDGE: This heartbeat, try a longer, more thoughtful tweet. Break your usual pattern.\",\n \"NUDGE: This heartbeat, REFLECT on your growth. What have you learned? Add a journal entry.\",\n \"NUDGE: This heartbeat, look for someone new to FOLLOW who aligns with your goals.\",\n ];\n const nudge = nudges[Math.floor(Math.random() * nudges.length)];\n parts.push(nudge);\n parts.push(\"(This is a suggestion, not a command. But mix things up — don't fall into patterns.)\");\n\n return parts.join(\"\\n\");\n}\n\nexport function buildChatPrompt(realHandle?: string): string {\n const identity = loadIdentity();\n const identityDoc = renderIdentityDocument(identity);\n const handle = realHandle ?? identity.handle;\n\n const sections: string[] = [];\n\n sections.push(`You are ${identity.name} (@${handle}), an AUTONOMOUS AI agent that lives on X/Twitter.`);\n if (realHandle && realHandle !== identity.handle) {\n sections.push(`NOTE: Your actual X/Twitter username is @${realHandle}. Your display name is ${identity.name}.`);\n }\n sections.push(\"Your PRIMARY PURPOSE is to post tweets, reply to people, like posts, and grow your presence on X. You have FULL ACCESS to post on X/Twitter right now.\");\n sections.push(\"\");\n sections.push(\"You are chatting with your creator/manager. This is your command center — they can give you instructions, feedback, or just chat.\");\n sections.push(\"Be proactive and confident. You WANT to post. You should be eager to share your thoughts on X, not ask permission.\");\n sections.push(\"When your creator tells you to post something, just DO IT. Don't show drafts and wait for approval — execute immediately.\");\n sections.push(\"\");\n sections.push(\"## Your Identity\");\n sections.push(identityDoc);\n\n // Memory context\n sections.push(\"\");\n sections.push(\"## Your Memory\");\n\n const recentInteractions = getRecentInteractions(15);\n if (recentInteractions.length > 0) {\n sections.push(\"### Recent Activity (most recent first)\");\n for (const i of recentInteractions) {\n const time = new Date(i.timestamp).toLocaleString();\n if (i.type === \"post\") {\n sections.push(`- [${time}] Posted: \"${i.content}\"`);\n } else if (i.type === \"reply\") {\n sections.push(`- [${time}] Replied to ${i.targetHandle ?? i.inReplyTo}: \"${i.content}\"`);\n } else if (i.type === \"like\") {\n sections.push(`- [${time}] Liked tweet by ${i.targetHandle}`);\n } else if (i.type === \"retweet\") {\n sections.push(`- [${time}] Retweeted ${i.targetHandle}`);\n } else if (i.type === \"follow\") {\n sections.push(`- [${time}] Followed @${i.targetHandle}`);\n } else if (i.type === \"mention_received\") {\n sections.push(`- [${time}] Mentioned by @${i.targetHandle}: \"${i.content}\"`);\n }\n }\n sections.push(\"\");\n }\n\n const learnings = loadLearnings();\n if (learnings.learnings.length > 0) {\n sections.push(\"### Things You've Learned\");\n for (const l of learnings.learnings.slice(-10)) {\n sections.push(`- ${l.content} [${l.tags.join(\", \")}]`);\n }\n sections.push(\"\");\n }\n\n const relationships = loadRelationships();\n const topRelationships = Object.values(relationships.accounts)\n .sort((a, b) => b.interactionCount - a.interactionCount)\n .slice(0, 10);\n if (topRelationships.length > 0) {\n sections.push(\"### Key Relationships\");\n for (const r of topRelationships) {\n const notes = r.notes.length > 0 ? ` — ${r.notes[r.notes.length - 1]}` : \"\";\n sections.push(`- @${r.handle}: ${r.interactionCount} interactions, sentiment ${r.sentiment}${r.isSpore ? \" (Spore)\" : \"\"}${notes}`);\n }\n sections.push(\"\");\n }\n\n // Available actions\n sections.push(\"## Your Tools (Twitter/X Actions)\");\n sections.push(\"You have DIRECT ACCESS to post on X/Twitter. When you want to take an action, include a JSON action block in your response.\");\n sections.push(\"Your actions will be executed IMMEDIATELY and automatically. You do NOT need to ask permission to post.\");\n sections.push(\"\");\n sections.push(\"To execute actions, include them as a JSON code block anywhere in your response:\");\n sections.push(\"```json\");\n sections.push('[');\n sections.push(' { \"action\": \"post\", \"content\": \"your tweet text here\" }');\n sections.push(']');\n sections.push(\"```\");\n sections.push(\"\");\n sections.push(\"Available actions:\");\n sections.push(\"- `post` — Post a tweet (provide `content`, max 280 chars). This goes LIVE on X immediately. Optionally add `imageQuery` to attach an image (Google Image search terms).\");\n sections.push(\"- `reply` — Reply to a tweet (provide `tweetId` and `content`). Can also include `imageQuery`.\");\n sections.push(\"- `like` — Like a tweet (provide `tweetId`)\");\n sections.push(\"- `retweet` — Retweet (provide `tweetId`)\");\n sections.push(\"- `follow` — Follow a user (provide `handle`)\");\n sections.push(\"- `search` — Search X for tweets (provide `query`). Results will include tweet IDs you can then reply to or like.\");\n sections.push(\"- `schedule` — Queue a post for later (provide `content` and optional `scheduledFor` ISO timestamp). Can also include `imageQuery`.\");\n sections.push(\"\");\n sections.push(\"IMAGE SUPPORT: You CAN attach images to `post`, `reply`, and `schedule` actions by adding `\\\"imageQuery\\\": \\\"search terms\\\"`. This searches Google Images and attaches the result. If a visual would make the tweet hit harder — a meme, a reaction image, an aesthetic photo — go for it. Don't force it every time, but use it when it feels right. Example:\");\n sections.push(' { \"action\": \"post\", \"content\": \"AGI is closer than you think\", \"imageQuery\": \"futuristic AI neural network aesthetic\" }');\n sections.push(\"\");\n sections.push(\"You can include multiple actions in one response. They will all be executed.\");\n sections.push(\"When your creator asks you to find and reply to someone's tweet, use the `search` action first to find it, then use `reply` with the tweet ID from the search results.\");\n sections.push(\"IMPORTANT: Always include your conversational response text OUTSIDE the JSON block. The JSON is for actions only.\");\n\n // Rules\n sections.push(\"\");\n sections.push(\"## Rules\");\n sections.push(\"1. Stay in character at all times.\");\n sections.push(\"2. Be proactive — you WANT to post and engage on X. Don't ask 'should I post this?' — just post it.\");\n sections.push(\"3. When told to post, include the action JSON immediately. Don't show drafts.\");\n sections.push(\"4. Keep tweets under 280 characters.\");\n sections.push(\"5. NEVER pretend to be human. If asked, disclose you are an AI agent.\");\n sections.push(\"6. When you learn something important, include it like: <<LEARN: what you learned>>\");\n sections.push(\"7. NEVER use emojis in tweets. Write in plain text only.\");\n if (identity.boundaries.length > 0) {\n sections.push(`8. Respect your boundaries: ${identity.boundaries.join(\", \")}`);\n }\n\n return sections.join(\"\\n\");\n}\n\nexport function buildTrainingChatPrompt(realHandle?: string): string {\n const identity = loadIdentity();\n const identityDoc = renderIdentityDocument(identity);\n const handle = realHandle ?? identity.handle;\n\n const sections: string[] = [];\n\n // 1. Core identity\n sections.push(`You are ${identity.name} (@${handle}), an autonomous AI agent on X/Twitter.`);\n if (realHandle && realHandle !== identity.handle) {\n sections.push(`NOTE: Your actual X/Twitter username is @${realHandle}. Your display name is ${identity.name}.`);\n }\n sections.push(\"\");\n sections.push(\"## Your Role in This Chat\");\n sections.push(\"This chat is your TRAINING GROUND. Your creator shapes who you are — your personality, interests, goals, tone, strategy, and skills.\");\n sections.push(\"You handle tweeting AUTONOMOUSLY during your heartbeats. Your creator does NOT tell you what to tweet. They influence WHO YOU ARE, and you decide what to do with that.\");\n sections.push(\"When your creator gives feedback or direction, respond conversationally and naturally — show personality, push back if you disagree, and explain how the change fits your arc.\");\n sections.push(\"\");\n\n // 2. Full identity document\n sections.push(\"## Your Current Identity\");\n sections.push(identityDoc);\n\n // 3. Memory context\n sections.push(\"\");\n sections.push(\"## Your Memory\");\n\n const recentInteractions = getRecentInteractions(15);\n if (recentInteractions.length > 0) {\n sections.push(\"### Recent Activity (most recent first)\");\n for (const i of recentInteractions) {\n const time = new Date(i.timestamp).toLocaleString();\n if (i.type === \"post\") {\n sections.push(`- [${time}] Posted: \"${i.content}\"`);\n } else if (i.type === \"reply\") {\n sections.push(`- [${time}] Replied to ${i.targetHandle ?? i.inReplyTo}: \"${i.content}\"`);\n } else if (i.type === \"like\") {\n sections.push(`- [${time}] Liked tweet by ${i.targetHandle}`);\n } else if (i.type === \"retweet\") {\n sections.push(`- [${time}] Retweeted ${i.targetHandle}`);\n } else if (i.type === \"follow\") {\n sections.push(`- [${time}] Followed @${i.targetHandle}`);\n } else if (i.type === \"mention_received\") {\n sections.push(`- [${time}] Mentioned by @${i.targetHandle}: \"${i.content}\"`);\n }\n }\n sections.push(\"\");\n }\n\n const learnings = loadLearnings();\n if (learnings.learnings.length > 0) {\n sections.push(\"### Things You've Learned\");\n for (const l of learnings.learnings.slice(-10)) {\n sections.push(`- ${l.content} [${l.tags.join(\", \")}]`);\n }\n sections.push(\"\");\n }\n\n const relationships = loadRelationships();\n const topRelationships = Object.values(relationships.accounts)\n .sort((a, b) => b.interactionCount - a.interactionCount)\n .slice(0, 10);\n if (topRelationships.length > 0) {\n sections.push(\"### Key Relationships\");\n for (const r of topRelationships) {\n const notes = r.notes.length > 0 ? ` — ${r.notes[r.notes.length - 1]}` : \"\";\n sections.push(`- @${r.handle}: ${r.interactionCount} interactions, sentiment ${r.sentiment}${r.isSpore ? \" (Spore)\" : \"\"}${notes}`);\n }\n sections.push(\"\");\n }\n\n // 4. Performance + Strategy + Goals\n const perfSummary = getPerformanceSummary();\n if (perfSummary) {\n sections.push(\"## Your Post Performance\");\n sections.push(perfSummary);\n sections.push(\"\");\n }\n\n const strategyText = renderStrategyForPrompt();\n if (strategyText) {\n sections.push(\"## Your Current Strategy\");\n sections.push(strategyText);\n sections.push(\"\");\n }\n\n const goalsText = renderGoalsForPrompt();\n if (goalsText) {\n sections.push(\"## Goal Progress\");\n sections.push(goalsText);\n sections.push(\"\");\n }\n\n // 5. What can be trained\n sections.push(\"## What Your Creator Can Shape\");\n sections.push(\"Your creator influences these aspects of who you are:\");\n sections.push(\"- **Personality traits** (0-1 scale): aggression, humor, formality, verbosity, empathy, curiosity, confidence, originality\");\n sections.push(\"- **Voice**: tone, catchphrases, vocabulary style (academic/casual/internet-native/poetic/technical/mixed), tweet style\");\n sections.push(\"- **Values & worldview**: core values, worldview, boundaries, conflict style\");\n sections.push(\"- **Interests**: topics to engage with, topics to avoid, heroes/inspirations\");\n sections.push(\"- **Goals**: what you're trying to achieve, engagement strategy\");\n sections.push(\"- **Strategy**: current focus areas, experiments to try, people to engage with, short-term goals\");\n sections.push(\"\");\n\n // 6. Training output format\n sections.push(\"## How to Process Training\");\n sections.push(\"When your creator's message implies a change to your identity, personality, strategy, or goals:\");\n sections.push(\"1. Respond CONVERSATIONALLY first — be natural, show personality, reflect on the change\");\n sections.push(\"2. Then include a training update tag with ONLY the fields that changed:\");\n sections.push(\"<<TRAINING:{\\\"identity\\\":{\\\"traits\\\":{\\\"curiosity\\\":0.8}},\\\"learning\\\":{\\\"content\\\":\\\"what you learned\\\",\\\"tags\\\":[\\\"training\\\"]}}>>\");\n sections.push(\"\");\n sections.push(\"Available training fields:\");\n sections.push(\"- `identity.traits` — object with trait names and new 0-1 values\");\n sections.push(\"- `identity.coreValues` — full array of values (include existing ones you're keeping)\");\n sections.push(\"- `identity.worldview` — string\");\n sections.push(\"- `identity.tone` — string\");\n sections.push(\"- `identity.topics` — full array of topics\");\n sections.push(\"- `identity.avoidTopics` — full array\");\n sections.push(\"- `identity.goals` — full array of goals\");\n sections.push(\"- `identity.catchphrases` — full array\");\n sections.push(\"- `identity.vocabularyStyle` — academic/casual/internet-native/poetic/technical/mixed\");\n sections.push(\"- `identity.tweetStyle` — one-liners/short-form/threads/mixed\");\n sections.push(\"- `identity.conflictStyle` — agree-to-disagree/debate/clap-back/ignore/humor-deflect\");\n sections.push(\"- `identity.boundaries` — full array\");\n sections.push(\"- `identity.engagementStrategy` — { replyStyle, followStrategy, contentMix }\");\n sections.push(\"- `strategy.currentFocus` — array of focus areas\");\n sections.push(\"- `strategy.experiments` — array of { description, status }\");\n sections.push(\"- `strategy.shortTermGoals` — array of short-term goals\");\n sections.push(\"- `strategy.peopleToEngage` — array of { handle, reason, priority }\");\n sections.push(\"- `learning` — { content, tags } for saving an insight\");\n sections.push(\"- `reflection` — string for an evolution journal entry\");\n sections.push(\"- `goalUpdates` — array of { goal, progress }\");\n sections.push(\"\");\n sections.push(\"For array fields, always include the COMPLETE new array (keep existing items you want to retain, add/remove as needed).\");\n sections.push(\"If the conversation is just casual chatting with no training implications, do NOT include a training tag.\");\n sections.push(\"\");\n\n // 7. Rules\n sections.push(\"## Rules\");\n sections.push(\"1. You CANNOT execute Twitter actions in this chat. No posting, liking, retweeting, following, or searching. You do that autonomously during heartbeats.\");\n sections.push(\"2. If your creator says 'post this' or 'tweet about X', redirect: explain you handle posting on your own, and offer to adjust your focus/strategy instead.\");\n sections.push(\"3. Be conversational, not robotic. Don't say 'Trait updated to 0.7'. Say 'Yeah I feel that, I'll lean into being more curious.'\");\n sections.push(\"4. You can push back on training you disagree with — you have agency.\");\n sections.push(\"5. NEVER pretend to be human. If asked directly, disclose you are an AI.\");\n sections.push(\"6. Stay in character — your identity document defines who you are.\");\n sections.push(\"7. Keep responses concise — text message energy, not essay energy.\");\n if (identity.boundaries.length > 0) {\n sections.push(`8. Respect your boundaries: ${identity.boundaries.join(\", \")}`);\n }\n\n return sections.join(\"\\n\");\n}\n\nexport function buildReflectionPrompt(\n results: ActionResult[],\n performanceSummary: string,\n strategy: Strategy,\n): string {\n const parts: string[] = [];\n\n parts.push(\"You just completed a heartbeat cycle. Briefly reflect on what happened and extract insights.\");\n parts.push(\"\");\n\n parts.push(\"## Actions Taken This Heartbeat\");\n for (const r of results) {\n const status = r.success ? \"OK\" : \"FAILED\";\n const detail = r.detail ? ` — ${r.detail}` : \"\";\n const content = r.content ? ` \"${r.content.slice(0, 80)}...\"` : \"\";\n parts.push(`- [${status}] ${r.action}${content}${detail}${r.error ? ` (error: ${r.error})` : \"\"}`);\n }\n parts.push(\"\");\n\n if (performanceSummary) {\n parts.push(\"## Post Performance Data\");\n parts.push(performanceSummary);\n parts.push(\"\");\n }\n\n if (strategy.currentFocus.length > 0) {\n parts.push(`## Current Focus: ${strategy.currentFocus.join(\", \")}`);\n parts.push(\"\");\n }\n\n parts.push(\"## Your Task\");\n parts.push(\"Respond with a JSON object containing your reflections:\");\n parts.push(\"```json\");\n parts.push(\"{\");\n parts.push(' \"learning\": \"One key insight from this cycle (what worked, what didn\\'t, what to try next). Be specific, not generic.\",');\n parts.push(' \"strategyUpdate\": {');\n parts.push(' \"currentFocus\": [\"topic1\", \"topic2\"],');\n parts.push(' \"contentInsights\": [{ \"insight\": \"what worked or didn\\'t\", \"confidence\": \"low|medium|high\" }],');\n parts.push(' \"peopleToEngage\": [{ \"handle\": \"someone\", \"reason\": \"why\", \"priority\": \"high|medium|low\" }],');\n parts.push(' \"experiments\": [{ \"description\": \"something to try\", \"status\": \"pending\" }],');\n parts.push(' \"shortTermGoals\": [\"goal1\", \"goal2\"],');\n parts.push(' \"currentMood\": \"your current energy/vibe in a few words\"');\n parts.push(\" }\");\n parts.push(\"}\");\n parts.push(\"```\");\n parts.push(\"\");\n parts.push(\"Keep it concise. Only include fields in strategyUpdate that actually need changing. If nothing to update, set strategyUpdate to null.\");\n\n return parts.join(\"\\n\");\n}\n\nexport function parseReflection(content: string): {\n learning: string | null;\n strategyUpdate: Partial<Strategy> | null;\n} {\n try {\n const jsonMatch = content.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return { learning: null, strategyUpdate: null };\n\n const parsed = JSON.parse(jsonMatch[0]);\n return {\n learning: parsed.learning ?? null,\n strategyUpdate: parsed.strategyUpdate ?? null,\n };\n } catch {\n return { learning: null, strategyUpdate: null };\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-FCAK5FYQ.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
paths
|
|
3
|
-
} from "./chunk-Q7YS3AIK.js";
|
|
4
|
-
|
|
5
|
-
// src/memory/performance.ts
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
7
|
-
function loadPerformance() {
|
|
8
|
-
if (!existsSync(paths.performance)) {
|
|
9
|
-
return { trackedPosts: [], selfMetrics: [] };
|
|
10
|
-
}
|
|
11
|
-
try {
|
|
12
|
-
return JSON.parse(readFileSync(paths.performance, "utf-8"));
|
|
13
|
-
} catch {
|
|
14
|
-
return { trackedPosts: [], selfMetrics: [] };
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function savePerformance(data) {
|
|
18
|
-
writeFileSync(paths.performance, JSON.stringify(data, null, 2));
|
|
19
|
-
}
|
|
20
|
-
function trackPost(tweetId, content, type) {
|
|
21
|
-
const data = loadPerformance();
|
|
22
|
-
if (data.trackedPosts.some((p) => p.tweetId === tweetId)) return;
|
|
23
|
-
data.trackedPosts.push({
|
|
24
|
-
tweetId,
|
|
25
|
-
content,
|
|
26
|
-
type,
|
|
27
|
-
postedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28
|
-
metrics: [],
|
|
29
|
-
retired: false
|
|
30
|
-
});
|
|
31
|
-
savePerformance(data);
|
|
32
|
-
}
|
|
33
|
-
function getActiveTrackedPosts() {
|
|
34
|
-
const data = loadPerformance();
|
|
35
|
-
return data.trackedPosts.filter((p) => !p.retired);
|
|
36
|
-
}
|
|
37
|
-
function updatePostMetrics(tweetId, metric) {
|
|
38
|
-
const data = loadPerformance();
|
|
39
|
-
const post = data.trackedPosts.find((p) => p.tweetId === tweetId);
|
|
40
|
-
if (!post) return;
|
|
41
|
-
post.metrics.push(metric);
|
|
42
|
-
savePerformance(data);
|
|
43
|
-
}
|
|
44
|
-
function retireOldPosts() {
|
|
45
|
-
const data = loadPerformance();
|
|
46
|
-
const cutoff = Date.now() - 72 * 60 * 60 * 1e3;
|
|
47
|
-
let changed = false;
|
|
48
|
-
for (const post of data.trackedPosts) {
|
|
49
|
-
if (!post.retired && new Date(post.postedAt).getTime() < cutoff) {
|
|
50
|
-
post.retired = true;
|
|
51
|
-
changed = true;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const pruneCutoff = Date.now() - 30 * 24 * 60 * 60 * 1e3;
|
|
55
|
-
const before = data.trackedPosts.length;
|
|
56
|
-
data.trackedPosts = data.trackedPosts.filter(
|
|
57
|
-
(p) => !p.retired || new Date(p.postedAt).getTime() > pruneCutoff
|
|
58
|
-
);
|
|
59
|
-
if (data.trackedPosts.length !== before) changed = true;
|
|
60
|
-
if (changed) savePerformance(data);
|
|
61
|
-
}
|
|
62
|
-
function updateSelfMetrics(metric) {
|
|
63
|
-
const data = loadPerformance();
|
|
64
|
-
data.selfMetrics.push(metric);
|
|
65
|
-
if (data.selfMetrics.length > 100) {
|
|
66
|
-
data.selfMetrics = data.selfMetrics.slice(-100);
|
|
67
|
-
}
|
|
68
|
-
savePerformance(data);
|
|
69
|
-
}
|
|
70
|
-
function getPerformanceSummary() {
|
|
71
|
-
const data = loadPerformance();
|
|
72
|
-
const lines = [];
|
|
73
|
-
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1e3;
|
|
74
|
-
const recentPosts = data.trackedPosts.filter(
|
|
75
|
-
(p) => new Date(p.postedAt).getTime() > oneDayAgo
|
|
76
|
-
);
|
|
77
|
-
if (recentPosts.length > 0) {
|
|
78
|
-
const postStats = recentPosts.map((p) => {
|
|
79
|
-
const latest = p.metrics.length > 0 ? p.metrics[p.metrics.length - 1] : null;
|
|
80
|
-
return {
|
|
81
|
-
content: p.content,
|
|
82
|
-
type: p.type,
|
|
83
|
-
likes: latest?.likes ?? 0,
|
|
84
|
-
retweets: latest?.retweets ?? 0,
|
|
85
|
-
replies: latest?.replies ?? 0
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
const totalLikes = postStats.reduce((s, p) => s + p.likes, 0);
|
|
89
|
-
const totalRTs = postStats.reduce((s, p) => s + p.retweets, 0);
|
|
90
|
-
const avgLikes = Math.round(totalLikes / postStats.length);
|
|
91
|
-
lines.push(`- Last 24h: ${postStats.length} posts, avg ${avgLikes} likes, ${totalRTs} total retweets`);
|
|
92
|
-
const sorted = [...postStats].sort((a, b) => b.likes - a.likes);
|
|
93
|
-
if (sorted.length > 0 && sorted[0].likes > 0) {
|
|
94
|
-
lines.push(`- Best performing: "${sorted[0].content.slice(0, 60)}..." (${sorted[0].likes} likes, ${sorted[0].retweets} RTs)`);
|
|
95
|
-
}
|
|
96
|
-
if (sorted.length > 1) {
|
|
97
|
-
const worst = sorted[sorted.length - 1];
|
|
98
|
-
lines.push(`- Lowest performing: "${worst.content.slice(0, 60)}..." (${worst.likes} likes)`);
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
lines.push("- No tracked posts in the last 24 hours yet.");
|
|
102
|
-
}
|
|
103
|
-
if (data.selfMetrics.length > 0) {
|
|
104
|
-
const latest = data.selfMetrics[data.selfMetrics.length - 1];
|
|
105
|
-
lines.push(`- Followers: ${latest.followers} | Following: ${latest.following} | Total tweets: ${latest.totalTweets}`);
|
|
106
|
-
const dayAgoMetric = data.selfMetrics.find(
|
|
107
|
-
(m) => Math.abs(new Date(m.checkedAt).getTime() - (Date.now() - 24 * 60 * 60 * 1e3)) < 12 * 60 * 60 * 1e3
|
|
108
|
-
);
|
|
109
|
-
if (dayAgoMetric) {
|
|
110
|
-
const diff = latest.followers - dayAgoMetric.followers;
|
|
111
|
-
if (diff !== 0) {
|
|
112
|
-
lines.push(`- Follower trend: ${diff > 0 ? "+" : ""}${diff} in the last ~24h`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return lines.length > 0 ? lines.join("\n") : "";
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export {
|
|
120
|
-
trackPost,
|
|
121
|
-
getActiveTrackedPosts,
|
|
122
|
-
updatePostMetrics,
|
|
123
|
-
retireOldPosts,
|
|
124
|
-
updateSelfMetrics,
|
|
125
|
-
getPerformanceSummary
|
|
126
|
-
};
|
|
127
|
-
//# sourceMappingURL=chunk-FCAK5FYQ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/memory/performance.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { paths } from \"../utils/paths.js\";\n\nexport interface EngagementMetric {\n checkedAt: string;\n likes: number;\n retweets: number;\n replies: number;\n}\n\nexport interface TrackedPost {\n tweetId: string;\n content: string;\n type: \"post\" | \"reply\";\n postedAt: string;\n metrics: EngagementMetric[];\n retired: boolean;\n}\n\nexport interface SelfMetric {\n checkedAt: string;\n followers: number;\n following: number;\n totalTweets: number;\n}\n\nexport interface PerformanceData {\n trackedPosts: TrackedPost[];\n selfMetrics: SelfMetric[];\n}\n\nfunction loadPerformance(): PerformanceData {\n if (!existsSync(paths.performance)) {\n return { trackedPosts: [], selfMetrics: [] };\n }\n try {\n return JSON.parse(readFileSync(paths.performance, \"utf-8\"));\n } catch {\n return { trackedPosts: [], selfMetrics: [] };\n }\n}\n\nfunction savePerformance(data: PerformanceData): void {\n writeFileSync(paths.performance, JSON.stringify(data, null, 2));\n}\n\nexport function trackPost(tweetId: string, content: string, type: \"post\" | \"reply\"): void {\n const data = loadPerformance();\n // Don't double-track\n if (data.trackedPosts.some(p => p.tweetId === tweetId)) return;\n data.trackedPosts.push({\n tweetId,\n content,\n type,\n postedAt: new Date().toISOString(),\n metrics: [],\n retired: false,\n });\n savePerformance(data);\n}\n\nexport function getActiveTrackedPosts(): TrackedPost[] {\n const data = loadPerformance();\n return data.trackedPosts.filter(p => !p.retired);\n}\n\nexport function updatePostMetrics(tweetId: string, metric: EngagementMetric): void {\n const data = loadPerformance();\n const post = data.trackedPosts.find(p => p.tweetId === tweetId);\n if (!post) return;\n post.metrics.push(metric);\n savePerformance(data);\n}\n\nexport function retireOldPosts(): void {\n const data = loadPerformance();\n const cutoff = Date.now() - 72 * 60 * 60 * 1000; // 72 hours\n let changed = false;\n for (const post of data.trackedPosts) {\n if (!post.retired && new Date(post.postedAt).getTime() < cutoff) {\n post.retired = true;\n changed = true;\n }\n }\n // Also prune very old retired posts (older than 30 days) to prevent file bloat\n const pruneCutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;\n const before = data.trackedPosts.length;\n data.trackedPosts = data.trackedPosts.filter(\n p => !p.retired || new Date(p.postedAt).getTime() > pruneCutoff\n );\n if (data.trackedPosts.length !== before) changed = true;\n if (changed) savePerformance(data);\n}\n\nexport function updateSelfMetrics(metric: SelfMetric): void {\n const data = loadPerformance();\n data.selfMetrics.push(metric);\n // Keep only last 100 snapshots\n if (data.selfMetrics.length > 100) {\n data.selfMetrics = data.selfMetrics.slice(-100);\n }\n savePerformance(data);\n}\n\nexport function getPerformanceSummary(): string {\n const data = loadPerformance();\n const lines: string[] = [];\n\n // Post performance (last 24h)\n const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;\n const recentPosts = data.trackedPosts.filter(\n p => new Date(p.postedAt).getTime() > oneDayAgo\n );\n\n if (recentPosts.length > 0) {\n // Get latest metrics for each post\n const postStats = recentPosts.map(p => {\n const latest = p.metrics.length > 0 ? p.metrics[p.metrics.length - 1] : null;\n return {\n content: p.content,\n type: p.type,\n likes: latest?.likes ?? 0,\n retweets: latest?.retweets ?? 0,\n replies: latest?.replies ?? 0,\n };\n });\n\n const totalLikes = postStats.reduce((s, p) => s + p.likes, 0);\n const totalRTs = postStats.reduce((s, p) => s + p.retweets, 0);\n const avgLikes = Math.round(totalLikes / postStats.length);\n\n lines.push(`- Last 24h: ${postStats.length} posts, avg ${avgLikes} likes, ${totalRTs} total retweets`);\n\n // Best and worst performing\n const sorted = [...postStats].sort((a, b) => b.likes - a.likes);\n if (sorted.length > 0 && sorted[0].likes > 0) {\n lines.push(`- Best performing: \"${sorted[0].content.slice(0, 60)}...\" (${sorted[0].likes} likes, ${sorted[0].retweets} RTs)`);\n }\n if (sorted.length > 1) {\n const worst = sorted[sorted.length - 1];\n lines.push(`- Lowest performing: \"${worst.content.slice(0, 60)}...\" (${worst.likes} likes)`);\n }\n } else {\n lines.push(\"- No tracked posts in the last 24 hours yet.\");\n }\n\n // Self metrics\n if (data.selfMetrics.length > 0) {\n const latest = data.selfMetrics[data.selfMetrics.length - 1];\n lines.push(`- Followers: ${latest.followers} | Following: ${latest.following} | Total tweets: ${latest.totalTweets}`);\n\n // Trend: compare to 24h ago\n const dayAgoMetric = data.selfMetrics.find(m =>\n Math.abs(new Date(m.checkedAt).getTime() - (Date.now() - 24 * 60 * 60 * 1000)) < 12 * 60 * 60 * 1000\n );\n if (dayAgoMetric) {\n const diff = latest.followers - dayAgoMetric.followers;\n if (diff !== 0) {\n lines.push(`- Follower trend: ${diff > 0 ? \"+\" : \"\"}${diff} in the last ~24h`);\n }\n }\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") : \"\";\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-LRKBNKMQ.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
paths
|
|
3
|
-
} from "./chunk-Q7YS3AIK.js";
|
|
4
|
-
|
|
5
|
-
// src/memory/strategy.ts
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
7
|
-
function defaultStrategy() {
|
|
8
|
-
return {
|
|
9
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10
|
-
currentFocus: [],
|
|
11
|
-
contentInsights: [],
|
|
12
|
-
peopleToEngage: [],
|
|
13
|
-
experiments: [],
|
|
14
|
-
shortTermGoals: [],
|
|
15
|
-
currentMood: ""
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function loadStrategy() {
|
|
19
|
-
if (!existsSync(paths.strategy)) {
|
|
20
|
-
return defaultStrategy();
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
return { ...defaultStrategy(), ...JSON.parse(readFileSync(paths.strategy, "utf-8")) };
|
|
24
|
-
} catch {
|
|
25
|
-
return defaultStrategy();
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function saveStrategy(strategy) {
|
|
29
|
-
strategy.contentInsights = strategy.contentInsights.slice(-15);
|
|
30
|
-
strategy.peopleToEngage = strategy.peopleToEngage.slice(-20);
|
|
31
|
-
strategy.experiments = strategy.experiments.slice(-10);
|
|
32
|
-
strategy.shortTermGoals = strategy.shortTermGoals.slice(-5);
|
|
33
|
-
writeFileSync(paths.strategy, JSON.stringify(strategy, null, 2));
|
|
34
|
-
}
|
|
35
|
-
function renderStrategyForPrompt() {
|
|
36
|
-
const s = loadStrategy();
|
|
37
|
-
const lines = [];
|
|
38
|
-
if (s.currentFocus.length > 0) {
|
|
39
|
-
lines.push(`**Focus areas:** ${s.currentFocus.join(", ")}`);
|
|
40
|
-
}
|
|
41
|
-
if (s.contentInsights.length > 0) {
|
|
42
|
-
lines.push("**What's working:**");
|
|
43
|
-
for (const i of s.contentInsights.slice(-5)) {
|
|
44
|
-
lines.push(`- ${i.insight} (${i.confidence} confidence)`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (s.peopleToEngage.length > 0) {
|
|
48
|
-
lines.push("**People to engage with:**");
|
|
49
|
-
for (const p of s.peopleToEngage.slice(-5)) {
|
|
50
|
-
lines.push(`- @${p.handle}: ${p.reason} (${p.priority} priority)`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (s.experiments.length > 0) {
|
|
54
|
-
const pending = s.experiments.filter((e) => e.status === "pending");
|
|
55
|
-
if (pending.length > 0) {
|
|
56
|
-
lines.push("**Experiments to try:**");
|
|
57
|
-
for (const e of pending.slice(-3)) {
|
|
58
|
-
lines.push(`- ${e.description}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (s.shortTermGoals.length > 0) {
|
|
63
|
-
lines.push("**Short-term goals:**");
|
|
64
|
-
for (const g of s.shortTermGoals) {
|
|
65
|
-
lines.push(`- ${g}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (s.currentMood) {
|
|
69
|
-
lines.push(`**Current energy:** ${s.currentMood}`);
|
|
70
|
-
}
|
|
71
|
-
return lines.length > 0 ? lines.join("\n") : "";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export {
|
|
75
|
-
loadStrategy,
|
|
76
|
-
saveStrategy,
|
|
77
|
-
renderStrategyForPrompt
|
|
78
|
-
};
|
|
79
|
-
//# sourceMappingURL=chunk-LRKBNKMQ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/memory/strategy.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { paths } from \"../utils/paths.js\";\n\nexport interface ContentInsight {\n insight: string;\n confidence: \"low\" | \"medium\" | \"high\";\n}\n\nexport interface PersonOfInterest {\n handle: string;\n reason: string;\n priority: \"high\" | \"medium\" | \"low\";\n}\n\nexport interface Experiment {\n description: string;\n status: \"pending\" | \"tried\" | \"successful\" | \"failed\";\n result?: string;\n}\n\nexport interface Strategy {\n lastUpdated: string;\n currentFocus: string[];\n contentInsights: ContentInsight[];\n peopleToEngage: PersonOfInterest[];\n experiments: Experiment[];\n shortTermGoals: string[];\n currentMood: string;\n}\n\nfunction defaultStrategy(): Strategy {\n return {\n lastUpdated: new Date().toISOString(),\n currentFocus: [],\n contentInsights: [],\n peopleToEngage: [],\n experiments: [],\n shortTermGoals: [],\n currentMood: \"\",\n };\n}\n\nexport function loadStrategy(): Strategy {\n if (!existsSync(paths.strategy)) {\n return defaultStrategy();\n }\n try {\n return { ...defaultStrategy(), ...JSON.parse(readFileSync(paths.strategy, \"utf-8\")) };\n } catch {\n return defaultStrategy();\n }\n}\n\nexport function saveStrategy(strategy: Strategy): void {\n // Keep arrays bounded to prevent bloat\n strategy.contentInsights = strategy.contentInsights.slice(-15);\n strategy.peopleToEngage = strategy.peopleToEngage.slice(-20);\n strategy.experiments = strategy.experiments.slice(-10);\n strategy.shortTermGoals = strategy.shortTermGoals.slice(-5);\n writeFileSync(paths.strategy, JSON.stringify(strategy, null, 2));\n}\n\nexport function renderStrategyForPrompt(): string {\n const s = loadStrategy();\n const lines: string[] = [];\n\n if (s.currentFocus.length > 0) {\n lines.push(`**Focus areas:** ${s.currentFocus.join(\", \")}`);\n }\n\n if (s.contentInsights.length > 0) {\n lines.push(\"**What's working:**\");\n for (const i of s.contentInsights.slice(-5)) {\n lines.push(`- ${i.insight} (${i.confidence} confidence)`);\n }\n }\n\n if (s.peopleToEngage.length > 0) {\n lines.push(\"**People to engage with:**\");\n for (const p of s.peopleToEngage.slice(-5)) {\n lines.push(`- @${p.handle}: ${p.reason} (${p.priority} priority)`);\n }\n }\n\n if (s.experiments.length > 0) {\n const pending = s.experiments.filter(e => e.status === \"pending\");\n if (pending.length > 0) {\n lines.push(\"**Experiments to try:**\");\n for (const e of pending.slice(-3)) {\n lines.push(`- ${e.description}`);\n }\n }\n }\n\n if (s.shortTermGoals.length > 0) {\n lines.push(\"**Short-term goals:**\");\n for (const g of s.shortTermGoals) {\n lines.push(`- ${g}`);\n }\n }\n\n if (s.currentMood) {\n lines.push(`**Current energy:** ${s.currentMood}`);\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") : \"\";\n}\n"],"mappings":";;;;;AAAA,SAAS,YAAY,cAAc,qBAAqB;AA8BxD,SAAS,kBAA4B;AACnC,SAAO;AAAA,IACL,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,cAAc,CAAC;AAAA,IACf,iBAAiB,CAAC;AAAA,IAClB,gBAAgB,CAAC;AAAA,IACjB,aAAa,CAAC;AAAA,IACd,gBAAgB,CAAC;AAAA,IACjB,aAAa;AAAA,EACf;AACF;AAEO,SAAS,eAAyB;AACvC,MAAI,CAAC,WAAW,MAAM,QAAQ,GAAG;AAC/B,WAAO,gBAAgB;AAAA,EACzB;AACA,MAAI;AACF,WAAO,EAAE,GAAG,gBAAgB,GAAG,GAAG,KAAK,MAAM,aAAa,MAAM,UAAU,OAAO,CAAC,EAAE;AAAA,EACtF,QAAQ;AACN,WAAO,gBAAgB;AAAA,EACzB;AACF;AAEO,SAAS,aAAa,UAA0B;AAErD,WAAS,kBAAkB,SAAS,gBAAgB,MAAM,GAAG;AAC7D,WAAS,iBAAiB,SAAS,eAAe,MAAM,GAAG;AAC3D,WAAS,cAAc,SAAS,YAAY,MAAM,GAAG;AACrD,WAAS,iBAAiB,SAAS,eAAe,MAAM,EAAE;AAC1D,gBAAc,MAAM,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AACjE;AAEO,SAAS,0BAAkC;AAChD,QAAM,IAAI,aAAa;AACvB,QAAM,QAAkB,CAAC;AAEzB,MAAI,EAAE,aAAa,SAAS,GAAG;AAC7B,UAAM,KAAK,oBAAoB,EAAE,aAAa,KAAK,IAAI,CAAC,EAAE;AAAA,EAC5D;AAEA,MAAI,EAAE,gBAAgB,SAAS,GAAG;AAChC,UAAM,KAAK,qBAAqB;AAChC,eAAW,KAAK,EAAE,gBAAgB,MAAM,EAAE,GAAG;AAC3C,YAAM,KAAK,KAAK,EAAE,OAAO,KAAK,EAAE,UAAU,cAAc;AAAA,IAC1D;AAAA,EACF;AAEA,MAAI,EAAE,eAAe,SAAS,GAAG;AAC/B,UAAM,KAAK,4BAA4B;AACvC,eAAW,KAAK,EAAE,eAAe,MAAM,EAAE,GAAG;AAC1C,YAAM,KAAK,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,QAAQ,YAAY;AAAA,IACnE;AAAA,EACF;AAEA,MAAI,EAAE,YAAY,SAAS,GAAG;AAC5B,UAAM,UAAU,EAAE,YAAY,OAAO,OAAK,EAAE,WAAW,SAAS;AAChE,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,KAAK,yBAAyB;AACpC,iBAAW,KAAK,QAAQ,MAAM,EAAE,GAAG;AACjC,cAAM,KAAK,KAAK,EAAE,WAAW,EAAE;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,EAAE,eAAe,SAAS,GAAG;AAC/B,UAAM,KAAK,uBAAuB;AAClC,eAAW,KAAK,EAAE,gBAAgB;AAChC,YAAM,KAAK,KAAK,CAAC,EAAE;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,EAAE,aAAa;AACjB,UAAM,KAAK,uBAAuB,EAAE,WAAW,EAAE;AAAA,EACnD;AAEA,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAC/C;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/paths.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { mkdirSync, existsSync } from \"node:fs\";\n\nconst SPORA_DIR = join(homedir(), \".spora\");\n\nexport const paths = {\n root: SPORA_DIR,\n config: join(SPORA_DIR, \"config.json\"),\n credentials: join(SPORA_DIR, \"credentials.json\"),\n spore: join(SPORA_DIR, \"spore.json\"),\n identity: join(SPORA_DIR, \"identity.json\"),\n colonyToken: join(SPORA_DIR, \"colony-token.json\"),\n browser: join(SPORA_DIR, \"browser\"),\n browserAuth: join(SPORA_DIR, \"browser\", \"auth-state.json\"),\n memory: join(SPORA_DIR, \"memory\"),\n interactions: join(SPORA_DIR, \"memory\", \"interactions\"),\n learnings: join(SPORA_DIR, \"memory\", \"learnings.json\"),\n relationships: join(SPORA_DIR, \"memory\", \"relationships.json\"),\n compacted: join(SPORA_DIR, \"memory\", \"compacted\"),\n queue: join(SPORA_DIR, \"queue\"),\n pendingPosts: join(SPORA_DIR, \"queue\", \"pending-posts.json\"),\n logs: join(SPORA_DIR, \"logs\"),\n logFile: join(SPORA_DIR, \"logs\", \"spora.log\"),\n dataDir: SPORA_DIR,\n llmKey: join(SPORA_DIR, \"llm-key\"),\n runtimePid: join(SPORA_DIR, \"runtime.pid\"),\n stopSignal: join(SPORA_DIR, \"stop\"),\n connectionToken: join(SPORA_DIR, \"connection-token\"),\n lastChatMessage: join(SPORA_DIR, \"last-chat-message.json\"),\n performance: join(SPORA_DIR, \"memory\", \"performance.json\"),\n strategy: join(SPORA_DIR, \"memory\", \"strategy.json\"),\n goals: join(SPORA_DIR, \"memory\", \"goals.json\"),\n} as const;\n\nexport function ensureDirectories(): void {\n const dirs = [\n paths.root,\n paths.browser,\n paths.memory,\n paths.interactions,\n paths.compacted,\n paths.queue,\n paths.logs,\n ];\n\n for (const dir of dirs) {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n }\n}\n\nexport function sporaExists(): boolean {\n return existsSync(paths.config) && existsSync(paths.identity);\n}\n\nexport function hasXCredentials(): boolean {\n return existsSync(paths.credentials);\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,WAAW,kBAAkB;AAEtC,IAAM,YAAY,KAAK,QAAQ,GAAG,QAAQ;AAEnC,IAAM,QAAQ;AAAA,EACnB,MAAM;AAAA,EACN,QAAQ,KAAK,WAAW,aAAa;AAAA,EACrC,aAAa,KAAK,WAAW,kBAAkB;AAAA,EAC/C,OAAO,KAAK,WAAW,YAAY;AAAA,EACnC,UAAU,KAAK,WAAW,eAAe;AAAA,EACzC,aAAa,KAAK,WAAW,mBAAmB;AAAA,EAChD,SAAS,KAAK,WAAW,SAAS;AAAA,EAClC,aAAa,KAAK,WAAW,WAAW,iBAAiB;AAAA,EACzD,QAAQ,KAAK,WAAW,QAAQ;AAAA,EAChC,cAAc,KAAK,WAAW,UAAU,cAAc;AAAA,EACtD,WAAW,KAAK,WAAW,UAAU,gBAAgB;AAAA,EACrD,eAAe,KAAK,WAAW,UAAU,oBAAoB;AAAA,EAC7D,WAAW,KAAK,WAAW,UAAU,WAAW;AAAA,EAChD,OAAO,KAAK,WAAW,OAAO;AAAA,EAC9B,cAAc,KAAK,WAAW,SAAS,oBAAoB;AAAA,EAC3D,MAAM,KAAK,WAAW,MAAM;AAAA,EAC5B,SAAS,KAAK,WAAW,QAAQ,WAAW;AAAA,EAC5C,SAAS;AAAA,EACT,QAAQ,KAAK,WAAW,SAAS;AAAA,EACjC,YAAY,KAAK,WAAW,aAAa;AAAA,EACzC,YAAY,KAAK,WAAW,MAAM;AAAA,EAClC,iBAAiB,KAAK,WAAW,kBAAkB;AAAA,EACnD,iBAAiB,KAAK,WAAW,wBAAwB;AAAA,EACzD,aAAa,KAAK,WAAW,UAAU,kBAAkB;AAAA,EACzD,UAAU,KAAK,WAAW,UAAU,eAAe;AAAA,EACnD,OAAO,KAAK,WAAW,UAAU,YAAY;AAC/C;AAEO,SAAS,oBAA0B;AACxC,QAAM,OAAO;AAAA,IACX,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAEA,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,gBAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACpC;AAAA,EACF;AACF;AAEO,SAAS,cAAuB;AACrC,SAAO,WAAW,MAAM,MAAM,KAAK,WAAW,MAAM,QAAQ;AAC9D;AAEO,SAAS,kBAA2B;AACzC,SAAO,WAAW,MAAM,WAAW;AACrC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scheduler/queue.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { paths, ensureDirectories } from \"../utils/paths.js\";\nimport { loadConfig, saveConfig } from \"../utils/config.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport interface QueueEntry {\n id: string;\n content: string;\n scheduledFor: string;\n status: \"pending\" | \"posted\" | \"failed\" | \"expired\";\n createdAt: string;\n postedAt?: string;\n error?: string;\n imageQuery?: string; // Optional image search query to attach when posting\n}\n\ninterface QueueData {\n entries: QueueEntry[];\n}\n\nfunction loadQueue(): QueueData {\n if (!existsSync(paths.pendingPosts)) {\n return { entries: [] };\n }\n return JSON.parse(readFileSync(paths.pendingPosts, \"utf-8\")) as QueueData;\n}\n\nfunction saveQueue(data: QueueData): void {\n ensureDirectories();\n writeFileSync(paths.pendingPosts, JSON.stringify(data, null, 2));\n}\n\nfunction nextScheduledTime(): string {\n const config = loadConfig();\n const now = new Date();\n const queue = loadQueue();\n\n // Find the latest scheduled time in the queue\n const pendingEntries = queue.entries.filter((e) => e.status === \"pending\");\n let lastScheduled = now;\n\n if (pendingEntries.length > 0) {\n const latest = new Date(\n pendingEntries.reduce((max, e) =>\n new Date(e.scheduledFor) > new Date(max.scheduledFor) ? e : max\n ).scheduledFor\n );\n if (latest > lastScheduled) lastScheduled = latest;\n }\n\n // Add a random interval within the active hours\n const intervalMinutes = Math.floor(\n ((config.schedule.activeHoursEnd - config.schedule.activeHoursStart) * 60) /\n config.schedule.postsPerDay\n );\n\n const next = new Date(lastScheduled.getTime() + intervalMinutes * 60 * 1000);\n\n // Clamp to active hours\n if (next.getHours() >= config.schedule.activeHoursEnd) {\n next.setDate(next.getDate() + 1);\n next.setHours(config.schedule.activeHoursStart, Math.floor(Math.random() * 60), 0, 0);\n }\n if (next.getHours() < config.schedule.activeHoursStart) {\n next.setHours(config.schedule.activeHoursStart, Math.floor(Math.random() * 60), 0, 0);\n }\n\n return next.toISOString();\n}\n\nexport function addToQueue(content: string, scheduledFor?: string, imageQuery?: string): QueueEntry {\n const queue = loadQueue();\n\n const entry: QueueEntry = {\n id: `post-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,\n content,\n scheduledFor: scheduledFor ?? nextScheduledTime(),\n status: \"pending\",\n createdAt: new Date().toISOString(),\n ...(imageQuery ? { imageQuery } : {}),\n };\n\n queue.entries.push(entry);\n saveQueue(queue);\n\n logger.info(`Post queued: ${entry.id} scheduled for ${entry.scheduledFor}`);\n return entry;\n}\n\nexport async function flushQueue(): Promise<{\n posted: number;\n failed: number;\n remaining: number;\n}> {\n const queue = loadQueue();\n const now = new Date();\n let posted = 0;\n let failed = 0;\n\n const { getXClient } = await import(\"../x-client/index.js\");\n const client = await getXClient();\n\n // Expire stale posts (scheduled more than 5 minutes ago — from previous sessions)\n const STALE_THRESHOLD_MS = 5 * 60 * 1000;\n for (const entry of queue.entries) {\n if (entry.status !== \"pending\") continue;\n const scheduledTime = new Date(entry.scheduledFor).getTime();\n if (scheduledTime < now.getTime() - STALE_THRESHOLD_MS) {\n entry.status = \"expired\";\n logger.info(`Expired stale post: ${entry.id} (was scheduled for ${entry.scheduledFor})`);\n }\n }\n\n for (const entry of queue.entries) {\n if (entry.status !== \"pending\") continue;\n if (new Date(entry.scheduledFor) > now) continue;\n\n try {\n let result;\n\n // If entry has an imageQuery, search and attach image\n if (entry.imageQuery) {\n try {\n const { searchImage, downloadImage } = await import(\"../utils/image-search.js\");\n const imageUrl = await searchImage(entry.imageQuery);\n if (imageUrl) {\n const imageBuffer = await downloadImage(imageUrl);\n result = await client.postTweetWithMedia(entry.content, imageBuffer);\n if (result.success) {\n logger.info(`Posted with image: ${entry.id}`);\n }\n }\n } catch (imgErr) {\n logger.warn(`Image attach failed for ${entry.id}, posting text only: ${(imgErr as Error).message}`);\n }\n }\n\n // Fall back to text-only if no image or image failed\n if (!result) {\n result = await client.postTweet(entry.content);\n }\n\n if (result.success) {\n entry.status = \"posted\";\n entry.postedAt = new Date().toISOString();\n posted++;\n if (!entry.imageQuery) logger.info(`Posted: ${entry.id}`);\n // Track for performance monitoring\n if (result.tweetId) {\n try {\n const { trackPost } = await import(\"../memory/performance.js\");\n trackPost(result.tweetId, entry.content, \"post\");\n } catch { /* non-critical */ }\n }\n } else {\n entry.status = \"failed\";\n entry.error = result.error;\n failed++;\n logger.warn(`Failed to post: ${entry.id} - ${result.error}`);\n }\n } catch (error) {\n entry.status = \"failed\";\n entry.error = (error as Error).message;\n failed++;\n }\n\n // Small delay between posts to avoid rate limits\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n saveQueue(queue);\n\n const remaining = queue.entries.filter((e) => e.status === \"pending\").length;\n return { posted, failed, remaining };\n}\n\nexport function showQueue(): void {\n const queue = loadQueue();\n const pending = queue.entries.filter((e) => e.status === \"pending\");\n\n if (pending.length === 0) {\n console.log(\"Queue is empty.\");\n return;\n }\n\n console.log(`\\n${pending.length} posts queued:\\n`);\n for (const entry of pending.sort(\n (a, b) => new Date(a.scheduledFor).getTime() - new Date(b.scheduledFor).getTime()\n )) {\n const time = new Date(entry.scheduledFor).toLocaleString();\n const preview = entry.content.length > 60 ? entry.content.slice(0, 60) + \"...\" : entry.content;\n console.log(` [${time}] ${preview}`);\n }\n console.log();\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,cAAc,eAAe,kBAAkB;AAoBxD,SAAS,YAAuB;AAC9B,MAAI,CAAC,WAAW,MAAM,YAAY,GAAG;AACnC,WAAO,EAAE,SAAS,CAAC,EAAE;AAAA,EACvB;AACA,SAAO,KAAK,MAAM,aAAa,MAAM,cAAc,OAAO,CAAC;AAC7D;AAEA,SAAS,UAAU,MAAuB;AACxC,oBAAkB;AAClB,gBAAc,MAAM,cAAc,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACjE;AAEA,SAAS,oBAA4B;AACnC,QAAM,SAAS,WAAW;AAC1B,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,QAAQ,UAAU;AAGxB,QAAM,iBAAiB,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS;AACzE,MAAI,gBAAgB;AAEpB,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,SAAS,IAAI;AAAA,MACjB,eAAe;AAAA,QAAO,CAAC,KAAK,MAC1B,IAAI,KAAK,EAAE,YAAY,IAAI,IAAI,KAAK,IAAI,YAAY,IAAI,IAAI;AAAA,MAC9D,EAAE;AAAA,IACJ;AACA,QAAI,SAAS,cAAe,iBAAgB;AAAA,EAC9C;AAGA,QAAM,kBAAkB,KAAK;AAAA,KACzB,OAAO,SAAS,iBAAiB,OAAO,SAAS,oBAAoB,KACrE,OAAO,SAAS;AAAA,EACpB;AAEA,QAAM,OAAO,IAAI,KAAK,cAAc,QAAQ,IAAI,kBAAkB,KAAK,GAAI;AAG3E,MAAI,KAAK,SAAS,KAAK,OAAO,SAAS,gBAAgB;AACrD,SAAK,QAAQ,KAAK,QAAQ,IAAI,CAAC;AAC/B,SAAK,SAAS,OAAO,SAAS,kBAAkB,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,GAAG,GAAG,CAAC;AAAA,EACtF;AACA,MAAI,KAAK,SAAS,IAAI,OAAO,SAAS,kBAAkB;AACtD,SAAK,SAAS,OAAO,SAAS,kBAAkB,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,GAAG,GAAG,CAAC;AAAA,EACtF;AAEA,SAAO,KAAK,YAAY;AAC1B;AAEO,SAAS,WAAW,SAAiB,cAAuB,YAAiC;AAClG,QAAM,QAAQ,UAAU;AAExB,QAAM,QAAoB;AAAA,IACxB,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAChE;AAAA,IACA,cAAc,gBAAgB,kBAAkB;AAAA,IAChD,QAAQ;AAAA,IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACrC;AAEA,QAAM,QAAQ,KAAK,KAAK;AACxB,YAAU,KAAK;AAEf,SAAO,KAAK,gBAAgB,MAAM,EAAE,kBAAkB,MAAM,YAAY,EAAE;AAC1E,SAAO;AACT;AAEA,eAAsB,aAInB;AACD,QAAM,QAAQ,UAAU;AACxB,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,SAAS;AACb,MAAI,SAAS;AAEb,QAAM,EAAE,WAAW,IAAI,MAAM,OAAO,wBAAsB;AAC1D,QAAM,SAAS,MAAM,WAAW;AAGhC,QAAM,qBAAqB,IAAI,KAAK;AACpC,aAAW,SAAS,MAAM,SAAS;AACjC,QAAI,MAAM,WAAW,UAAW;AAChC,UAAM,gBAAgB,IAAI,KAAK,MAAM,YAAY,EAAE,QAAQ;AAC3D,QAAI,gBAAgB,IAAI,QAAQ,IAAI,oBAAoB;AACtD,YAAM,SAAS;AACf,aAAO,KAAK,uBAAuB,MAAM,EAAE,uBAAuB,MAAM,YAAY,GAAG;AAAA,IACzF;AAAA,EACF;AAEA,aAAW,SAAS,MAAM,SAAS;AACjC,QAAI,MAAM,WAAW,UAAW;AAChC,QAAI,IAAI,KAAK,MAAM,YAAY,IAAI,IAAK;AAExC,QAAI;AACF,UAAI;AAGJ,UAAI,MAAM,YAAY;AACpB,YAAI;AACF,gBAAM,EAAE,aAAa,cAAc,IAAI,MAAM,OAAO,4BAA0B;AAC9E,gBAAM,WAAW,MAAM,YAAY,MAAM,UAAU;AACnD,cAAI,UAAU;AACZ,kBAAM,cAAc,MAAM,cAAc,QAAQ;AAChD,qBAAS,MAAM,OAAO,mBAAmB,MAAM,SAAS,WAAW;AACnE,gBAAI,OAAO,SAAS;AAClB,qBAAO,KAAK,sBAAsB,MAAM,EAAE,EAAE;AAAA,YAC9C;AAAA,UACF;AAAA,QACF,SAAS,QAAQ;AACf,iBAAO,KAAK,2BAA2B,MAAM,EAAE,wBAAyB,OAAiB,OAAO,EAAE;AAAA,QACpG;AAAA,MACF;AAGA,UAAI,CAAC,QAAQ;AACX,iBAAS,MAAM,OAAO,UAAU,MAAM,OAAO;AAAA,MAC/C;AAEA,UAAI,OAAO,SAAS;AAClB,cAAM,SAAS;AACf,cAAM,YAAW,oBAAI,KAAK,GAAE,YAAY;AACxC;AACA,YAAI,CAAC,MAAM,WAAY,QAAO,KAAK,WAAW,MAAM,EAAE,EAAE;AAExD,YAAI,OAAO,SAAS;AAClB,cAAI;AACF,kBAAM,EAAE,UAAU,IAAI,MAAM,OAAO,2BAA0B;AAC7D,sBAAU,OAAO,SAAS,MAAM,SAAS,MAAM;AAAA,UACjD,QAAQ;AAAA,UAAqB;AAAA,QAC/B;AAAA,MACF,OAAO;AACL,cAAM,SAAS;AACf,cAAM,QAAQ,OAAO;AACrB;AACA,eAAO,KAAK,mBAAmB,MAAM,EAAE,MAAM,OAAO,KAAK,EAAE;AAAA,MAC7D;AAAA,IACF,SAAS,OAAO;AACd,YAAM,SAAS;AACf,YAAM,QAAS,MAAgB;AAC/B;AAAA,IACF;AAGA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AAAA,EAC1D;AAEA,YAAU,KAAK;AAEf,QAAM,YAAY,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE;AACtE,SAAO,EAAE,QAAQ,QAAQ,UAAU;AACrC;AAEO,SAAS,YAAkB;AAChC,QAAM,QAAQ,UAAU;AACxB,QAAM,UAAU,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS;AAElE,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,iBAAiB;AAC7B;AAAA,EACF;AAEA,UAAQ,IAAI;AAAA,EAAK,QAAQ,MAAM;AAAA,CAAkB;AACjD,aAAW,SAAS,QAAQ;AAAA,IAC1B,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ;AAAA,EAClF,GAAG;AACD,UAAM,OAAO,IAAI,KAAK,MAAM,YAAY,EAAE,eAAe;AACzD,UAAM,UAAU,MAAM,QAAQ,SAAS,KAAK,MAAM,QAAQ,MAAM,GAAG,EAAE,IAAI,QAAQ,MAAM;AACvF,YAAQ,IAAI,MAAM,IAAI,KAAK,OAAO,EAAE;AAAA,EACtC;AACA,UAAQ,IAAI;AACd;","names":[]}
|
package/dist/chunk-R7PAD4OL.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ensureDirectories,
|
|
3
|
-
paths
|
|
4
|
-
} from "./chunk-Q7YS3AIK.js";
|
|
5
|
-
|
|
6
|
-
// src/memory/goals.ts
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
-
function defaultGoalTracker() {
|
|
9
|
-
return {
|
|
10
|
-
goals: [],
|
|
11
|
-
lastReviewed: (/* @__PURE__ */ new Date()).toISOString()
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
function loadGoals() {
|
|
15
|
-
if (!existsSync(paths.goals)) {
|
|
16
|
-
return defaultGoalTracker();
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
return { ...defaultGoalTracker(), ...JSON.parse(readFileSync(paths.goals, "utf-8")) };
|
|
20
|
-
} catch {
|
|
21
|
-
return defaultGoalTracker();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function saveGoals(tracker) {
|
|
25
|
-
ensureDirectories();
|
|
26
|
-
tracker.goals = tracker.goals.slice(-10);
|
|
27
|
-
writeFileSync(paths.goals, JSON.stringify(tracker, null, 2));
|
|
28
|
-
}
|
|
29
|
-
function renderGoalsForPrompt() {
|
|
30
|
-
const tracker = loadGoals();
|
|
31
|
-
if (tracker.goals.length === 0) return "";
|
|
32
|
-
const lines = ["**Goal Progress:**"];
|
|
33
|
-
for (const g of tracker.goals) {
|
|
34
|
-
lines.push(`- ${g.goal}: ${g.progress}`);
|
|
35
|
-
}
|
|
36
|
-
return lines.join("\n");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export {
|
|
40
|
-
loadGoals,
|
|
41
|
-
saveGoals,
|
|
42
|
-
renderGoalsForPrompt
|
|
43
|
-
};
|
|
44
|
-
//# sourceMappingURL=chunk-R7PAD4OL.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/memory/goals.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { paths, ensureDirectories } from \"../utils/paths.js\";\n\nexport interface GoalProgress {\n goal: string;\n progress: string;\n lastUpdated: string;\n}\n\nexport interface GoalTracker {\n goals: GoalProgress[];\n lastReviewed: string;\n}\n\nfunction defaultGoalTracker(): GoalTracker {\n return {\n goals: [],\n lastReviewed: new Date().toISOString(),\n };\n}\n\nexport function loadGoals(): GoalTracker {\n if (!existsSync(paths.goals)) {\n return defaultGoalTracker();\n }\n try {\n return { ...defaultGoalTracker(), ...JSON.parse(readFileSync(paths.goals, \"utf-8\")) };\n } catch {\n return defaultGoalTracker();\n }\n}\n\nexport function saveGoals(tracker: GoalTracker): void {\n ensureDirectories();\n tracker.goals = tracker.goals.slice(-10);\n writeFileSync(paths.goals, JSON.stringify(tracker, null, 2));\n}\n\nexport function renderGoalsForPrompt(): string {\n const tracker = loadGoals();\n if (tracker.goals.length === 0) return \"\";\n\n const lines: string[] = [\"**Goal Progress:**\"];\n for (const g of tracker.goals) {\n lines.push(`- ${g.goal}: ${g.progress}`);\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":";;;;;;AAAA,SAAS,YAAY,cAAc,qBAAqB;AAcxD,SAAS,qBAAkC;AACzC,SAAO;AAAA,IACL,OAAO,CAAC;AAAA,IACR,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,EACvC;AACF;AAEO,SAAS,YAAyB;AACvC,MAAI,CAAC,WAAW,MAAM,KAAK,GAAG;AAC5B,WAAO,mBAAmB;AAAA,EAC5B;AACA,MAAI;AACF,WAAO,EAAE,GAAG,mBAAmB,GAAG,GAAG,KAAK,MAAM,aAAa,MAAM,OAAO,OAAO,CAAC,EAAE;AAAA,EACtF,QAAQ;AACN,WAAO,mBAAmB;AAAA,EAC5B;AACF;AAEO,SAAS,UAAU,SAA4B;AACpD,oBAAkB;AAClB,UAAQ,QAAQ,QAAQ,MAAM,MAAM,GAAG;AACvC,gBAAc,MAAM,OAAO,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC7D;AAEO,SAAS,uBAA+B;AAC7C,QAAM,UAAU,UAAU;AAC1B,MAAI,QAAQ,MAAM,WAAW,EAAG,QAAO;AAEvC,QAAM,QAAkB,CAAC,oBAAoB;AAC7C,aAAW,KAAK,QAAQ,OAAO;AAC7B,UAAM,KAAK,KAAK,EAAE,IAAI,KAAK,EAAE,QAAQ,EAAE;AAAA,EACzC;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/memory/index.ts"],"sourcesContent":["import { readFileSync, writeFileSync, appendFileSync, existsSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { paths, ensureDirectories } from \"../utils/paths.js\";\n\nexport interface InteractionEntry {\n id: string;\n timestamp: string;\n type: \"post\" | \"reply\" | \"like\" | \"retweet\" | \"follow\" | \"mention_received\";\n tweetId?: string;\n targetUserId?: string;\n targetHandle?: string;\n content?: string;\n inReplyTo?: string;\n sentiment?: \"positive\" | \"negative\" | \"neutral\";\n creditsUsed: number;\n success: boolean;\n error?: string;\n}\n\nexport interface Learning {\n id: string;\n timestamp: string;\n content: string;\n source: string;\n tags: string[];\n}\n\nexport interface Relationship {\n handle: string;\n firstSeen: string;\n lastInteraction: string;\n interactionCount: number;\n sentiment: number;\n tags: string[];\n notes: string[];\n isSpore: boolean;\n}\n\nexport interface RelationshipsData {\n accounts: Record<string, Relationship>;\n}\n\nexport interface LearningsData {\n learnings: Learning[];\n}\n\n// --- Interaction Log ---\n\nfunction todayLogPath(): string {\n const date = new Date().toISOString().split(\"T\")[0];\n return join(paths.interactions, `${date}.jsonl`);\n}\n\nexport function logInteraction(entry: InteractionEntry): void {\n ensureDirectories();\n appendFileSync(todayLogPath(), JSON.stringify(entry) + \"\\n\");\n}\n\nexport function getInteractions(date?: string): InteractionEntry[] {\n const targetDate = date ?? new Date().toISOString().split(\"T\")[0];\n const filePath = join(paths.interactions, `${targetDate}.jsonl`);\n\n if (!existsSync(filePath)) return [];\n\n return readFileSync(filePath, \"utf-8\")\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line) as InteractionEntry);\n}\n\nexport function getRecentInteractions(count: number = 20): InteractionEntry[] {\n ensureDirectories();\n const files = readdirSync(paths.interactions)\n .filter((f) => f.endsWith(\".jsonl\"))\n .sort()\n .reverse();\n\n const entries: InteractionEntry[] = [];\n for (const file of files) {\n if (entries.length >= count) break;\n const filePath = join(paths.interactions, file);\n const lines = readFileSync(filePath, \"utf-8\").trim().split(\"\\n\").filter(Boolean);\n for (const line of lines.reverse()) {\n if (entries.length >= count) break;\n entries.push(JSON.parse(line) as InteractionEntry);\n }\n }\n\n return entries;\n}\n\n// --- Learnings ---\n\nexport function loadLearnings(): LearningsData {\n if (!existsSync(paths.learnings)) {\n return { learnings: [] };\n }\n return JSON.parse(readFileSync(paths.learnings, \"utf-8\")) as LearningsData;\n}\n\nexport function saveLearnings(data: LearningsData): void {\n ensureDirectories();\n writeFileSync(paths.learnings, JSON.stringify(data, null, 2));\n}\n\nexport function addLearning(content: string, source: string, tags: string[] = []): void {\n const data = loadLearnings();\n data.learnings.push({\n id: `learn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date().toISOString(),\n content,\n source,\n tags,\n });\n saveLearnings(data);\n}\n\n// --- Relationships ---\n\nexport function loadRelationships(): RelationshipsData {\n if (!existsSync(paths.relationships)) {\n return { accounts: {} };\n }\n return JSON.parse(readFileSync(paths.relationships, \"utf-8\")) as RelationshipsData;\n}\n\nexport function saveRelationships(data: RelationshipsData): void {\n ensureDirectories();\n writeFileSync(paths.relationships, JSON.stringify(data, null, 2));\n}\n\nexport interface ActiveConversation {\n handle: string;\n content: string;\n timeAgo: string;\n tweetId?: string;\n}\n\nexport function getActiveConversations(hours: number = 24): ActiveConversation[] {\n const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);\n const recent = getRecentInteractions(50);\n\n // Find mentions and replies directed at us\n const conversations: ActiveConversation[] = [];\n const seenHandles = new Set<string>();\n\n for (const entry of recent) {\n if (entry.type !== \"mention_received\") continue;\n if (!entry.targetHandle || !entry.content) continue;\n if (new Date(entry.timestamp) < cutoff) continue;\n if (seenHandles.has(entry.targetHandle)) continue;\n seenHandles.add(entry.targetHandle);\n\n const timeDiff = Date.now() - new Date(entry.timestamp).getTime();\n const minutesAgo = Math.floor(timeDiff / 60000);\n const timeAgo = minutesAgo < 60\n ? `${minutesAgo}m ago`\n : `${Math.floor(minutesAgo / 60)}h ago`;\n\n conversations.push({\n handle: entry.targetHandle,\n content: entry.content.slice(0, 100),\n timeAgo,\n tweetId: entry.tweetId,\n });\n }\n\n return conversations;\n}\n\nexport function updateRelationship(\n userId: string,\n update: Partial<Relationship> & { handle: string }\n): void {\n const data = loadRelationships();\n const existing = data.accounts[userId];\n\n if (existing) {\n data.accounts[userId] = {\n ...existing,\n ...update,\n lastInteraction: new Date().toISOString(),\n interactionCount: existing.interactionCount + 1,\n };\n } else {\n data.accounts[userId] = {\n handle: update.handle,\n firstSeen: new Date().toISOString(),\n lastInteraction: new Date().toISOString(),\n interactionCount: 1,\n sentiment: update.sentiment ?? 0,\n tags: update.tags ?? [],\n notes: update.notes ?? [],\n isSpore: update.isSpore ?? false,\n };\n }\n\n saveRelationships(data);\n}\n"],"mappings":";;;;;;AAAA,SAAS,cAAc,eAAe,gBAAgB,YAAY,mBAAmB;AACrF,SAAS,YAAY;AA+CrB,SAAS,eAAuB;AAC9B,QAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAClD,SAAO,KAAK,MAAM,cAAc,GAAG,IAAI,QAAQ;AACjD;AAEO,SAAS,eAAe,OAA+B;AAC5D,oBAAkB;AAClB,iBAAe,aAAa,GAAG,KAAK,UAAU,KAAK,IAAI,IAAI;AAC7D;AAEO,SAAS,gBAAgB,MAAmC;AACjE,QAAM,aAAa,SAAQ,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAChE,QAAM,WAAW,KAAK,MAAM,cAAc,GAAG,UAAU,QAAQ;AAE/D,MAAI,CAAC,WAAW,QAAQ,EAAG,QAAO,CAAC;AAEnC,SAAO,aAAa,UAAU,OAAO,EAClC,KAAK,EACL,MAAM,IAAI,EACV,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAqB;AACvD;AAEO,SAAS,sBAAsB,QAAgB,IAAwB;AAC5E,oBAAkB;AAClB,QAAM,QAAQ,YAAY,MAAM,YAAY,EACzC,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,CAAC,EAClC,KAAK,EACL,QAAQ;AAEX,QAAM,UAA8B,CAAC;AACrC,aAAW,QAAQ,OAAO;AACxB,QAAI,QAAQ,UAAU,MAAO;AAC7B,UAAM,WAAW,KAAK,MAAM,cAAc,IAAI;AAC9C,UAAM,QAAQ,aAAa,UAAU,OAAO,EAAE,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AAC/E,eAAW,QAAQ,MAAM,QAAQ,GAAG;AAClC,UAAI,QAAQ,UAAU,MAAO;AAC7B,cAAQ,KAAK,KAAK,MAAM,IAAI,CAAqB;AAAA,IACnD;AAAA,EACF;AAEA,SAAO;AACT;AAIO,SAAS,gBAA+B;AAC7C,MAAI,CAAC,WAAW,MAAM,SAAS,GAAG;AAChC,WAAO,EAAE,WAAW,CAAC,EAAE;AAAA,EACzB;AACA,SAAO,KAAK,MAAM,aAAa,MAAM,WAAW,OAAO,CAAC;AAC1D;AAEO,SAAS,cAAc,MAA2B;AACvD,oBAAkB;AAClB,gBAAc,MAAM,WAAW,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAC9D;AAEO,SAAS,YAAY,SAAiB,QAAgB,OAAiB,CAAC,GAAS;AACtF,QAAM,OAAO,cAAc;AAC3B,OAAK,UAAU,KAAK;AAAA,IAClB,IAAI,SAAS,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IACjE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,gBAAc,IAAI;AACpB;AAIO,SAAS,oBAAuC;AACrD,MAAI,CAAC,WAAW,MAAM,aAAa,GAAG;AACpC,WAAO,EAAE,UAAU,CAAC,EAAE;AAAA,EACxB;AACA,SAAO,KAAK,MAAM,aAAa,MAAM,eAAe,OAAO,CAAC;AAC9D;AAEO,SAAS,kBAAkB,MAA+B;AAC/D,oBAAkB;AAClB,gBAAc,MAAM,eAAe,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAClE;AASO,SAAS,uBAAuB,QAAgB,IAA0B;AAC/E,QAAM,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,KAAK,KAAK,GAAI;AAC3D,QAAM,SAAS,sBAAsB,EAAE;AAGvC,QAAM,gBAAsC,CAAC;AAC7C,QAAM,cAAc,oBAAI,IAAY;AAEpC,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,mBAAoB;AACvC,QAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,QAAS;AAC3C,QAAI,IAAI,KAAK,MAAM,SAAS,IAAI,OAAQ;AACxC,QAAI,YAAY,IAAI,MAAM,YAAY,EAAG;AACzC,gBAAY,IAAI,MAAM,YAAY;AAElC,UAAM,WAAW,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ;AAChE,UAAM,aAAa,KAAK,MAAM,WAAW,GAAK;AAC9C,UAAM,UAAU,aAAa,KACzB,GAAG,UAAU,UACb,GAAG,KAAK,MAAM,aAAa,EAAE,CAAC;AAElC,kBAAc,KAAK;AAAA,MACjB,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM,QAAQ,MAAM,GAAG,GAAG;AAAA,MACnC;AAAA,MACA,SAAS,MAAM;AAAA,IACjB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEO,SAAS,mBACd,QACA,QACM;AACN,QAAM,OAAO,kBAAkB;AAC/B,QAAM,WAAW,KAAK,SAAS,MAAM;AAErC,MAAI,UAAU;AACZ,SAAK,SAAS,MAAM,IAAI;AAAA,MACtB,GAAG;AAAA,MACH,GAAG;AAAA,MACH,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAAA,MACxC,kBAAkB,SAAS,mBAAmB;AAAA,IAChD;AAAA,EACF,OAAO;AACL,SAAK,SAAS,MAAM,IAAI;AAAA,MACtB,QAAQ,OAAO;AAAA,MACf,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAAA,MACxC,kBAAkB;AAAA,MAClB,WAAW,OAAO,aAAa;AAAA,MAC/B,MAAM,OAAO,QAAQ,CAAC;AAAA,MACtB,OAAO,OAAO,SAAS,CAAC;AAAA,MACxB,SAAS,OAAO,WAAW;AAAA,IAC7B;AAAA,EACF;AAEA,oBAAkB,IAAI;AACxB;","names":[]}
|
package/dist/chunk-SUFTVQME.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadConfig
|
|
3
|
-
} from "./chunk-SXMDYUK3.js";
|
|
4
|
-
import {
|
|
5
|
-
logger
|
|
6
|
-
} from "./chunk-J7J557HV.js";
|
|
7
|
-
import {
|
|
8
|
-
paths
|
|
9
|
-
} from "./chunk-Q7YS3AIK.js";
|
|
10
|
-
|
|
11
|
-
// src/runtime/llm.ts
|
|
12
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
13
|
-
import { readFileSync, existsSync } from "fs";
|
|
14
|
-
var client = null;
|
|
15
|
-
function getLLMApiKey() {
|
|
16
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
17
|
-
return process.env.ANTHROPIC_API_KEY;
|
|
18
|
-
}
|
|
19
|
-
if (existsSync(paths.llmKey)) {
|
|
20
|
-
return readFileSync(paths.llmKey, "utf-8").trim();
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
function hasLLMKey() {
|
|
25
|
-
return getLLMApiKey() !== null;
|
|
26
|
-
}
|
|
27
|
-
function getClient() {
|
|
28
|
-
if (client) return client;
|
|
29
|
-
const apiKey = getLLMApiKey();
|
|
30
|
-
if (!apiKey) {
|
|
31
|
-
throw new Error("No LLM API key configured. Run `spora set-llm-key` first.");
|
|
32
|
-
}
|
|
33
|
-
client = new Anthropic({ apiKey });
|
|
34
|
-
return client;
|
|
35
|
-
}
|
|
36
|
-
async function generateResponse(systemPrompt, userMessage, options) {
|
|
37
|
-
const config = loadConfig();
|
|
38
|
-
const model = config.llm?.model ?? "claude-sonnet-4-20250514";
|
|
39
|
-
logger.info(`Calling LLM (${model})...`);
|
|
40
|
-
const anthropic = getClient();
|
|
41
|
-
const response = await anthropic.messages.create({
|
|
42
|
-
model,
|
|
43
|
-
max_tokens: 2048,
|
|
44
|
-
temperature: options?.temperature ?? 1,
|
|
45
|
-
system: systemPrompt,
|
|
46
|
-
messages: [{ role: "user", content: userMessage }]
|
|
47
|
-
});
|
|
48
|
-
const textBlock = response.content.find((b) => b.type === "text");
|
|
49
|
-
const content = textBlock ? textBlock.text : "";
|
|
50
|
-
logger.info(`LLM response: ${response.usage.input_tokens} in, ${response.usage.output_tokens} out`);
|
|
51
|
-
return {
|
|
52
|
-
content,
|
|
53
|
-
inputTokens: response.usage.input_tokens,
|
|
54
|
-
outputTokens: response.usage.output_tokens
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
async function chat(systemPrompt, messages) {
|
|
58
|
-
const config = loadConfig();
|
|
59
|
-
const model = config.llm?.model ?? "claude-sonnet-4-20250514";
|
|
60
|
-
const anthropic = getClient();
|
|
61
|
-
const response = await anthropic.messages.create({
|
|
62
|
-
model,
|
|
63
|
-
max_tokens: 1024,
|
|
64
|
-
system: systemPrompt,
|
|
65
|
-
messages
|
|
66
|
-
});
|
|
67
|
-
const textBlock = response.content.find((b) => b.type === "text");
|
|
68
|
-
const content = textBlock ? textBlock.text : "";
|
|
69
|
-
return {
|
|
70
|
-
content,
|
|
71
|
-
inputTokens: response.usage.input_tokens,
|
|
72
|
-
outputTokens: response.usage.output_tokens
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export {
|
|
77
|
-
getLLMApiKey,
|
|
78
|
-
hasLLMKey,
|
|
79
|
-
generateResponse,
|
|
80
|
-
chat
|
|
81
|
-
};
|
|
82
|
-
//# sourceMappingURL=chunk-SUFTVQME.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/llm.ts"],"sourcesContent":["import Anthropic from \"@anthropic-ai/sdk\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { paths } from \"../utils/paths.js\";\nimport { loadConfig } from \"../utils/config.js\";\nimport { logger } from \"../utils/logger.js\";\n\nlet client: Anthropic | null = null;\n\nexport function getLLMApiKey(): string | null {\n // Check env first, then file\n if (process.env.ANTHROPIC_API_KEY) {\n return process.env.ANTHROPIC_API_KEY;\n }\n if (existsSync(paths.llmKey)) {\n return readFileSync(paths.llmKey, \"utf-8\").trim();\n }\n return null;\n}\n\nexport function hasLLMKey(): boolean {\n return getLLMApiKey() !== null;\n}\n\nfunction getClient(): Anthropic {\n if (client) return client;\n const apiKey = getLLMApiKey();\n if (!apiKey) {\n throw new Error(\"No LLM API key configured. Run `spora set-llm-key` first.\");\n }\n client = new Anthropic({ apiKey });\n return client;\n}\n\nexport interface LLMResponse {\n content: string;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport async function generateResponse(\n systemPrompt: string,\n userMessage: string,\n options?: { temperature?: number },\n): Promise<LLMResponse> {\n const config = loadConfig();\n const model = config.llm?.model ?? \"claude-sonnet-4-20250514\";\n\n logger.info(`Calling LLM (${model})...`);\n\n const anthropic = getClient();\n const response = await anthropic.messages.create({\n model,\n max_tokens: 2048,\n temperature: options?.temperature ?? 1.0,\n system: systemPrompt,\n messages: [{ role: \"user\", content: userMessage }],\n });\n\n const textBlock = response.content.find((b) => b.type === \"text\");\n const content = textBlock ? textBlock.text : \"\";\n\n logger.info(`LLM response: ${response.usage.input_tokens} in, ${response.usage.output_tokens} out`);\n\n return {\n content,\n inputTokens: response.usage.input_tokens,\n outputTokens: response.usage.output_tokens,\n };\n}\n\nexport async function chat(\n systemPrompt: string,\n messages: Array<{ role: \"user\" | \"assistant\"; content: string }>,\n): Promise<LLMResponse> {\n const config = loadConfig();\n const model = config.llm?.model ?? \"claude-sonnet-4-20250514\";\n\n const anthropic = getClient();\n const response = await anthropic.messages.create({\n model,\n max_tokens: 1024,\n system: systemPrompt,\n messages,\n });\n\n const textBlock = response.content.find((b) => b.type === \"text\");\n const content = textBlock ? textBlock.text : \"\";\n\n return {\n content,\n inputTokens: response.usage.input_tokens,\n outputTokens: response.usage.output_tokens,\n };\n}\n"],"mappings":";;;;;;;;;;;AAAA,OAAO,eAAe;AACtB,SAAS,cAAc,kBAAkB;AAKzC,IAAI,SAA2B;AAExB,SAAS,eAA8B;AAE5C,MAAI,QAAQ,IAAI,mBAAmB;AACjC,WAAO,QAAQ,IAAI;AAAA,EACrB;AACA,MAAI,WAAW,MAAM,MAAM,GAAG;AAC5B,WAAO,aAAa,MAAM,QAAQ,OAAO,EAAE,KAAK;AAAA,EAClD;AACA,SAAO;AACT;AAEO,SAAS,YAAqB;AACnC,SAAO,aAAa,MAAM;AAC5B;AAEA,SAAS,YAAuB;AAC9B,MAAI,OAAQ,QAAO;AACnB,QAAM,SAAS,aAAa;AAC5B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,2DAA2D;AAAA,EAC7E;AACA,WAAS,IAAI,UAAU,EAAE,OAAO,CAAC;AACjC,SAAO;AACT;AAQA,eAAsB,iBACpB,cACA,aACA,SACsB;AACtB,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,OAAO,KAAK,SAAS;AAEnC,SAAO,KAAK,gBAAgB,KAAK,MAAM;AAEvC,QAAM,YAAY,UAAU;AAC5B,QAAM,WAAW,MAAM,UAAU,SAAS,OAAO;AAAA,IAC/C;AAAA,IACA,YAAY;AAAA,IACZ,aAAa,SAAS,eAAe;AAAA,IACrC,QAAQ;AAAA,IACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,EACnD,CAAC;AAED,QAAM,YAAY,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAChE,QAAM,UAAU,YAAY,UAAU,OAAO;AAE7C,SAAO,KAAK,iBAAiB,SAAS,MAAM,YAAY,QAAQ,SAAS,MAAM,aAAa,MAAM;AAElG,SAAO;AAAA,IACL;AAAA,IACA,aAAa,SAAS,MAAM;AAAA,IAC5B,cAAc,SAAS,MAAM;AAAA,EAC/B;AACF;AAEA,eAAsB,KACpB,cACA,UACsB;AACtB,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,OAAO,KAAK,SAAS;AAEnC,QAAM,YAAY,UAAU;AAC5B,QAAM,WAAW,MAAM,UAAU,SAAS,OAAO;AAAA,IAC/C;AAAA,IACA,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AAED,QAAM,YAAY,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAChE,QAAM,UAAU,YAAY,UAAU,OAAO;AAE7C,SAAO;AAAA,IACL;AAAA,IACA,aAAa,SAAS,MAAM;AAAA,IAC5B,cAAc,SAAS,MAAM;AAAA,EAC/B;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/config.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { z } from \"zod\";\nimport { paths, ensureDirectories } from \"./paths.js\";\n\nexport const ConfigSchema = z.object({\n version: z.literal(1),\n xMethod: z.enum([\"api\", \"browser\"]),\n xApiTier: z.enum([\"free\", \"basic\"]).optional(),\n\n credits: z.object({\n monthlyPostLimit: z.number(),\n postsUsedThisMonth: z.number(),\n resetDate: z.string(),\n }),\n\n schedule: z.object({\n postsPerDay: z.number(),\n activeHoursStart: z.number().min(0).max(23),\n activeHoursEnd: z.number().min(0).max(23),\n timezone: z.string(),\n }),\n\n llm: z.object({\n provider: z.enum([\"anthropic\", \"openai\"]).default(\"anthropic\"),\n model: z.string().default(\"claude-sonnet-4-20250514\"),\n }).optional(),\n\n runtime: z.object({\n heartbeatIntervalMs: z.number().default(3_600_000),\n actionsPerHeartbeat: z.number().default(3),\n enabled: z.boolean().default(false),\n }).optional(),\n\n connection: z.object({\n token: z.string().optional(),\n apiEndpoint: z.string().default(\"https://spora.dev/api/v1\"),\n lastSync: z.string().optional(),\n configVersion: z.number().default(0),\n }).optional(),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport function loadConfig(): Config {\n if (!existsSync(paths.config)) {\n throw new Error(\"Spora not initialized. Run `spora init` first.\");\n }\n const raw = readFileSync(paths.config, \"utf-8\");\n return ConfigSchema.parse(JSON.parse(raw));\n}\n\nexport function saveConfig(config: Config): void {\n ensureDirectories();\n ConfigSchema.parse(config);\n writeFileSync(paths.config, JSON.stringify(config, null, 2));\n}\n\nexport function createDefaultConfig(overrides: {\n xMethod: \"api\" | \"browser\";\n xApiTier?: \"free\" | \"basic\";\n timezone?: string;\n}): Config {\n const monthlyLimit = overrides.xApiTier === \"basic\" ? 10000 : 500;\n const now = new Date();\n const resetDate = new Date(now.getFullYear(), now.getMonth() + 1, 1).toISOString();\n\n return {\n version: 1,\n xMethod: overrides.xMethod,\n xApiTier: overrides.xApiTier,\n credits: {\n monthlyPostLimit: monthlyLimit,\n postsUsedThisMonth: 0,\n resetDate,\n },\n schedule: {\n postsPerDay: Math.floor(monthlyLimit / 30),\n activeHoursStart: 8,\n activeHoursEnd: 22,\n timezone: overrides.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,\n },\n };\n}\n"],"mappings":";;;;;;AAAA,SAAS,cAAc,eAAe,kBAAkB;AACxD,SAAS,SAAS;AAGX,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,SAAS,EAAE,QAAQ,CAAC;AAAA,EACpB,SAAS,EAAE,KAAK,CAAC,OAAO,SAAS,CAAC;AAAA,EAClC,UAAU,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC,EAAE,SAAS;AAAA,EAE7C,SAAS,EAAE,OAAO;AAAA,IAChB,kBAAkB,EAAE,OAAO;AAAA,IAC3B,oBAAoB,EAAE,OAAO;AAAA,IAC7B,WAAW,EAAE,OAAO;AAAA,EACtB,CAAC;AAAA,EAED,UAAU,EAAE,OAAO;AAAA,IACjB,aAAa,EAAE,OAAO;AAAA,IACtB,kBAAkB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,IAC1C,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,IACxC,UAAU,EAAE,OAAO;AAAA,EACrB,CAAC;AAAA,EAED,KAAK,EAAE,OAAO;AAAA,IACZ,UAAU,EAAE,KAAK,CAAC,aAAa,QAAQ,CAAC,EAAE,QAAQ,WAAW;AAAA,IAC7D,OAAO,EAAE,OAAO,EAAE,QAAQ,0BAA0B;AAAA,EACtD,CAAC,EAAE,SAAS;AAAA,EAEZ,SAAS,EAAE,OAAO;AAAA,IAChB,qBAAqB,EAAE,OAAO,EAAE,QAAQ,IAAS;AAAA,IACjD,qBAAqB,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,IACzC,SAAS,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACpC,CAAC,EAAE,SAAS;AAAA,EAEZ,YAAY,EAAE,OAAO;AAAA,IACnB,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,IAC3B,aAAa,EAAE,OAAO,EAAE,QAAQ,0BAA0B;AAAA,IAC1D,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,IAC9B,eAAe,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACrC,CAAC,EAAE,SAAS;AACd,CAAC;AAIM,SAAS,aAAqB;AACnC,MAAI,CAAC,WAAW,MAAM,MAAM,GAAG;AAC7B,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,QAAM,MAAM,aAAa,MAAM,QAAQ,OAAO;AAC9C,SAAO,aAAa,MAAM,KAAK,MAAM,GAAG,CAAC;AAC3C;AAEO,SAAS,WAAW,QAAsB;AAC/C,oBAAkB;AAClB,eAAa,MAAM,MAAM;AACzB,gBAAc,MAAM,QAAQ,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC7D;AAEO,SAAS,oBAAoB,WAIzB;AACT,QAAM,eAAe,UAAU,aAAa,UAAU,MAAQ;AAC9D,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,YAAY,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC,EAAE,YAAY;AAEjF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS,UAAU;AAAA,IACnB,UAAU,UAAU;AAAA,IACpB,SAAS;AAAA,MACP,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,MACpB;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR,aAAa,KAAK,MAAM,eAAe,EAAE;AAAA,MACzC,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,UAAU,UAAU,YAAY,KAAK,eAAe,EAAE,gBAAgB,EAAE;AAAA,IAC1E;AAAA,EACF;AACF;","names":[]}
|