alive-ai 0.1.0

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 (168) hide show
  1. package/Dockerfile +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/alive_ai/__init__.py +3 -0
  5. package/brain/__init__.py +59 -0
  6. package/brain/almost_said.py +154 -0
  7. package/brain/bid_detector.py +636 -0
  8. package/brain/conversation_flow.py +135 -0
  9. package/brain/curiosity.py +328 -0
  10. package/brain/default_mode.py +1438 -0
  11. package/brain/dreams.py +220 -0
  12. package/brain/embeddings/__init__.py +82 -0
  13. package/brain/emotional_memory.py +949 -0
  14. package/brain/global_activity.py +173 -0
  15. package/brain/group_dynamics.py +63 -0
  16. package/brain/linguistic.py +235 -0
  17. package/brain/llm/__init__.py +63 -0
  18. package/brain/llm/base.py +33 -0
  19. package/brain/llm/fallback_router.py +309 -0
  20. package/brain/llm/manifest.md +30 -0
  21. package/brain/llm/ollama.py +218 -0
  22. package/brain/llm/openrouter.py +151 -0
  23. package/brain/llm/provider.py +205 -0
  24. package/brain/llm/unified.py +423 -0
  25. package/brain/llm/zai.py +169 -0
  26. package/brain/manifest.md +23 -0
  27. package/brain/memory/__init__.py +123 -0
  28. package/brain/memory/episodic.py +92 -0
  29. package/brain/memory/fact_extractor.py +209 -0
  30. package/brain/memory/index.py +54 -0
  31. package/brain/memory/manager.py +151 -0
  32. package/brain/memory/summarizer.py +102 -0
  33. package/brain/memory/vector_store.py +297 -0
  34. package/brain/memory/working.py +43 -0
  35. package/brain/narrative.py +343 -0
  36. package/brain/stt/__init__.py +4 -0
  37. package/brain/stt/google_stt.py +83 -0
  38. package/brain/stt/whisper_stt.py +82 -0
  39. package/brain/subconscious/__init__.py +33 -0
  40. package/brain/subconscious/actions.py +136 -0
  41. package/brain/subconscious/evaluation.py +166 -0
  42. package/brain/subconscious/goal_system.py +90 -0
  43. package/brain/subconscious/goals.py +41 -0
  44. package/brain/subconscious/impulse_generator.py +200 -0
  45. package/brain/subconscious/impulses.py +48 -0
  46. package/brain/subconscious/learning.py +24 -0
  47. package/brain/subconscious/learning_system.py +79 -0
  48. package/brain/subconscious/loop.py +398 -0
  49. package/brain/subconscious/manifest.md +32 -0
  50. package/brain/subconscious/relationship.py +47 -0
  51. package/brain/subconscious/relationship_memory.py +83 -0
  52. package/brain/subconscious/response_analyzer.py +74 -0
  53. package/brain/subconscious/templates.py +70 -0
  54. package/brain/subconscious/thought.py +37 -0
  55. package/brain/subconscious/working_memory.py +97 -0
  56. package/cli/index.js +371 -0
  57. package/config/directives.example.json +28 -0
  58. package/config/instructions.example.md +16 -0
  59. package/config/self.example.json +74 -0
  60. package/config/settings.example.json +95 -0
  61. package/core/__init__.py +1 -0
  62. package/core/config.py +54 -0
  63. package/core/directives.py +198 -0
  64. package/core/events.py +50 -0
  65. package/core/follow_up.py +267 -0
  66. package/core/hot_reload.py +174 -0
  67. package/core/initialization.py +253 -0
  68. package/core/manifest.md +28 -0
  69. package/core/media_handler.py +241 -0
  70. package/core/memory_monitor.py +200 -0
  71. package/core/message_handler.py +1440 -0
  72. package/core/proactive_generator.py +277 -0
  73. package/core/self.py +188 -0
  74. package/core/settings.py +169 -0
  75. package/core/skills_registry.py +357 -0
  76. package/core/state.py +27 -0
  77. package/core/subconscious_bridge.py +93 -0
  78. package/core/thinking.py +175 -0
  79. package/core/user_manager.py +306 -0
  80. package/core/user_tracker.py +144 -0
  81. package/demo/index.html +144 -0
  82. package/docker-compose.yml +28 -0
  83. package/docs/assets/logo.svg +15 -0
  84. package/docs/index.html +355 -0
  85. package/heart/__init__.py +93 -0
  86. package/heart/afterglow.py +215 -0
  87. package/heart/attachment.py +186 -0
  88. package/heart/circadian.py +251 -0
  89. package/heart/complex_emotions.py +114 -0
  90. package/heart/conflicts.py +589 -0
  91. package/heart/core.py +387 -0
  92. package/heart/emotional_decay.py +59 -0
  93. package/heart/emotional_memory.py +261 -0
  94. package/heart/emotional_state.py +146 -0
  95. package/heart/emotional_variability.py +156 -0
  96. package/heart/hormonal.py +424 -0
  97. package/heart/inconsistency.py +1222 -0
  98. package/heart/integrity.py +469 -0
  99. package/heart/interoception.py +997 -0
  100. package/heart/love.py +120 -0
  101. package/heart/manifest.md +25 -0
  102. package/heart/mood_shifts.py +169 -0
  103. package/heart/phantom_somatic.py +259 -0
  104. package/heart/predictive.py +374 -0
  105. package/heart/scars.py +474 -0
  106. package/heart/somatic.py +482 -0
  107. package/heart/soul.py +633 -0
  108. package/heart/telemetry.py +942 -0
  109. package/heart/triggers.py +119 -0
  110. package/heart/unconscious.py +443 -0
  111. package/input/__init__.py +1 -0
  112. package/input/manifest.md +24 -0
  113. package/input/telegram/__init__.py +1 -0
  114. package/input/telegram/commands.py +762 -0
  115. package/input/telegram/listener.py +532 -0
  116. package/main.py +90 -0
  117. package/manifest.md +28 -0
  118. package/mypics/.gitkeep +1 -0
  119. package/myvids/.gitkeep +1 -0
  120. package/output/__init__.py +1 -0
  121. package/output/images/__init__.py +1 -0
  122. package/output/images/fal_gen.py +43 -0
  123. package/output/manifest.md +26 -0
  124. package/output/text/__init__.py +1 -0
  125. package/output/text/sender.py +22 -0
  126. package/output/voice/__init__.py +64 -0
  127. package/output/voice/google_tts.py +252 -0
  128. package/output/voice/gtts_tts.py +214 -0
  129. package/output/voice/vibe_tts.py +190 -0
  130. package/package.json +58 -0
  131. package/pyproject.toml +23 -0
  132. package/requirements.txt +21 -0
  133. package/skills/__init__.py +1 -0
  134. package/skills/anticipation_engine/__init__.py +8 -0
  135. package/skills/anticipation_engine/engine.py +618 -0
  136. package/skills/anticipation_engine/manifest.md +192 -0
  137. package/skills/calendar/__init__.py +1 -0
  138. package/skills/content_unlocks/__init__.py +8 -0
  139. package/skills/content_unlocks/manifest.md +231 -0
  140. package/skills/content_unlocks/unlocks.py +945 -0
  141. package/skills/exclusive_moments/__init__.py +8 -0
  142. package/skills/exclusive_moments/manifest.md +145 -0
  143. package/skills/exclusive_moments/moments.py +506 -0
  144. package/skills/intimacy_layers/__init__.py +8 -0
  145. package/skills/intimacy_layers/layers.py +703 -0
  146. package/skills/intimacy_layers/manifest.md +203 -0
  147. package/skills/manifest.md +67 -0
  148. package/skills/memory_callbacks/__init__.py +9 -0
  149. package/skills/memory_callbacks/callbacks.py +748 -0
  150. package/skills/memory_callbacks/manifest.md +170 -0
  151. package/skills/message_scheduler/__init__.py +19 -0
  152. package/skills/message_scheduler/manifest.md +107 -0
  153. package/skills/message_scheduler/scheduler.py +510 -0
  154. package/skills/photo_manager/__init__.py +1 -0
  155. package/skills/photo_manager/scanner.py +296 -0
  156. package/skills/relationship_milestones/__init__.py +8 -0
  157. package/skills/relationship_milestones/manifest.md +206 -0
  158. package/skills/relationship_milestones/tracker.py +494 -0
  159. package/skills/self_authorship/__init__.py +23 -0
  160. package/skills/self_authorship/author.py +331 -0
  161. package/skills/self_authorship/manifest.md +24 -0
  162. package/skills/video_manager/__init__.py +5 -0
  163. package/skills/video_manager/manifest.md +37 -0
  164. package/skills/video_manager/scanner.py +229 -0
  165. package/webui/__init__.py +3 -0
  166. package/webui/app.py +936 -0
  167. package/webui/bridge.py +366 -0
  168. package/webui/static/index.html +2070 -0
@@ -0,0 +1,532 @@
1
+ """
2
+ Input: Telegram Listener
3
+ Listen for Telegram messages and send reactions, voice, images
4
+ """
5
+
6
+ import asyncio
7
+ import os
8
+ from pathlib import Path
9
+ from telegram import Update, InputFile, ReactionTypeEmoji
10
+ from telegram.ext import Application, MessageHandler, filters
11
+ from telegram.constants import ChatAction
12
+ from .commands import CommandHandler
13
+ from brain.group_dynamics import GroupDynamics
14
+
15
+ # Ensure UPLOAD_VIDEO is available (might not be in older versions)
16
+ if not hasattr(ChatAction, 'UPLOAD_VIDEO'):
17
+ ChatAction.UPLOAD_VIDEO = "upload_video"
18
+
19
+ class TelegramListener:
20
+ """Telegram message listener with full send capabilities"""
21
+
22
+ def __init__(self, nervous, config, stt=None, heart=None):
23
+ self.nervous = nervous
24
+ self.config = config
25
+ self.stt = stt # Speech-to-text
26
+ self.heart = heart # Emotional system
27
+ self.app = None
28
+ self.chat_id = None
29
+ self.user_id = None
30
+ self.last_message_id = None
31
+
32
+ # Command handler (initialized later with dependencies)
33
+ self.commands = None
34
+
35
+ # Register all send handlers
36
+ nervous.on("send_reaction", self._send_reaction)
37
+ nervous.on("send_text", self._send_text)
38
+ nervous.on("send_voice_file", self._send_voice_file)
39
+ nervous.on("send_image", self._send_image)
40
+ nervous.on("send_video", self._send_video)
41
+
42
+ # Chat action handlers
43
+ nervous.on("chat_action_typing", self._send_typing)
44
+ nervous.on("chat_action_voice", self._send_recording_voice)
45
+ nervous.on("chat_action_photo", self._send_uploading_photo)
46
+ nervous.on("chat_action_video", self._send_uploading_video)
47
+
48
+ # Autonomous content handler
49
+ nervous.on("proactive_message", self._send_proactive_message)
50
+ # Default mode initiations (pending initiations that need sending)
51
+ nervous.on("proactive_message_ready", self._send_default_mode_initiation)
52
+
53
+ def init_commands(self, heart, subconscious, llm, voice, photos, videos, ai=None):
54
+ """Initialize command handler with dependencies"""
55
+ self.commands = CommandHandler(
56
+ self.nervous, heart, subconscious, llm, voice, photos, videos
57
+ )
58
+ # Initialize owner commands if ai reference provided
59
+ if ai:
60
+ self.commands.init_owner_commands(ai)
61
+
62
+ async def start(self):
63
+ """Start listening - blocks forever"""
64
+ # First check environment variable (from secrets.env), then settings
65
+ token = os.environ.get("TELEGRAM_TOKEN") or self.config.settings.get("telegram_token")
66
+
67
+ if not token:
68
+ print("[Telegram] No token configured, skipping...")
69
+ # Keep running even without telegram
70
+ await asyncio.Event().wait()
71
+ return
72
+
73
+ print(f"[Telegram] Connecting with token ...{token[-6:]}")
74
+ self.app = Application.builder().token(token).build()
75
+ self.app.add_handler(MessageHandler(filters.ALL, self._on_message))
76
+
77
+ await self.app.initialize()
78
+ await self.app.start()
79
+
80
+ # Register commands with Telegram (shows in menu)
81
+ from telegram import BotCommand, BotCommandScopeAllPrivateChats, BotCommandScopeChat
82
+
83
+ # Public commands - visible to everyone
84
+ public_commands = [
85
+ BotCommand("start", "Welcome message"),
86
+ BotCommand("help", "Show commands"),
87
+ ]
88
+
89
+ # Set public commands for all users
90
+ await self.app.bot.set_my_commands(public_commands)
91
+
92
+ # Set owner commands if owner ID is configured
93
+ owner_id = os.environ.get("TELEGRAM_OWNER_ID", "")
94
+ if owner_id:
95
+ try:
96
+ # Full owner command list for the owner's menu
97
+ owner_commands = [
98
+ # Basics
99
+ BotCommand("start", "Welcome"),
100
+ BotCommand("help", "Show commands"),
101
+ BotCommand("owner", "Owner menu"),
102
+ BotCommand("dashboard", "Open WebUI"),
103
+ # Status & Stats
104
+ BotCommand("status", "Emotional state"),
105
+ BotCommand("stats", "System statistics"),
106
+ BotCommand("owner_status", "Business status"),
107
+ BotCommand("skills", "Available skills"),
108
+ # Control
109
+ BotCommand("reset", "Reset emotions"),
110
+ BotCommand("settings", "Runtime settings"),
111
+ BotCommand("advanced", "Advanced mode"),
112
+ BotCommand("impulse", "Force proactive msg"),
113
+ # Self-Authorship
114
+ BotCommand("self", "Who I am"),
115
+ BotCommand("discover", "Learn about myself"),
116
+ BotCommand("iam", "Define who I am"),
117
+ BotCommand("ilike", "Add something I like"),
118
+ BotCommand("ihate", "Add something I dislike"),
119
+ BotCommand("rethink", "Feel my changes"),
120
+ # Content Calendar
121
+ BotCommand("schedule", "Scheduled posts"),
122
+ BotCommand("schedule_add", "Add to schedule"),
123
+ BotCommand("optimal_times", "Best posting times"),
124
+ # Weekly Calendar
125
+ BotCommand("weekly", "Weekly calendar menu"),
126
+ BotCommand("weekly_start", "Start new week"),
127
+ BotCommand("weekly_next", "Generate next post"),
128
+ BotCommand("weekly_status", "Week progress"),
129
+ # Image Prompts
130
+ BotCommand("prompt", "Generate FLUX prompt"),
131
+ BotCommand("prompt_platform", "Platform suggestions"),
132
+ BotCommand("caption", "Generate caption"),
133
+ BotCommand("hashtags", "Hashtag suggestions"),
134
+ # Content Vault
135
+ BotCommand("vault", "Content vault"),
136
+ BotCommand("vault_add", "Add to vault"),
137
+ BotCommand("vault_suggest", "Get suggestion"),
138
+ # Testing
139
+ BotCommand("10min", "Long voice test"),
140
+ ]
141
+ # Set owner-specific commands (only visible to owner)
142
+ await self.app.bot.set_my_commands(
143
+ owner_commands,
144
+ scope=BotCommandScopeChat(chat_id=int(owner_id))
145
+ )
146
+ print(f"[Telegram] Owner commands registered for user {owner_id}")
147
+ except Exception as e:
148
+ print(f"[Telegram] Could not set owner commands: {e}")
149
+
150
+ print("[Telegram] Commands registered")
151
+
152
+ print("[Telegram] Connected and listening...")
153
+
154
+ await self.app.updater.start_polling()
155
+
156
+ # Block forever - keep the bot alive
157
+ await asyncio.Event().wait()
158
+
159
+ async def _on_message(self, update: Update, context):
160
+ """Handle incoming message"""
161
+ if not update.message:
162
+ return
163
+
164
+ # Store per-user state to avoid race conditions with multiple users
165
+ chat_id = update.message.chat_id
166
+ user_id = update.effective_user.id if update.effective_user else None
167
+ # Keep instance vars for backwards compat (reactions, typing, etc.)
168
+ self.chat_id = chat_id
169
+ self.user_id = user_id
170
+ self.last_message_id = update.message.message_id
171
+
172
+ # Check for commands first
173
+ if update.message.text and update.message.text.startswith("/"):
174
+ if self.commands:
175
+ parts = update.message.text[1:].split()
176
+ cmd = parts[0] if parts else ""
177
+ args = parts[1:] if len(parts) > 1 else []
178
+ await self.commands.handle(update, cmd, args)
179
+ return
180
+
181
+ # Handle text messages
182
+ if update.message.text:
183
+ # Check if this is a group chat
184
+ is_group = update.message.chat.type in ("group", "supergroup")
185
+ should_process = True
186
+
187
+ if is_group:
188
+ bot_name = self.config.identity.get("name", "Alive-AI")
189
+ # Group chat turn-taking logic
190
+ try:
191
+ # Get recent history for context
192
+ from core.self import Self # Need llm and memory access
193
+ # Note: nervous.heart is bound, but not llm directly here,
194
+ # We can use the fast LLM from initialization if attached,
195
+ # or fallback to substring matching if unavailable.
196
+ # As a clever hack, if this class was passed 'heart', we can get 'nervous'
197
+ # But the easiest way is to let the message flow through,
198
+ # however "should I speak" is better evaluated here to prevent spam
199
+
200
+ # We will emit a special event that the core can intercept to check group dynamics
201
+ # Or evaluate it directly if we attach llm to the listener during init
202
+ pass # We will actually evaluate this right here by extending __init__ below
203
+ except Exception as e:
204
+ print(f"[Telegram] Group dynamics error: {e}")
205
+ should_process = bot_name.lower() in update.message.text.lower()
206
+
207
+ if not is_group:
208
+ await self.nervous.emit("message_received", {
209
+ "text": update.message.text,
210
+ "chat_id": update.message.chat_id,
211
+ "user_id": self.user_id,
212
+ "message_id": update.message.message_id
213
+ })
214
+ else:
215
+ # For groups, we emit a 'group_message_received' event that the message handler
216
+ # will evaluate using GroupDynamics before fully processing
217
+ await self.nervous.emit("group_message_received", {
218
+ "text": update.message.text,
219
+ "chat_id": update.message.chat_id,
220
+ "user_id": self.user_id,
221
+ "message_id": update.message.message_id
222
+ })
223
+
224
+ # Handle voice messages
225
+ elif update.message.voice:
226
+ voice = update.message.voice
227
+
228
+ # Try to transcribe if STT is available
229
+ transcription = ""
230
+ if self.stt:
231
+ print("[Telegram] Transcribing voice message...")
232
+ transcription = await self.stt.transcribe_telegram_voice(
233
+ self.app.bot,
234
+ voice.file_id
235
+ )
236
+
237
+ text = transcription if transcription else "[user sent a voice message]"
238
+
239
+ await self.nervous.emit("message_received", {
240
+ "text": text,
241
+ "chat_id": update.message.chat_id,
242
+ "user_id": self.user_id,
243
+ "has_voice": True,
244
+ "voice_file_id": voice.file_id,
245
+ "message_id": update.message.message_id
246
+ })
247
+
248
+ # Handle photos (with or without caption)
249
+ elif update.message.photo:
250
+ photos = update.message.photo
251
+ caption = update.message.caption
252
+ if caption:
253
+ text = f"[user sent a photo with caption: {caption}]"
254
+ else:
255
+ text = "[user sent a photo]"
256
+ await self.nervous.emit("message_received", {
257
+ "text": text,
258
+ "chat_id": update.message.chat_id,
259
+ "user_id": self.user_id,
260
+ "has_photo": True,
261
+ "photo_file_id": photos[-1].file_id if photos else None,
262
+ "message_id": update.message.message_id
263
+ })
264
+
265
+ async def _send_reaction(self, data: dict):
266
+ """Send native emoji reaction to message"""
267
+ if not self.chat_id or not self.app or not self.last_message_id:
268
+ return
269
+
270
+ emoji = data.get("emoji", "❤️")
271
+ try:
272
+ # Use Telegram's native reaction API
273
+ await self.app.bot.set_message_reaction(
274
+ chat_id=self.chat_id,
275
+ message_id=self.last_message_id,
276
+ reaction=[ReactionTypeEmoji(emoji=emoji)]
277
+ )
278
+ print(f"[Telegram] Reacted with: {emoji}")
279
+ except Exception as e:
280
+ print(f"[Telegram] Reaction error: {e}")
281
+
282
+ async def _send_text(self, data: dict):
283
+ """Send text message"""
284
+ if not self.app:
285
+ return
286
+
287
+ chat_id = data.get("chat_id", self.chat_id)
288
+ text = data.get("text", "")
289
+
290
+ if not chat_id or not text:
291
+ return
292
+
293
+ # Reasoning leakage filter removed - was too aggressive
294
+ # The think() function already handles this check
295
+
296
+ # Telegram limit is 4096 chars - split if needed
297
+ MAX_LEN = 4000
298
+ try:
299
+ if len(text) <= MAX_LEN:
300
+ await self.app.bot.send_message(chat_id=chat_id, text=text)
301
+ print(f"[Telegram] Sent text: {text[:50]}...")
302
+ else:
303
+ # Split into multiple messages at sentence boundaries
304
+ parts = []
305
+ current = ""
306
+ for sentence in text.replace(". ", ".|").split("|"):
307
+ if len(current) + len(sentence) <= MAX_LEN:
308
+ current += sentence
309
+ else:
310
+ if current:
311
+ parts.append(current.strip())
312
+ current = sentence
313
+ if current:
314
+ parts.append(current.strip())
315
+
316
+ for i, part in enumerate(parts[:3]): # Max 3 messages
317
+ await self.app.bot.send_message(chat_id=chat_id, text=part)
318
+ print(f"[Telegram] Sent text part {i+1}: {part[:50]}...")
319
+ if i < len(parts) - 1:
320
+ await asyncio.sleep(0.5) # Brief pause between parts
321
+ except Exception as e:
322
+ print(f"[Telegram] Send text error: {e}")
323
+
324
+ async def _send_voice_file(self, data: dict):
325
+ """Send voice file (OGG format for Telegram)"""
326
+ if not self.app:
327
+ return
328
+
329
+ chat_id = data.get("chat_id", self.chat_id)
330
+ file_path = data.get("file_path", "")
331
+
332
+ if not chat_id or not file_path:
333
+ return
334
+
335
+ path = Path(file_path)
336
+ if not path.exists():
337
+ print(f"[Telegram] Voice file not found: {file_path}")
338
+ return
339
+
340
+ try:
341
+ with open(path, "rb") as voice_file:
342
+ await self.app.bot.send_voice(
343
+ chat_id=chat_id,
344
+ voice=voice_file,
345
+ caption=data.get("caption", "")
346
+ )
347
+ print(f"[Telegram] Sent voice: {file_path}")
348
+ except Exception as e:
349
+ print(f"[Telegram] Send voice error: {e}")
350
+
351
+ async def _send_image(self, data: dict):
352
+ """Send image file"""
353
+ if not self.app:
354
+ return
355
+
356
+ chat_id = data.get("chat_id", self.chat_id)
357
+ file_path = data.get("file_path", "")
358
+ caption = data.get("caption", "")
359
+
360
+ if not chat_id or not file_path:
361
+ return
362
+
363
+ path = Path(file_path)
364
+ if not path.exists():
365
+ print(f"[Telegram] Image file not found: {file_path}")
366
+ return
367
+
368
+ try:
369
+ with open(path, "rb") as img_file:
370
+ await self.app.bot.send_photo(
371
+ chat_id=chat_id,
372
+ photo=img_file,
373
+ caption=caption
374
+ )
375
+ print(f"[Telegram] Sent image: {file_path}")
376
+ except Exception as e:
377
+ print(f"[Telegram] Send image error: {e}")
378
+
379
+ async def _send_typing(self, data: dict = None):
380
+ """Show 'typing...' status in chat header"""
381
+ if not self.app or not self.chat_id:
382
+ return
383
+ try:
384
+ await self.app.bot.send_chat_action(
385
+ chat_id=self.chat_id,
386
+ action=ChatAction.TYPING
387
+ )
388
+ except Exception as e:
389
+ print(f"[Telegram] Typing action error: {e}")
390
+
391
+ async def _send_recording_voice(self, data: dict = None):
392
+ """Show 'recording audio...' status in chat header"""
393
+ if not self.app or not self.chat_id:
394
+ return
395
+ try:
396
+ await self.app.bot.send_chat_action(
397
+ chat_id=self.chat_id,
398
+ action=ChatAction.RECORD_VOICE
399
+ )
400
+ except Exception as e:
401
+ print(f"[Telegram] Recording action error: {e}")
402
+
403
+ async def _send_uploading_photo(self, data: dict = None):
404
+ """Show 'uploading photo...' status in chat header"""
405
+ if not self.app or not self.chat_id:
406
+ return
407
+ try:
408
+ await self.app.bot.send_chat_action(
409
+ chat_id=self.chat_id,
410
+ action=ChatAction.UPLOAD_PHOTO
411
+ )
412
+ except Exception as e:
413
+ print(f"[Telegram] Upload photo action error: {e}")
414
+
415
+ async def _send_uploading_video(self, data: dict = None):
416
+ """Show 'uploading video...' status in chat header"""
417
+ if not self.app or not self.chat_id:
418
+ return
419
+ try:
420
+ await self.app.bot.send_chat_action(
421
+ chat_id=self.chat_id,
422
+ action=ChatAction.UPLOAD_VIDEO
423
+ )
424
+ except Exception as e:
425
+ print(f"[Telegram] Upload video action error: {e}")
426
+
427
+ async def _send_video(self, data: dict):
428
+ """Send video file"""
429
+ if not self.app:
430
+ return
431
+
432
+ chat_id = data.get("chat_id", self.chat_id)
433
+ file_path = data.get("file_path", "")
434
+ caption = data.get("caption", "")
435
+
436
+ if not chat_id or not file_path:
437
+ return
438
+
439
+ path = Path(file_path)
440
+ if not path.exists():
441
+ print(f"[Telegram] Video file not found: {file_path}")
442
+ return
443
+
444
+ try:
445
+ with open(path, "rb") as video_file:
446
+ await self.app.bot.send_video(
447
+ chat_id=chat_id,
448
+ video=video_file,
449
+ caption=caption,
450
+ supports_streaming=True
451
+ )
452
+ print(f"[Telegram] Sent video: {file_path}")
453
+ except Exception as e:
454
+ print(f"[Telegram] Send video error: {e}")
455
+
456
+ async def _send_default_mode_initiation(self, data: dict):
457
+ """Send a proactive message from the DefaultMode pending initiation system"""
458
+ if not self.app:
459
+ return
460
+
461
+ message = data.get("message", "")
462
+ user_id = data.get("user_id")
463
+ if not message or not user_id:
464
+ return
465
+
466
+ # Look up chat_id from user tracker
467
+ try:
468
+ from core.user_tracker import get_user_tracker
469
+ tracker = get_user_tracker()
470
+ user = tracker.get_user(str(user_id))
471
+ if user and user.chat_id:
472
+ chat_id = user.chat_id
473
+ else:
474
+ # Fall back: for Telegram, user_id == chat_id for private chats
475
+ chat_id = int(user_id)
476
+ except Exception:
477
+ chat_id = int(user_id)
478
+
479
+ # Delegate to the existing proactive message sender
480
+ await self._send_proactive_message({
481
+ "message": message,
482
+ "chat_id": chat_id,
483
+ "user_id": user_id,
484
+ })
485
+
486
+ async def _send_proactive_message(self, data: dict):
487
+ """Send autonomous proactive message (from subconscious/content system)"""
488
+ if not self.app:
489
+ return
490
+
491
+ message = data.get("message", "")
492
+ if not message:
493
+ return
494
+
495
+ # Get target user - either from data or fall back to last active
496
+ target_chat_id = data.get("chat_id", self.chat_id)
497
+ target_user_id = data.get("user_id", self.user_id)
498
+
499
+ if not target_chat_id:
500
+ print("[Telegram] No chat_id for proactive message")
501
+ return
502
+
503
+ try:
504
+ # Show typing first for natural feel
505
+ await self.app.bot.send_chat_action(
506
+ chat_id=target_chat_id,
507
+ action=ChatAction.TYPING
508
+ )
509
+ await asyncio.sleep(1.5) # Brief pause
510
+
511
+ await self.app.bot.send_message(
512
+ chat_id=target_chat_id,
513
+ text=message
514
+ )
515
+ print(f"[Telegram] Sent proactive message to {target_user_id}: {message[:50]}...")
516
+
517
+ # Save to memory so context is preserved when user replies
518
+ if target_user_id:
519
+ await self.nervous.emit("memory_save", {
520
+ "type": "conversation",
521
+ "user_message": "", # No user message - this is proactive
522
+ "ai_response": message,
523
+ "emotion": {"mood": "proactive", "proactive": True},
524
+ "user_id": target_user_id # Include user_id for per-user memory
525
+ })
526
+ print(f"[Telegram] Saved proactive message to memory for user {target_user_id}")
527
+
528
+ # Track for follow-up system (so she knows she asked a question)
529
+ from core.message_handler import get_follow_up_system
530
+ get_follow_up_system().record_message_sent(message)
531
+ except Exception as e:
532
+ print(f"[Telegram] Proactive message error: {e}")
package/main.py ADDED
@@ -0,0 +1,90 @@
1
+ """
2
+ Alive-AI runtime entrypoint.
3
+
4
+ Run with:
5
+ npx alive-ai start
6
+ or:
7
+ python main.py
8
+ """
9
+
10
+ import asyncio
11
+ import json
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+
16
+
17
+ ROOT = Path(__file__).parent.resolve()
18
+
19
+
20
+ def load_env_file(path: Path) -> None:
21
+ if not path.exists():
22
+ return
23
+ for raw_line in path.read_text().splitlines():
24
+ line = raw_line.strip()
25
+ if not line or line.startswith("#") or "=" not in line:
26
+ continue
27
+ key, _, value = line.partition("=")
28
+ if key.strip():
29
+ os.environ.setdefault(key.strip(), value.strip())
30
+
31
+
32
+ def load_settings_to_env(path: Path) -> dict:
33
+ if not path.exists():
34
+ return {}
35
+ settings = json.loads(path.read_text())
36
+ for key, value in settings.items():
37
+ if key.startswith("_"):
38
+ continue
39
+ if isinstance(value, dict):
40
+ continue
41
+ if isinstance(value, bool):
42
+ os.environ[key] = "true" if value else "false"
43
+ elif value is not None:
44
+ os.environ[key] = str(value)
45
+ return settings
46
+
47
+
48
+ def ensure_config() -> dict:
49
+ config_dir = ROOT / "config"
50
+ settings_path = config_dir / "settings.json"
51
+ missing = [
52
+ name for name in ("settings.json", "self.json", "directives.json", "instructions.md")
53
+ if not (config_dir / name).exists()
54
+ ]
55
+ if missing:
56
+ print("Alive-AI is not configured yet.")
57
+ print(f"Missing: {', '.join('config/' + name for name in missing)}")
58
+ print("Run: npx alive-ai setup")
59
+ sys.exit(2)
60
+ return load_settings_to_env(settings_path)
61
+
62
+
63
+ async def main() -> None:
64
+ load_env_file(ROOT / ".env")
65
+ load_env_file(ROOT / "config" / "secrets.env")
66
+ settings = ensure_config()
67
+
68
+ from core.self import Self
69
+
70
+ ai = Self(ROOT)
71
+ webui_enabled = str(settings.get("WEBUI_ENABLED", os.environ.get("WEBUI_ENABLED", "true"))).lower() != "false"
72
+ webui_port = int(settings.get("WEBUI_PORT", os.environ.get("WEBUI_PORT", "8080")))
73
+
74
+ if webui_enabled:
75
+ try:
76
+ from webui.bridge import init_bridge, init_soul_bridge, start_webui
77
+
78
+ init_bridge(ai.nervous)
79
+ if hasattr(ai, "_heart") and ai._heart and hasattr(ai._heart, "soul"):
80
+ init_soul_bridge(ai._heart.soul)
81
+ asyncio.create_task(start_webui(host="127.0.0.1", port=webui_port))
82
+ print(f"[Alive-AI] Dashboard running at http://127.0.0.1:{webui_port}")
83
+ except Exception as exc:
84
+ print(f"[Alive-AI] WebUI unavailable: {exc}")
85
+
86
+ await ai.start()
87
+
88
+
89
+ if __name__ == "__main__":
90
+ asyncio.run(main())
package/manifest.md ADDED
@@ -0,0 +1,28 @@
1
+ # Alive-AI - AI Influencer
2
+
3
+ A modular AI that can grow, feel emotions, and form relationships.
4
+
5
+ ## Architecture
6
+ - `core/` - Essential modules (events, config, self, state)
7
+ - `brain/` - Memory, LLM providers, subconscious, embeddings, STT
8
+ - `heart/` - Emotions, reactions, arousal, love/attachment system
9
+ - `input/` - Telegram listener, voice reader, commands
10
+ - `output/` - Text, voice (VibeTTS), images (Fal.ai), video
11
+ - `skills/` - Calendar, photo manager, video manager
12
+
13
+ ## Key Features
14
+ - **Multi-provider LLM** - ZAI or OpenRouter (main/thinking/fast models)
15
+ - **Subconscious loop** - 24/7 background process, generates impulses
16
+ - **Vector memory** - Redis-based semantic search with embeddings
17
+ - **Emotional system** - Continuous state with natural decay
18
+ - **Proactive messaging** - Sends messages based on impulses/feelings
19
+ - **Voice synthesis** - VibeTTS with mood-based CFG, auto-splits long text
20
+ - **Media management** - Photos/videos with categories and no-repeat
21
+ - **Admin commands** - /status, /impulse, /stats, /reset, /10min
22
+
23
+ ## Entry Point
24
+ ```python
25
+ from core.self import Self
26
+ ai = Self(Path("."))
27
+ await ai.start()
28
+ ```
@@ -0,0 +1 @@
1
+
@@ -0,0 +1 @@
1
+
@@ -0,0 +1 @@
1
+ """Output modules"""
@@ -0,0 +1 @@
1
+ """Image output"""