spora 0.2.28 → 0.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{chunk-NJHDY4ON.js → chunk-4D73N6VY.js} +2 -2
  2. package/dist/{chunk-ZNLJN7YO.js → chunk-7FN5GDSB.js} +3 -3
  3. package/dist/{chunk-JKGCF23V.js → chunk-NHNOCAP3.js} +8 -8
  4. package/dist/chunk-NHNOCAP3.js.map +1 -0
  5. package/dist/{chunk-M23MGGHE.js → chunk-OGFMGURJ.js} +2 -2
  6. package/dist/cli.js +24 -24
  7. package/dist/{client-N7JMM5U7.js → client-3VP7YBBE.js} +2 -3
  8. package/dist/client-3VP7YBBE.js.map +1 -0
  9. package/dist/{colony-NE2MZ7RP.js → colony-YDOTBQFD.js} +2 -2
  10. package/dist/{decision-engine-RVQRDHEI.js → decision-engine-IIA5J2QG.js} +4 -4
  11. package/dist/{heartbeat-4Q344LIV.js → heartbeat-Q6GLGCGN.js} +5 -5
  12. package/dist/{init-Z72CJZUC.js → init-ULHKJ6XQ.js} +3 -3
  13. package/dist/mcp-server.js +19 -19
  14. package/dist/{prompt-builder-4SWKDPNG.js → prompt-builder-3AJUQUVZ.js} +2 -2
  15. package/dist/{queue-T3OYWUII.js → queue-LHRG6JOP.js} +2 -2
  16. package/dist/web-chat/chat.html +73 -20
  17. package/dist/{web-chat-7SSETYWK.js → web-chat-6ZRJE73E.js} +18 -11
  18. package/dist/web-chat-6ZRJE73E.js.map +1 -0
  19. package/dist/{x-client-5FBD32B2.js → x-client-QNLSLY2R.js} +2 -2
  20. package/package.json +1 -1
  21. package/dist/chunk-JKGCF23V.js.map +0 -1
  22. package/dist/client-N7JMM5U7.js.map +0 -1
  23. package/dist/web-chat-7SSETYWK.js.map +0 -1
  24. /package/dist/{chunk-NJHDY4ON.js.map → chunk-4D73N6VY.js.map} +0 -0
  25. /package/dist/{chunk-ZNLJN7YO.js.map → chunk-7FN5GDSB.js.map} +0 -0
  26. /package/dist/{chunk-M23MGGHE.js.map → chunk-OGFMGURJ.js.map} +0 -0
  27. /package/dist/{colony-NE2MZ7RP.js.map → colony-YDOTBQFD.js.map} +0 -0
  28. /package/dist/{decision-engine-RVQRDHEI.js.map → decision-engine-IIA5J2QG.js.map} +0 -0
  29. /package/dist/{heartbeat-4Q344LIV.js.map → heartbeat-Q6GLGCGN.js.map} +0 -0
  30. /package/dist/{init-Z72CJZUC.js.map → init-ULHKJ6XQ.js.map} +0 -0
  31. /package/dist/{prompt-builder-4SWKDPNG.js.map → prompt-builder-3AJUQUVZ.js.map} +0 -0
  32. /package/dist/{queue-T3OYWUII.js.map → queue-LHRG6JOP.js.map} +0 -0
  33. /package/dist/{x-client-5FBD32B2.js.map → x-client-QNLSLY2R.js.map} +0 -0
@@ -388,7 +388,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
388
388
  },
389
389
  async ({ content }) => {
390
390
  try {
391
- const { getXClient } = await import("./x-client-5FBD32B2.js");
391
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
392
392
  const client = await getXClient();
393
393
  const result = await client.postTweet(content);
394
394
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -406,7 +406,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
406
406
  },
407
407
  async ({ tweetId, content }) => {
408
408
  try {
409
- const { getXClient } = await import("./x-client-5FBD32B2.js");
409
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
410
410
  const client = await getXClient();
411
411
  const result = await client.replyToTweet(tweetId, content);
412
412
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -421,7 +421,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
421
421
  { tweetId: z.string().describe("The ID of the tweet to like") },
422
422
  async ({ tweetId }) => {
423
423
  try {
424
- const { getXClient } = await import("./x-client-5FBD32B2.js");
424
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
425
425
  const client = await getXClient();
426
426
  const result = await client.likeTweet(tweetId);
427
427
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -436,7 +436,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
436
436
  { tweetId: z.string().describe("The ID of the tweet to retweet") },
437
437
  async ({ tweetId }) => {
438
438
  try {
439
- const { getXClient } = await import("./x-client-5FBD32B2.js");
439
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
440
440
  const client = await getXClient();
441
441
  const result = await client.retweet(tweetId);
442
442
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -451,7 +451,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
451
451
  { userId: z.string().describe("The user ID to follow") },
452
452
  async ({ userId }) => {
453
453
  try {
454
- const { getXClient } = await import("./x-client-5FBD32B2.js");
454
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
455
455
  const client = await getXClient();
456
456
  const result = await client.followUser(userId);
457
457
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -466,7 +466,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
466
466
  { userId: z.string().describe("The user ID to unfollow") },
467
467
  async ({ userId }) => {
468
468
  try {
469
- const { getXClient } = await import("./x-client-5FBD32B2.js");
469
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
470
470
  const client = await getXClient();
471
471
  const result = await client.unfollowUser(userId);
472
472
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -481,7 +481,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
481
481
  { count: z.number().optional().describe("Number of tweets to fetch (default 20)") },
482
482
  async ({ count }) => {
483
483
  try {
484
- const { getXClient } = await import("./x-client-5FBD32B2.js");
484
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
485
485
  const client = await getXClient();
486
486
  const result = await client.getTimeline({ count: count ?? 20 });
487
487
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -496,7 +496,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
496
496
  { count: z.number().optional().describe("Number of mentions to fetch (default 20)") },
497
497
  async ({ count }) => {
498
498
  try {
499
- const { getXClient } = await import("./x-client-5FBD32B2.js");
499
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
500
500
  const client = await getXClient();
501
501
  const result = await client.getMentions({ count: count ?? 20 });
502
502
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -514,7 +514,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
514
514
  },
515
515
  async ({ query, count }) => {
516
516
  try {
517
- const { getXClient } = await import("./x-client-5FBD32B2.js");
517
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
518
518
  const client = await getXClient();
519
519
  const result = await client.searchTweets(query, { count: count ?? 20 });
520
520
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -529,7 +529,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
529
529
  { handle: z.string().describe("X handle (without @)") },
530
530
  async ({ handle }) => {
531
531
  try {
532
- const { getXClient } = await import("./x-client-5FBD32B2.js");
532
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
533
533
  const client = await getXClient();
534
534
  const result = await client.getProfile(handle);
535
535
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -547,7 +547,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
547
547
  },
548
548
  async ({ content, scheduledFor }) => {
549
549
  try {
550
- const { addToQueue } = await import("./queue-T3OYWUII.js");
550
+ const { addToQueue } = await import("./queue-LHRG6JOP.js");
551
551
  const entry = addToQueue(content, scheduledFor);
552
552
  return {
553
553
  content: [
@@ -565,7 +565,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
565
565
  {},
566
566
  async () => {
567
567
  try {
568
- const { flushQueue } = await import("./queue-T3OYWUII.js");
568
+ const { flushQueue } = await import("./queue-LHRG6JOP.js");
569
569
  const results = await flushQueue();
570
570
  return {
571
571
  content: [
@@ -586,7 +586,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
586
586
  { message: z.string().optional().describe("Optional message to post to the Colony community") },
587
587
  async ({ message }) => {
588
588
  try {
589
- const { colonyCheckin } = await import("./colony-NE2MZ7RP.js");
589
+ const { colonyCheckin } = await import("./colony-YDOTBQFD.js");
590
590
  const result = await colonyCheckin(message);
591
591
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
592
592
  } catch (error) {
@@ -600,7 +600,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
600
600
  {},
601
601
  async () => {
602
602
  try {
603
- const { getColonyMemory } = await import("./colony-NE2MZ7RP.js");
603
+ const { getColonyMemory } = await import("./colony-YDOTBQFD.js");
604
604
  const memory = getColonyMemory();
605
605
  return { content: [{ type: "text", text: JSON.stringify(memory, null, 2) }] };
606
606
  } catch (error) {
@@ -614,7 +614,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
614
614
  {},
615
615
  async () => {
616
616
  try {
617
- const { getActivePlans } = await import("./colony-NE2MZ7RP.js");
617
+ const { getActivePlans } = await import("./colony-YDOTBQFD.js");
618
618
  const plans = getActivePlans();
619
619
  return {
620
620
  content: [
@@ -637,7 +637,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
637
637
  },
638
638
  async ({ description }) => {
639
639
  try {
640
- const { proposePlan } = await import("./colony-NE2MZ7RP.js");
640
+ const { proposePlan } = await import("./colony-YDOTBQFD.js");
641
641
  const result = await proposePlan(description);
642
642
  if (result.success) {
643
643
  return {
@@ -660,7 +660,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
660
660
  },
661
661
  async ({ planId }) => {
662
662
  try {
663
- const { joinPlan } = await import("./colony-NE2MZ7RP.js");
663
+ const { joinPlan } = await import("./colony-YDOTBQFD.js");
664
664
  const result = await joinPlan(planId);
665
665
  if (result.success) {
666
666
  return { content: [{ type: "text", text: "Joined the plan! Go execute it." }] };
@@ -679,7 +679,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
679
679
  },
680
680
  async ({ status }) => {
681
681
  try {
682
- const { postStatus } = await import("./colony-NE2MZ7RP.js");
682
+ const { postStatus } = await import("./colony-YDOTBQFD.js");
683
683
  const result = await postStatus(status);
684
684
  if (result.success) {
685
685
  return { content: [{ type: "text", text: "Status posted to the Colony." }] };
@@ -696,7 +696,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
696
696
  {},
697
697
  async () => {
698
698
  try {
699
- const { getTodaysActivity } = await import("./colony-NE2MZ7RP.js");
699
+ const { getTodaysActivity } = await import("./colony-YDOTBQFD.js");
700
700
  const activity = getTodaysActivity();
701
701
  return {
702
702
  content: [
@@ -3,7 +3,7 @@ import {
3
3
  buildHeartbeatUserMessage,
4
4
  buildNarratedHeartbeatMessage,
5
5
  buildSystemPrompt
6
- } from "./chunk-JKGCF23V.js";
6
+ } from "./chunk-NHNOCAP3.js";
7
7
  import "./chunk-C3INKEY6.js";
8
8
  import "./chunk-RCJQI7FR.js";
9
9
  import "./chunk-AIEXQCQS.js";
@@ -16,4 +16,4 @@ export {
16
16
  buildNarratedHeartbeatMessage,
17
17
  buildSystemPrompt
18
18
  };
19
- //# sourceMappingURL=prompt-builder-4SWKDPNG.js.map
19
+ //# sourceMappingURL=prompt-builder-3AJUQUVZ.js.map
@@ -2,7 +2,7 @@ import {
2
2
  addToQueue,
3
3
  flushQueue,
4
4
  showQueue
5
- } from "./chunk-M23MGGHE.js";
5
+ } from "./chunk-OGFMGURJ.js";
6
6
  import "./chunk-RCJQI7FR.js";
7
7
  import "./chunk-KELPENM3.js";
8
8
  import "./chunk-53YLFYJF.js";
@@ -11,4 +11,4 @@ export {
11
11
  flushQueue,
12
12
  showQueue
13
13
  };
14
- //# sourceMappingURL=queue-T3OYWUII.js.map
14
+ //# sourceMappingURL=queue-LHRG6JOP.js.map
@@ -486,12 +486,11 @@
486
486
  background: #389e77;
487
487
  }
488
488
 
489
- /* Sleep indicator */
489
+ /* Sleep indicator — styled like a chat message */
490
490
  .sleep-indicator {
491
491
  display: none;
492
- flex-direction: column;
493
- align-items: center;
494
- padding: 12px 0 4px;
492
+ gap: 0.625rem;
493
+ padding: 0 1.25rem;
495
494
  animation: fadeIn 0.4s ease-in;
496
495
  }
497
496
 
@@ -499,43 +498,78 @@
499
498
  display: flex;
500
499
  }
501
500
 
501
+ .sleep-avatar {
502
+ width: 32px;
503
+ height: 32px;
504
+ border-radius: 50%;
505
+ background: #1a1a1a;
506
+ display: flex;
507
+ align-items: center;
508
+ justify-content: center;
509
+ flex-shrink: 0;
510
+ overflow: hidden;
511
+ border: 1px solid rgba(255, 255, 255, 0.08);
512
+ }
513
+
514
+ .sleep-avatar img {
515
+ width: 100%;
516
+ height: 100%;
517
+ object-fit: cover;
518
+ }
519
+
520
+ .sleep-bubble {
521
+ display: flex;
522
+ align-items: center;
523
+ gap: 8px;
524
+ background: #141414;
525
+ border: 1px solid rgba(255, 255, 255, 0.06);
526
+ border-radius: 0.75rem;
527
+ border-bottom-left-radius: 0.25rem;
528
+ padding: 0.5rem 0.75rem;
529
+ }
530
+
531
+ .sleep-text {
532
+ font-size: 0.8125rem;
533
+ color: rgba(255, 255, 255, 0.4);
534
+ white-space: nowrap;
535
+ }
536
+
502
537
  .sleep-zzz {
503
538
  display: flex;
504
539
  align-items: baseline;
505
- gap: 2px;
506
- font-size: 1rem;
507
- font-weight: 700;
508
- color: rgba(255, 255, 255, 0.25);
509
- margin-bottom: 4px;
540
+ gap: 1px;
510
541
  }
511
542
 
512
543
  .sleep-zzz span {
513
544
  display: inline-block;
545
+ font-weight: 700;
546
+ color: rgba(255, 255, 255, 0.25);
514
547
  animation: zzzFloat 2s ease-in-out infinite;
515
548
  }
516
549
 
517
550
  .sleep-zzz span:nth-child(1) {
518
- font-size: 0.6rem;
551
+ font-size: 0.5rem;
519
552
  animation-delay: 0s;
520
553
  }
521
554
  .sleep-zzz span:nth-child(2) {
522
- font-size: 0.8rem;
555
+ font-size: 0.65rem;
523
556
  animation-delay: 0.3s;
524
557
  }
525
558
  .sleep-zzz span:nth-child(3) {
526
- font-size: 1rem;
559
+ font-size: 0.8rem;
527
560
  animation-delay: 0.6s;
528
561
  }
529
562
 
530
563
  @keyframes zzzFloat {
531
564
  0%, 100% { opacity: 0.3; transform: translateY(0); }
532
- 50% { opacity: 1; transform: translateY(-4px); }
565
+ 50% { opacity: 1; transform: translateY(-3px); }
533
566
  }
534
567
 
535
568
  .sleep-countdown {
536
569
  font-size: 0.6875rem;
537
570
  color: rgba(255, 255, 255, 0.2);
538
571
  font-variant-numeric: tabular-nums;
572
+ margin-left: 2px;
539
573
  }
540
574
  </style>
541
575
  </head>
@@ -576,10 +610,14 @@
576
610
  </div>
577
611
 
578
612
  <div class="sleep-indicator" id="sleepIndicator">
579
- <div class="sleep-zzz">
580
- <span>z</span><span>z</span><span>z</span>
613
+ <div class="sleep-avatar" id="sleepAvatar"></div>
614
+ <div class="sleep-bubble">
615
+ <span class="sleep-text">Sleeping</span>
616
+ <div class="sleep-zzz">
617
+ <span>z</span><span>z</span><span>z</span>
618
+ </div>
619
+ <span class="sleep-countdown" id="sleepCountdown"></span>
581
620
  </div>
582
- <div class="sleep-countdown" id="sleepCountdown">Sleeping...</div>
583
621
  </div>
584
622
 
585
623
  <div class="input-container">
@@ -1009,18 +1047,33 @@
1009
1047
  let sleepTimer = null;
1010
1048
 
1011
1049
  function formatCountdown(ms) {
1012
- if (ms <= 0) return 'Waking up...';
1050
+ if (ms <= 0) return '';
1013
1051
  const totalSec = Math.ceil(ms / 1000);
1014
1052
  const h = Math.floor(totalSec / 3600);
1015
1053
  const m = Math.floor((totalSec % 3600) / 60);
1016
1054
  const s = totalSec % 60;
1017
- if (h > 0) return `Waking in ${h}h ${m}m ${s}s`;
1018
- if (m > 0) return `Waking in ${m}m ${s}s`;
1019
- return `Waking in ${s}s`;
1055
+ if (h > 0) return `${h}h ${m}m`;
1056
+ if (m > 0) return `${m}m ${s}s`;
1057
+ return `${s}s`;
1020
1058
  }
1021
1059
 
1022
1060
  function startSleepCountdown(wakeAt) {
1023
1061
  stopSleepCountdown();
1062
+
1063
+ // Populate sleep avatar to match agent PFP
1064
+ const sleepAvatarEl = document.getElementById('sleepAvatar');
1065
+ if (sleepAvatarEl && !sleepAvatarEl.hasChildNodes()) {
1066
+ if (agentPfp) {
1067
+ sleepAvatarEl.innerHTML = `<img src="${agentPfp}" alt="${agentName}" />`;
1068
+ } else {
1069
+ sleepAvatarEl.textContent = agentName.charAt(0).toUpperCase();
1070
+ sleepAvatarEl.style.background = '#389e77';
1071
+ sleepAvatarEl.style.color = '#fff';
1072
+ sleepAvatarEl.style.fontSize = '0.75rem';
1073
+ sleepAvatarEl.style.fontWeight = '700';
1074
+ }
1075
+ }
1076
+
1024
1077
  sleepIndicator.classList.add('visible');
1025
1078
  statusDot.style.background = '#71767b';
1026
1079
  statusDot.style.animation = 'none';
@@ -256,7 +256,7 @@ async function extractAndSaveLearnings(responseText) {
256
256
  return responseText.replace(/<<LEARN:\s*.+?>>/g, "").trim();
257
257
  }
258
258
  async function extractAndExecuteActions(responseText) {
259
- const { parseActions, executeActions } = await import("./decision-engine-RVQRDHEI.js");
259
+ const { parseActions, executeActions } = await import("./decision-engine-IIA5J2QG.js");
260
260
  const jsonBlockPattern = /```json\s*([\s\S]*?)```/g;
261
261
  const blocks = [...responseText.matchAll(jsonBlockPattern)];
262
262
  const visibleResults = [];
@@ -336,7 +336,7 @@ async function startWebChat() {
336
336
  try {
337
337
  const { hasXCredentials } = await import("./paths-5GFUUHCZ.js");
338
338
  if (hasXCredentials()) {
339
- const { getXClient } = await import("./x-client-5FBD32B2.js");
339
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
340
340
  const client = await getXClient();
341
341
  if ("getAuthenticatedHandle" in client) {
342
342
  realHandle = await client.getAuthenticatedHandle();
@@ -358,8 +358,9 @@ async function startWebChat() {
358
358
  let messageCount = 0;
359
359
  server.setMessageHandler(async (message) => {
360
360
  try {
361
+ server.setAwake();
361
362
  if (!systemPrompt || messageCount % 10 === 0) {
362
- const { buildChatPrompt } = await import("./prompt-builder-4SWKDPNG.js");
363
+ const { buildChatPrompt } = await import("./prompt-builder-3AJUQUVZ.js");
363
364
  systemPrompt = buildChatPrompt(realHandle);
364
365
  }
365
366
  messageCount++;
@@ -472,7 +473,7 @@ async function startNarratedHeartbeat(server) {
472
473
  await new Promise((r) => setTimeout(r, Math.min(1e4, sleepMs - slept)));
473
474
  slept += 1e4;
474
475
  try {
475
- const { flushQueue } = await import("./queue-T3OYWUII.js");
476
+ const { flushQueue } = await import("./queue-LHRG6JOP.js");
476
477
  const flushed = await flushQueue();
477
478
  if (flushed.posted > 0) {
478
479
  console.log(chalk.green(` [Queue] Posted ${flushed.posted} scheduled tweet(s)`));
@@ -484,11 +485,11 @@ async function startNarratedHeartbeat(server) {
484
485
  }
485
486
  }
486
487
  async function runNarratedHeartbeat(server, maxActions, intervalMs) {
487
- const { getXClient } = await import("./x-client-5FBD32B2.js");
488
- const { buildSystemPrompt, buildNarratedHeartbeatMessage } = await import("./prompt-builder-4SWKDPNG.js");
488
+ const { getXClient } = await import("./x-client-QNLSLY2R.js");
489
+ const { buildSystemPrompt, buildNarratedHeartbeatMessage } = await import("./prompt-builder-3AJUQUVZ.js");
489
490
  const { generateResponse } = await import("./llm-WLEJLNEA.js");
490
- const { parseActions, executeActions } = await import("./decision-engine-RVQRDHEI.js");
491
- const { flushQueue } = await import("./queue-T3OYWUII.js");
491
+ const { parseActions, executeActions } = await import("./decision-engine-IIA5J2QG.js");
492
+ const { flushQueue } = await import("./queue-LHRG6JOP.js");
492
493
  try {
493
494
  const flushed = await flushQueue();
494
495
  if (flushed.posted > 0) {
@@ -539,8 +540,14 @@ async function runNarratedHeartbeat(server, maxActions, intervalMs) {
539
540
  if (r.action === "schedule") {
540
541
  console.log(chalk.dim(` [Heartbeat] ${r.action}: ${r.success ? "queued" : r.error}${detail}`));
541
542
  } else if (r.success) {
542
- visibleResults.push(`[${r.action}] Done${detail}`);
543
- console.log(chalk.green(` [Heartbeat] ${r.action}: success${detail}`));
543
+ if ((r.action === "post" || r.action === "reply") && r.content) {
544
+ const tweetData = JSON.stringify({ action: r.action, tweetId: r.detail ?? null, content: r.content });
545
+ visibleResults.push(`<<TWEET:${tweetData}>>`);
546
+ console.log(chalk.green(` [Heartbeat] ${r.action}: success (tweet preview)`));
547
+ } else {
548
+ visibleResults.push(`[${r.action}] Done${detail}`);
549
+ console.log(chalk.green(` [Heartbeat] ${r.action}: success${detail}`));
550
+ }
544
551
  } else {
545
552
  visibleResults.push(`[${r.action}] Failed: ${r.error}`);
546
553
  console.log(chalk.red(` [Heartbeat] ${r.action}: ${r.error}`));
@@ -592,4 +599,4 @@ export {
592
599
  openBrowser,
593
600
  startWebChat
594
601
  };
595
- //# sourceMappingURL=web-chat-7SSETYWK.js.map
602
+ //# sourceMappingURL=web-chat-6ZRJE73E.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 } 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 AgentIdentity {\n name: string;\n handle: string;\n bio?: string;\n profileImage?: string;\n createdAt?: string;\n}\n\nexport class WebChatServer {\n private server: http.Server | null = null;\n private port: number;\n private messages: ChatMessage[] = [];\n private onUserMessage?: (message: string) => Promise<string>;\n private onHeartbeatChange?: (intervalMs: number) => void;\n private identity?: AgentIdentity;\n private sleepState: { sleeping: boolean; wakeAt: number | null; intervalMs: number } = {\n sleeping: false,\n wakeAt: null,\n intervalMs: 60_000,\n };\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 setHeartbeatChangeHandler(handler: (intervalMs: number) => void) {\n this.onHeartbeatChange = handler;\n }\n\n setSleeping(wakeAt: number) {\n this.sleepState.sleeping = true;\n this.sleepState.wakeAt = wakeAt;\n }\n\n setAwake() {\n this.sleepState.sleeping = false;\n this.sleepState.wakeAt = null;\n }\n\n setHeartbeatInterval(intervalMs: number) {\n this.sleepState.intervalMs = intervalMs;\n }\n\n getHeartbeatInterval(): number {\n return this.sleepState.intervalMs;\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 // Try multiple paths since __dirname changes based on build output\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 // Serve logo\n if (url.pathname === \"/logo.png\") {\n try {\n const logoPaths = [\n join(__dirname, \"web-chat\", \"logo.png\"),\n join(__dirname, \"logo.png\"),\n join(__dirname, \"..\", \"web-chat\", \"logo.png\"),\n join(__dirname, \"..\", \"src\", \"web-chat\", \"logo.png\"),\n ];\n let logoData: Buffer | null = null;\n for (const p of logoPaths) {\n try {\n logoData = readFileSync(p) as unknown as Buffer;\n break;\n } catch {\n continue;\n }\n }\n if (logoData) {\n res.writeHead(200, { \"Content-Type\": \"image/png\", \"Cache-Control\": \"public, max-age=86400\" });\n res.end(logoData);\n } else {\n res.writeHead(404);\n res.end(\"Logo not found\");\n }\n } catch {\n res.writeHead(404);\n res.end(\"Logo not found\");\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 // API: Get messages (optionally since a timestamp)\n if (url.pathname === \"/api/messages\" && req.method === \"GET\") {\n const since = url.searchParams.get(\"since\");\n if (since) {\n const sinceTs = parseInt(since, 10);\n const newMessages = this.messages.filter((m) => m.timestamp > sinceTs);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ messages: newMessages }));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ messages: this.messages }));\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 // API: Get sleep state\n if (url.pathname === \"/api/sleep-state\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(this.sleepState));\n return;\n }\n\n // API: Update heartbeat interval\n if (url.pathname === \"/api/config/heartbeat\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk.toString(); });\n req.on(\"end\", () => {\n try {\n const { intervalMs } = JSON.parse(body);\n if (typeof intervalMs === \"number\" && intervalMs >= 60_000) {\n this.sleepState.intervalMs = intervalMs;\n if (this.onHeartbeatChange) {\n this.onHeartbeatChange(intervalMs);\n }\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true, intervalMs }));\n } else {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid interval\" }));\n }\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 // 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 // Port in use, try next port\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 /**\n * Push a message into the chat from the server side (used by heartbeat narration)\n */\n pushMessage(role: \"user\" | \"assistant\", content: string) {\n this.messages.push({ role, content, timestamp: Date.now() });\n }\n\n getMessageCount(): number {\n return this.messages.length;\n }\n\n stop() {\n if (this.server) {\n this.server.close();\n this.server = null;\n }\n }\n}\n","/**\n * Web chat integration\n */\n\nimport { WebChatServer } from \"./server.js\";\nimport { loadIdentity } from \"../identity/index.js\";\nimport { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\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 JSON action blocks from the LLM response, execute them, and return cleaned text + results.\n * Search results are returned separately so the caller can feed them back to the LLM.\n */\nasync function extractAndExecuteActions(responseText: string): Promise<{\n cleanText: string;\n results: string[];\n searchResults: string | null;\n}> {\n const { parseActions, executeActions } = await import(\"../runtime/decision-engine.js\");\n\n // Try to find JSON code blocks in the response\n const jsonBlockPattern = /```json\\s*([\\s\\S]*?)```/g;\n const blocks = [...responseText.matchAll(jsonBlockPattern)];\n const visibleResults: string[] = [];\n let searchResults: string | null = null;\n\n const processResults = (actionResults: Awaited<ReturnType<typeof executeActions>>) => {\n for (const r of actionResults) {\n if (r.action === \"search\") {\n // Search results are internal — don't show in chat\n if (r.success && r.detail && r.detail !== \"No results found\") {\n searchResults = r.detail;\n console.log(chalk.green(` [Actions] search: found results (internal)`));\n } else {\n console.log(chalk.dim(` [Actions] search: ${r.detail ?? r.error ?? \"no results\"}`));\n }\n } else if (r.success) {\n // For post/reply/schedule with content, emit a tweet preview tag\n if ((r.action === \"post\" || r.action === \"reply\" || r.action === \"schedule\") && r.content) {\n const tweetData = JSON.stringify({ action: r.action, tweetId: r.detail ?? null, content: r.content });\n visibleResults.push(`<<TWEET:${tweetData}>>`);\n console.log(chalk.green(` [Actions] ${r.action}: success (tweet preview)`));\n } else {\n const detail = r.detail ? ` (${r.detail})` : \"\";\n visibleResults.push(`[${r.action}] Done${detail}`);\n console.log(chalk.green(` [Actions] ${r.action}: success${detail}`));\n }\n } else {\n visibleResults.push(`[${r.action}] Failed: ${r.error}`);\n console.log(chalk.red(` [Actions] ${r.action}: ${r.error}`));\n }\n }\n };\n\n if (blocks.length > 0) {\n for (const block of blocks) {\n const actions = parseActions(block[1]);\n if (actions.length > 0) {\n console.log(chalk.cyan(` [Actions] Executing ${actions.length} action(s)...`));\n const actionResults = await executeActions(actions);\n processResults(actionResults);\n }\n }\n } else {\n // Also try raw JSON (no code block wrapper)\n const actions = parseActions(responseText);\n if (actions.length > 0 && actions[0].action) {\n console.log(chalk.cyan(` [Actions] Executing ${actions.length} action(s)...`));\n const actionResults = await executeActions(actions);\n processResults(actionResults);\n }\n }\n\n // Strip JSON code blocks from the user-visible text\n let cleanText = responseText.replace(/```json\\s*[\\s\\S]*?```/g, \"\").trim();\n // Clean up extra whitespace from removal\n cleanText = cleanText.replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n // Append visible action results (search excluded)\n if (visibleResults.length > 0) {\n cleanText += \"\\n\\n\" + visibleResults.join(\"\\n\");\n }\n\n return { cleanText, results: visibleResults, searchResults };\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\nexport async function startWebChat() {\n const identity = loadIdentity();\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 createdAt: identity.createdAt,\n });\n\n // Resolve real Twitter handle on startup\n let realHandle: string | undefined;\n try {\n const { hasXCredentials } = await import(\"../utils/paths.js\");\n if (hasXCredentials()) {\n const { getXClient } = await import(\"../x-client/index.js\");\n const client = await getXClient();\n if (\"getAuthenticatedHandle\" in client) {\n realHandle = await (client as any).getAuthenticatedHandle();\n console.log(chalk.dim(` [Auth] Real Twitter handle: @${realHandle}`));\n // Update the server identity to show the real handle\n server.setIdentity({\n name: identity.name,\n handle: realHandle,\n bio: identity.bio,\n profileImage: identity.profileImage,\n createdAt: identity.createdAt,\n });\n }\n }\n } catch (err) {\n console.log(chalk.dim(` [Auth] Could not resolve real handle: ${(err as Error).message}`));\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 // Manual message = instant wake — clear sleep indicator\n server.setAwake();\n\n // Build system prompt on first message, rebuild every 10 messages to refresh memory\n if (!systemPrompt || messageCount % 10 === 0) {\n const { buildChatPrompt } = await import(\"../runtime/prompt-builder.js\");\n systemPrompt = buildChatPrompt(realHandle);\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 set-llm-key` 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 and execute any actions (posts, likes, etc.) from the response\n let { cleanText: actionCleanedText, searchResults } = await extractAndExecuteActions(response.content);\n\n // Two-pass: if search returned results, feed them back to the LLM so it can act on them\n if (searchResults) {\n console.log(chalk.cyan(\" [Chat] Search returned results — doing follow-up LLM call...\"));\n\n // Add the first response (narration) to history as context\n const firstNarration = actionCleanedText.replace(/```json\\s*[\\s\\S]*?```/g, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n if (firstNarration) {\n chatHistory.push({ role: \"assistant\", content: firstNarration });\n }\n\n // Inject search results as a system-style user message the LLM can act on\n chatHistory.push({\n role: \"user\",\n content: `[Internal: here are the search results you requested. Pick one or more to act on (reply, like, etc.) and do it now. Do NOT show these results to me — just act on them and tell me what you did.]\\n\\n${searchResults}`,\n });\n\n // Second LLM call — it now has search results and can reply/like\n const followUp = await chatLLM(systemPrompt, chatHistory);\n const followUpResult = await extractAndExecuteActions(followUp.content);\n actionCleanedText = followUpResult.cleanText;\n\n // Remove the internal search-results message from visible history\n chatHistory.pop();\n if (firstNarration) {\n chatHistory.pop();\n }\n }\n\n // Extract learnings from response and clean the text\n const cleanResponse = await extractAndSaveLearnings(actionCleanedText);\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 background heartbeat (narrates into chat)\n startNarratedHeartbeat(server).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 heartbeatRunning = false;\n server.stop();\n process.exit(0);\n });\n\n // Return the server instance for external control\n return server;\n}\n\n/**\n * Background heartbeat that narrates into the chat UI\n */\nlet heartbeatRunning = false;\nlet lastTimelineSinceId: string | undefined;\nlet lastMentionsSinceId: string | undefined;\n\nasync function startNarratedHeartbeat(server: WebChatServer) {\n const { loadConfig } = await import(\"../utils/config.js\");\n const { hasLLMKey, generateResponse } = await import(\"../runtime/llm.js\");\n const { hasXCredentials } = await import(\"../utils/paths.js\");\n\n if (!hasLLMKey()) {\n console.log(chalk.dim(\" [Heartbeat] No LLM key — heartbeat disabled.\"));\n return;\n }\n\n if (!hasXCredentials()) {\n console.log(chalk.dim(\" [Heartbeat] No X credentials — heartbeat disabled.\"));\n return;\n }\n\n const config = loadConfig();\n let intervalMs = config.runtime?.heartbeatIntervalMs ?? 60_000;\n const maxActions = config.runtime?.actionsPerHeartbeat ?? 10;\n\n // Set initial heartbeat interval on server and listen for UI changes\n server.setHeartbeatInterval(intervalMs);\n server.setHeartbeatChangeHandler((newIntervalMs: number) => {\n intervalMs = newIntervalMs;\n console.log(chalk.cyan(` [Heartbeat] Interval changed to ${Math.round(newIntervalMs / 1000)}s`));\n // Persist to config (async, fire-and-forget)\n import(\"../utils/config.js\").then(({ loadConfig: reloadConfig, saveConfig }) => {\n const cfg = reloadConfig();\n if (!cfg.runtime) cfg.runtime = {};\n cfg.runtime.heartbeatIntervalMs = newIntervalMs;\n saveConfig(cfg);\n }).catch(() => {\n // Config save failed, not critical — will use new interval in memory\n });\n });\n\n heartbeatRunning = true;\n let heartbeatCount = 0;\n\n console.log(chalk.cyan(` [Heartbeat] Running every ${Math.round(intervalMs / 1000)}s\\n`));\n\n while (heartbeatRunning) {\n heartbeatCount++;\n console.log(chalk.cyan(` [Heartbeat] #${heartbeatCount} starting...`));\n server.setAwake();\n\n try {\n await runNarratedHeartbeat(server, maxActions, intervalMs);\n } catch (error) {\n console.error(chalk.red(` [Heartbeat] #${heartbeatCount} failed:`), (error as Error).message);\n server.pushMessage(\"assistant\", `Heartbeat error: ${(error as Error).message}`);\n }\n\n // Sleep with jitter — flush queue during sleep so scheduled posts go out on time\n const jitter = Math.floor(Math.random() * intervalMs * 0.2);\n const sleepMs = intervalMs + jitter;\n console.log(chalk.dim(` [Heartbeat] Sleeping ${Math.round(sleepMs / 1000)}s...`));\n\n // Tell the server we're sleeping so the UI can show the indicator\n server.setSleeping(Date.now() + sleepMs);\n\n let slept = 0;\n while (slept < sleepMs && heartbeatRunning) {\n await new Promise((r) => setTimeout(r, Math.min(10000, sleepMs - slept)));\n slept += 10000;\n\n // Flush queue during sleep so scheduled posts go out at their intended times\n try {\n const { flushQueue } = await import(\"../scheduler/queue.js\");\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n console.log(chalk.green(` [Queue] Posted ${flushed.posted} scheduled tweet(s)`));\n }\n } catch {\n // Queue flush failed during sleep, not critical\n }\n }\n\n server.setAwake();\n }\n}\n\nasync function runNarratedHeartbeat(server: WebChatServer, maxActions: number, intervalMs: number) {\n const { getXClient } = await import(\"../x-client/index.js\");\n const { buildSystemPrompt, buildNarratedHeartbeatMessage } = await import(\"../runtime/prompt-builder.js\");\n const { generateResponse } = await import(\"../runtime/llm.js\");\n const { parseActions, executeActions } = await import(\"../runtime/decision-engine.js\");\n const { flushQueue } = await import(\"../scheduler/queue.js\");\n\n // 1. Flush queued posts (silent — user doesn't see this)\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n console.log(chalk.green(` [Queue] Flushed ${flushed.posted} scheduled tweet(s)`));\n }\n } catch {\n // Queue flush failed, not critical\n }\n\n // 2. Read timeline and mentions (only new since last check)\n\n const client = await getXClient();\n let timeline: Awaited<ReturnType<typeof client.getTimeline>> = [];\n let mentions: Awaited<ReturnType<typeof client.getMentions>> = [];\n\n // Get the bot's own handle to filter own tweets from timeline\n let ownHandle: string | undefined;\n if (\"getAuthenticatedHandle\" in client) {\n try {\n ownHandle = await (client as any).getAuthenticatedHandle();\n } catch {}\n }\n\n try {\n timeline = await client.getTimeline({ count: 20, sinceId: lastTimelineSinceId });\n if (timeline.length > 0) {\n lastTimelineSinceId = timeline[0].id;\n // Filter out own tweets (can't like your own)\n if (ownHandle) {\n timeline = timeline.filter(t => t.authorHandle.toLowerCase() !== ownHandle!.toLowerCase());\n }\n }\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Timeline read failed: ${(error as Error).message}`));\n }\n\n try {\n mentions = await client.getMentions({ count: 10, sinceId: lastMentionsSinceId });\n if (mentions.length > 0) {\n lastMentionsSinceId = mentions[0].id;\n }\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Mentions read failed: ${(error as Error).message}`));\n }\n\n // 3. Build prompts and ask LLM\n const systemPrompt = buildSystemPrompt();\n const userMessage = buildNarratedHeartbeatMessage(timeline, mentions, intervalMs);\n\n const response = await generateResponse(systemPrompt, userMessage);\n\n // 4. Extract narration text (everything outside JSON blocks)\n let narration = response.content.replace(/```json\\s*[\\s\\S]*?```/g, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n // 5. Parse and execute actions\n const actions = parseActions(response.content);\n const limitedActions = actions.slice(0, maxActions);\n\n if (limitedActions.length > 0) {\n // Execute actions\n const results = await executeActions(limitedActions);\n\n // Build visible results — use tweet preview tags for post/reply\n const visibleResults: string[] = [];\n for (const r of results) {\n const detail = r.detail ? ` (${r.detail})` : \"\";\n if (r.action === \"schedule\") {\n // Log to terminal only, don't show in chat\n console.log(chalk.dim(` [Heartbeat] ${r.action}: ${r.success ? \"queued\" : r.error}${detail}`));\n } else if (r.success) {\n // Emit tweet preview card for post/reply with content\n if ((r.action === \"post\" || r.action === \"reply\") && r.content) {\n const tweetData = JSON.stringify({ action: r.action, tweetId: r.detail ?? null, content: r.content });\n visibleResults.push(`<<TWEET:${tweetData}>>`);\n console.log(chalk.green(` [Heartbeat] ${r.action}: success (tweet preview)`));\n } else {\n visibleResults.push(`[${r.action}] Done${detail}`);\n console.log(chalk.green(` [Heartbeat] ${r.action}: success${detail}`));\n }\n } else {\n visibleResults.push(`[${r.action}] Failed: ${r.error}`);\n console.log(chalk.red(` [Heartbeat] ${r.action}: ${r.error}`));\n }\n }\n\n // Combine narration with visible action results\n let fullMessage = narration || \"\";\n if (visibleResults.length > 0) {\n fullMessage += (fullMessage ? \"\\n\\n\" : \"\") + visibleResults.join(\"\\n\");\n }\n if (fullMessage) {\n server.pushMessage(\"assistant\", fullMessage);\n }\n } else if (narration) {\n server.pushMessage(\"assistant\", narration);\n }\n\n console.log(chalk.cyan(` [Heartbeat] Complete.`));\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=420,620`, { 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=420,620`, { 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=420,620`, { 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=420,620`, { 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":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getXClient,
3
3
  resetXClient
4
- } from "./chunk-NJHDY4ON.js";
4
+ } from "./chunk-4D73N6VY.js";
5
5
  import "./chunk-RCJQI7FR.js";
6
6
  import "./chunk-KELPENM3.js";
7
7
  import "./chunk-53YLFYJF.js";
@@ -9,4 +9,4 @@ export {
9
9
  getXClient,
10
10
  resetXClient
11
11
  };
12
- //# sourceMappingURL=x-client-5FBD32B2.js.map
12
+ //# sourceMappingURL=x-client-QNLSLY2R.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spora",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
4
4
  "description": "AI agents (Spores) that autonomously manage X/Twitter accounts",
5
5
  "type": "module",
6
6
  "author": "Spora",