nexo-brain 5.3.13 → 5.3.15

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 (230) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/nexo-brain.js +52 -1
  3. package/package.json +1 -1
  4. package/src/crons/sync.py +18 -4
  5. package/src/dashboard/static/favicon 2.svg +32 -0
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +40 -0
  8. package/src/dashboard/static/style 2.css +2458 -0
  9. package/src/dashboard/templates/adaptive 2.html +118 -0
  10. package/src/dashboard/templates/artifacts 2.html +133 -0
  11. package/src/dashboard/templates/backups 2.html +136 -0
  12. package/src/dashboard/templates/base 2.html +417 -0
  13. package/src/dashboard/templates/calendar 2.html +591 -0
  14. package/src/dashboard/templates/chat 2.html +356 -0
  15. package/src/dashboard/templates/claims 2.html +259 -0
  16. package/src/dashboard/templates/cortex 2.html +321 -0
  17. package/src/dashboard/templates/credentials 2.html +128 -0
  18. package/src/dashboard/templates/crons 2.html +370 -0
  19. package/src/dashboard/templates/dashboard 2.html +494 -0
  20. package/src/dashboard/templates/dreams 2.html +252 -0
  21. package/src/dashboard/templates/email 2.html +160 -0
  22. package/src/dashboard/templates/evolution 2.html +189 -0
  23. package/src/dashboard/templates/feed 2.html +249 -0
  24. package/src/dashboard/templates/followup_health 2.html +170 -0
  25. package/src/dashboard/templates/graph 2.html +201 -0
  26. package/src/dashboard/templates/guard 2.html +259 -0
  27. package/src/dashboard/templates/inbox 2.html +251 -0
  28. package/src/dashboard/templates/memory 2.html +420 -0
  29. package/src/dashboard/templates/operations 2.html +608 -0
  30. package/src/dashboard/templates/plugins 2.html +185 -0
  31. package/src/dashboard/templates/protocol 2.html +199 -0
  32. package/src/dashboard/templates/rules 2.html +246 -0
  33. package/src/dashboard/templates/sentiment 2.html +247 -0
  34. package/src/dashboard/templates/sessions 2.html +218 -0
  35. package/src/dashboard/templates/skills 2.html +329 -0
  36. package/src/dashboard/templates/somatic 2.html +73 -0
  37. package/src/dashboard/templates/triggers 2.html +133 -0
  38. package/src/dashboard/templates/trust 2.html +360 -0
  39. package/src/db/__init__ 2.py +259 -0
  40. package/src/db/_core 2.py +437 -0
  41. package/src/db/_credentials 2.py +124 -0
  42. package/src/db/_entities.py +1 -1
  43. package/src/db/_episodic 2.py +762 -0
  44. package/src/db/_evolution 2.py +54 -0
  45. package/src/db/_fts 2.py +406 -0
  46. package/src/db/_goal_profiles 2.py +376 -0
  47. package/src/db/_hot_context 2.py +660 -0
  48. package/src/db/_outcomes 2.py +800 -0
  49. package/src/db/_personal_scripts 2.py +582 -0
  50. package/src/db/_sessions 2.py +330 -0
  51. package/src/db/_tasks 2.py +91 -0
  52. package/src/db/_watchers 2.py +173 -0
  53. package/src/doctor/formatters 2.py +52 -0
  54. package/src/doctor/models 2.py +69 -0
  55. package/src/doctor/planes 2.py +87 -0
  56. package/src/doctor/providers/__init__ 2.py +1 -0
  57. package/src/doctor/providers/deep 2.py +367 -0
  58. package/src/evolution_cycle 2.py +519 -0
  59. package/src/hooks/auto_capture 2.py +208 -0
  60. package/src/hooks/caffeinate-guard 2.sh +8 -0
  61. package/src/hooks/capture-session 2.sh +21 -0
  62. package/src/hooks/capture-tool-logs 2.sh +158 -0
  63. package/src/hooks/daily-briefing-check 2.sh +33 -0
  64. package/src/hooks/heartbeat-enforcement 2.py +90 -0
  65. package/src/hooks/heartbeat-posttool 2.sh +18 -0
  66. package/src/hooks/inbox-hook 2.sh +76 -0
  67. package/src/hooks/post-compact 2.sh +152 -0
  68. package/src/hooks/pre-compact 2.sh +169 -0
  69. package/src/hooks/protocol-guardrail 2.sh +10 -0
  70. package/src/hooks/protocol-pretool-guardrail 2.sh +9 -0
  71. package/src/hooks/session-stop 2.sh +52 -0
  72. package/src/kg_populate 2.py +292 -0
  73. package/src/maintenance 2.py +53 -0
  74. package/src/memory_backends 2.py +71 -0
  75. package/src/migrate_embeddings 2.py +124 -0
  76. package/src/nexo_sdk 2.py +103 -0
  77. package/src/observability 2.py +199 -0
  78. package/src/plugin_loader 2.py +217 -0
  79. package/src/plugins/__init__ 2.py +0 -0
  80. package/src/plugins/agents.py +10 -3
  81. package/src/plugins/artifact_registry 2.py +450 -0
  82. package/src/plugins/backup 2.py +127 -0
  83. package/src/plugins/claims_tools 2.py +119 -0
  84. package/src/plugins/cognitive_memory 2.py +609 -0
  85. package/src/plugins/core_rules 2.py +252 -0
  86. package/src/plugins/cortex 2.py +1155 -0
  87. package/src/plugins/entities 2.py +67 -0
  88. package/src/plugins/episodic_memory 2.py +560 -0
  89. package/src/plugins/evolution 2.py +167 -0
  90. package/src/plugins/goal_engine 2.py +142 -0
  91. package/src/plugins/guard 2.py +862 -0
  92. package/src/plugins/impact 2.py +29 -0
  93. package/src/plugins/knowledge_graph_tools 2.py +137 -0
  94. package/src/plugins/media_memory_tools 2.py +98 -0
  95. package/src/plugins/memory_export 2.py +196 -0
  96. package/src/plugins/outcomes 2.py +130 -0
  97. package/src/plugins/personal_scripts 2.py +117 -0
  98. package/src/plugins/preferences 2.py +47 -0
  99. package/src/plugins/protocol 2.py +1449 -0
  100. package/src/plugins/schedule.py +2 -1
  101. package/src/plugins/simple_api 2.py +106 -0
  102. package/src/plugins/skills 2.py +341 -0
  103. package/src/plugins/state_watchers 2.py +79 -0
  104. package/src/plugins/update 2.py +986 -0
  105. package/src/plugins/user_state_tools 2.py +43 -0
  106. package/src/plugins/workflow 2.py +588 -0
  107. package/src/protocol_settings 2.py +59 -0
  108. package/src/public_contribution 2.py +466 -0
  109. package/src/public_evolution_queue 2.py +241 -0
  110. package/src/requirements 2.txt +14 -0
  111. package/src/requirements.txt +1 -1
  112. package/src/retroactive_learnings 2.py +373 -0
  113. package/src/rules/__init__ 2.py +0 -0
  114. package/src/rules/core-rules 2.json +331 -0
  115. package/src/rules/migrate 2.py +207 -0
  116. package/src/runtime_power 2.py +874 -0
  117. package/src/runtime_power.py +18 -1
  118. package/src/script_registry 2.py +1559 -0
  119. package/src/scripts/check-context 2.py +272 -0
  120. package/src/scripts/deep-sleep/apply_findings 2.py +2327 -0
  121. package/src/scripts/deep-sleep/collect 2.py +928 -0
  122. package/src/scripts/deep-sleep/extract 2.py +330 -0
  123. package/src/scripts/deep-sleep/extract-prompt 2.md +285 -0
  124. package/src/scripts/deep-sleep/synthesize 2.py +312 -0
  125. package/src/scripts/deep-sleep/synthesize-prompt 2.md +336 -0
  126. package/src/scripts/nexo-agent-run 2.py +75 -0
  127. package/src/scripts/nexo-auto-update 2.py +6 -0
  128. package/src/scripts/nexo-backup 2.sh +25 -0
  129. package/src/scripts/nexo-brain-activation 2.sh +140 -0
  130. package/src/scripts/nexo-catchup 2.py +300 -0
  131. package/src/scripts/nexo-cognitive-decay 2.py +257 -0
  132. package/src/scripts/nexo-cortex-cycle 2.py +293 -0
  133. package/src/scripts/nexo-cron-wrapper 2.sh +53 -0
  134. package/src/scripts/nexo-cron-wrapper.sh +7 -0
  135. package/src/scripts/nexo-daily-self-audit 2.py +2161 -0
  136. package/src/scripts/nexo-dashboard 2.sh +29 -0
  137. package/src/scripts/nexo-deep-sleep 2.sh +86 -0
  138. package/src/scripts/nexo-evolution-run 2.py +1664 -0
  139. package/src/scripts/nexo-followup-hygiene 2.py +139 -0
  140. package/src/scripts/nexo-hook-record 2.py +42 -0
  141. package/src/scripts/nexo-immune 2.py +936 -0
  142. package/src/scripts/nexo-impact-scorer 2.py +117 -0
  143. package/src/scripts/nexo-inbox-hook 2.sh +74 -0
  144. package/src/scripts/nexo-install 2.py +6 -0
  145. package/src/scripts/nexo-learning-housekeep 2.py +401 -0
  146. package/src/scripts/nexo-learning-validator 2.py +266 -0
  147. package/src/scripts/nexo-migrate 2.py +260 -0
  148. package/src/scripts/nexo-outcome-checker 2.py +127 -0
  149. package/src/scripts/nexo-postmortem-consolidator 2.py +456 -0
  150. package/src/scripts/nexo-pre-commit 2.py +120 -0
  151. package/src/scripts/nexo-prevent-sleep 2.sh +35 -0
  152. package/src/scripts/nexo-proactive-dashboard 2.py +354 -0
  153. package/src/scripts/nexo-reflection 2.py +256 -0
  154. package/src/scripts/nexo-runtime-preflight 2.py +274 -0
  155. package/src/scripts/nexo-sleep 2.py +631 -0
  156. package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
  157. package/src/scripts/nexo-sync-clients 2.py +16 -0
  158. package/src/scripts/nexo-synthesis 2.py +475 -0
  159. package/src/scripts/nexo-tcc-approve 2.sh +79 -0
  160. package/src/scripts/nexo-update 2.sh +306 -0
  161. package/src/scripts/nexo-watchdog 2.sh +1207 -0
  162. package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
  163. package/src/scripts/rehydrate_learnings_from_archive 2.py +245 -0
  164. package/src/server 2.py +1296 -0
  165. package/src/skills/run-nexo-audit-phase/guide 2.md +43 -0
  166. package/src/skills/run-nexo-audit-phase/skill 2.json +59 -0
  167. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +17 -0
  168. package/src/skills/run-nexo-core-fix-cycle/script 2.py +276 -0
  169. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +58 -0
  170. package/src/skills/run-release-final-audit/guide 2.md +16 -0
  171. package/src/skills/run-release-final-audit/script 2.py +259 -0
  172. package/src/skills/run-release-final-audit/skill 2.json +77 -0
  173. package/src/skills/run-runtime-doctor/guide 2.md +12 -0
  174. package/src/skills/run-runtime-doctor/script 2.py +21 -0
  175. package/src/skills/run-runtime-doctor/skill 2.json +25 -0
  176. package/src/skills_runtime 2.py +932 -0
  177. package/src/state_watchers_runtime 2.py +475 -0
  178. package/src/storage_router 2.py +32 -0
  179. package/src/system_catalog 2.py +786 -0
  180. package/src/tools_coordination 2.py +103 -0
  181. package/src/tools_credentials 2.py +68 -0
  182. package/src/tools_drive 2.py +487 -0
  183. package/src/tools_hot_context 2.py +163 -0
  184. package/src/tools_learnings 2.py +612 -0
  185. package/src/tools_menu 2.py +229 -0
  186. package/src/tools_reminders 2.py +88 -0
  187. package/src/tools_reminders_crud 2.py +363 -0
  188. package/src/tools_sessions 2.py +1054 -0
  189. package/src/tools_system_catalog 2.py +19 -0
  190. package/src/tools_task_history 2.py +57 -0
  191. package/src/tools_transcripts 2.py +98 -0
  192. package/src/transcript_utils 2.py +412 -0
  193. package/src/user_context 2.py +46 -0
  194. package/src/user_data_portability 2.py +328 -0
  195. package/src/user_state_model 2.py +170 -0
  196. package/templates/CLAUDE.md 2.template +108 -0
  197. package/templates/CODEX.AGENTS.md 2.template +66 -0
  198. package/templates/launchagents/README 2.md +132 -0
  199. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +39 -0
  200. package/templates/launchagents/com.nexo.auto-close-sessions.plist +1 -1
  201. package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
  202. package/templates/launchagents/com.nexo.catchup.plist +1 -1
  203. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +40 -0
  204. package/templates/launchagents/com.nexo.dashboard 2.plist +43 -0
  205. package/templates/launchagents/com.nexo.dashboard.plist +1 -1
  206. package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
  207. package/templates/launchagents/com.nexo.deep-sleep.plist +1 -1
  208. package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
  209. package/templates/launchagents/com.nexo.evolution.plist +1 -1
  210. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
  211. package/templates/launchagents/com.nexo.followup-hygiene.plist +1 -1
  212. package/templates/launchagents/com.nexo.immune 2.plist +41 -0
  213. package/templates/launchagents/com.nexo.immune.plist +1 -1
  214. package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
  215. package/templates/launchagents/com.nexo.postmortem.plist +1 -1
  216. package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
  217. package/templates/launchagents/com.nexo.self-audit.plist +1 -1
  218. package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
  219. package/templates/launchagents/com.nexo.synthesis.plist +1 -1
  220. package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
  221. package/templates/launchagents/com.nexo.watchdog.plist +1 -1
  222. package/templates/nexo_helper 2.py +301 -0
  223. package/templates/openclaw 2.json +13 -0
  224. package/templates/plugin-template 2.py +40 -0
  225. package/templates/script-template 2.py +59 -0
  226. package/templates/script-template 2.sh +13 -0
  227. package/templates/script-template.py +5 -4
  228. package/templates/skill-script-template 2.py +48 -0
  229. package/templates/skill-script-template.py +2 -1
  230. package/templates/skill-template 2.md +33 -0
@@ -0,0 +1,252 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Dream Journal{% endblock %}
4
+ {% block page_title %}Dream Journal{% endblock %}
5
+
6
+ {% block head %}
7
+ <style>
8
+ .dream-bg {
9
+ position: fixed;
10
+ top: 0; left: 0; right: 0; bottom: 0;
11
+ pointer-events: none;
12
+ z-index: 0;
13
+ overflow: hidden;
14
+ }
15
+ .star {
16
+ position: absolute;
17
+ width: 2px; height: 2px;
18
+ background: white;
19
+ border-radius: 50%;
20
+ opacity: 0;
21
+ animation: twinkle var(--duration) ease-in-out var(--delay) infinite;
22
+ }
23
+ @keyframes twinkle {
24
+ 0%, 100% { opacity: 0; }
25
+ 50% { opacity: var(--max-opacity); }
26
+ }
27
+ @keyframes floatUp {
28
+ 0% { opacity: 0; transform: translateY(20px) scale(0.95); }
29
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
30
+ }
31
+ .dream-card {
32
+ animation: floatUp 0.6s ease-out forwards;
33
+ opacity: 0;
34
+ transition: border-color 0.3s, box-shadow 0.3s, transform 0.3s;
35
+ }
36
+ .dream-card:hover {
37
+ border-color: rgba(139, 92, 246, 0.4);
38
+ box-shadow: 0 0 30px rgba(139, 92, 246, 0.12);
39
+ transform: translateY(-2px);
40
+ }
41
+ .insight-glow {
42
+ text-shadow: 0 0 20px rgba(139, 92, 246, 0.3);
43
+ }
44
+ .connection-line {
45
+ background: linear-gradient(90deg, rgba(99, 102, 241, 0.3), rgba(139, 92, 246, 0.6), rgba(99, 102, 241, 0.3));
46
+ height: 1px;
47
+ position: relative;
48
+ }
49
+ .connection-line::after {
50
+ content: '';
51
+ position: absolute;
52
+ top: -1px; left: 0; right: 0; height: 3px;
53
+ background: linear-gradient(90deg, transparent, rgba(139, 92, 246, 0.4), transparent);
54
+ animation: shimmer 3s ease-in-out infinite;
55
+ }
56
+ @keyframes shimmer {
57
+ 0%, 100% { opacity: 0.3; }
58
+ 50% { opacity: 1; }
59
+ }
60
+ .night-divider {
61
+ position: relative;
62
+ text-align: center;
63
+ margin: 2rem 0 1.5rem;
64
+ }
65
+ .night-divider::before {
66
+ content: '';
67
+ position: absolute;
68
+ left: 0; right: 0; top: 50%;
69
+ height: 1px;
70
+ background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.3), transparent);
71
+ }
72
+ .night-divider span {
73
+ position: relative;
74
+ background: #030712;
75
+ padding: 0 1rem;
76
+ }
77
+ </style>
78
+ {% endblock %}
79
+
80
+ {% block content %}
81
+ <!-- Star background -->
82
+ <div class="dream-bg" id="star-field"></div>
83
+
84
+ <!-- Stats header -->
85
+ <div class="relative z-10 grid grid-cols-2 sm:grid-cols-3 gap-4 mb-6">
86
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 text-center">
87
+ <div class="text-xs uppercase tracking-wider text-slate-500 font-semibold mb-2">Total Dreams</div>
88
+ <div class="text-3xl font-display font-bold text-indigo-300" id="stat-total">--</div>
89
+ </div>
90
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 text-center">
91
+ <div class="text-xs uppercase tracking-wider text-slate-500 font-semibold mb-2">Insights Generated</div>
92
+ <div class="text-3xl font-display font-bold text-violet-300" id="stat-insights">--</div>
93
+ </div>
94
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 text-center sm:col-span-1 col-span-2">
95
+ <div class="text-xs uppercase tracking-wider text-slate-500 font-semibold mb-2">Sleep Entries</div>
96
+ <div class="text-3xl font-display font-bold text-slate-300" id="stat-sleep">--</div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Dream Timeline -->
101
+ <div class="relative z-10 mb-8" id="dream-timeline">
102
+ <h2 class="text-xs uppercase tracking-wider text-slate-500 font-semibold mb-4 flex items-center gap-2">
103
+ <svg class="w-4 h-4 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
104
+ Dream Timeline
105
+ </h2>
106
+ <div id="dreams-container" class="space-y-1">
107
+ <div class="text-xs text-slate-600 text-center py-12">Loading dream data...</div>
108
+ </div>
109
+ </div>
110
+
111
+ <!-- Sleep Diary -->
112
+ <div class="relative z-10" id="sleep-section">
113
+ <h2 class="text-xs uppercase tracking-wider text-slate-500 font-semibold mb-4 flex items-center gap-2">
114
+ <svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
115
+ Sleep Diary
116
+ </h2>
117
+ <div id="sleep-container" class="space-y-3">
118
+ <div class="text-xs text-slate-600 text-center py-8">Loading sleep entries...</div>
119
+ </div>
120
+ </div>
121
+ {% endblock %}
122
+
123
+ {% block scripts %}
124
+ <script>
125
+ const REFRESH_MS = 60000;
126
+
127
+ // Generate star field
128
+ function createStars() {
129
+ const field = document.getElementById('star-field');
130
+ for (let i = 0; i < 80; i++) {
131
+ const star = document.createElement('div');
132
+ star.className = 'star';
133
+ star.style.left = Math.random() * 100 + '%';
134
+ star.style.top = Math.random() * 100 + '%';
135
+ star.style.setProperty('--duration', (2 + Math.random() * 4) + 's');
136
+ star.style.setProperty('--delay', (Math.random() * 3) + 's');
137
+ star.style.setProperty('--max-opacity', (0.2 + Math.random() * 0.5).toString());
138
+ const size = 1 + Math.random() * 2;
139
+ star.style.width = size + 'px';
140
+ star.style.height = size + 'px';
141
+ field.appendChild(star);
142
+ }
143
+ }
144
+
145
+ function groupByNight(dreams) {
146
+ const groups = {};
147
+ for (const d of dreams) {
148
+ const date = (d.created_at || '').split(/[T ]/)[0] || 'Unknown';
149
+ if (!groups[date]) groups[date] = [];
150
+ groups[date].push(d);
151
+ }
152
+ return groups;
153
+ }
154
+
155
+ function renderDreamCard(d, idx) {
156
+ const insight = d.insight_content || d.insight || 'No insight recorded';
157
+ const memA = d.memory_a_content || `Memory #${d.memory_a_id || '?'}`;
158
+ const memB = d.memory_b_content || `Memory #${d.memory_b_id || '?'}`;
159
+ const created = d.created_at ? relativeTime(d.created_at) : '';
160
+ const delay = idx * 0.1;
161
+
162
+ return `<div class="dream-card bg-slate-900/50 border border-indigo-500/20 rounded-xl p-5 mb-3" style="animation-delay:${delay}s">
163
+ <div class="flex items-center justify-between mb-3">
164
+ <div class="flex items-center gap-2">
165
+ <svg class="w-4 h-4 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
166
+ <span class="text-xs text-indigo-400 font-medium">Dream Connection</span>
167
+ </div>
168
+ <span class="text-[10px] text-slate-600 font-mono">${escapeHtml(created)}</span>
169
+ </div>
170
+
171
+ <!-- Two memories connected -->
172
+ <div class="flex items-stretch gap-0 mb-4">
173
+ <div class="flex-1 bg-slate-800/50 border border-slate-700/50 rounded-lg p-3">
174
+ <div class="text-[10px] uppercase tracking-wider text-slate-500 mb-1">Memory A</div>
175
+ <p class="text-xs text-slate-300 leading-relaxed">${escapeHtml(typeof memA === 'string' ? (memA.length > 120 ? memA.substring(0, 120) + '...' : memA) : String(memA))}</p>
176
+ </div>
177
+ <div class="flex flex-col items-center justify-center px-3">
178
+ <div class="w-8 connection-line"></div>
179
+ <svg class="w-4 h-4 text-violet-400 my-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
180
+ <div class="w-8 connection-line"></div>
181
+ </div>
182
+ <div class="flex-1 bg-slate-800/50 border border-slate-700/50 rounded-lg p-3">
183
+ <div class="text-[10px] uppercase tracking-wider text-slate-500 mb-1">Memory B</div>
184
+ <p class="text-xs text-slate-300 leading-relaxed">${escapeHtml(typeof memB === 'string' ? (memB.length > 120 ? memB.substring(0, 120) + '...' : memB) : String(memB))}</p>
185
+ </div>
186
+ </div>
187
+
188
+ <!-- Insight -->
189
+ <div class="bg-violet-500/5 border border-violet-500/20 rounded-lg p-3">
190
+ <div class="text-[10px] uppercase tracking-wider text-violet-400 font-semibold mb-1">Insight</div>
191
+ <p class="text-sm text-violet-200 leading-relaxed insight-glow">${escapeHtml(insight)}</p>
192
+ </div>
193
+ </div>`;
194
+ }
195
+
196
+ function renderSleepEntry(entry) {
197
+ const content = entry.content || entry.summary || 'Sleep cycle completed';
198
+ const created = entry.created_at ? relativeTime(entry.created_at) : '';
199
+ return `<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 transition-all hover:border-slate-700/50">
200
+ <div class="flex items-center justify-between mb-2">
201
+ <span class="text-[10px] px-1.5 py-0.5 rounded bg-slate-700 text-slate-400 font-medium">sleep</span>
202
+ <span class="text-[10px] text-slate-600 font-mono">${escapeHtml(created)}</span>
203
+ </div>
204
+ <p class="text-sm text-slate-300 leading-relaxed">${escapeHtml(content)}</p>
205
+ </div>`;
206
+ }
207
+
208
+ async function loadDreams() {
209
+ const data = await fetchJSON('/api/dreams');
210
+ if (!data) return;
211
+
212
+ const dreams = data.dreams || [];
213
+ const sleepEntries = data.sleep_entries || [];
214
+
215
+ // Stats
216
+ document.getElementById('stat-total').textContent = formatNumber(dreams.length);
217
+ document.getElementById('stat-insights').textContent = formatNumber(dreams.filter(d => d.insight_content || d.insight_id).length);
218
+ document.getElementById('stat-sleep').textContent = formatNumber(sleepEntries.length);
219
+
220
+ // Timeline grouped by night
221
+ const container = document.getElementById('dreams-container');
222
+ if (dreams.length === 0) {
223
+ container.innerHTML = '<div class="text-center py-12"><div class="text-2xl mb-2">&#127769;</div><div class="text-sm text-slate-500">No dreams recorded yet. Dreams are generated during deep sleep cycles.</div></div>';
224
+ return;
225
+ }
226
+
227
+ const groups = groupByNight(dreams);
228
+ let html = '';
229
+ let cardIdx = 0;
230
+ for (const [night, nightDreams] of Object.entries(groups).sort((a, b) => b[0].localeCompare(a[0]))) {
231
+ const d = new Date(night + 'T00:00:00');
232
+ const label = isNaN(d.getTime()) ? night : d.toLocaleDateString('en', { weekday: 'long', month: 'short', day: 'numeric' });
233
+ html += `<div class="night-divider"><span class="text-xs text-indigo-400/60 font-mono">${escapeHtml(label)} &mdash; ${nightDreams.length} dream${nightDreams.length > 1 ? 's' : ''}</span></div>`;
234
+ for (const dream of nightDreams) {
235
+ html += renderDreamCard(dream, cardIdx++);
236
+ }
237
+ }
238
+ container.innerHTML = html;
239
+
240
+ // Sleep entries
241
+ const sleepContainer = document.getElementById('sleep-container');
242
+ sleepContainer.innerHTML = sleepEntries.length === 0
243
+ ? '<div class="text-xs text-slate-600 text-center py-6">No sleep diary entries</div>'
244
+ : sleepEntries.map(renderSleepEntry).join('');
245
+ }
246
+
247
+ // Init
248
+ createStars();
249
+ loadDreams();
250
+ setInterval(loadDreams, REFRESH_MS);
251
+ </script>
252
+ {% endblock %}
@@ -0,0 +1,160 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Email Monitor{% endblock %}
4
+ {% block page_title %}Email Monitor{% endblock %}
5
+
6
+ {% block content %}
7
+ <div class="space-y-5">
8
+ <!-- Stats cards -->
9
+ <div class="grid grid-cols-4 gap-4">
10
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
11
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-2">Total Emails</div>
12
+ <div class="text-xl font-mono font-semibold text-slate-200" id="email-total">--</div>
13
+ </div>
14
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
15
+ <div class="text-xs uppercase tracking-wider text-emerald-400/70 font-medium mb-2">Processed</div>
16
+ <div class="text-xl font-mono font-semibold text-emerald-400" id="email-processed">--</div>
17
+ </div>
18
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
19
+ <div class="text-xs uppercase tracking-wider text-amber-400/70 font-medium mb-2">Pending</div>
20
+ <div class="text-xl font-mono font-semibold text-amber-400" id="email-pending">--</div>
21
+ </div>
22
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
23
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-2">Processing Rate</div>
24
+ <div class="text-xl font-mono font-semibold text-violet-400" id="email-rate">--%</div>
25
+ </div>
26
+ </div>
27
+
28
+ <!-- Processing funnel -->
29
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
30
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Processing Funnel</div>
31
+ <div class="flex items-end gap-1 justify-center" id="funnel" style="height: 80px;">
32
+ <!-- JS fills -->
33
+ </div>
34
+ </div>
35
+
36
+ <!-- Recent emails table -->
37
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
38
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Recent Emails</div>
39
+ <div class="overflow-x-auto">
40
+ <table class="w-full text-left">
41
+ <thead>
42
+ <tr class="border-b border-slate-800/50">
43
+ <th class="text-[10px] uppercase tracking-wider text-slate-500 font-medium pb-2 pr-4">Status</th>
44
+ <th class="text-[10px] uppercase tracking-wider text-slate-500 font-medium pb-2 pr-4">From</th>
45
+ <th class="text-[10px] uppercase tracking-wider text-slate-500 font-medium pb-2 pr-4">Subject</th>
46
+ <th class="text-[10px] uppercase tracking-wider text-slate-500 font-medium pb-2 text-right">Received</th>
47
+ </tr>
48
+ </thead>
49
+ <tbody id="email-table" class="divide-y divide-slate-800/30">
50
+ <tr><td colspan="4" class="text-xs text-slate-600 py-8 text-center">loading...</td></tr>
51
+ </tbody>
52
+ </table>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- Thread grouping -->
57
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
58
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Thread Groups</div>
59
+ <div class="space-y-2" id="thread-groups">
60
+ <div class="text-xs text-slate-600 py-4 text-center">loading...</div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ {% endblock %}
65
+
66
+ {% block scripts %}
67
+ <script>
68
+ const EMAIL_STATUS = {
69
+ processed: { bg: 'bg-emerald-500/10', text: 'text-emerald-400', dot: 'bg-emerald-500' },
70
+ pending: { bg: 'bg-amber-500/10', text: 'text-amber-400', dot: 'bg-amber-500' },
71
+ failed: { bg: 'bg-red-500/10', text: 'text-red-400', dot: 'bg-red-500' },
72
+ skipped: { bg: 'bg-slate-500/10', text: 'text-slate-400', dot: 'bg-slate-600' },
73
+ };
74
+
75
+ function renderFunnel(stats) {
76
+ const container = document.getElementById('funnel');
77
+ const total = stats.total || 1;
78
+ const processed = stats.processed || 0;
79
+ const pending = stats.pending || 0;
80
+ const failed = total - processed - pending;
81
+
82
+ const stages = [
83
+ { label: 'Received', value: total, color: 'bg-violet-500' },
84
+ { label: 'Processed', value: processed, color: 'bg-emerald-500' },
85
+ { label: 'Pending', value: pending, color: 'bg-amber-500' },
86
+ ];
87
+ if (failed > 0) stages.push({ label: 'Failed', value: failed, color: 'bg-red-500' });
88
+
89
+ const maxVal = Math.max(...stages.map(s => s.value), 1);
90
+
91
+ container.innerHTML = stages.map(s => {
92
+ const pct = Math.max(8, (s.value / maxVal) * 100);
93
+ return `<div class="flex-1 flex flex-col items-center gap-1">
94
+ <span class="text-xs font-mono text-slate-300">${s.value}</span>
95
+ <div class="w-full rounded-t-md ${s.color} transition-all duration-500" style="height:${pct}%"></div>
96
+ <span class="text-[10px] text-slate-500">${s.label}</span>
97
+ </div>`;
98
+ }).join('');
99
+ }
100
+
101
+ function renderTable(emails) {
102
+ const tbody = document.getElementById('email-table');
103
+ if (!emails || !emails.length) {
104
+ tbody.innerHTML = '<tr><td colspan="4" class="text-xs text-slate-600 py-8 text-center">No emails found</td></tr>';
105
+ return;
106
+ }
107
+
108
+ tbody.innerHTML = emails.map(e => {
109
+ const status = e.status || 'pending';
110
+ const sc = EMAIL_STATUS[status] || EMAIL_STATUS.pending;
111
+ return `<tr class="hover:bg-slate-800/20 transition-colors">
112
+ <td class="py-2.5 pr-4"><span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-[10px] font-medium ${sc.bg} ${sc.text}"><span class="w-1.5 h-1.5 rounded-full ${sc.dot}"></span>${escapeHtml(status)}</span></td>
113
+ <td class="py-2.5 pr-4 text-xs text-slate-300 truncate max-w-[200px]">${escapeHtml(e.from_addr || '--')}</td>
114
+ <td class="py-2.5 pr-4 text-xs text-slate-400 truncate max-w-[300px]">${escapeHtml(e.subject || '(no subject)')}</td>
115
+ <td class="py-2.5 text-xs text-slate-500 text-right whitespace-nowrap">${relativeTime(e.received_at)}</td>
116
+ </tr>`;
117
+ }).join('');
118
+ }
119
+
120
+ function renderThreads(threads) {
121
+ const container = document.getElementById('thread-groups');
122
+ if (!threads || !threads.length) {
123
+ container.innerHTML = '<div class="text-xs text-slate-600 py-4 text-center">No thread data</div>';
124
+ return;
125
+ }
126
+
127
+ container.innerHTML = threads.map(t => {
128
+ const count = t.count || t.messages || 1;
129
+ return `<div class="flex items-center gap-3 py-2 px-3 rounded-lg hover:bg-slate-800/20 transition-colors">
130
+ <svg class="w-4 h-4 text-slate-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg>
131
+ <div class="flex-1 min-w-0">
132
+ <div class="text-xs text-slate-300 truncate">${escapeHtml(t.subject || t.thread_id || '--')}</div>
133
+ <div class="text-[10px] text-slate-500">${count} message${count !== 1 ? 's' : ''}</div>
134
+ </div>
135
+ <span class="text-[10px] text-slate-600">${relativeTime(t.last_at || t.updated_at)}</span>
136
+ </div>`;
137
+ }).join('');
138
+ }
139
+
140
+ async function loadEmail() {
141
+ const data = await fetchJSON('/api/email');
142
+ if (!data) return;
143
+
144
+ const stats = data.stats || {};
145
+ document.getElementById('email-total').textContent = formatNumber(stats.total || 0);
146
+ document.getElementById('email-processed').textContent = formatNumber(stats.processed || 0);
147
+ document.getElementById('email-pending').textContent = formatNumber(stats.pending || 0);
148
+
149
+ const rate = stats.total ? Math.round((stats.processed / stats.total) * 100) : 0;
150
+ document.getElementById('email-rate').textContent = rate + '%';
151
+
152
+ renderFunnel(stats);
153
+ renderTable(data.recent || []);
154
+ renderThreads(data.threads || []);
155
+ }
156
+
157
+ loadEmail();
158
+ setInterval(loadEmail, 60000);
159
+ </script>
160
+ {% endblock %}
@@ -0,0 +1,189 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Evolution Cockpit{% endblock %}
4
+ {% block page_title %}Evolution Cockpit{% endblock %}
5
+
6
+ {% block content %}
7
+ <div class="space-y-5">
8
+ <!-- Top stats -->
9
+ <div class="grid grid-cols-5 gap-4" id="dim-cards">
10
+ <!-- Populated by JS -->
11
+ </div>
12
+
13
+ <!-- Main row: Radar + History -->
14
+ <div class="grid grid-cols-2 gap-5">
15
+ <!-- Radar Chart -->
16
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
17
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Dimension Radar</div>
18
+ <div class="flex items-center justify-center" style="min-height: 320px;">
19
+ <svg id="radar-svg" viewBox="0 0 400 400" class="w-full max-w-sm"></svg>
20
+ </div>
21
+ </div>
22
+
23
+ <!-- Score History -->
24
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
25
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Score History</div>
26
+ <div class="space-y-3" id="score-history">
27
+ <div class="text-xs text-slate-600 py-8 text-center">loading...</div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Evolution Log Timeline -->
33
+ <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
34
+ <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Evolution Log</div>
35
+ <div class="space-y-3" id="evo-log">
36
+ <div class="text-xs text-slate-600 py-4 text-center">loading...</div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ {% endblock %}
41
+
42
+ {% block scripts %}
43
+ <script>
44
+ const DIMS = ['autonomy', 'reasoning', 'memory', 'trust', 'creativity'];
45
+ const DIM_COLORS = {
46
+ autonomy: '#7C3AED', reasoning: '#3B82F6', memory: '#EC4899',
47
+ trust: '#10B981', creativity: '#F59E0B'
48
+ };
49
+
50
+ function drawRadar(dimensions) {
51
+ const svg = document.getElementById('radar-svg');
52
+ const cx = 200, cy = 200, maxR = 150;
53
+ const n = DIMS.length;
54
+ const angleStep = (2 * Math.PI) / n;
55
+
56
+ let html = '';
57
+ // Grid rings
58
+ for (let ring = 1; ring <= 5; ring++) {
59
+ const r = (ring / 5) * maxR;
60
+ let points = '';
61
+ for (let i = 0; i < n; i++) {
62
+ const a = -Math.PI / 2 + i * angleStep;
63
+ points += `${cx + r * Math.cos(a)},${cy + r * Math.sin(a)} `;
64
+ }
65
+ html += `<polygon points="${points}" fill="none" stroke="rgba(51,65,85,0.4)" stroke-width="1"/>`;
66
+ if (ring === 5) {
67
+ html += `<text x="${cx + 4}" y="${cy - r - 4}" class="text-[10px]" fill="#64748b">100</text>`;
68
+ }
69
+ }
70
+
71
+ // Axes
72
+ for (let i = 0; i < n; i++) {
73
+ const a = -Math.PI / 2 + i * angleStep;
74
+ const ex = cx + maxR * Math.cos(a);
75
+ const ey = cy + maxR * Math.sin(a);
76
+ html += `<line x1="${cx}" y1="${cy}" x2="${ex}" y2="${ey}" stroke="rgba(51,65,85,0.3)" stroke-width="1"/>`;
77
+ const lx = cx + (maxR + 20) * Math.cos(a);
78
+ const ly = cy + (maxR + 20) * Math.sin(a);
79
+ html += `<text x="${lx}" y="${ly}" text-anchor="middle" dominant-baseline="middle" fill="${DIM_COLORS[DIMS[i]]}" class="text-xs font-display font-semibold" style="font-size:11px">${DIMS[i]}</text>`;
80
+ }
81
+
82
+ // Data polygon
83
+ if (dimensions) {
84
+ let points = '';
85
+ for (let i = 0; i < n; i++) {
86
+ const score = dimensions[DIMS[i]]?.score ?? 0;
87
+ const r = (score / 100) * maxR;
88
+ const a = -Math.PI / 2 + i * angleStep;
89
+ points += `${cx + r * Math.cos(a)},${cy + r * Math.sin(a)} `;
90
+ }
91
+ html += `<polygon points="${points}" fill="rgba(124,58,237,0.15)" stroke="#7C3AED" stroke-width="2"/>`;
92
+
93
+ // Data points
94
+ for (let i = 0; i < n; i++) {
95
+ const score = dimensions[DIMS[i]]?.score ?? 0;
96
+ const r = (score / 100) * maxR;
97
+ const a = -Math.PI / 2 + i * angleStep;
98
+ const px = cx + r * Math.cos(a);
99
+ const py = cy + r * Math.sin(a);
100
+ html += `<circle cx="${px}" cy="${py}" r="4" fill="${DIM_COLORS[DIMS[i]]}" stroke="#0f172a" stroke-width="2"/>`;
101
+ }
102
+ }
103
+
104
+ svg.innerHTML = html;
105
+ }
106
+
107
+ function renderDimCards(dimensions) {
108
+ const container = document.getElementById('dim-cards');
109
+ if (!dimensions) { container.innerHTML = '<div class="col-span-5 text-xs text-slate-600 text-center py-4">No dimension data</div>'; return; }
110
+
111
+ container.innerHTML = DIMS.map(dim => {
112
+ const d = dimensions[dim] || {};
113
+ const score = d.score ?? 0;
114
+ const delta = d.delta ?? 0;
115
+ const deltaSign = delta > 0 ? '+' : '';
116
+ const deltaColor = delta > 0 ? 'text-emerald-400' : delta < 0 ? 'text-red-400' : 'text-slate-500';
117
+ const color = DIM_COLORS[dim];
118
+ return `<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
119
+ <div class="flex items-center gap-2 mb-2">
120
+ <span class="w-2 h-2 rounded-full" style="background:${color}"></span>
121
+ <span class="text-xs uppercase tracking-wider text-slate-400 font-medium">${dim}</span>
122
+ </div>
123
+ <div class="flex items-end gap-2">
124
+ <span class="text-xl font-mono font-semibold text-slate-200">${score.toFixed(1)}</span>
125
+ <span class="text-xs font-mono ${deltaColor} mb-0.5">${deltaSign}${delta.toFixed(1)}</span>
126
+ </div>
127
+ <div class="mt-2 h-1 bg-slate-800 rounded-full overflow-hidden">
128
+ <div class="h-full rounded-full transition-all duration-700" style="width:${Math.min(100, score)}%;background:${color}"></div>
129
+ </div>
130
+ <div class="mt-1 text-[10px] text-slate-600">${d.measured_at ? relativeTime(d.measured_at) : '--'}</div>
131
+ </div>`;
132
+ }).join('');
133
+ }
134
+
135
+ function renderHistory(metrics) {
136
+ const container = document.getElementById('score-history');
137
+ if (!metrics || !metrics.length) { container.innerHTML = '<div class="text-xs text-slate-600 py-8 text-center">No history data</div>'; return; }
138
+
139
+ const maxEntries = 20;
140
+ const items = metrics.slice(0, maxEntries);
141
+ container.innerHTML = items.map(m => {
142
+ const dim = m.dimension || m.name || '--';
143
+ const score = m.score ?? m.value ?? 0;
144
+ const color = DIM_COLORS[dim] || '#64748b';
145
+ return `<div class="flex items-center gap-3">
146
+ <span class="w-16 text-[10px] text-slate-500 font-mono">${relativeTime(m.measured_at || m.created_at)}</span>
147
+ <span class="w-2 h-2 rounded-full flex-shrink-0" style="background:${color}"></span>
148
+ <span class="text-xs text-slate-400 flex-1">${escapeHtml(dim)}</span>
149
+ <span class="text-xs font-mono text-slate-300">${score.toFixed(1)}</span>
150
+ </div>`;
151
+ }).join('');
152
+ }
153
+
154
+ function renderLog(logs) {
155
+ const container = document.getElementById('evo-log');
156
+ if (!logs || !logs.length) { container.innerHTML = '<div class="text-xs text-slate-600 py-4 text-center">No evolution logs</div>'; return; }
157
+
158
+ container.innerHTML = logs.map(log => {
159
+ const status = log.status || 'proposed';
160
+ const statusColors = { approved: 'bg-emerald-500/10 text-emerald-400', rejected: 'bg-red-500/10 text-red-400', proposed: 'bg-amber-500/10 text-amber-400' };
161
+ const sc = statusColors[status] || statusColors.proposed;
162
+ return `<div class="flex items-start gap-3 py-2 border-b border-slate-800/30 last:border-0">
163
+ <div class="w-1 h-1 rounded-full bg-slate-600 mt-1.5 flex-shrink-0"></div>
164
+ <div class="flex-1 min-w-0">
165
+ <div class="flex items-center gap-2 flex-wrap">
166
+ <span class="text-xs text-slate-300">${escapeHtml(log.proposal || log.description || log.text || '--')}</span>
167
+ <span class="px-1.5 py-0.5 rounded text-[10px] font-medium ${sc}">${escapeHtml(status)}</span>
168
+ </div>
169
+ <div class="text-[10px] text-slate-600 mt-0.5">${escapeHtml(log.dimension || '')} ${log.created_at ? '-- ' + relativeTime(log.created_at) : ''}</div>
170
+ </div>
171
+ </div>`;
172
+ }).join('');
173
+ }
174
+
175
+ async function loadEvolution() {
176
+ const data = await fetchJSON('/api/evolution');
177
+ if (!data) return;
178
+
179
+ const dimensions = data.dimensions || {};
180
+ drawRadar(dimensions);
181
+ renderDimCards(dimensions);
182
+ renderHistory(data.metrics || []);
183
+ renderLog(data.logs || []);
184
+ }
185
+
186
+ loadEvolution();
187
+ setInterval(loadEvolution, 60000);
188
+ </script>
189
+ {% endblock %}