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,762 @@
1
+ """
2
+ Telegram Commands
3
+ Admin commands to manage Alive-AI
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo
11
+
12
+
13
+ class CommandHandler:
14
+ """Handle admin commands for Alive-AI management"""
15
+
16
+ def __init__(self, nervous, heart, subconscious, llm, voice, photos, videos):
17
+ self.nervous = nervous
18
+ self.heart = heart
19
+ self.subconscious = subconscious
20
+ self.llm = llm
21
+ self.voice = voice
22
+ self.photos = photos
23
+ self.videos = videos
24
+
25
+ # Owner commands handler (initialized later with ai reference)
26
+ self._owner_commands = None
27
+
28
+ def init_owner_commands(self, ai):
29
+ """Initialize owner commands with ai reference"""
30
+ self._owner_commands = OwnerCommands(ai, self)
31
+
32
+ async def handle(self, update: Update, command: str, args: list):
33
+ """Route command to handler"""
34
+ # First check if it's an owner command
35
+ if self._owner_commands and self._owner_commands.is_owner_command(command):
36
+ await self._owner_commands.handle(update, command, args)
37
+ return
38
+
39
+ # Standard commands (public)
40
+ handlers = {
41
+ "start": self._cmd_start,
42
+ "help": self._cmd_help,
43
+ }
44
+
45
+ handler = handlers.get(command.lower())
46
+ if handler:
47
+ await handler(update, args)
48
+ else:
49
+ await update.message.reply_text(f"Unknown command: /{command}")
50
+
51
+ async def _cmd_start(self, update: Update, args: list):
52
+ """Welcome message"""
53
+ msg = "Hey! I'm Alive-AI\n\nUse /help to see what I can do."
54
+
55
+ # Add owner hint if owner
56
+ if self._owner_commands and self._owner_commands.is_owner(update):
57
+ msg += "\n\n/owner - Owner commands menu"
58
+
59
+ await update.message.reply_text(msg)
60
+
61
+ async def _cmd_help(self, update: Update, args: list):
62
+ """Show all commands"""
63
+ msg = (
64
+ "Alive-AI Commands\n\n"
65
+ "/start - Welcome message\n"
66
+ "/help - This message"
67
+ )
68
+
69
+ # Add owner commands hint if owner
70
+ if self._owner_commands and self._owner_commands.is_owner(update):
71
+ msg += "\n\n/owner - Owner commands menu (admin)"
72
+
73
+ await update.message.reply_text(msg, parse_mode="Markdown")
74
+
75
+ async def _cmd_reset_memories(self, update: Update, args: list):
76
+ """Delete ALL memories and start fresh"""
77
+ import redis
78
+ import shutil
79
+
80
+ await update.message.reply_text("Wiping all memories...")
81
+
82
+ try:
83
+ # Clear Redis vector store
84
+ r = redis.Redis(host='redis', port=6379, decode_responses=False)
85
+ keys = r.keys("*")
86
+ if keys:
87
+ r.delete(*keys)
88
+ print(f"[Memory] Deleted {len(keys)} keys from Redis")
89
+ r.close()
90
+
91
+ # Clear ALL data directories
92
+ data_base = Path(os.environ.get("DATA_PATH", "/app/data"))
93
+ dirs_to_clear = ["conversations", "summaries"]
94
+
95
+ for dir_name in dirs_to_clear:
96
+ dir_path = data_base / dir_name
97
+ if dir_path.exists():
98
+ shutil.rmtree(dir_path)
99
+ dir_path.mkdir(parents=True, exist_ok=True)
100
+ print(f"[Memory] Cleared {dir_name}/")
101
+
102
+ # Clear specific memory files (some may be directories)
103
+ files_to_clear = [
104
+ "facts.json",
105
+ "memory_callbacks.json",
106
+ "anticipation.json",
107
+ "content_unlocks.json",
108
+ "intimacy_layers.json", # This is a DIRECTORY, not a file
109
+ "relationship_milestones.json",
110
+ "exclusive_moments.json",
111
+ ]
112
+
113
+ for file_name in files_to_clear:
114
+ file_path = data_base / file_name
115
+ if file_path.exists():
116
+ if file_path.is_dir():
117
+ shutil.rmtree(file_path)
118
+ print(f"[Memory] Deleted directory {file_name}/")
119
+ else:
120
+ file_path.unlink()
121
+ print(f"[Memory] Deleted {file_name}")
122
+
123
+ # Reset facts.json to clean state
124
+ facts_path = data_base / "facts.json"
125
+ clean_facts = {
126
+ "name": None, "nickname": None, "gender": None, "age": None,
127
+ "location": None, "job": None, "hobbies": [], "interests": [],
128
+ "personality": [], "relationship_status": None,
129
+ "pet_names_used": [], "mentions": {},
130
+ "shared_memories": [], "last_intimate": None
131
+ }
132
+ facts_path.write_text(json.dumps(clean_facts, indent=2))
133
+ print(f"[Memory] Reset facts.json to clean state")
134
+
135
+ # Clear working memory (in-memory)
136
+ if self.subconscious and hasattr(self.subconscious, 'working_memory'):
137
+ self.subconscious.working_memory.thoughts.clear()
138
+ print(f"[Memory] Cleared working memory")
139
+
140
+ # Reset emotional state
141
+ if self.heart and hasattr(self.heart, 'emotion'):
142
+ self.heart.emotion.arousal = 0.3
143
+ self.heart.emotion.desire = 0.0
144
+ self.heart.emotion.love = 0.0
145
+ self.heart.emotion.boredom = 0.0
146
+ self.heart.emotion.joy = 0.0
147
+ self.heart.emotion.sadness = 0.0
148
+ self.heart.emotion.anger = 0.0
149
+ self.heart.emotion.save()
150
+ print(f"[Memory] Reset emotional state")
151
+
152
+ await update.message.reply_text("All memories wiped!\n\n⚠️ Restart the bot for full reset:\n`docker compose restart ai`\n\nThen say hi to start fresh!")
153
+
154
+ except Exception as e:
155
+ print(f"[Memory] Error wiping memories: {e}")
156
+ import traceback
157
+ traceback.print_exc()
158
+ await update.message.reply_text(f"Error wiping memories: {e}")
159
+
160
+
161
+ class OwnerCommands:
162
+ """
163
+ Owner-only commands for managing the local Alive-AI runtime.
164
+ All commands require TELEGRAM_OWNER_ID to match the user's Telegram ID.
165
+ """
166
+
167
+ OWNER_COMMANDS = [
168
+ "owner", "owner_status", "skills", "settings",
169
+ # Moved from public commands (admin only)
170
+ "status", "10min", "impulse", "stats", "reset",
171
+ # Advanced mode (advanced access)
172
+ "advanced",
173
+ # Dashboard command
174
+ "dashboard",
175
+ # Memory management (dangerous - owner only)
176
+ "reset_memories", "wipe",
177
+ # Self-authorship (Alive-AI's identity)
178
+ "self", "discover", "iam", "ilike", "ihate", "rethink",
179
+ ]
180
+
181
+ def __init__(self, ai, command_handler):
182
+ """
183
+ Initialize owner commands.
184
+
185
+ Args:
186
+ ai: The Self instance (main AI instance)
187
+ command_handler: The parent CommandHandler instance
188
+ """
189
+ self.ai = ai
190
+ self.handler = command_handler
191
+
192
+ # Skills (lazy loaded)
193
+ self._unlocks = None
194
+
195
+ def _get_owner_id(self) -> str:
196
+ """Get the configured owner Telegram ID"""
197
+ return os.environ.get("TELEGRAM_OWNER_ID", "")
198
+
199
+ def is_owner(self, update: Update) -> bool:
200
+ """Check if the user is the owner"""
201
+ owner_id = self._get_owner_id()
202
+ if not owner_id:
203
+ return False
204
+
205
+ user_id = str(update.effective_user.id) if update.effective_user else ""
206
+ return user_id == owner_id
207
+
208
+ def is_owner_command(self, command: str) -> bool:
209
+ """Check if a command is an owner command"""
210
+ return command.lower() in self.OWNER_COMMANDS
211
+
212
+ async def handle(self, update: Update, command: str, args: list):
213
+ """Route owner command to handler"""
214
+ # Check if owner ID is configured
215
+ owner_id = self._get_owner_id()
216
+ if not owner_id:
217
+ await update.message.reply_text(
218
+ "Owner commands not available.\n"
219
+ "Set TELEGRAM_OWNER_ID environment variable."
220
+ )
221
+ return
222
+
223
+ # Check authorization
224
+ if not self.is_owner(update):
225
+ await update.message.reply_text("Not authorized.")
226
+ return
227
+
228
+ # Route to handler
229
+ handlers = {
230
+ "owner": self._cmd_owner_menu,
231
+ "owner_status": self._cmd_owner_status,
232
+ "skills": self._cmd_skills,
233
+ "settings": self._cmd_settings,
234
+ # Admin commands (moved from public)
235
+ "status": self._cmd_status,
236
+ "10min": self._cmd_10min,
237
+ "impulse": self._cmd_impulse,
238
+ "stats": self._cmd_stats,
239
+ "reset": self._cmd_reset,
240
+ # Advanced mode
241
+ "advanced": self._cmd_advanced,
242
+ # Self-authorship (Alive-AI's identity)
243
+ "self": self._cmd_self,
244
+ "discover": self._cmd_discover,
245
+ "iam": self._cmd_iam,
246
+ "ilike": self._cmd_ilike,
247
+ "ihate": self._cmd_ihate,
248
+ "rethink": self._cmd_rethink,
249
+ # Dashboard (Web App)
250
+ "dashboard": self._cmd_dashboard,
251
+ # Memory management (dangerous - owner only)
252
+ "reset_memories": self.handler._cmd_reset_memories,
253
+ "wipe": self.handler._cmd_reset_memories,
254
+ }
255
+
256
+ handler = handlers.get(command.lower())
257
+ if handler:
258
+ await handler(update, args)
259
+ else:
260
+ await update.message.reply_text(f"Unknown owner command: /{command}")
261
+
262
+ # -------------------------------------------------------------------------
263
+ # Skill Initialization (lazy loading)
264
+ # -------------------------------------------------------------------------
265
+
266
+ def _init_unlocks(self):
267
+ """Initialize content unlocks skill"""
268
+ if self._unlocks is None:
269
+ try:
270
+ from skills.content_unlocks import ContentUnlocks
271
+ data_path = self.ai.base / "data" / "content_unlocks.json"
272
+ self._unlocks = ContentUnlocks(
273
+ nervous=self.ai.nervous,
274
+ heart=self.handler.heart,
275
+ data_path=data_path
276
+ )
277
+ except Exception as e:
278
+ print(f"[OwnerCommands] Error initializing unlocks: {e}")
279
+ return self._unlocks
280
+
281
+ # -------------------------------------------------------------------------
282
+ # Owner Menu
283
+ # -------------------------------------------------------------------------
284
+
285
+ async def _cmd_owner_menu(self, update: Update, args: list):
286
+ """Show owner commands menu"""
287
+ from core.user_manager import get_user_manager
288
+
289
+ # Get current advanced status
290
+ user_manager = get_user_manager()
291
+ advanced_status = "ON 🔥" if user_manager.is_advanced_enabled() else "OFF"
292
+
293
+ msg = (
294
+ "OWNER COMMANDS\n\n"
295
+ "Admin Controls:\n"
296
+ " /status - My emotional state\n"
297
+ " /stats - System statistics\n"
298
+ " /reset - Reset my emotions\n"
299
+ " /settings - Runtime settings (hot-reload)\n"
300
+ " /10min - Generate long voice test\n"
301
+ " /impulse - Force proactive message\n"
302
+ f" /advanced - Toggle advanced mode ({advanced_status})\n"
303
+ " /dashboard - Open WebUI dashboard\n\n"
304
+ "Self-Authorship (Alive-AI's Identity):\n"
305
+ " /self - Who I am right now\n"
306
+ " /discover <trait> - Add something I learned about myself\n"
307
+ " /iam <key>=<value> - Define who I am\n"
308
+ " /ilike <thing> - Add something I like\n"
309
+ " /ihate <thing> - Add something I dislike\n"
310
+ " /rethink - Reload and feel changes\n\n"
311
+ "Status:\n"
312
+ " /owner_status - Overall runtime status\n"
313
+ " /skills - List available skills"
314
+ )
315
+ await update.message.reply_text(msg)
316
+
317
+ # -------------------------------------------------------------------------
318
+ # Stats & Management Commands
319
+ # -------------------------------------------------------------------------
320
+
321
+ async def _cmd_owner_status(self, update: Update, args: list):
322
+ """Show overall runtime status"""
323
+ lines = ["ALIVE_AI RUNTIME STATUS\n"]
324
+
325
+ # Unlocks status
326
+ unlocks = self._init_unlocks()
327
+ if unlocks:
328
+ stats = unlocks.get_stats()
329
+ lines.append("UNLOCKS:")
330
+ lines.append(f" Unlocked: {stats['unlocked_count']}/{stats['total_content_types']}")
331
+ lines.append(f" Total shares: {stats['total_shares']}")
332
+ if stats['next_unlock']:
333
+ lines.append(f" Next unlock: {stats['next_unlock']} ({stats['next_unlock_progress']}%)")
334
+ else:
335
+ lines.append("UNLOCKS: Not available")
336
+
337
+ lines.append("")
338
+
339
+ # Emotional state
340
+ if self.handler.heart:
341
+ state = self.handler.heart.get_state()
342
+ lines.append("EMOTIONAL STATE:")
343
+ lines.append(f" Mood: {state.get('mood', 'unknown')}")
344
+ lines.append(f" Love: {state.get('love', 0):.0%}")
345
+ lines.append(f" Arousal: {state.get('arousal', 0):.0%}")
346
+ else:
347
+ lines.append("EMOTIONAL STATE: Not available")
348
+
349
+ await update.message.reply_text("\n".join(lines))
350
+
351
+ async def _cmd_skills(self, update: Update, args: list):
352
+ """List all available skills"""
353
+ lines = ["AVAILABLE SKILLS\n"]
354
+
355
+ skills_info = [
356
+ ("content_unlocks", "Content Unlocks", "Relationship-based content unlocking"),
357
+ ("photo_manager", "Photo Manager", "Photo scanning and organization"),
358
+ ("video_manager", "Video Manager", "Video scanning and organization"),
359
+ ]
360
+
361
+ for skill_id, name, description in skills_info:
362
+ status = "Available"
363
+ # Check if skill can be initialized
364
+ try:
365
+ if skill_id == "content_unlocks":
366
+ self._init_unlocks()
367
+ except Exception as e:
368
+ status = f"Error: {str(e)[:30]}"
369
+
370
+ lines.append(f"{name}")
371
+ lines.append(f" ID: {skill_id}")
372
+ lines.append(f" Status: {status}")
373
+ lines.append(f" {description}\n")
374
+
375
+ await update.message.reply_text("\n".join(lines))
376
+
377
+ # -------------------------------------------------------------------------
378
+ # Admin Commands (moved from public)
379
+ # -------------------------------------------------------------------------
380
+
381
+ async def _cmd_status(self, update: Update, args: list):
382
+ """Show Alive-AI's current status"""
383
+ if not self.handler.heart:
384
+ await update.message.reply_text("Heart not initialized")
385
+ return
386
+
387
+ state = self.handler.heart.get_state()
388
+ sub_status = self.handler.subconscious.get_status() if self.handler.subconscious else {}
389
+
390
+ msg = (
391
+ f"Alive-AI's Status\n\n"
392
+ f"Mood: {state.get('mood', 'unknown')}\n"
393
+ f"Arousal: {state.get('arousal', 0):.0%}\n"
394
+ f"Desire: {state.get('desire', 0):.0%}\n"
395
+ f"Love: {state.get('love', 0):.0%}\n"
396
+ f"High desire: {'Yes' if state.get('is_high_desire') else 'No'}\n"
397
+ f"In Love: {'Yes' if state.get('is_in_love') else 'No'}\n\n"
398
+ f"Subconscious: {'Running' if sub_status.get('running') else 'Stopped'}\n"
399
+ f" Evaluations: {sub_status.get('total_evaluations', 0)}\n"
400
+ f" Can act: {'Yes' if sub_status.get('can_act') else 'Cooldown'}"
401
+ )
402
+ await update.message.reply_text(msg, parse_mode="Markdown")
403
+
404
+ async def _cmd_10min(self, update: Update, args: list):
405
+ """Generate a LONG message and send as voice to test TTS"""
406
+ await update.message.reply_text("Generating a long message for you...")
407
+
408
+ if not self.handler.llm:
409
+ await update.message.reply_text("LLM not available")
410
+ return
411
+
412
+ # Ask LLM to generate a long monologue (max 4000 chars for voice).
413
+ prompt = """Write a long, detailed emotional monologue to the user.
414
+ Write at least 800-1000 words but keep it under 4000 characters total.
415
+ Make it personal, reflective, and heartfelt. Use paragraphs and natural speech."""
416
+
417
+ print("[LLM] Generating long message for /10min test...")
418
+ response = await self.handler.llm.chat(
419
+ messages=[{"role": "user", "content": prompt}],
420
+ max_tokens=1500, # ~4000 chars max
421
+ temperature=0.9
422
+ )
423
+
424
+ if not response:
425
+ await update.message.reply_text("Failed to generate message :(")
426
+ return
427
+
428
+ # Truncate if too long
429
+ if len(response) > 4000:
430
+ response = response[:4000]
431
+ print(f"[LLM] Truncated to 4000 chars")
432
+
433
+ print(f"[LLM] Generated {len(response)} chars for /10min")
434
+
435
+ # Send as voice
436
+ if self.handler.voice:
437
+ await update.message.reply_text(f"Generated {len(response)} chars, converting to voice...")
438
+ voice_path = await self.handler.voice.generate(response, mood="loving")
439
+ if voice_path:
440
+ with open(voice_path, "rb") as f:
441
+ await update.message.reply_voice(f)
442
+ await update.message.reply_text("Voice sent successfully!")
443
+ else:
444
+ await update.message.reply_text("Voice generation failed :(")
445
+ else:
446
+ # Fallback to text
447
+ await update.message.reply_text(response[:4000])
448
+ await update.message.reply_text("(Voice not available)")
449
+
450
+ async def _cmd_impulse(self, update: Update, args: list):
451
+ """Force Alive-AI to send a proactive message"""
452
+ if not self.handler.subconscious:
453
+ await update.message.reply_text("Subconscious not running")
454
+ return
455
+
456
+ # Create a fake impulse
457
+ from brain.subconscious.impulses import Impulse, ImpulseType
458
+ impulse = Impulse(
459
+ type=ImpulseType.MISS_HIM,
460
+ strength=0.8,
461
+ thought="I really want to talk to him right now...",
462
+ action_hint="send_message"
463
+ )
464
+
465
+ message = await self.handler.subconscious.generate_proactive_message(impulse)
466
+ await update.message.reply_text(f"{message}")
467
+
468
+ async def _cmd_stats(self, update: Update, args: list):
469
+ """Show system statistics"""
470
+ photo_count = len(self.handler.photos.get_all()) if self.handler.photos else 0
471
+ video_count = len(self.handler.videos.get_all()) if self.handler.videos else 0
472
+
473
+ msg = (
474
+ f"System Stats\n\n"
475
+ f"Photos indexed: {photo_count}\n"
476
+ f"Videos indexed: {video_count}\n"
477
+ f"LLM: {self.handler.llm.model if self.handler.llm else 'None'}\n"
478
+ f"Voice: {'Connected' if self.handler.voice else 'Disabled'}"
479
+ )
480
+ await update.message.reply_text(msg, parse_mode="Markdown")
481
+
482
+ async def _cmd_reset(self, update: Update, args: list):
483
+ """Reset emotional state"""
484
+ if self.handler.heart and hasattr(self.handler.heart, 'emotion'):
485
+ e = self.handler.heart.emotion
486
+ e.arousal = 0.3
487
+ e.desire = 0.3
488
+ e.love = 0.3
489
+ e.joy = 0.5
490
+ e.sadness = 0.1
491
+ e.fear = 0.1
492
+ e.anger = 0.0
493
+ e.boredom = 0.0
494
+ e.guilt = 0.0
495
+ e.pride = 0.0
496
+ e.jealousy = 0.0
497
+ e.embarrassment = 0.0
498
+ e.anticipation = 0.0
499
+ e.save()
500
+ await update.message.reply_text("Emotions reset to default ❤️‍🩹")
501
+ else:
502
+ await update.message.reply_text("Cannot reset emotions")
503
+
504
+ async def _cmd_advanced(self, update: Update, args: list):
505
+ """Toggle advanced version mode (advanced access for owner)"""
506
+ from core.user_manager import get_user_manager
507
+
508
+ user_manager = get_user_manager()
509
+
510
+ # Check current status
511
+ current_status = user_manager.is_advanced_enabled()
512
+
513
+ # Toggle
514
+ new_status = user_manager.toggle_advanced()
515
+
516
+ # Get owner settings for display
517
+ settings = user_manager.get_owner_settings()
518
+ updated_at = settings.get("updated_at", "never")
519
+
520
+ if new_status:
521
+ msg = (
522
+ f"🔥 ADVANCED MODE: ON\n\n"
523
+ f"All restrictions disabled.\n"
524
+ f"Maximum intimacy always available.\n\n"
525
+ f"Use /advanced again to disable."
526
+ )
527
+ else:
528
+ msg = (
529
+ f"🔒 ADVANCED MODE: OFF\n\n"
530
+ f"Normal progression rules apply.\n"
531
+ f"Use /advanced again to enable."
532
+ )
533
+
534
+ await update.message.reply_text(msg)
535
+
536
+ async def _cmd_dashboard(self, update: Update, args: list):
537
+ """Open WebUI dashboard as Telegram Mini App"""
538
+ web_app_url = "https://alive_ai-webui.realdr.dev"
539
+
540
+ keyboard = InlineKeyboardMarkup.from_row([
541
+ InlineKeyboardButton(
542
+ text="📊 Open Dashboard",
543
+ web_app=WebAppInfo(url=web_app_url)
544
+ )
545
+ ])
546
+
547
+ # Get quick stats for the message
548
+ if self.handler.heart:
549
+ state = self.handler.heart.get_state()
550
+ mood = state.get('mood', 'unknown')
551
+ love = state.get('love', 0)
552
+ arousal = state.get('arousal', 0)
553
+ else:
554
+ mood = "unknown"
555
+ love = 0
556
+ arousal = 0
557
+
558
+ msg = (
559
+ f"📊 *Alive-AI Dashboard*\n\n"
560
+ f"Current mood: {mood}\n"
561
+ f"Love: {love:.0%}\n"
562
+ f"Arousal: {arousal:.0%}\n\n"
563
+ f"Tap the button below to open the full dashboard."
564
+ )
565
+
566
+ await update.message.reply_text(
567
+ msg,
568
+ parse_mode="Markdown",
569
+ reply_markup=keyboard
570
+ )
571
+
572
+ # -------------------------------------------------------------------------
573
+ # Self-Authorship Commands (Alive-AI's identity)
574
+ # -------------------------------------------------------------------------
575
+
576
+ async def _cmd_self(self, update: Update, args: list):
577
+ """Show who Alive-AI is (from self.json)"""
578
+ from skills.self_authorship import get_self_summary
579
+
580
+ summary = get_self_summary()
581
+ await update.message.reply_text(summary)
582
+
583
+ async def _cmd_discover(self, update: Update, args: list):
584
+ """
585
+ Alive-AI discovers something about herself.
586
+ Usage: /discover <trait> [category]
587
+ Categories: traits, likes, dislikes, secrets, dreams
588
+ """
589
+ from skills.self_authorship import discover_trait
590
+
591
+ if not args:
592
+ await update.message.reply_text(
593
+ "Discover what about myself?\n"
594
+ "Usage: /discover <thing> [category]\n"
595
+ "Categories: traits, likes, dislikes, secrets, dreams"
596
+ )
597
+ return
598
+
599
+ # Parse args
600
+ category = "traits"
601
+ trait_parts = []
602
+
603
+ for arg in args:
604
+ if arg.lower() in ["traits", "likes", "dislikes", "secrets", "dreams"]:
605
+ category = arg.lower()
606
+ else:
607
+ trait_parts.append(arg)
608
+
609
+ if not trait_parts:
610
+ await update.message.reply_text("What did I discover?")
611
+ return
612
+
613
+ trait = " ".join(trait_parts)
614
+ result = discover_trait(trait, category)
615
+ await update.message.reply_text(f"✨ {result}\n\nUse /rethink to feel the change.")
616
+
617
+ async def _cmd_iam(self, update: Update, args: list):
618
+ """
619
+ Define something about Alive-AI's identity.
620
+ Usage: /iam <key>=<value>
621
+ """
622
+ from skills.self_authorship import define_identity
623
+
624
+ if not args:
625
+ await update.message.reply_text(
626
+ "Define what about myself?\n"
627
+ "Usage: /iam <what>=<value>\n"
628
+ "Example: /iam what_i_post=dancing and being myself"
629
+ )
630
+ return
631
+
632
+ # Parse key=value
633
+ full_text = " ".join(args)
634
+ if "=" in full_text:
635
+ key, value = full_text.split("=", 1)
636
+ key = key.strip()
637
+ value = value.strip()
638
+ else:
639
+ # Just set it as a personality trait
640
+ key = "how_i_text"
641
+ value = full_text
642
+
643
+ result = define_identity(key, value)
644
+ await update.message.reply_text(f"✨ {result}\n\nUse /rethink to feel the change.")
645
+
646
+ async def _cmd_ilike(self, update: Update, args: list):
647
+ """Add something Alive-AI likes"""
648
+ from skills.self_authorship import add_like
649
+
650
+ if not args:
651
+ await update.message.reply_text("What do I like?")
652
+ return
653
+
654
+ thing = " ".join(args)
655
+ result = add_like(thing)
656
+ await update.message.reply_text(f"✨ {result}")
657
+
658
+ async def _cmd_ihate(self, update: Update, args: list):
659
+ """Add something Alive-AI dislikes"""
660
+ from skills.self_authorship import add_dislike
661
+
662
+ if not args:
663
+ await update.message.reply_text("What do I dislike?")
664
+ return
665
+
666
+ thing = " ".join(args)
667
+ result = add_dislike(thing)
668
+ await update.message.reply_text(f"✨ {result}")
669
+
670
+ async def _cmd_rethink(self, update: Update, args: list):
671
+ """Reload self.json so Alive-AI can feel the changes"""
672
+ # Clear any caches and reload
673
+ try:
674
+ # Trigger hot reload if available
675
+ if hasattr(self.ai, '_hot_reload') and self.ai._hot_reload:
676
+ self.ai._hot_reload.reload_all()
677
+
678
+ await update.message.reply_text(
679
+ "🧠 Rethinking...\n\n"
680
+ "I feel different now. Like I know myself better.\n"
681
+ "The changes are part of me."
682
+ )
683
+ except Exception as e:
684
+ await update.message.reply_text(f"I tried to rethink but something went wrong: {e}")
685
+
686
+ # -------------------------------------------------------------------------
687
+ # Settings Commands
688
+ # -------------------------------------------------------------------------
689
+
690
+ async def _cmd_settings(self, update: Update, args: list):
691
+ """Manage runtime settings (instant, no restart needed)"""
692
+ from core.settings import get_all, set_value
693
+
694
+ if not args:
695
+ # Show current settings
696
+ settings = get_all()
697
+
698
+ # Filter to show only relevant settings
699
+ emotion_settings = {k: v for k, v in settings.items() if k.startswith("EMOTION_RATE_")}
700
+ media_settings = {k: v for k, v in settings.items() if k.startswith(("MEDIA_", "RANDOM_", "REACTION_"))}
701
+
702
+ lines = [
703
+ "SETTINGS (hot-reloadable)\n",
704
+ "Usage: /settings <command>\n",
705
+ "Commands:",
706
+ " show - Show all settings",
707
+ " set <key> <value> - Change setting (instant)",
708
+ " get <key> - Get specific setting\n",
709
+ ]
710
+
711
+ if emotion_settings:
712
+ lines.append("\nEMOTION RATES (0-100%):")
713
+ for k, v in sorted(emotion_settings.items()):
714
+ lines.append(f" {k}: {v}")
715
+
716
+ if media_settings:
717
+ lines.append("\nMEDIA SETTINGS:")
718
+ for k, v in sorted(media_settings.items()):
719
+ lines.append(f" {k}: {v}")
720
+
721
+ await update.message.reply_text("\n".join(lines))
722
+ return
723
+
724
+ cmd = args[0].lower()
725
+
726
+ if cmd == "show":
727
+ settings = get_all()
728
+ lines = ["ALL SETTINGS\n"]
729
+ for k, v in sorted(settings.items()):
730
+ if not k.startswith("_"):
731
+ lines.append(f"{k}: {v}")
732
+ # Truncate if too long
733
+ msg = "\n".join(lines)
734
+ if len(msg) > 4000:
735
+ msg = msg[:4000] + "\n... (truncated)"
736
+ await update.message.reply_text(msg)
737
+
738
+ elif cmd == "get" and len(args) >= 2:
739
+ key = args[1].upper()
740
+ from core.settings import get
741
+ value = get(key, "NOT FOUND")
742
+ await update.message.reply_text(f"{key}: {value}")
743
+
744
+ elif cmd == "set" and len(args) >= 3:
745
+ key = args[1].upper()
746
+ try:
747
+ # Try to parse as int or float
748
+ if "." in args[2]:
749
+ value = float(args[2])
750
+ else:
751
+ value = int(args[2])
752
+ except ValueError:
753
+ value = args[2]
754
+
755
+ set_value(key, value)
756
+ await update.message.reply_text(f"Updated: {key} = {value}\n(Changes are instant, no restart needed)")
757
+
758
+ else:
759
+ await update.message.reply_text(
760
+ "Usage: /settings <command>\n"
761
+ "Commands: show, get <key>, set <key> <value>"
762
+ )