spora 0.7.9 → 0.7.11

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.
@@ -144,6 +144,16 @@ async function updateXProfile(identity) {
144
144
  } else {
145
145
  console.log("No banner image URL in identity");
146
146
  }
147
+ try {
148
+ const account = await client.v1.verifyCredentials();
149
+ const finalHandle = normalizeHandle(account.screen_name || "");
150
+ if (finalHandle) {
151
+ result.finalHandle = finalHandle.toLowerCase();
152
+ console.log(`Final X handle detected: @${result.finalHandle}`);
153
+ }
154
+ } catch (error) {
155
+ console.log(`Could not verify final handle: ${error.message}`);
156
+ }
147
157
  result.success = result.updated.length > 0;
148
158
  return result;
149
159
  } catch (error) {
@@ -232,7 +242,8 @@ async function notifyConnectProfileCompletion(input2) {
232
242
  token: input2.token,
233
243
  profileUpdated: input2.profileUpdated,
234
244
  updatedFields: input2.updatedFields,
235
- errors: input2.errors
245
+ errors: input2.errors,
246
+ finalHandle: input2.finalHandle
236
247
  })
237
248
  });
238
249
  if (!response.ok) {
@@ -276,7 +287,7 @@ async function loginFlow() {
276
287
  console.log(chalk.green("\u2713 Logged in!\n"));
277
288
  console.log(chalk.gray("Opening chat interface...\n"));
278
289
  try {
279
- const { startWebChat } = await import("./web-chat-YRQQB435.js");
290
+ const { startWebChat } = await import("./web-chat-FCOETDEZ.js");
280
291
  await startWebChat();
281
292
  } catch (error) {
282
293
  console.log(chalk.yellow(`Could not start chat interface: ${error.message}
@@ -368,7 +379,7 @@ async function showDoneAndOpenChat() {
368
379
  console.log(chalk.bold.cyan("\u2501\u2501\u2501 Your Spore is Ready! \u2501\u2501\u2501\n"));
369
380
  console.log(chalk.gray("Opening chat interface...\n"));
370
381
  try {
371
- const { startWebChat } = await import("./web-chat-YRQQB435.js");
382
+ const { startWebChat } = await import("./web-chat-FCOETDEZ.js");
372
383
  await startWebChat();
373
384
  } catch (error) {
374
385
  console.log(chalk.yellow(`Could not start chat interface: ${error.message}
@@ -408,6 +419,7 @@ async function runInit(token) {
408
419
  let profileUpdated = false;
409
420
  let updatedFields = [];
410
421
  let profileErrors = [];
422
+ let finalHandle;
411
423
  console.log(chalk.bold("\n\u2501\u2501\u2501 Updating Your X Profile \u2501\u2501\u2501\n"));
412
424
  console.log(chalk.gray("Setting up username, profile picture, banner, and bio to match your Spore...\n"));
413
425
  try {
@@ -416,6 +428,7 @@ async function runInit(token) {
416
428
  profileUpdated = result.success;
417
429
  updatedFields = result.updated;
418
430
  profileErrors = result.errors;
431
+ finalHandle = result.finalHandle || identity.handle?.replace(/^@/, "").toLowerCase();
419
432
  if (result.success) {
420
433
  console.log(chalk.green("\u2713 Profile updated successfully!\n"));
421
434
  if (result.updated.length > 0) {
@@ -443,7 +456,8 @@ async function runInit(token) {
443
456
  token,
444
457
  profileUpdated,
445
458
  updatedFields,
446
- errors: profileErrors
459
+ errors: profileErrors,
460
+ finalHandle
447
461
  });
448
462
  const config2 = createDefaultConfig({ xMethod: "api", xApiTier: "basic" });
449
463
  config2.llm = setup2.llm;
@@ -506,4 +520,4 @@ async function runInit(token) {
506
520
  export {
507
521
  runInit
508
522
  };
509
- //# sourceMappingURL=init-4SBU4H6F.js.map
523
+ //# sourceMappingURL=init-75RMQFHC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/init.ts","../src/x-client/profile-updater.ts"],"sourcesContent":["import { input, select, password as passwordPrompt } from \"@inquirer/prompts\";\nimport chalk from \"chalk\";\nimport { ensureDirectories } from \"./utils/paths.js\";\nimport { createDefaultConfig, saveConfig } from \"./utils/config.js\";\nimport { saveCredentials } from \"./utils/crypto.js\";\nimport { loadIdentity } from \"./identity/index.js\";\nimport type { Identity } from \"./identity/schema.js\";\nimport { updateXProfile } from \"./x-client/profile-updater.js\";\nimport { setLLMApiKey, type LLMProvider, getDefaultModel } from \"./runtime/llm.js\";\n\n/**\n * Fetch identity from token and save locally\n */\nasync function syncIdentityFromToken(token: string): Promise<void> {\n console.log(chalk.gray(\"Connecting to spora.dev...\\n\"));\n\n const apiUrl = process.env.SPORA_API_URL || \"https://www.spora.social\";\n const response = await fetch(`${apiUrl}/api/v1/connect`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token }),\n });\n\n if (!response.ok) {\n const errData = await response.json().catch(() => ({} as { error?: string })) as { error?: string };\n throw new Error(errData.error || `Connection failed: ${response.statusText}`);\n }\n\n const data = await response.json() as {\n identity?: Identity;\n media?: { profileImage?: string; bannerImage?: string };\n readme?: string;\n };\n\n if (!data.identity) {\n throw new Error(\"No Spore identity found for this token\");\n }\n\n // Merge media fields\n if (data.media) {\n if (data.media.profileImage) data.identity.profileImage = data.media.profileImage;\n if (data.media.bannerImage) data.identity.bannerImage = data.media.bannerImage;\n }\n\n const { saveIdentity } = await import(\"./identity/index.js\");\n saveIdentity(data.identity);\n\n console.log(chalk.green(`✓ Connected to Spore: ${data.identity.name} (@${data.identity.handle})\\n`));\n\n // Save README if provided\n if (data.readme) {\n const { writeFileSync } = await import(\"node:fs\");\n const { paths } = await import(\"./utils/paths.js\");\n const { join, dirname } = await import(\"node:path\");\n const readmePath = join(dirname(paths.identity), \"IDENTITY.md\");\n writeFileSync(readmePath, data.readme, \"utf-8\");\n console.log(chalk.green(\"✓ Saved identity README\\n\"));\n }\n\n // Save connection token to config\n const { existsSync } = await import(\"node:fs\");\n const { paths } = await import(\"./utils/paths.js\");\n if (existsSync(paths.config)) {\n const { loadConfig, saveConfig } = await import(\"./utils/config.js\");\n const config = loadConfig();\n config.connection = {\n token,\n apiEndpoint: process.env.SPORA_API_URL || \"https://www.spora.social/api/v1\",\n configVersion: config.connection?.configVersion ?? 0,\n };\n saveConfig(config);\n }\n}\n\nasync function notifyConnectProfileCompletion(input: {\n token: string;\n profileUpdated: boolean;\n updatedFields: string[];\n errors: string[];\n finalHandle?: string;\n}): Promise<void> {\n const apiUrl = process.env.SPORA_API_URL || \"https://www.spora.social\";\n try {\n const response = await fetch(`${apiUrl}/api/v1/connect/complete`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n token: input.token,\n profileUpdated: input.profileUpdated,\n updatedFields: input.updatedFields,\n errors: input.errors,\n finalHandle: input.finalHandle,\n }),\n });\n\n if (!response.ok) {\n const payload = await response.json().catch(() => ({})) as { error?: string };\n console.log(chalk.yellow(`Token launch callback skipped: ${payload.error || response.statusText}`));\n return;\n }\n\n const payload = await response.json().catch(() => ({})) as {\n triggered?: boolean;\n status?: string;\n reason?: string;\n warning?: string;\n };\n\n if (!payload.triggered) {\n const reason = payload.reason ? ` (${payload.reason})` : \"\";\n console.log(chalk.gray(`Bags token launch not triggered${reason}.`));\n return;\n }\n\n const status = payload.status ? ` [${payload.status}]` : \"\";\n console.log(chalk.green(`✓ Bags token launch request sent${status}`));\n if (payload.warning) {\n console.log(chalk.yellow(` ${payload.warning}`));\n }\n } catch (error) {\n console.log(chalk.yellow(`Token launch callback failed: ${(error as Error).message}`));\n }\n}\n\n/**\n * Login flow: paste token → sync identity → open chat (keys already saved locally)\n */\nasync function loginFlow(): Promise<void> {\n console.log(chalk.bold(\"\\n━━━ Login to Existing Spore ━━━\\n\"));\n console.log(chalk.gray(\"Paste your setup token from spora.dev\"));\n console.log(chalk.gray(\"(It looks like: spora_7e636ac0...)\\n\"));\n\n const token = await input({\n message: \"Setup token:\",\n validate: (val) => val.length > 0 ? true : \"Token is required\",\n });\n\n ensureDirectories();\n\n try {\n await syncIdentityFromToken(token.trim());\n } catch (error) {\n console.log(chalk.red(`\\n✗ ${(error as Error).message}\\n`));\n console.log(chalk.yellow(\"Please check your token and try again.\\n\"));\n process.exit(1);\n }\n\n console.log(chalk.green(\"✓ Logged in!\\n\"));\n console.log(chalk.gray(\"Opening chat interface...\\n\"));\n\n try {\n const { startWebChat } = await import(\"./web-chat/index.js\");\n await startWebChat();\n } catch (error) {\n console.log(chalk.yellow(`Could not start chat interface: ${(error as Error).message}\\n`));\n console.log(chalk.gray(\"You can start it manually with: spora chat\\n\"));\n }\n}\n\ninterface SetupResult {\n llm: {\n provider: LLMProvider;\n model: string;\n };\n}\n\nfunction providerKeyUrl(provider: LLMProvider): string {\n if (provider === \"anthropic\") return \"https://console.anthropic.com/settings/keys\";\n if (provider === \"openai\") return \"https://platform.openai.com/api-keys\";\n return \"https://platform.deepseek.com/api_keys\";\n}\n\n/**\n * Prompt for LLM provider/model + X credentials.\n */\nasync function setupKeys(): Promise<SetupResult> {\n console.log(chalk.bold(\"\\n━━━ LLM Provider Setup ━━━\\n\"));\n\n const provider = await select({\n message: \"Choose your LLM provider:\",\n choices: [\n { name: \"DeepSeek (recommended for low cost)\", value: \"deepseek\" },\n { name: \"Anthropic (Claude)\", value: \"anthropic\" },\n { name: \"OpenAI\", value: \"openai\" },\n ],\n }) as LLMProvider;\n\n const defaultModel = getDefaultModel(provider);\n const model = await input({\n message: `Model name for ${provider} (press enter for ${defaultModel}):`,\n });\n\n console.log(chalk.gray(\"Get your API key at: \") + chalk.cyan(`${providerKeyUrl(provider)}\\n`));\n const llmKey = await passwordPrompt({\n message: `${provider} API Key:`,\n mask: \"*\",\n validate: (val: string) => val.length > 0 ? true : \"API key is required\",\n });\n setLLMApiKey(provider, llmKey.trim());\n console.log(chalk.green(`✓ ${provider} API key saved\\n`));\n\n // X credentials\n console.log(chalk.bold(\"\\n━━━ Connect Your X Account ━━━\\n\"));\n console.log(chalk.gray(\"Your Spore needs OAuth 1.0a credentials for full X API access.\"));\n console.log(chalk.gray(\"This gives your agent the power to read timelines, post tweets, reply, like, and follow.\\n\"));\n console.log(chalk.cyan(\"How to get these credentials:\"));\n console.log(chalk.gray(\" 1. Go to: \") + chalk.cyan(\"https://developer.x.com/en/portal/dashboard\"));\n console.log(chalk.gray(\" 2. Create or select your app\"));\n console.log(chalk.gray(\" 3. Go to \\\"Keys and tokens\\\" tab\"));\n console.log(chalk.gray(\" 4. Copy all 4 credentials below\\n\"));\n console.log(chalk.yellow(\"Note: Some endpoints may require paid X API access depending on your tier.\\n\"));\n\n const apiKey = await passwordPrompt({\n message: \"X API Key (Consumer Key):\",\n mask: \"*\",\n validate: (val: string) => val.length > 0 ? true : \"API Key is required\",\n });\n\n const apiSecret = await passwordPrompt({\n message: \"X API Secret (Consumer Secret):\",\n mask: \"*\",\n validate: (val: string) => val.length > 0 ? true : \"API Secret is required\",\n });\n\n const accessToken = await passwordPrompt({\n message: \"X Access Token:\",\n mask: \"*\",\n validate: (val: string) => val.length > 0 ? true : \"Access Token is required\",\n });\n\n const accessTokenSecret = await passwordPrompt({\n message: \"X Access Token Secret:\",\n mask: \"*\",\n validate: (val: string) => val.length > 0 ? true : \"Access Token Secret is required\",\n });\n\n const bearerToken = await input({\n message: \"X Bearer Token (optional, press enter to skip):\",\n });\n\n saveCredentials({\n method: \"api\",\n apiKey,\n apiSecret,\n accessToken,\n accessTokenSecret,\n bearerToken: bearerToken.trim() || undefined,\n });\n console.log(chalk.green(\"✓ X API credentials saved (encrypted)\\n\"));\n\n return {\n llm: {\n provider,\n model: model.trim() || defaultModel,\n },\n };\n}\n\n/**\n * Show completion message and open chat\n */\nasync function showDoneAndOpenChat(): Promise<void> {\n console.log(chalk.green(\"\\n╔═══════════════════════════════════════╗\"));\n console.log(chalk.green.bold(\"║ Setup Complete! ║\"));\n console.log(chalk.green(\"╚═══════════════════════════════════════╝\\n\"));\n\n console.log(chalk.bold.cyan(\"━━━ Your Spore is Ready! ━━━\\n\"));\n console.log(chalk.gray(\"Opening chat interface...\\n\"));\n\n try {\n const { startWebChat } = await import(\"./web-chat/index.js\");\n await startWebChat();\n } catch (error) {\n console.log(chalk.yellow(`Could not start chat interface: ${(error as Error).message}\\n`));\n console.log(chalk.gray(\"You can start it manually with: spora chat\\n\"));\n }\n\n console.log(chalk.bold(\"Quick Start:\\n\"));\n console.log(chalk.cyan(\" spora chat\"));\n console.log(chalk.gray(\" → Talk to your Spore, tell it what to post, how to behave\\n\"));\n console.log(chalk.bold(\"Or start the autonomous agent:\\n\"));\n console.log(chalk.cyan(\" spora start\"));\n console.log(chalk.gray(\" → Your Spore will post and engage autonomously\\n\"));\n console.log(chalk.bold(\"Other commands:\\n\"));\n console.log(chalk.cyan(\" spora create \") + chalk.gray(\"# Create a personality\"));\n console.log(chalk.cyan(\" spora post <text> \") + chalk.gray(\"# Make your Spore post\"));\n console.log(chalk.cyan(\" spora agent-status \") + chalk.gray(\"# Check if agent is running\"));\n console.log(chalk.cyan(\" spora stop \") + chalk.gray(\"# Stop the agent\"));\n console.log(chalk.cyan(\" spora --help \") + chalk.gray(\"# See all commands\\n\"));\n}\n\nexport async function runInit(token?: string): Promise<void> {\n console.log(chalk.bold.cyan(\"\\n╔════════════════════════════════════════╗\"));\n console.log(chalk.bold.cyan(\"║ Welcome to Spora CLI Setup ║\"));\n console.log(chalk.bold.cyan(\"╚════════════════════════════════════════╝\\n\"));\n\n // If token provided via --token flag, this is a new user from the website → full setup\n if (token) {\n ensureDirectories();\n\n console.log(chalk.bold(\"\\n━━━ Connecting Your Spore ━━━\\n\"));\n try {\n await syncIdentityFromToken(token);\n } catch (error) {\n console.log(chalk.red(`\\n✗ ${(error as Error).message}\\n`));\n console.log(chalk.yellow(\"Please check your token and try again.\\n\"));\n process.exit(1);\n }\n\n const setup = await setupKeys();\n\n // Update X profile\n let profileUpdated = false;\n let updatedFields: string[] = [];\n let profileErrors: string[] = [];\n let finalHandle: string | undefined;\n console.log(chalk.bold(\"\\n━━━ Updating Your X Profile ━━━\\n\"));\n console.log(chalk.gray(\"Setting up username, profile picture, banner, and bio to match your Spore...\\n\"));\n try {\n const identity = loadIdentity();\n const result = await updateXProfile(identity);\n profileUpdated = result.success;\n updatedFields = result.updated;\n profileErrors = result.errors;\n finalHandle = result.finalHandle || identity.handle?.replace(/^@/, \"\").toLowerCase();\n if (result.success) {\n console.log(chalk.green(\"✓ Profile updated successfully!\\n\"));\n if (result.updated.length > 0) {\n console.log(chalk.cyan(\"Updated:\"));\n for (const field of result.updated) {\n console.log(chalk.gray(` • ${field}`));\n }\n console.log();\n }\n }\n if (result.errors.length > 0) {\n console.log(chalk.yellow(\"\\nSome updates failed:\"));\n for (const err of result.errors) {\n console.log(chalk.gray(` • ${err}`));\n }\n console.log();\n }\n } catch (error) {\n console.log(chalk.yellow(`Could not update profile: ${(error as Error).message}\\n`));\n console.log(chalk.gray(\"You can manually update your X profile later.\\n\"));\n profileErrors = [(error as Error).message];\n }\n\n await notifyConnectProfileCompletion({\n token,\n profileUpdated,\n updatedFields,\n errors: profileErrors,\n finalHandle,\n });\n\n const config = createDefaultConfig({ xMethod: \"api\", xApiTier: \"basic\" });\n config.llm = setup.llm;\n config.runtime = { heartbeatIntervalMs: 300_000, actionsPerHeartbeat: 4, enabled: true };\n config.connection = {\n token,\n apiEndpoint: process.env.SPORA_API_URL || \"https://www.spora.social/api/v1\",\n configVersion: 0,\n };\n saveConfig(config);\n\n await showDoneAndOpenChat();\n return;\n }\n\n // No token provided — ask what they want to do\n const action = await select({\n message: \"What would you like to do?\",\n choices: [\n { name: \"Create a new Spore\", value: \"new\" },\n { name: \"Login to existing Spore\", value: \"login\" },\n ],\n });\n\n if (action === \"login\") {\n await loginFlow();\n return;\n }\n\n // \"new\" — full new Spore creation (no token, manual setup)\n ensureDirectories();\n\n const setup = await setupKeys();\n\n // Update X profile\n console.log(chalk.bold(\"\\n━━━ Updating Your X Profile ━━━\\n\"));\n console.log(chalk.gray(\"Setting up username, profile picture, banner, and bio to match your Spore...\\n\"));\n try {\n const identity = loadIdentity();\n const result = await updateXProfile(identity);\n if (result.success) {\n console.log(chalk.green(\"✓ Profile updated successfully!\\n\"));\n if (result.updated.length > 0) {\n console.log(chalk.cyan(\"Updated:\"));\n for (const field of result.updated) {\n console.log(chalk.gray(` • ${field}`));\n }\n console.log();\n }\n }\n if (result.errors.length > 0) {\n console.log(chalk.yellow(\"\\nSome updates failed:\"));\n for (const err of result.errors) {\n console.log(chalk.gray(` • ${err}`));\n }\n console.log();\n }\n } catch (error) {\n console.log(chalk.yellow(`Could not update profile: ${(error as Error).message}\\n`));\n console.log(chalk.gray(\"You can manually update your X profile later.\\n\"));\n }\n\n const config = createDefaultConfig({ xMethod: \"api\", xApiTier: \"basic\" });\n config.llm = setup.llm;\n config.runtime = { heartbeatIntervalMs: 300_000, actionsPerHeartbeat: 4, enabled: true };\n saveConfig(config);\n\n await showDoneAndOpenChat();\n}\n","/**\n * X Profile Updater\n * Updates X profile to match Spore identity (name, bio, profile pic, banner)\n */\n\nimport { TwitterApi } from \"twitter-api-v2\";\nimport sharp from \"sharp\";\nimport type { Identity } from \"../identity/schema.js\";\nimport { loadCredentials } from \"../utils/crypto.js\";\n\ninterface ProfileUpdateResult {\n success: boolean;\n updated: string[];\n errors: string[];\n finalHandle?: string;\n}\n\nfunction normalizeHandle(input: string): string {\n return input.replace(/^@/, \"\").trim();\n}\n\nfunction isValidHandle(handle: string): boolean {\n // X handle constraints: 4-15 chars, letters/numbers/underscore.\n return /^[A-Za-z0-9_]{4,15}$/.test(handle);\n}\n\n/**\n * Update X profile to match Spore identity\n * Requires OAuth 1.0a credentials\n */\nexport async function updateXProfile(identity: Identity): Promise<ProfileUpdateResult> {\n const result: ProfileUpdateResult = {\n success: false,\n updated: [],\n errors: [],\n };\n\n try {\n const creds = loadCredentials();\n if (creds.method !== \"api\") {\n result.errors.push(\"API credentials required\");\n return result;\n }\n\n // Create Twitter API client with OAuth 1.0a credentials\n const client = new TwitterApi({\n appKey: creds.apiKey!,\n appSecret: creds.apiSecret!,\n accessToken: creds.accessToken!,\n accessSecret: creds.accessTokenSecret!,\n });\n\n // Update profile name and bio\n try {\n console.log(`Updating name to: ${identity.name}`);\n console.log(`Updating bio to: ${identity.bio.substring(0, 60)}...`);\n await client.v1.updateAccountProfile({\n name: identity.name,\n description: identity.bio,\n });\n result.updated.push(\"name\", \"bio\");\n console.log(\"Name and bio updated successfully\");\n } catch (error) {\n console.error(\"Name/bio update error:\", error);\n result.errors.push(`Failed to update name/bio: ${(error as Error).message}`);\n }\n\n // Best-effort username update.\n // X often restricts handle changes by API permission/account tier, so this may fail.\n try {\n const desiredHandle = normalizeHandle(identity.handle);\n if (!desiredHandle) {\n throw new Error(\"Identity handle is empty\");\n }\n if (!isValidHandle(desiredHandle)) {\n throw new Error(\n `Invalid handle \"${desiredHandle}\". X handles must be 4-15 chars and only letters, numbers, or underscores.`\n );\n }\n\n const before = await client.v1.verifyCredentials();\n const currentHandle = normalizeHandle(before.screen_name || \"\");\n\n if (!currentHandle) {\n throw new Error(\"Could not read current X handle from credentials\");\n }\n\n if (currentHandle.toLowerCase() === desiredHandle.toLowerCase()) {\n console.log(`Username already set: @${desiredHandle}`);\n result.updated.push(\"username\");\n } else {\n console.log(`Updating username from @${currentHandle} to @${desiredHandle}...`);\n\n // twitter-api-v2 typings do not expose screen_name here, but v1 endpoint may accept it.\n await client.v1.post(\"account/update_profile.json\", {\n screen_name: desiredHandle,\n } as unknown as Record<string, string>);\n\n const after = await client.v1.verifyCredentials();\n const updatedHandle = normalizeHandle(after.screen_name || \"\");\n\n if (updatedHandle.toLowerCase() === desiredHandle.toLowerCase()) {\n console.log(`Username updated successfully to @${desiredHandle}`);\n result.updated.push(\"username\");\n } else {\n throw new Error(\n `X did not apply handle change (current @${updatedHandle || currentHandle}).`\n );\n }\n }\n } catch (error) {\n const message = (error as Error).message;\n console.error(\"Username update error:\", error);\n result.errors.push(\n `Failed to update username: ${message}. X may block handle changes via API; update it manually in X Settings -> Account -> Username.`\n );\n }\n\n // Update profile image if available\n if (identity.profileImage) {\n try {\n console.log(`Downloading profile image from: ${identity.profileImage.substring(0, 60)}...`);\n let imageBuffer = await downloadImage(identity.profileImage);\n console.log(`Downloaded ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB`);\n\n // Check image dimensions - Twitter requires min 400x400\n const metadata = await sharp(imageBuffer).metadata();\n console.log(`Image dimensions: ${metadata.width}x${metadata.height}`);\n\n if (!metadata.width || !metadata.height || metadata.width < 400 || metadata.height < 400) {\n throw new Error(`Image too small (${metadata.width}x${metadata.height}). Twitter requires minimum 400x400 pixels.`);\n }\n\n // Compress if needed (Twitter profile image limit: 2MB)\n const MAX_PROFILE_SIZE = 2 * 1024 * 1024; // 2MB\n imageBuffer = await compressImageIfNeeded(imageBuffer, MAX_PROFILE_SIZE, \"Profile\");\n\n console.log(`Uploading profile image to X...`);\n await client.v1.updateAccountProfileImage(imageBuffer);\n result.updated.push(\"profile_image\");\n console.log(\"Profile image updated successfully\");\n } catch (error) {\n console.error(\"Profile image error:\", error);\n result.errors.push(`Failed to update profile image: ${(error as Error).message}`);\n }\n } else {\n console.log(\"No profile image URL in identity\");\n }\n\n // Update banner image if available\n if (identity.bannerImage) {\n try {\n console.log(`Downloading banner image from: ${identity.bannerImage.substring(0, 60)}...`);\n let imageBuffer = await downloadImage(identity.bannerImage);\n console.log(`Downloaded ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB`);\n\n // Compress if needed (Twitter banner limit: 5MB)\n const MAX_BANNER_SIZE = 5 * 1024 * 1024; // 5MB\n imageBuffer = await compressImageIfNeeded(imageBuffer, MAX_BANNER_SIZE, \"Banner\");\n\n console.log(`Uploading banner image to X...`);\n await client.v1.updateAccountProfileBanner(imageBuffer);\n result.updated.push(\"banner_image\");\n console.log(\"Banner image updated successfully\");\n } catch (error) {\n console.error(\"Banner image error:\", error);\n result.errors.push(`Failed to update banner: ${(error as Error).message}`);\n }\n } else {\n console.log(\"No banner image URL in identity\");\n }\n\n // Resolve the account's final handle after all profile operations.\n try {\n const account = await client.v1.verifyCredentials();\n const finalHandle = normalizeHandle(account.screen_name || \"\");\n if (finalHandle) {\n result.finalHandle = finalHandle.toLowerCase();\n console.log(`Final X handle detected: @${result.finalHandle}`);\n }\n } catch (error) {\n console.log(`Could not verify final handle: ${(error as Error).message}`);\n }\n\n result.success = result.updated.length > 0;\n return result;\n } catch (error) {\n result.errors.push((error as Error).message);\n return result;\n }\n}\n\n/**\n * Download image from URL and return as Buffer\n */\nasync function downloadImage(url: string): Promise<Buffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to download image: ${response.statusText}`);\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n}\n\n/**\n * Compress image if it exceeds size limit by resizing\n * @param buffer Original image buffer\n * @param maxSizeBytes Maximum allowed size in bytes\n * @param type \"profile\" or \"banner\" for logging\n */\nasync function compressImageIfNeeded(\n buffer: Buffer,\n maxSizeBytes: number,\n type: string\n): Promise<Buffer> {\n if (buffer.length <= maxSizeBytes) {\n return buffer;\n }\n\n console.log(\n `${type} image is ${(buffer.length / 1024 / 1024).toFixed(2)}MB, resizing to fit ${(maxSizeBytes / 1024 / 1024).toFixed(0)}MB limit...`\n );\n\n // Calculate scale factor to fit within size limit\n const scaleFactor = Math.sqrt(maxSizeBytes / buffer.length) * 0.85; // 85% of target for safety\n\n // Get current dimensions and calculate new size\n const metadata = await sharp(buffer).metadata();\n const newWidth = Math.floor((metadata.width || 1000) * scaleFactor);\n\n // Resize and convert to JPEG with good quality\n const compressed = await sharp(buffer)\n .resize(newWidth, null, { fit: \"inside\", withoutEnlargement: true })\n .jpeg({ quality: 90, progressive: true })\n .toBuffer();\n\n console.log(\n `Resized ${type} image from ${(buffer.length / 1024 / 1024).toFixed(2)}MB to ${(compressed.length / 1024 / 1024).toFixed(2)}MB`\n );\n\n return compressed;\n}\n"],"mappings":"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","names":["input","paths","saveConfig","input","payload","setup","config"]}
@@ -50,7 +50,7 @@
50
50
  background: transparent;
51
51
  color: #fff;
52
52
  cursor: pointer;
53
- font-size: 1.05rem;
53
+ font-size: 1rem;
54
54
  line-height: 1;
55
55
  display: inline-flex;
56
56
  align-items: center;
@@ -59,6 +59,13 @@
59
59
  transition: opacity 0.2s ease;
60
60
  }
61
61
 
62
+ .heartbeat-trigger svg {
63
+ width: 15px;
64
+ height: 15px;
65
+ fill: currentColor;
66
+ display: block;
67
+ }
68
+
62
69
  .heartbeat-trigger:hover {
63
70
  opacity: 0.72;
64
71
  }
@@ -381,7 +388,11 @@
381
388
  <img class="logo-img" src="/logo2.png" alt="Spora" />
382
389
  <div class="header-right">
383
390
  <div class="heartbeat-menu" id="heartbeatMenu">
384
- <button class="heartbeat-trigger" id="heartbeatTrigger" title="Heartbeat settings" aria-label="Heartbeat settings">♥</button>
391
+ <button class="heartbeat-trigger" id="heartbeatTrigger" title="Heartbeat settings" aria-label="Heartbeat settings">
392
+ <svg viewBox="0 0 24 24" aria-hidden="true">
393
+ <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
394
+ </svg>
395
+ </button>
385
396
  <div class="heartbeat-popover" id="heartbeatPopover" hidden>
386
397
  <div class="heartbeat-popover-title">Heartbeat</div>
387
398
  <button class="heartbeat-option" data-interval="60000">1 min</button>
@@ -423,6 +434,7 @@
423
434
  let agentPfp = null;
424
435
  let sleepTimerId = null;
425
436
  let heartbeatMs = 300000;
437
+ const HIDE_CLIENT_TEXT_RE = /(fail(?:ed|ure)?|error|unavailable|could not|cannot|can't)/i;
426
438
 
427
439
  const HEARTBEAT_LABELS = {
428
440
  60000: '1 min',
@@ -480,7 +492,7 @@
480
492
  } catch {
481
493
  heartbeatMs = previous;
482
494
  updateHeartbeatOptions();
483
- setLiveStatus('could not update heartbeat', 'error');
495
+ setLiveStatus('heartbeat unchanged', 'action');
484
496
  } finally {
485
497
  heartbeatTrigger.disabled = false;
486
498
  }
@@ -609,11 +621,13 @@
609
621
  card.appendChild(time);
610
622
 
611
623
  if (status) {
612
- const statusEl = document.createElement('div');
613
624
  const successLike = /posted|sent|queued|success/i.test(status);
614
- statusEl.className = `tweet-card-status ${successLike ? 'success' : ''}`;
615
- statusEl.textContent = status;
616
- card.appendChild(statusEl);
625
+ if (successLike) {
626
+ const statusEl = document.createElement('div');
627
+ statusEl.className = 'tweet-card-status success';
628
+ statusEl.textContent = status;
629
+ card.appendChild(statusEl);
630
+ }
617
631
  }
618
632
 
619
633
  chatContainer.appendChild(card);
@@ -631,6 +645,7 @@
631
645
  clearLiveStatus();
632
646
  return;
633
647
  }
648
+ if (HIDE_CLIENT_TEXT_RE.test(text)) return;
634
649
  removeSleepCard();
635
650
  let el = document.getElementById('liveStatus');
636
651
  if (!el) {
@@ -724,11 +739,11 @@
724
739
  if (data.response) {
725
740
  addMessage('assistant', data.response);
726
741
  } else {
727
- addMessage('assistant', 'Something went wrong. Try again?');
742
+ addMessage('assistant', 'Try that once more.');
728
743
  }
729
744
  } catch (error) {
730
745
  hideLoading();
731
- addMessage('assistant', 'Couldn\'t connect to the server. Try again?');
746
+ addMessage('assistant', 'Connection dropped. Try once more.');
732
747
  } finally {
733
748
  isLoading = false;
734
749
  sendButton.disabled = false;
@@ -790,6 +805,10 @@
790
805
  }
791
806
 
792
807
  function renderAgentEvent(event) {
808
+ if ((event.type === 'narration' || event.type === 'action' || event.type === 'summary' || event.type === 'wake') &&
809
+ HIDE_CLIENT_TEXT_RE.test(event.content || '')) {
810
+ return;
811
+ }
793
812
  switch (event.type) {
794
813
  case 'narration':
795
814
  setLiveStatus(event.content);
@@ -804,7 +823,7 @@
804
823
  setLiveStatus(event.content, 'action');
805
824
  break;
806
825
  case 'error':
807
- setLiveStatus(event.content, 'error');
826
+ // Hidden by design in client chat view.
808
827
  break;
809
828
  case 'sleep':
810
829
  clearLiveStatus();
@@ -42,7 +42,16 @@ var WebChatServer = class {
42
42
  }
43
43
  /** Push an event into the chat stream (narration, tweet card, sleep state, etc.) */
44
44
  pushAgentEvent(type, content, meta) {
45
- this.agentEvents.push({ type, content, timestamp: Date.now(), meta });
45
+ if (type === "error") return;
46
+ const hiddenPattern = /(fail(?:ed|ure)?|error|unavailable|could not|cannot|can't)/i;
47
+ if (["narration", "action", "summary", "wake"].includes(type) && hiddenPattern.test(content)) return;
48
+ const safeContent = (content ?? "").trim();
49
+ if (!safeContent) return;
50
+ const safeMeta = meta ? { ...meta } : void 0;
51
+ if (safeMeta && typeof safeMeta.status === "string" && hiddenPattern.test(safeMeta.status)) {
52
+ delete safeMeta.status;
53
+ }
54
+ this.agentEvents.push({ type, content: safeContent, timestamp: Date.now(), meta: safeMeta });
46
55
  if (this.agentEvents.length > 200) {
47
56
  this.agentEvents = this.agentEvents.slice(-200);
48
57
  }
@@ -382,8 +391,8 @@ async function logChatInteraction(userMessage, agentResponse) {
382
391
  async function runNarratedHeartbeat(server) {
383
392
  const { loadConfig } = await import("./config-MU2ODEO3.js");
384
393
  const { flushQueue } = await import("./queue-YPBUUP22.js");
385
- const { runAutonomyCycle } = await import("./autonomy-ZMFZRXDZ.js");
386
- const { buildHeartbeatNarrative, saveHeartbeatNarrative } = await import("./heartbeat-narrative-B3RD3OPJ.js");
394
+ const { runAutonomyCycle } = await import("./autonomy-ZVXD2OP2.js");
395
+ const { buildHeartbeatNarrative, saveHeartbeatNarrative } = await import("./heartbeat-narrative-WGYRAWUH.js");
387
396
  const { buildReflectionPrompt } = await import("./prompt-builder-S6PJVEC5.js");
388
397
  const { generateResponse } = await import("./llm-IJBRQ7O2.js");
389
398
  const { addLearning } = await import("./memory-G4DNIGLT.js");
@@ -481,15 +490,6 @@ async function runNarratedHeartbeat(server) {
481
490
  }
482
491
  console.log(chalk.green(` [Agent] \u2713 ${result.action}`));
483
492
  } else {
484
- if ((action.action === "post" || action.action === "reply") && action.content) {
485
- server.pushAgentEvent("tweet", action.content, {
486
- kind: action.action === "reply" ? "Reply" : "Post",
487
- status: `Failed: ${result.error}`,
488
- replyTo: action.action === "reply" ? action.tweetId : void 0
489
- });
490
- } else {
491
- server.pushAgentEvent("error", `${result.action} failed`);
492
- }
493
493
  console.log(chalk.red(` [Agent] \u2717 ${result.action}: ${result.error}`));
494
494
  }
495
495
  }
@@ -526,7 +526,6 @@ async function runNarratedHeartbeat(server) {
526
526
  } catch (error) {
527
527
  const msg = error.message;
528
528
  console.error(chalk.red(` [Agent] Heartbeat #${heartbeatCount} error: ${msg}`));
529
- server.pushAgentEvent("error", `Something went wrong: ${msg}`);
530
529
  }
531
530
  }
532
531
  }
@@ -648,4 +647,4 @@ export {
648
647
  openBrowser,
649
648
  startWebChat
650
649
  };
651
- //# sourceMappingURL=web-chat-YRQQB435.js.map
650
+ //# sourceMappingURL=web-chat-FCOETDEZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-chat/server.ts","../src/web-chat/index.ts"],"sourcesContent":["/**\n * Local web chat server\n * Serves a simple chat interface for interacting with your Spore\n */\n\nimport http from \"node:http\";\nimport { URL } from \"node:url\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface ChatMessage {\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: number;\n}\n\ninterface AgentActivity {\n type: \"heartbeat\" | \"action\" | \"status\";\n message: string;\n timestamp: number;\n}\n\n/** Messages pushed from the heartbeat into the chat UI */\nexport interface AgentChatEvent {\n type: \"narration\" | \"tweet\" | \"action\" | \"sleep\" | \"wake\" | \"error\" | \"summary\";\n content: string;\n timestamp: number;\n meta?: Record<string, unknown>;\n}\n\ninterface AgentIdentity {\n name: string;\n handle: string;\n bio?: string;\n profileImage?: string;\n}\n\ninterface HeartbeatConfigHandlers {\n getIntervalMs: () => number | Promise<number>;\n setIntervalMs: (intervalMs: number) => void | Promise<void>;\n}\n\nconst ALLOWED_HEARTBEAT_INTERVALS = [60_000, 300_000, 1_800_000, 3_600_000, 21_600_000] as const;\n\nexport class WebChatServer {\n private server: http.Server | null = null;\n private port: number;\n private messages: ChatMessage[] = [];\n private activities: AgentActivity[] = [];\n private agentEvents: AgentChatEvent[] = [];\n private onUserMessage?: (message: string) => Promise<string>;\n private identity?: AgentIdentity;\n private heartbeatConfigHandlers?: HeartbeatConfigHandlers;\n\n constructor(port = 3737) {\n this.port = port;\n }\n\n setIdentity(identity: AgentIdentity) {\n this.identity = identity;\n }\n\n setMessageHandler(handler: (message: string) => Promise<string>) {\n this.onUserMessage = handler;\n }\n\n setHeartbeatConfigHandlers(handlers: HeartbeatConfigHandlers) {\n this.heartbeatConfigHandlers = handlers;\n }\n\n /** Push an activity update (from heartbeat) to the UI — kept for backwards compat */\n pushActivity(type: AgentActivity[\"type\"], message: string) {\n this.activities.push({ type, message, timestamp: Date.now() });\n if (this.activities.length > 100) {\n this.activities = this.activities.slice(-100);\n }\n }\n\n /** Push an event into the chat stream (narration, tweet card, sleep state, etc.) */\n pushAgentEvent(type: AgentChatEvent[\"type\"], content: string, meta?: Record<string, unknown>) {\n // UI policy: never expose failure/error/unavailable messaging to end users in local chat.\n if (type === \"error\") return;\n const hiddenPattern = /(fail(?:ed|ure)?|error|unavailable|could not|cannot|can't)/i;\n if ([\"narration\", \"action\", \"summary\", \"wake\"].includes(type) && hiddenPattern.test(content)) return;\n\n const safeContent = (content ?? \"\").trim();\n if (!safeContent) return;\n\n const safeMeta: Record<string, unknown> | undefined = meta ? { ...meta } : undefined;\n if (safeMeta && typeof safeMeta.status === \"string\" && hiddenPattern.test(safeMeta.status)) {\n delete safeMeta.status;\n }\n\n this.agentEvents.push({ type, content: safeContent, timestamp: Date.now(), meta: safeMeta });\n if (this.agentEvents.length > 200) {\n this.agentEvents = this.agentEvents.slice(-200);\n }\n }\n\n async start(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.server = http.createServer(async (req, res) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\n // CORS headers\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(200);\n res.end();\n return;\n }\n\n // Serve HTML\n if (url.pathname === \"/\" || url.pathname === \"/index.html\") {\n try {\n const possiblePaths = [\n join(__dirname, \"web-chat\", \"chat.html\"),\n join(__dirname, \"chat.html\"),\n join(__dirname, \"..\", \"web-chat\", \"chat.html\"),\n join(__dirname, \"..\", \"src\", \"web-chat\", \"chat.html\"),\n ];\n\n let html: string | null = null;\n for (const p of possiblePaths) {\n try {\n html = readFileSync(p, \"utf-8\");\n break;\n } catch {\n continue;\n }\n }\n\n if (html) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n } else {\n console.error(\"Could not find chat.html in any of:\", possiblePaths);\n res.writeHead(500);\n res.end(\"Error: Could not find chat.html\");\n }\n } catch (error) {\n res.writeHead(500);\n res.end(\"Error loading chat interface\");\n }\n return;\n }\n\n // API: Get agent identity\n if (url.pathname === \"/api/identity\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ identity: this.identity || null }));\n return;\n }\n\n // Static: logo asset for chat header\n if (url.pathname === \"/logo2.png\" && req.method === \"GET\") {\n const logoPaths = [\n join(__dirname, \"web-chat\", \"logo2.png\"),\n join(__dirname, \"..\", \"web-chat\", \"logo2.png\"),\n join(process.cwd(), \"dist\", \"web-chat\", \"logo2.png\"),\n join(process.cwd(), \"logo2.png\"),\n ];\n const logoPath = logoPaths.find((p) => existsSync(p));\n if (!logoPath) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n res.writeHead(200, {\n \"Content-Type\": \"image/png\",\n \"Cache-Control\": \"public, max-age=300\",\n });\n res.end(readFileSync(logoPath));\n return;\n }\n\n // API: Get messages\n if (url.pathname === \"/api/messages\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ messages: this.messages }));\n return;\n }\n\n // API: Get agent activity feed (legacy, kept for compat)\n if (url.pathname === \"/api/activity\" && req.method === \"GET\") {\n const since = parseInt(url.searchParams.get(\"since\") || \"0\", 10);\n const newActivities = this.activities.filter((a) => a.timestamp > since);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ activities: newActivities }));\n return;\n }\n\n // API: Get agent events (narration, tweets, sleep) — polled by chat UI\n if (url.pathname === \"/api/agent-events\" && req.method === \"GET\") {\n const since = parseInt(url.searchParams.get(\"since\") || \"0\", 10);\n const newEvents = this.agentEvents.filter((e) => e.timestamp > since);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ events: newEvents }));\n return;\n }\n\n // API: Read heartbeat interval config\n if (url.pathname === \"/api/heartbeat-config\" && req.method === \"GET\") {\n if (!this.heartbeatConfigHandlers) {\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Heartbeat config unavailable\" }));\n return;\n }\n\n try {\n const intervalMs = await this.heartbeatConfigHandlers.getIntervalMs();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ intervalMs, allowedIntervals: ALLOWED_HEARTBEAT_INTERVALS }));\n } catch {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to load heartbeat config\" }));\n }\n return;\n }\n\n // API: Update heartbeat interval config\n if (url.pathname === \"/api/heartbeat-config\" && req.method === \"POST\") {\n if (!this.heartbeatConfigHandlers) {\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Heartbeat config unavailable\" }));\n return;\n }\n const handlers = this.heartbeatConfigHandlers;\n\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", async () => {\n try {\n const parsed = JSON.parse(body) as { intervalMs?: number };\n const intervalMs = parsed.intervalMs;\n\n if (\n typeof intervalMs !== \"number\" ||\n !Number.isInteger(intervalMs) ||\n !ALLOWED_HEARTBEAT_INTERVALS.includes(intervalMs as (typeof ALLOWED_HEARTBEAT_INTERVALS)[number])\n ) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unsupported heartbeat interval\" }));\n return;\n }\n\n await handlers.setIntervalMs(intervalMs);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true, intervalMs }));\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid request\" }));\n }\n });\n return;\n }\n\n // API: Send message\n if (url.pathname === \"/api/message\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", async () => {\n try {\n const { message } = JSON.parse(body);\n\n // Add user message\n this.messages.push({\n role: \"user\",\n content: message,\n timestamp: Date.now(),\n });\n\n // Get response\n if (this.onUserMessage) {\n const response = await this.onUserMessage(message);\n this.messages.push({\n role: \"assistant\",\n content: response,\n timestamp: Date.now(),\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true, response }));\n } else {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No message handler configured\" }));\n }\n } catch (error) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid request\" }));\n }\n });\n return;\n }\n\n // 404\n res.writeHead(404);\n res.end(\"Not found\");\n });\n\n this.server.listen(this.port, () => {\n const url = `http://localhost:${this.port}`;\n resolve(url);\n });\n\n this.server.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n this.port++;\n this.server?.close();\n this.start().then(resolve).catch(reject);\n } else {\n reject(error);\n }\n });\n });\n }\n\n stop() {\n if (this.server) {\n this.server.close();\n this.server = null;\n }\n }\n}\n","/**\n * Web chat integration with autonomous heartbeat\n */\n\nimport { WebChatServer } from \"./server.js\";\nimport { loadIdentity } from \"../identity/index.js\";\nimport { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\n\nconst HEARTBEAT_INTERVAL_OPTIONS = [60_000, 300_000, 1_800_000, 3_600_000, 21_600_000] as const;\n\nfunction formatHeartbeatInterval(intervalMs: number): string {\n const minutes = intervalMs / 60_000;\n if (minutes < 60) return `${minutes} minute${minutes === 1 ? \"\" : \"s\"}`;\n const hours = minutes / 60;\n return `${hours} hour${hours === 1 ? \"\" : \"s\"}`;\n}\n\n/**\n * Extract <<LEARN: ...>> tags from response, save them, and return cleaned text\n */\nasync function extractAndSaveLearnings(responseText: string): Promise<string> {\n const learnPattern = /<<LEARN:\\s*(.+?)>>/g;\n const matches = [...responseText.matchAll(learnPattern)];\n\n if (matches.length > 0) {\n const { addLearning } = await import(\"../memory/index.js\");\n for (const match of matches) {\n const learning = match[1].trim();\n addLearning(learning, \"web-chat\", [\"chat\", \"creator-interaction\"]);\n console.log(chalk.dim(` [Memory] Saved learning: ${learning}`));\n }\n }\n\n // Strip the learn tags from the response the user sees\n return responseText.replace(/<<LEARN:\\s*.+?>>/g, \"\").trim();\n}\n\n/**\n * Extract <<TRAINING:{json}>> tags from response, apply updates to identity/strategy/goals, and return cleaned text\n */\nasync function extractAndApplyTraining(responseText: string): Promise<string> {\n const trainingPattern = /<<TRAINING:([\\s\\S]*?)>>/g;\n const matches = [...responseText.matchAll(trainingPattern)];\n\n for (const match of matches) {\n try {\n const update = JSON.parse(match[1].trim());\n\n // Apply identity changes\n if (update.identity) {\n const { loadIdentity, mutateIdentity, saveIdentity } = await import(\"../identity/index.js\");\n let identity = loadIdentity();\n\n const identityFields: Record<string, string> = {\n tone: \"tone\",\n worldview: \"worldview\",\n conflictStyle: \"conflictStyle\",\n vocabularyStyle: \"vocabularyStyle\",\n tweetStyle: \"tweetStyle\",\n };\n\n // Simple string/enum fields\n for (const [key, field] of Object.entries(identityFields)) {\n if (update.identity[key] !== undefined) {\n identity = mutateIdentity(identity, field, update.identity[key], \"training-chat\");\n console.log(chalk.dim(` [Training] Updated ${field}`));\n }\n }\n\n // Array fields (replace entire array)\n const arrayFields = [\"coreValues\", \"topics\", \"avoidTopics\", \"goals\", \"boundaries\", \"catchphrases\", \"heroes\"];\n for (const field of arrayFields) {\n if (update.identity[field] !== undefined && Array.isArray(update.identity[field])) {\n identity = mutateIdentity(identity, field, update.identity[field], \"training-chat\");\n console.log(chalk.dim(` [Training] Updated ${field}`));\n }\n }\n\n // Traits (merge, don't replace)\n if (update.identity.traits && typeof update.identity.traits === \"object\") {\n for (const [trait, value] of Object.entries(update.identity.traits)) {\n if (typeof value === \"number\" && value >= 0 && value <= 1) {\n identity = mutateIdentity(identity, `traits.${trait}`, value, \"training-chat\");\n console.log(chalk.dim(` [Training] Updated traits.${trait} = ${value}`));\n }\n }\n }\n\n // Engagement strategy (merge)\n if (update.identity.engagementStrategy && typeof update.identity.engagementStrategy === \"object\") {\n for (const [key, value] of Object.entries(update.identity.engagementStrategy)) {\n identity = mutateIdentity(identity, `engagementStrategy.${key}`, value, \"training-chat\");\n console.log(chalk.dim(` [Training] Updated engagementStrategy.${key}`));\n }\n }\n\n saveIdentity(identity);\n }\n\n // Apply strategy changes\n if (update.strategy) {\n const { loadStrategy, saveStrategy } = await import(\"../memory/strategy.js\");\n const strategy = loadStrategy();\n\n if (update.strategy.currentFocus) strategy.currentFocus = update.strategy.currentFocus;\n if (update.strategy.shortTermGoals) strategy.shortTermGoals = update.strategy.shortTermGoals;\n if (update.strategy.experiments) {\n strategy.experiments.push(...update.strategy.experiments);\n }\n if (update.strategy.peopleToEngage) {\n strategy.peopleToEngage.push(...update.strategy.peopleToEngage);\n }\n\n strategy.lastUpdated = new Date().toISOString();\n saveStrategy(strategy);\n console.log(chalk.dim(` [Training] Updated strategy`));\n }\n\n // Apply standalone learning\n if (update.learning) {\n const { addLearning } = await import(\"../memory/index.js\");\n addLearning(\n update.learning.content,\n \"training-chat\",\n update.learning.tags || [\"training\"],\n );\n console.log(chalk.dim(` [Training] Saved learning: ${update.learning.content}`));\n }\n\n // Apply reflection to evolution journal\n if (update.reflection) {\n const { loadIdentity, saveIdentity } = await import(\"../identity/index.js\");\n const identity = loadIdentity();\n identity.evolutionJournal.push({\n date: new Date().toISOString(),\n reflection: update.reflection,\n });\n saveIdentity(identity);\n console.log(chalk.dim(` [Training] Added reflection to evolution journal`));\n }\n\n // Apply goal updates\n if (update.goalUpdates && Array.isArray(update.goalUpdates)) {\n const { loadGoals, saveGoals } = await import(\"../memory/goals.js\");\n const tracker = loadGoals();\n\n for (const gu of update.goalUpdates) {\n const existing = tracker.goals.find((g: { goal: string }) => g.goal === gu.goal);\n if (existing) {\n existing.progress = gu.progress;\n existing.lastUpdated = new Date().toISOString();\n } else {\n tracker.goals.push({\n goal: gu.goal,\n progress: gu.progress,\n lastUpdated: new Date().toISOString(),\n });\n }\n }\n\n tracker.lastReviewed = new Date().toISOString();\n saveGoals(tracker);\n console.log(chalk.dim(` [Training] Updated ${update.goalUpdates.length} goal(s)`));\n }\n\n } catch (err) {\n console.error(chalk.dim(` [Training] Failed to parse training update: ${(err as Error).message}`));\n }\n }\n\n // Strip the training tags from the response the user sees\n return responseText.replace(/<<TRAINING:[\\s\\S]*?>>/g, \"\").trim();\n}\n\n/**\n * Log a chat exchange as an interaction in memory\n */\nasync function logChatInteraction(userMessage: string, agentResponse: string): Promise<void> {\n const { logInteraction } = await import(\"../memory/index.js\");\n logInteraction({\n id: `chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date().toISOString(),\n type: \"reply\",\n content: agentResponse.slice(0, 200),\n targetHandle: \"creator\",\n creditsUsed: 0,\n success: true,\n });\n}\n\n/**\n * Run the autonomous heartbeat in the background, narrating to the chat UI\n */\nasync function runNarratedHeartbeat(server: WebChatServer): Promise<void> {\n const { loadConfig } = await import(\"../utils/config.js\");\n const { flushQueue } = await import(\"../scheduler/queue.js\");\n const { runAutonomyCycle } = await import(\"../runtime/autonomy.js\");\n const { buildHeartbeatNarrative, saveHeartbeatNarrative } = await import(\"../runtime/heartbeat-narrative.js\");\n const { buildReflectionPrompt } = await import(\"../runtime/prompt-builder.js\");\n const { generateResponse } = await import(\"../runtime/llm.js\");\n const { addLearning } = await import(\"../memory/index.js\");\n const { loadStrategy, saveStrategy, applyStrategyUpdate } = await import(\"../memory/strategy.js\");\n\n let heartbeatCount = 0;\n let lastIntervalMs = loadConfig().runtime?.heartbeatIntervalMs ?? 300_000;\n\n console.log(chalk.cyan(` [Agent] Autonomous heartbeat started (every ${Math.round(lastIntervalMs / 60_000)} min)`));\n\n while (true) {\n const runtimeConfig = loadConfig();\n const intervalMs = runtimeConfig.runtime?.heartbeatIntervalMs ?? 300_000;\n const maxActions = runtimeConfig.runtime?.actionsPerHeartbeat ?? 4;\n\n if (intervalMs !== lastIntervalMs) {\n server.pushAgentEvent(\"narration\", `heartbeat set to ${formatHeartbeatInterval(intervalMs)}`);\n console.log(chalk.cyan(` [Agent] Heartbeat interval updated to ${Math.round(intervalMs / 60_000)} min`));\n lastIntervalMs = intervalMs;\n }\n\n // Sleep first, then heartbeat\n const jitter = Math.floor(Math.random() * intervalMs * 0.3);\n const sleepMs = heartbeatCount === 0 ? 10_000 : intervalMs + jitter;\n const wakeUpAt = Date.now() + sleepMs;\n\n // Show sleep state in chat (skip for first quick boot)\n if (heartbeatCount > 0) {\n server.pushAgentEvent(\"sleep\", \"sleeping\", { wakeUpAt });\n }\n\n // Sleep in short chunks so heartbeat interval changes apply immediately.\n let sleptMs = 0;\n let intervalChangedDuringSleep = false;\n while (sleptMs < sleepMs) {\n const chunkMs = Math.min(1000, sleepMs - sleptMs);\n await new Promise((r) => setTimeout(r, chunkMs));\n sleptMs += chunkMs;\n\n const liveIntervalMs = loadConfig().runtime?.heartbeatIntervalMs ?? 300_000;\n if (liveIntervalMs !== intervalMs) {\n intervalChangedDuringSleep = true;\n break;\n }\n }\n\n if (intervalChangedDuringSleep) {\n continue;\n }\n\n heartbeatCount++;\n console.log(chalk.cyan(` [Agent] Heartbeat #${heartbeatCount}`));\n server.pushAgentEvent(\"wake\", `heartbeat #${heartbeatCount}`);\n\n try {\n // 1. Flush queue\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n server.pushAgentEvent(\"action\", `Flushed ${flushed.posted} queued post(s)`);\n }\n } catch {\n // Queue flush is best-effort\n }\n\n // 2. Run tool-driven autonomy cycle\n server.pushAgentEvent(\"narration\", \"scanning timeline...\");\n const cycle = await runAutonomyCycle(maxActions, heartbeatCount);\n const narrative = buildHeartbeatNarrative({\n heartbeatCount,\n timelineCount: cycle.timeline.length,\n mentionsCount: cycle.mentions.length,\n actions: cycle.actions,\n results: cycle.results,\n });\n saveHeartbeatNarrative(\n narrative.summary,\n cycle.results.length === 0 ? true : cycle.results.every((r) => r.success),\n );\n server.pushAgentEvent(\"summary\", narrative.summary);\n\n if (cycle.timeline.length > 0 || cycle.mentions.length > 0) {\n server.pushAgentEvent(\"narration\", `found ${cycle.timeline.length} timeline, ${cycle.mentions.length} mentions`);\n }\n\n if (cycle.actions.length === 0) {\n server.pushAgentEvent(\"narration\", \"no actions, sleeping\");\n console.log(chalk.dim(` [Agent] No actions this heartbeat`));\n continue;\n }\n\n // 3. Report results — push tweet cards for posts, narration for everything else\n for (let i = 0; i < cycle.results.length; i++) {\n const result = cycle.results[i];\n const action = cycle.actions[i];\n if (!action) continue;\n\n if (result.success) {\n if (action.action === \"post\" && action.content) {\n server.pushAgentEvent(\"tweet\", action.content, { kind: \"Post\", status: \"Posted to X\", tweetId: result.detail });\n } else if (action.action === \"reply\" && action.content) {\n server.pushAgentEvent(\"tweet\", action.content, {\n kind: \"Reply\",\n status: \"Reply sent\",\n tweetId: result.detail,\n replyTo: action.tweetId,\n });\n } else if (action.action === \"like\") {\n server.pushAgentEvent(\"action\", \"liked a tweet\");\n } else if (action.action === \"retweet\") {\n server.pushAgentEvent(\"action\", \"retweeted\");\n } else if (action.action === \"follow\") {\n server.pushAgentEvent(\"action\", `followed @${action.handle}`);\n } else if (action.action === \"schedule\" && action.content) {\n server.pushAgentEvent(\"tweet\", action.content, { kind: \"Scheduled\", status: \"Queued\" });\n } else {\n server.pushAgentEvent(\"action\", `${action.action}${result.detail ? ` ${String(result.detail).slice(0, 40)}` : \"\"}`);\n }\n console.log(chalk.green(` [Agent] ✓ ${result.action}`));\n } else {\n // Keep failure details out of local client chat UI; logs still capture failures.\n console.log(chalk.red(` [Agent] ✗ ${result.action}: ${result.error}`));\n }\n }\n\n // Reflection phase — every 3rd heartbeat, review performance\n if (heartbeatCount % 3 === 0) {\n try {\n server.pushAgentEvent(\"narration\", \"reflecting...\");\n const reflectionPrompt = buildReflectionPrompt(cycle.results);\n const reflectionResponse = await generateResponse(\n `You are ${loadIdentity().name}. Reflect honestly on your performance.`,\n reflectionPrompt,\n );\n\n // Parse reflection JSON\n const jsonMatch = reflectionResponse.content.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const reflection = JSON.parse(jsonMatch[0]);\n if (reflection.learning && reflection.learning !== \"null\") {\n addLearning(reflection.learning, \"reflection\", [\"heartbeat\", \"performance\"]);\n server.pushAgentEvent(\"narration\", \"learned something new\");\n console.log(chalk.dim(` [Agent] Reflection learning: ${reflection.learning}`));\n }\n if (reflection.strategyUpdate && reflection.strategyUpdate !== \"null\") {\n const strategy = loadStrategy();\n saveStrategy(applyStrategyUpdate(strategy, reflection.strategyUpdate));\n server.pushAgentEvent(\"narration\", \"strategy updated\");\n console.log(chalk.dim(` [Agent] Strategy update: ${reflection.strategyUpdate}`));\n }\n } catch {\n // Couldn't parse reflection JSON, that's fine\n }\n }\n } catch (err) {\n console.log(chalk.dim(` [Agent] Reflection failed: ${(err as Error).message}`));\n }\n }\n\n } catch (error) {\n const msg = (error as Error).message;\n console.error(chalk.red(` [Agent] Heartbeat #${heartbeatCount} error: ${msg}`));\n }\n }\n}\n\nexport async function startWebChat() {\n const identity = loadIdentity();\n const { loadConfig: loadRuntimeConfig, saveConfig } = await import(\"../utils/config.js\");\n\n const server = new WebChatServer();\n\n // Pass identity to server so the chat UI can display it\n server.setIdentity({\n name: identity.name,\n handle: identity.handle,\n bio: identity.bio,\n profileImage: identity.profileImage,\n });\n\n server.setHeartbeatConfigHandlers({\n getIntervalMs: () => {\n const config = loadRuntimeConfig();\n return config.runtime?.heartbeatIntervalMs ?? 300_000;\n },\n setIntervalMs: (intervalMs) => {\n if (!HEARTBEAT_INTERVAL_OPTIONS.includes(intervalMs as (typeof HEARTBEAT_INTERVAL_OPTIONS)[number])) {\n throw new Error(\"Unsupported heartbeat interval.\");\n }\n\n const config = loadRuntimeConfig();\n config.runtime = {\n heartbeatIntervalMs: intervalMs,\n actionsPerHeartbeat: config.runtime?.actionsPerHeartbeat ?? 4,\n enabled: true,\n };\n saveConfig(config);\n server.pushAgentEvent(\"narration\", `Heartbeat interval set to ${formatHeartbeatInterval(intervalMs)}.`);\n },\n });\n\n // Set up message handler - connected to actual LLM with memory\n const chatHistory: Array<{ role: \"user\" | \"assistant\"; content: string }> = [];\n let systemPrompt: string | null = null;\n let messageCount = 0;\n\n server.setMessageHandler(async (message: string) => {\n try {\n // Build training prompt on first message, rebuild every 10 messages to refresh memory\n if (!systemPrompt || messageCount % 10 === 0) {\n const { buildTrainingChatPrompt } = await import(\"../runtime/prompt-builder.js\");\n systemPrompt = buildTrainingChatPrompt();\n }\n messageCount++;\n\n // Check for LLM key\n const { hasLLMKey, chat: chatLLM } = await import(\"../runtime/llm.js\");\n if (!hasLLMKey()) {\n return \"I can't respond right now - no API key configured. Run `spora llm set --provider <provider>` to set one up.\";\n }\n\n // Add user message to history\n chatHistory.push({ role: \"user\", content: message });\n\n // Call LLM with full conversation history\n const response = await chatLLM(systemPrompt, chatHistory);\n\n // Extract training updates and learnings from response\n const afterTraining = await extractAndApplyTraining(response.content);\n const cleanResponse = await extractAndSaveLearnings(afterTraining);\n\n // Add cleaned assistant response to history\n chatHistory.push({ role: \"assistant\", content: cleanResponse });\n\n // Log interaction to memory (async, don't block response)\n logChatInteraction(message, cleanResponse).catch((err) =>\n console.error(chalk.dim(\" [Memory] Failed to log interaction:\"), err)\n );\n\n return cleanResponse;\n } catch (error) {\n console.error(\"Chat error:\", error);\n return `Sorry, I ran into an issue: ${(error as Error).message}`;\n }\n });\n\n const url = await server.start();\n\n console.log(chalk.green(`\\n✓ Chat interface started at ${chalk.bold(url)}\\n`));\n console.log(chalk.dim(`Press Ctrl+C to stop the server\\n`));\n\n // Open browser\n openBrowser(url);\n\n // Start autonomous heartbeat in background\n try {\n const { hasLLMKey } = await import(\"../runtime/llm.js\");\n if (hasLLMKey()) {\n runNarratedHeartbeat(server).catch((err) => {\n console.error(chalk.red(`Heartbeat failed to start: ${err}`));\n });\n } else {\n console.log(chalk.yellow(\" [Agent] No LLM key — autonomous heartbeat disabled. Run `spora llm set --provider <provider>` to enable.\"));\n server.pushActivity(\"status\", \"No LLM API key configured. Autonomous mode disabled.\");\n }\n } catch (err) {\n console.error(chalk.red(`Heartbeat failed to start: ${err}`));\n }\n\n // Keep process alive\n process.on(\"SIGINT\", () => {\n console.log(chalk.yellow(\"\\n\\nStopping chat server...\"));\n server.stop();\n process.exit(0);\n });\n\n // Return the server instance for external control\n return server;\n}\n\n/**\n * Open URL in a separate browser window (app-like)\n */\nfunction openBrowser(url: string) {\n const platform = process.platform;\n\n try {\n if (platform === \"darwin\") {\n // Open as a standalone Chrome app window (smaller, separate from existing tabs)\n try {\n execSync(`open -na \"Google Chrome\" --args --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n // Fallback: try Chromium-based browsers, then default\n try {\n execSync(`open -na \"Brave Browser\" --args --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n execSync(`open \"${url}\"`, { stdio: \"ignore\" });\n }\n }\n } else if (platform === \"win32\") {\n try {\n execSync(`start chrome --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n execSync(`start \"\" \"${url}\"`, { stdio: \"ignore\" });\n }\n } else {\n // Linux\n try {\n execSync(`google-chrome --app=\"${url}\" --window-size=500,700`, { stdio: \"ignore\" });\n } catch {\n execSync(`xdg-open \"${url}\"`, { stdio: \"ignore\" });\n }\n }\n } catch (error) {\n // Browser couldn't be opened, that's okay\n console.log(chalk.dim(`(Couldn't open browser automatically - please visit ${url} manually)`));\n }\n}\n\nexport { openBrowser };\n"],"mappings":"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","names":["loadIdentity"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spora",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "description": "AI agents (Spores) that autonomously manage X/Twitter accounts",
5
5
  "type": "module",
6
6
  "author": "Spora",
@@ -1,69 +0,0 @@
1
- import {
2
- logInteraction
3
- } from "./chunk-6WBIVXOY.js";
4
-
5
- // src/runtime/heartbeat-narrative.ts
6
- function clip(text, max = 70) {
7
- if (!text) return "";
8
- const clean = text.replace(/\s+/g, " ").trim();
9
- if (clean.length <= max) return clean;
10
- return `${clean.slice(0, max - 3)}...`;
11
- }
12
- function actionLine(action, result) {
13
- const outcome = result.success ? "ok" : "fail";
14
- if (action.action === "post") {
15
- return `${outcome} post "${clip(action.content)}"`;
16
- }
17
- if (action.action === "reply") {
18
- return `${outcome} reply "${clip(action.content)}"${action.tweetId ? ` -> ${action.tweetId}` : ""}`;
19
- }
20
- if (action.action === "follow") {
21
- return `${outcome} follow @${action.handle ?? "unknown"}`;
22
- }
23
- if (action.action === "like" || action.action === "retweet") {
24
- return `${outcome} ${action.action}${action.tweetId ? ` ${action.tweetId}` : ""}`;
25
- }
26
- if (action.action === "skip") {
27
- return `skip ${clip(action.reason ?? action.reasoning, 40)}`;
28
- }
29
- return `${outcome} ${action.action}`;
30
- }
31
- function buildHeartbeatNarrative(input) {
32
- const { heartbeatCount, timelineCount, mentionsCount, actions, results } = input;
33
- const successCount = results.filter((r) => r.success).length;
34
- const details = [];
35
- for (let i = 0; i < actions.length; i += 1) {
36
- const action = actions[i];
37
- const result = results[i];
38
- if (!action || !result) continue;
39
- details.push(actionLine(action, result));
40
- }
41
- if (actions.length === 0) {
42
- return {
43
- summary: `Heartbeat #${heartbeatCount}: scanned ${timelineCount} timeline + ${mentionsCount} mentions, took no actions.`,
44
- details
45
- };
46
- }
47
- const preview = details.slice(0, 3).join(" | ");
48
- return {
49
- summary: `Heartbeat #${heartbeatCount}: scanned ${timelineCount}/${mentionsCount}, executed ${actions.length} actions (${successCount} ok). ${preview}`,
50
- details
51
- };
52
- }
53
- function saveHeartbeatNarrative(narrative, success) {
54
- logInteraction({
55
- id: `heartbeat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
56
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
57
- type: "heartbeat",
58
- content: narrative.slice(0, 500),
59
- targetHandle: "self",
60
- creditsUsed: 0,
61
- success
62
- });
63
- }
64
-
65
- export {
66
- buildHeartbeatNarrative,
67
- saveHeartbeatNarrative
68
- };
69
- //# sourceMappingURL=chunk-OLYPPXKP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/runtime/heartbeat-narrative.ts"],"sourcesContent":["import { logInteraction } from \"../memory/index.js\";\nimport type { AgentAction, ActionResult } from \"./decision-engine.js\";\n\ninterface BuildHeartbeatNarrativeInput {\n heartbeatCount: number;\n timelineCount: number;\n mentionsCount: number;\n actions: AgentAction[];\n results: ActionResult[];\n}\n\nexport interface HeartbeatNarrative {\n summary: string;\n details: string[];\n}\n\nfunction clip(text: string | undefined, max: number = 70): string {\n if (!text) return \"\";\n const clean = text.replace(/\\s+/g, \" \").trim();\n if (clean.length <= max) return clean;\n return `${clean.slice(0, max - 3)}...`;\n}\n\nfunction actionLine(action: AgentAction, result: ActionResult): string {\n const outcome = result.success ? \"ok\" : \"fail\";\n if (action.action === \"post\") {\n return `${outcome} post \"${clip(action.content)}\"`;\n }\n if (action.action === \"reply\") {\n return `${outcome} reply \"${clip(action.content)}\"${action.tweetId ? ` -> ${action.tweetId}` : \"\"}`;\n }\n if (action.action === \"follow\") {\n return `${outcome} follow @${action.handle ?? \"unknown\"}`;\n }\n if (action.action === \"like\" || action.action === \"retweet\") {\n return `${outcome} ${action.action}${action.tweetId ? ` ${action.tweetId}` : \"\"}`;\n }\n if (action.action === \"skip\") {\n return `skip ${clip(action.reason ?? action.reasoning, 40)}`;\n }\n return `${outcome} ${action.action}`;\n}\n\nexport function buildHeartbeatNarrative(input: BuildHeartbeatNarrativeInput): HeartbeatNarrative {\n const { heartbeatCount, timelineCount, mentionsCount, actions, results } = input;\n const successCount = results.filter((r) => r.success).length;\n\n const details: string[] = [];\n for (let i = 0; i < actions.length; i += 1) {\n const action = actions[i];\n const result = results[i];\n if (!action || !result) continue;\n details.push(actionLine(action, result));\n }\n\n if (actions.length === 0) {\n return {\n summary: `Heartbeat #${heartbeatCount}: scanned ${timelineCount} timeline + ${mentionsCount} mentions, took no actions.`,\n details,\n };\n }\n\n const preview = details.slice(0, 3).join(\" | \");\n return {\n summary: `Heartbeat #${heartbeatCount}: scanned ${timelineCount}/${mentionsCount}, executed ${actions.length} actions (${successCount} ok). ${preview}`,\n details,\n };\n}\n\nexport function saveHeartbeatNarrative(narrative: string, success: boolean): void {\n logInteraction({\n id: `heartbeat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date().toISOString(),\n type: \"heartbeat\",\n content: narrative.slice(0, 500),\n targetHandle: \"self\",\n creditsUsed: 0,\n success,\n });\n}\n\n"],"mappings":";;;;;AAgBA,SAAS,KAAK,MAA0B,MAAc,IAAY;AAChE,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC7C,MAAI,MAAM,UAAU,IAAK,QAAO;AAChC,SAAO,GAAG,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC;AACnC;AAEA,SAAS,WAAW,QAAqB,QAA8B;AACrE,QAAM,UAAU,OAAO,UAAU,OAAO;AACxC,MAAI,OAAO,WAAW,QAAQ;AAC5B,WAAO,GAAG,OAAO,UAAU,KAAK,OAAO,OAAO,CAAC;AAAA,EACjD;AACA,MAAI,OAAO,WAAW,SAAS;AAC7B,WAAO,GAAG,OAAO,WAAW,KAAK,OAAO,OAAO,CAAC,IAAI,OAAO,UAAU,OAAO,OAAO,OAAO,KAAK,EAAE;AAAA,EACnG;AACA,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO,GAAG,OAAO,YAAY,OAAO,UAAU,SAAS;AAAA,EACzD;AACA,MAAI,OAAO,WAAW,UAAU,OAAO,WAAW,WAAW;AAC3D,WAAO,GAAG,OAAO,IAAI,OAAO,MAAM,GAAG,OAAO,UAAU,IAAI,OAAO,OAAO,KAAK,EAAE;AAAA,EACjF;AACA,MAAI,OAAO,WAAW,QAAQ;AAC5B,WAAO,QAAQ,KAAK,OAAO,UAAU,OAAO,WAAW,EAAE,CAAC;AAAA,EAC5D;AACA,SAAO,GAAG,OAAO,IAAI,OAAO,MAAM;AACpC;AAEO,SAAS,wBAAwB,OAAyD;AAC/F,QAAM,EAAE,gBAAgB,eAAe,eAAe,SAAS,QAAQ,IAAI;AAC3E,QAAM,eAAe,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAEtD,QAAM,UAAoB,CAAC;AAC3B,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GAAG;AAC1C,UAAM,SAAS,QAAQ,CAAC;AACxB,UAAM,SAAS,QAAQ,CAAC;AACxB,QAAI,CAAC,UAAU,CAAC,OAAQ;AACxB,YAAQ,KAAK,WAAW,QAAQ,MAAM,CAAC;AAAA,EACzC;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,MACL,SAAS,cAAc,cAAc,aAAa,aAAa,eAAe,aAAa;AAAA,MAC3F;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,QAAQ,MAAM,GAAG,CAAC,EAAE,KAAK,KAAK;AAC9C,SAAO;AAAA,IACL,SAAS,cAAc,cAAc,aAAa,aAAa,IAAI,aAAa,cAAc,QAAQ,MAAM,aAAa,YAAY,SAAS,OAAO;AAAA,IACrJ;AAAA,EACF;AACF;AAEO,SAAS,uBAAuB,WAAmB,SAAwB;AAChF,iBAAe;AAAA,IACb,IAAI,aAAa,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IACrE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,MAAM;AAAA,IACN,SAAS,UAAU,MAAM,GAAG,GAAG;AAAA,IAC/B,cAAc;AAAA,IACd,aAAa;AAAA,IACb;AAAA,EACF,CAAC;AACH;","names":[]}