@mooncompany/uplink-chat 0.5.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.

Potentially problematic release.


This version of @mooncompany/uplink-chat might be problematic. Click here for more details.

Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/bin/uplink.js +279 -0
  4. package/middleware/error-handler.js +69 -0
  5. package/package.json +93 -0
  6. package/public/css/agents.36b98c0f.css +1469 -0
  7. package/public/css/agents.css +1469 -0
  8. package/public/css/app.a6a7f8f5.css +2731 -0
  9. package/public/css/app.css +2731 -0
  10. package/public/css/artifacts.css +444 -0
  11. package/public/css/commands.css +55 -0
  12. package/public/css/connection.css +131 -0
  13. package/public/css/dashboard.css +233 -0
  14. package/public/css/developer.css +328 -0
  15. package/public/css/files.css +123 -0
  16. package/public/css/markdown.css +156 -0
  17. package/public/css/message-actions.css +278 -0
  18. package/public/css/mobile.css +614 -0
  19. package/public/css/panels-unified.css +483 -0
  20. package/public/css/premium.css +415 -0
  21. package/public/css/realtime.css +189 -0
  22. package/public/css/satellites.css +401 -0
  23. package/public/css/shortcuts.css +185 -0
  24. package/public/css/split-view.4def0262.css +673 -0
  25. package/public/css/split-view.css +673 -0
  26. package/public/css/theme-generator.css +391 -0
  27. package/public/css/themes.css +387 -0
  28. package/public/css/timestamps.css +54 -0
  29. package/public/css/variables.css +78 -0
  30. package/public/dist/bundle.b55050c4.js +15757 -0
  31. package/public/favicon.svg +24 -0
  32. package/public/img/agents/ada.png +0 -0
  33. package/public/img/agents/clarice.png +0 -0
  34. package/public/img/agents/dennis-nedry.png +0 -0
  35. package/public/img/agents/elliot-alderson.png +0 -0
  36. package/public/img/agents/main.png +0 -0
  37. package/public/img/agents/scotty.png +0 -0
  38. package/public/img/agents/top-flight-security.png +0 -0
  39. package/public/index.html +1083 -0
  40. package/public/js/agents-data.js +234 -0
  41. package/public/js/agents-ui.js +72 -0
  42. package/public/js/agents.js +1525 -0
  43. package/public/js/app.js +79 -0
  44. package/public/js/appearance-settings.js +111 -0
  45. package/public/js/artifacts.js +432 -0
  46. package/public/js/audio-queue.js +168 -0
  47. package/public/js/bootstrap.js +54 -0
  48. package/public/js/chat.js +1211 -0
  49. package/public/js/commands.js +581 -0
  50. package/public/js/connection-api.js +121 -0
  51. package/public/js/connection.js +1231 -0
  52. package/public/js/context-tracker.js +271 -0
  53. package/public/js/core.js +172 -0
  54. package/public/js/dashboard.js +452 -0
  55. package/public/js/developer.js +432 -0
  56. package/public/js/encryption.js +124 -0
  57. package/public/js/errors.js +122 -0
  58. package/public/js/event-bus.js +77 -0
  59. package/public/js/fetch-utils.js +171 -0
  60. package/public/js/file-handler.js +229 -0
  61. package/public/js/files.js +352 -0
  62. package/public/js/gateway-chat.js +538 -0
  63. package/public/js/logger.js +112 -0
  64. package/public/js/markdown.js +190 -0
  65. package/public/js/message-actions.js +431 -0
  66. package/public/js/message-renderer.js +288 -0
  67. package/public/js/missed-messages.js +235 -0
  68. package/public/js/mobile-debug.js +95 -0
  69. package/public/js/notifications.js +367 -0
  70. package/public/js/offline-queue.js +178 -0
  71. package/public/js/onboarding.js +543 -0
  72. package/public/js/panels.js +156 -0
  73. package/public/js/premium.js +412 -0
  74. package/public/js/realtime-voice.js +844 -0
  75. package/public/js/satellite-sync.js +256 -0
  76. package/public/js/satellite-ui.js +175 -0
  77. package/public/js/satellites.js +1516 -0
  78. package/public/js/settings.js +1087 -0
  79. package/public/js/shortcuts.js +381 -0
  80. package/public/js/split-chat.js +1234 -0
  81. package/public/js/split-resize.js +211 -0
  82. package/public/js/splitview.js +340 -0
  83. package/public/js/storage.js +408 -0
  84. package/public/js/streaming-handler.js +324 -0
  85. package/public/js/stt-settings.js +316 -0
  86. package/public/js/theme-generator.js +661 -0
  87. package/public/js/themes.js +164 -0
  88. package/public/js/timestamps.js +198 -0
  89. package/public/js/tts-settings.js +575 -0
  90. package/public/js/ui.js +267 -0
  91. package/public/js/update-notifier.js +143 -0
  92. package/public/js/utils/constants.js +165 -0
  93. package/public/js/utils/sanitize.js +93 -0
  94. package/public/js/utils/sse-parser.js +195 -0
  95. package/public/js/voice.js +883 -0
  96. package/public/manifest.json +58 -0
  97. package/public/moon_texture.jpg +0 -0
  98. package/public/sw.js +221 -0
  99. package/public/three.min.js +6 -0
  100. package/server/channel.js +529 -0
  101. package/server/chat.js +270 -0
  102. package/server/config-store.js +362 -0
  103. package/server/config.js +159 -0
  104. package/server/context.js +131 -0
  105. package/server/gateway-commands.js +211 -0
  106. package/server/gateway-proxy.js +318 -0
  107. package/server/index.js +22 -0
  108. package/server/logger.js +89 -0
  109. package/server/middleware/auth.js +188 -0
  110. package/server/middleware.js +218 -0
  111. package/server/openclaw-discover.js +308 -0
  112. package/server/premium/index.js +156 -0
  113. package/server/premium/license.js +140 -0
  114. package/server/realtime/bridge.js +837 -0
  115. package/server/realtime/index.js +349 -0
  116. package/server/realtime/tts-stream.js +446 -0
  117. package/server/routes/agents.js +564 -0
  118. package/server/routes/artifacts.js +174 -0
  119. package/server/routes/chat.js +311 -0
  120. package/server/routes/config-settings.js +345 -0
  121. package/server/routes/config.js +603 -0
  122. package/server/routes/files.js +307 -0
  123. package/server/routes/index.js +18 -0
  124. package/server/routes/media.js +451 -0
  125. package/server/routes/missed-messages.js +107 -0
  126. package/server/routes/premium.js +75 -0
  127. package/server/routes/push.js +156 -0
  128. package/server/routes/satellite.js +406 -0
  129. package/server/routes/status.js +251 -0
  130. package/server/routes/stt.js +35 -0
  131. package/server/routes/voice.js +260 -0
  132. package/server/routes/webhooks.js +203 -0
  133. package/server/routes.js +206 -0
  134. package/server/runtime-config.js +336 -0
  135. package/server/share.js +305 -0
  136. package/server/stt/faster-whisper.js +72 -0
  137. package/server/stt/groq.js +51 -0
  138. package/server/stt/index.js +196 -0
  139. package/server/stt/openai.js +49 -0
  140. package/server/sync.js +244 -0
  141. package/server/tailscale-https.js +175 -0
  142. package/server/tts.js +646 -0
  143. package/server/update-checker.js +172 -0
  144. package/server/utils/filename.js +129 -0
  145. package/server/utils.js +147 -0
  146. package/server/watchdog.js +318 -0
  147. package/server/websocket/broadcast.js +359 -0
  148. package/server/websocket/connections.js +339 -0
  149. package/server/websocket/index.js +215 -0
  150. package/server/websocket/routing.js +277 -0
  151. package/server/websocket/sync.js +102 -0
  152. package/server.js +404 -0
  153. package/utils/detect-tool-usage.js +93 -0
  154. package/utils/errors.js +158 -0
  155. package/utils/html-escape.js +84 -0
  156. package/utils/id-sanitize.js +94 -0
  157. package/utils/response.js +130 -0
  158. package/utils/with-retry.js +105 -0
@@ -0,0 +1,164 @@
1
+ // ============================================
2
+ // THEMES MODULE
3
+ // CSS-based theme system using data-theme attribute
4
+ // ============================================
5
+
6
+ import { UplinkCore } from './core.js';
7
+ import { UplinkStorage } from './storage.js';
8
+
9
+ // Available themes (visual definitions live in themes.css)
10
+ // 'custom' is added dynamically by theme-generator.js
11
+ const THEMES = ['midnight', 'daylight', 'ember', 'forest', 'phantom', 'custom'];
12
+ const DEFAULT_THEME = 'midnight';
13
+
14
+ // Theme accent colors for the visual picker
15
+ const THEME_COLORS = {
16
+ midnight: { accent: '#00f0ff', bg: '#000000' },
17
+ daylight: { accent: '#c84b4b', bg: '#f8f5f1' },
18
+ ember: { accent: '#f48c06', bg: '#03071e' },
19
+ forest: { accent: '#a3b18a', bg: '#131813' },
20
+ phantom: { accent: '#a0a0a0', bg: '#000000' },
21
+ };
22
+
23
+ function init() {
24
+ const current = document.documentElement.getAttribute('data-theme');
25
+ if (!current || !THEMES.includes(current)) {
26
+ document.documentElement.setAttribute('data-theme', DEFAULT_THEME);
27
+ }
28
+ buildPicker();
29
+ console.log('Themes: Initialized (' + (current || DEFAULT_THEME) + ')');
30
+ }
31
+
32
+ function apply(themeName) {
33
+ const theme = THEMES.includes(themeName) ? themeName : DEFAULT_THEME;
34
+
35
+ // Premium check: only allow free themes if not premium
36
+ if (window.UplinkPremium && !window.UplinkPremium.isActive()) {
37
+ const status = window.UplinkPremium.getStatus();
38
+ const allowed = (status && status.themes) || [DEFAULT_THEME];
39
+ if (!allowed.includes(theme)) {
40
+ window.UplinkPremium.showUpgradeModal('Premium themes');
41
+ return;
42
+ }
43
+ }
44
+
45
+ document.documentElement.setAttribute('data-theme', theme);
46
+
47
+ // Sync hidden select
48
+ const select = document.getElementById('themeSelect');
49
+ if (select) select.value = theme;
50
+
51
+ // Update visual picker
52
+ updatePickerState(theme);
53
+
54
+ // Dispatch event for any listeners
55
+ window.dispatchEvent(new CustomEvent('uplink:themeChange', {
56
+ detail: { theme: theme }
57
+ }));
58
+ }
59
+
60
+ function get() {
61
+ return document.documentElement.getAttribute('data-theme') || DEFAULT_THEME;
62
+ }
63
+
64
+ // ── Visual theme picker ──
65
+
66
+ function buildPicker() {
67
+ const picker = document.getElementById('themePicker');
68
+ if (!picker) return;
69
+
70
+ const current = get();
71
+ const isPremium = window.UplinkPremium ? window.UplinkPremium.isActive() : true;
72
+ const allowedThemes = isPremium ? THEMES : ((window.UplinkPremium && window.UplinkPremium.getStatus().themes) || [DEFAULT_THEME]);
73
+
74
+ // Build items for built-in themes (not 'custom' — that's handled by theme generator)
75
+ const builtIn = THEMES.filter(function(t) { return t !== 'custom'; });
76
+
77
+ picker.innerHTML = builtIn.map(function(theme) {
78
+ const colors = THEME_COLORS[theme];
79
+ const isActive = current === theme;
80
+ const isLocked = !isPremium && !allowedThemes.includes(theme);
81
+ const label = theme.charAt(0).toUpperCase() + theme.slice(1);
82
+ const swatchStyle = 'background:linear-gradient(135deg,' + colors.bg + ' 50%,' + colors.accent + ' 100%)';
83
+
84
+ return '<div class="theme-picker-item' + (isActive ? ' active' : '') + '"' +
85
+ ' data-theme="' + theme + '"' +
86
+ (isLocked ? ' data-premium-locked="true"' : '') +
87
+ ' role="option" aria-selected="' + isActive + '"' +
88
+ ' title="' + label + (isLocked ? ' (Premium)' : '') + '">' +
89
+ '<div class="theme-picker-swatch" style="' + swatchStyle + '"></div>' +
90
+ '<span class="theme-picker-name">' + label + '</span>' +
91
+ (isLocked ? '<span class="theme-picker-lock">🔒</span>' : '') +
92
+ '</div>';
93
+ }).join('');
94
+
95
+ // If custom theme is active, add it
96
+ if (current === 'custom') {
97
+ var customAccent = '#8b5cf6';
98
+ if (window.UplinkThemeGenerator && window.UplinkThemeGenerator.getAccent) {
99
+ customAccent = window.UplinkThemeGenerator.getAccent();
100
+ }
101
+ picker.innerHTML += '<div class="theme-picker-item active" data-theme="custom" role="option" aria-selected="true">' +
102
+ '<div class="theme-picker-swatch" style="background:' + customAccent + '"></div>' +
103
+ '<span class="theme-picker-name">✦ Custom</span>' +
104
+ '</div>';
105
+ }
106
+
107
+ // Click handlers
108
+ picker.querySelectorAll('.theme-picker-item').forEach(function(item) {
109
+ item.addEventListener('click', function() {
110
+ var theme = item.dataset.theme;
111
+ if (item.dataset.premiumLocked === 'true') {
112
+ if (window.UplinkPremium) window.UplinkPremium.showUpgradeModal('Premium themes');
113
+ return;
114
+ }
115
+ apply(theme);
116
+ // Save preference
117
+ UplinkStorage.saveSettings({ theme: theme });
118
+ });
119
+ });
120
+ }
121
+
122
+ function updatePickerState(activeTheme) {
123
+ var picker = document.getElementById('themePicker');
124
+ if (!picker) return;
125
+
126
+ // Update active state on existing items
127
+ picker.querySelectorAll('.theme-picker-item').forEach(function(item) {
128
+ var isActive = item.dataset.theme === activeTheme;
129
+ item.classList.toggle('active', isActive);
130
+ item.setAttribute('aria-selected', isActive);
131
+ });
132
+
133
+ // If switching to custom and no custom item exists, rebuild
134
+ if (activeTheme === 'custom' && !picker.querySelector('[data-theme="custom"]')) {
135
+ buildPicker();
136
+ }
137
+ // If switching away from custom, remove the custom item
138
+ if (activeTheme !== 'custom') {
139
+ var customItem = picker.querySelector('[data-theme="custom"]');
140
+ if (customItem) customItem.remove();
141
+ }
142
+ }
143
+
144
+ // Rebuild picker when premium status changes
145
+ window.addEventListener('uplink:premiumChange', function() {
146
+ buildPicker();
147
+ });
148
+
149
+ // Export API
150
+ export const UplinkThemes = {
151
+ apply: apply,
152
+ get: get,
153
+ list: () => [...THEMES],
154
+ default: DEFAULT_THEME,
155
+ buildPicker: buildPicker,
156
+ };
157
+
158
+ export { apply, get, buildPicker, DEFAULT_THEME, THEMES };
159
+
160
+ // Backward compat: assign to window
161
+ window.UplinkThemes = UplinkThemes;
162
+
163
+ // Register and init
164
+ UplinkCore.registerModule('themes', init);
@@ -0,0 +1,198 @@
1
+ // ============================================
2
+ // TIMESTAMPS MODULE
3
+ // Show message timestamps below bubble text
4
+ // ============================================
5
+
6
+ const STORAGE_KEY = 'uplink-timestamps';
7
+ let enabled = false;
8
+ let messagesObserver = null;
9
+
10
+ function init() {
11
+ loadSetting();
12
+ addSettingsUI();
13
+ applyTimestamps();
14
+
15
+ // Re-apply when messages load from gateway (they arrive after init)
16
+ window.addEventListener('uplink:satellite-switched', function() {
17
+ if (enabled) setTimeout(applyTimestamps, 200);
18
+ });
19
+ // Also re-apply periodically during initial load to catch gateway messages
20
+ if (enabled) {
21
+ var retries = 0;
22
+ var reapplyInterval = setInterval(function() {
23
+ applyTimestamps();
24
+ retries++;
25
+ if (retries >= 5) clearInterval(reapplyInterval);
26
+ }, 1000);
27
+ }
28
+
29
+ // Cleanup previous observer if init called again
30
+ if (messagesObserver) {
31
+ messagesObserver.disconnect();
32
+ messagesObserver = null;
33
+ }
34
+
35
+ // Watch for new messages
36
+ const messagesEl = document.getElementById('messages');
37
+ if (messagesEl) {
38
+ messagesObserver = new MutationObserver((mutations) => {
39
+ if (!enabled) return;
40
+ mutations.forEach(mutation => {
41
+ mutation.addedNodes.forEach(node => {
42
+ if (node.classList?.contains('message')) {
43
+ addTimestampToMessage(node);
44
+ }
45
+ });
46
+ });
47
+ });
48
+ messagesObserver.observe(messagesEl, { childList: true });
49
+ }
50
+
51
+ console.log('Timestamps: Initialized');
52
+ }
53
+
54
+ function loadSetting() {
55
+ enabled = localStorage.getItem(STORAGE_KEY) === 'true';
56
+ }
57
+
58
+ function saveSetting() {
59
+ localStorage.setItem(STORAGE_KEY, enabled.toString());
60
+ }
61
+
62
+ function addSettingsUI() {
63
+ const settingsPanel = document.getElementById('settingsPanel');
64
+ if (!settingsPanel) {
65
+ setTimeout(addSettingsUI, 100);
66
+ return;
67
+ }
68
+
69
+ if (document.getElementById('timestampsRow')) return;
70
+
71
+ const row = document.createElement('div');
72
+ row.className = 'setting-row';
73
+ row.id = 'timestampsRow';
74
+ row.innerHTML = `
75
+ <div>
76
+ <div class="setting-label">Message timestamps</div>
77
+ <div class="setting-desc">Show when messages were sent</div>
78
+ </div>
79
+ <div class="toggle ${enabled ? 'on' : ''}" id="timestampsToggle" tabindex="0" role="switch" aria-checked="${enabled}" aria-label="Toggle message timestamps"></div>
80
+ `;
81
+
82
+ // Insert into General section, after text size row
83
+ const generalSection = document.getElementById('section-general');
84
+ if (generalSection) {
85
+ generalSection.appendChild(row);
86
+ } else {
87
+ settingsPanel.appendChild(row);
88
+ }
89
+
90
+ const toggle = document.getElementById('timestampsToggle');
91
+ toggle.addEventListener('click', () => {
92
+ enabled = !enabled;
93
+ toggle.classList.toggle('on', enabled);
94
+ toggle.setAttribute('aria-checked', enabled.toString());
95
+ saveSetting();
96
+ applyTimestamps();
97
+ });
98
+ toggle.addEventListener('keydown', (e) => {
99
+ if (e.key === 'Enter' || e.key === ' ') {
100
+ e.preventDefault();
101
+ toggle.click();
102
+ }
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Get the time for a message from its data-time attribute
108
+ */
109
+ function getMessageTime(msg) {
110
+ var timeStr = msg.dataset.time;
111
+ if (timeStr) return new Date(parseInt(timeStr, 10));
112
+ return null;
113
+ }
114
+
115
+ /**
116
+ * Add a timestamp element inside a message bubble
117
+ */
118
+ function addTimestampToMessage(msg) {
119
+ if (!msg || msg.classList.contains('system')) return;
120
+ if (!msg.querySelector('.message-timestamp')) {
121
+ var time = getMessageTime(msg);
122
+ var ts = document.createElement('span');
123
+ ts.className = 'message-timestamp';
124
+ ts.textContent = time ? formatTime(time) : '';
125
+ msg.appendChild(ts);
126
+ }
127
+ if (enabled) {
128
+ msg.classList.add('show-timestamp');
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Apply or remove timestamps on all existing messages
134
+ */
135
+ function applyTimestamps() {
136
+ var messages = document.querySelectorAll('#messages .message');
137
+
138
+ messages.forEach(function(msg) {
139
+ if (msg.classList.contains('system')) return;
140
+
141
+ if (enabled) {
142
+ if (!msg.querySelector('.message-timestamp')) {
143
+ var time = getMessageTime(msg);
144
+ var ts = document.createElement('span');
145
+ ts.className = 'message-timestamp';
146
+ ts.textContent = time ? formatTime(time) : '';
147
+ msg.appendChild(ts);
148
+ }
149
+ msg.classList.add('show-timestamp');
150
+ } else {
151
+ msg.classList.remove('show-timestamp');
152
+ }
153
+ });
154
+ }
155
+
156
+ function formatTime(date) {
157
+ const now = new Date();
158
+ const isToday = date.toDateString() === now.toDateString();
159
+
160
+ if (isToday) {
161
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
162
+ }
163
+
164
+ const yesterday = new Date(now);
165
+ yesterday.setDate(yesterday.getDate() - 1);
166
+ if (date.toDateString() === yesterday.toDateString()) {
167
+ return 'Yesterday ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
168
+ }
169
+
170
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' +
171
+ date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
172
+ }
173
+
174
+ // Cleanup function
175
+ function destroy() {
176
+ if (messagesObserver) {
177
+ messagesObserver.disconnect();
178
+ messagesObserver = null;
179
+ }
180
+ }
181
+
182
+ // Expose API
183
+ export const UplinkTimestamps = {
184
+ enable: () => { enabled = true; saveSetting(); applyTimestamps(); },
185
+ disable: () => { enabled = false; saveSetting(); applyTimestamps(); },
186
+ toggle: () => { enabled = !enabled; saveSetting(); applyTimestamps(); },
187
+ isEnabled: () => enabled,
188
+ format: formatTime,
189
+ destroy
190
+ };
191
+
192
+ import { UplinkCore } from './core.js';
193
+
194
+ // Backward compat: assign to window
195
+ window.UplinkTimestamps = UplinkTimestamps;
196
+
197
+ // Register and init
198
+ UplinkCore.registerModule('timestamps', init);