@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,412 @@
1
+ // ============================================
2
+ // PREMIUM MODULE
3
+ // Client-side premium status & UI gating
4
+ // ============================================
5
+
6
+ let premiumStatus = { active: false, features: {}, themes: ['midnight'], comingSoon: false };
7
+
8
+ // ── Fetch premium status from server ──
9
+
10
+ async function fetchStatus() {
11
+ try {
12
+ const res = await fetch('/api/premium/status');
13
+ if (res.ok) {
14
+ premiumStatus = await res.json();
15
+ }
16
+ } catch (e) {
17
+ console.warn('Premium: Failed to fetch status', e);
18
+ }
19
+ return premiumStatus;
20
+ }
21
+
22
+ // ── UI Updates ──
23
+
24
+ function updateUI() {
25
+ const isActive = premiumStatus.active;
26
+
27
+ // Premium section in settings
28
+ const freeState = document.getElementById('premiumFreeState');
29
+ const activeState = document.getElementById('premiumActiveState');
30
+ const activeBadge = document.getElementById('premiumActiveBadge');
31
+
32
+ if (freeState) freeState.classList.toggle('setting-hidden', isActive);
33
+ if (activeState) activeState.classList.toggle('setting-hidden', !isActive);
34
+ if (activeBadge) activeBadge.classList.toggle('setting-hidden', !isActive);
35
+
36
+ // Voice lock icon
37
+ const voiceLock = document.getElementById('voicePremiumLock');
38
+ if (voiceLock) voiceLock.classList.toggle('setting-hidden', isActive);
39
+
40
+ // Voice section overlay
41
+ const voiceSection = document.querySelector('[data-section="voice"] .settings-section-body');
42
+ if (voiceSection) {
43
+ if (!isActive) {
44
+ voiceSection.classList.add('premium-locked');
45
+ // Add overlay if not already present
46
+ if (!voiceSection.querySelector('.premium-overlay')) {
47
+ const overlay = document.createElement('div');
48
+ overlay.className = 'premium-overlay';
49
+ if (premiumStatus.comingSoon) {
50
+ overlay.innerHTML = '<div class="premium-overlay-content">' +
51
+ '<span class="premium-overlay-icon">🔒</span>' +
52
+ '<span class="premium-overlay-text">Premium · Coming Soon</span>' +
53
+ '</div>';
54
+ } else {
55
+ overlay.innerHTML = '<div class="premium-overlay-content">' +
56
+ '<span class="premium-overlay-icon">🔒</span>' +
57
+ '<span class="premium-overlay-text">Requires Uplink Premium</span>' +
58
+ '<button class="setting-btn setting-btn-primary premium-overlay-btn" data-premium-show-modal="Voice chat">Upgrade to Premium</button>' +
59
+ '</div>';
60
+ overlay.querySelector('[data-premium-show-modal]').addEventListener('click', function() {
61
+ showUpgradeModal('Voice chat');
62
+ });
63
+ }
64
+ voiceSection.appendChild(overlay);
65
+ }
66
+ } else {
67
+ voiceSection.classList.remove('premium-locked');
68
+ const overlay = voiceSection.querySelector('.premium-overlay');
69
+ if (overlay) overlay.remove();
70
+ }
71
+ }
72
+
73
+ // Theme select - restrict options for free users
74
+ const themeSelect = document.getElementById('themeSelect');
75
+ if (themeSelect) {
76
+ const allowedThemes = premiumStatus.themes || ['midnight'];
77
+ Array.from(themeSelect.options).forEach(function(opt) {
78
+ if (!allowedThemes.includes(opt.value)) {
79
+ opt.disabled = true;
80
+ opt.textContent = opt.textContent.replace(' 🔒', '') + ' 🔒';
81
+ } else {
82
+ opt.disabled = false;
83
+ opt.textContent = opt.textContent.replace(' 🔒', '');
84
+ }
85
+ });
86
+
87
+ // If current theme is not allowed, revert to midnight
88
+ if (!isActive && !allowedThemes.includes(themeSelect.value)) {
89
+ themeSelect.value = 'midnight';
90
+ if (window.UplinkThemes) {
91
+ window.UplinkThemes.apply('midnight');
92
+ }
93
+ }
94
+ }
95
+
96
+ // Agents tab gate is handled in satellites.js switchTab()
97
+
98
+ // Dispatch event for other modules
99
+ window.dispatchEvent(new CustomEvent('uplink:premiumChange', {
100
+ detail: premiumStatus
101
+ }));
102
+ }
103
+
104
+ // ── Activation ──
105
+
106
+ async function activate(key) {
107
+ try {
108
+ const res = await fetch('/api/premium/activate', {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/json' },
111
+ body: JSON.stringify({ key: key })
112
+ });
113
+ const data = await res.json();
114
+
115
+ if (data.success) {
116
+ premiumStatus = {
117
+ active: data.active,
118
+ features: data.features,
119
+ themes: data.themes,
120
+ activatedAt: data.activatedAt
121
+ };
122
+ updateUI();
123
+ return { success: true };
124
+ }
125
+ return { success: false, error: data.message || 'Invalid key' };
126
+ } catch (e) {
127
+ return { success: false, error: 'Connection error' };
128
+ }
129
+ }
130
+
131
+ async function deactivate() {
132
+ try {
133
+ const res = await fetch('/api/premium/deactivate', { method: 'POST' });
134
+ const data = await res.json();
135
+ if (data.success) {
136
+ premiumStatus = {
137
+ active: false,
138
+ features: data.features,
139
+ themes: data.themes
140
+ };
141
+ updateUI();
142
+ }
143
+ return data;
144
+ } catch (e) {
145
+ return { success: false, error: 'Connection error' };
146
+ }
147
+ }
148
+
149
+ // ── Event Handlers ──
150
+
151
+ function setupEvents() {
152
+ // "Enter Key" button toggles key input
153
+ const enterKeyBtn = document.getElementById('premiumEnterKeyBtn');
154
+ const keyEntry = document.getElementById('premiumKeyEntry');
155
+ if (enterKeyBtn && keyEntry) {
156
+ enterKeyBtn.addEventListener('click', function() {
157
+ keyEntry.classList.toggle('setting-hidden');
158
+ const input = document.getElementById('premiumKeyInput');
159
+ if (input && !keyEntry.classList.contains('setting-hidden')) {
160
+ input.focus();
161
+ }
162
+ });
163
+ }
164
+
165
+ // Activate button
166
+ const activateBtn = document.getElementById('premiumActivateBtn');
167
+ if (activateBtn) {
168
+ activateBtn.addEventListener('click', async function() {
169
+ var input = document.getElementById('premiumKeyInput');
170
+ var status = document.getElementById('premiumKeyStatus');
171
+ var key = input ? input.value.trim() : '';
172
+
173
+ if (!key) {
174
+ if (status) status.textContent = 'Please enter a license key';
175
+ return;
176
+ }
177
+
178
+ activateBtn.disabled = true;
179
+ activateBtn.textContent = 'Activating...';
180
+
181
+ var result = await activate(key);
182
+
183
+ if (result.success) {
184
+ if (status) {
185
+ status.textContent = '✓ Premium activated!';
186
+ status.classList.add('premium-success');
187
+ }
188
+ // Clear input
189
+ if (input) input.value = '';
190
+ } else {
191
+ if (status) {
192
+ status.textContent = result.error || 'Invalid license key';
193
+ status.classList.add('premium-error');
194
+ }
195
+ }
196
+
197
+ activateBtn.disabled = false;
198
+ activateBtn.textContent = 'Activate';
199
+ });
200
+ }
201
+
202
+ // Enter key in input triggers activate
203
+ var keyInput = document.getElementById('premiumKeyInput');
204
+ if (keyInput) {
205
+ keyInput.addEventListener('keydown', function(e) {
206
+ if (e.key === 'Enter') {
207
+ e.preventDefault();
208
+ var btn = document.getElementById('premiumActivateBtn');
209
+ if (btn) btn.click();
210
+ }
211
+ });
212
+ }
213
+
214
+ // Deactivate button
215
+ var deactivateBtn = document.getElementById('premiumDeactivateBtn');
216
+ if (deactivateBtn) {
217
+ deactivateBtn.addEventListener('click', async function() {
218
+ if (!confirm('Deactivate Premium? You can re-enter your key anytime.')) return;
219
+ await deactivate();
220
+ });
221
+ }
222
+
223
+ // Upgrade buttons (anywhere in the UI) - open modal
224
+ document.addEventListener('click', function(e) {
225
+ if (e.target.matches('[data-premium-upgrade]')) {
226
+ e.preventDefault();
227
+ showUpgradeModal();
228
+ }
229
+ });
230
+
231
+ // Note: agents tab gate is handled in satellites.js switchTab()
232
+ // The old agentsBtn intercept was removed - there is no standalone agents button
233
+ }
234
+
235
+ // ── Feature descriptions for upgrade modal ──
236
+
237
+ var featureInfo = {
238
+ 'Voice chat': {
239
+ icon: '🎙️',
240
+ title: 'Voice Chat',
241
+ desc: 'Talk to your AI with push-to-talk or hands-free mode. Responses are spoken back with natural TTS.',
242
+ },
243
+ 'Agent management': {
244
+ icon: '🤖',
245
+ title: 'Agent Management',
246
+ desc: 'Create, configure, and switch between multiple AI agents - each with their own personality and settings.',
247
+ },
248
+ 'Premium themes': {
249
+ icon: '🎨',
250
+ title: 'All Themes',
251
+ desc: 'Unlock every built-in theme plus the custom theme generator. Make Uplink look exactly how you want.',
252
+ },
253
+ 'Early access': {
254
+ icon: '🚀',
255
+ title: 'Early Access',
256
+ desc: 'Get new features first - including upcoming split view, custom themes, and more.',
257
+ }
258
+ };
259
+
260
+ var defaultFeatureInfo = {
261
+ icon: '✨',
262
+ title: 'Premium Feature',
263
+ desc: 'This feature is part of Uplink Premium.',
264
+ };
265
+
266
+ var allFeatures = [
267
+ { check: '✓', text: 'Voice chat with TTS', highlight: false },
268
+ { check: '✓', text: 'All themes + custom generator', highlight: false },
269
+ { check: '✓', text: 'Agent management', highlight: false },
270
+ { check: '✓', text: 'Early access to new features', highlight: false },
271
+ ];
272
+
273
+ // ── Upgrade modal ──
274
+
275
+ function showUpgradeModal(featureName) {
276
+ // Remove existing modal if any
277
+ var existing = document.querySelector('.premium-modal');
278
+ if (existing) existing.remove();
279
+
280
+ var info = featureInfo[featureName] || defaultFeatureInfo;
281
+
282
+ // Build feature list - highlight the relevant one
283
+ var featureListHTML = allFeatures.map(function(f) {
284
+ var isHighlight = featureName && f.text.toLowerCase().indexOf(featureName.toLowerCase().replace(/^premium\s*/i, '')) !== -1;
285
+ return '<li>' +
286
+ '<span class="feature-check">' + f.check + '</span>' +
287
+ '<span' + (isHighlight ? ' class="feature-highlight"' : '') + '>' + f.text + '</span>' +
288
+ '</li>';
289
+ }).join('');
290
+
291
+ var modal = document.createElement('div');
292
+ modal.className = 'premium-modal entering';
293
+ modal.innerHTML =
294
+ '<div class="premium-modal-card">' +
295
+ '<div class="premium-modal-header">' +
296
+ '<button class="premium-modal-close" aria-label="Close"><svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="1" y1="1" x2="13" y2="13"/><line x1="13" y1="1" x2="1" y2="13"/></svg></button>' +
297
+ '<span class="premium-modal-icon">' + info.icon + '</span>' +
298
+ '<h3 class="premium-modal-title">' + info.title + '</h3>' +
299
+ '<p class="premium-modal-desc">' + info.desc + '</p>' +
300
+ '</div>' +
301
+ '<div class="premium-modal-body">' +
302
+ '<ul class="premium-modal-features">' + featureListHTML + '</ul>' +
303
+ '</div>' +
304
+ '<div class="premium-modal-footer">' +
305
+ (premiumStatus.comingSoon
306
+ ? '<div class="premium-modal-price">Premium is coming soon</div>' +
307
+ '<div class="premium-modal-coming-soon">We\'re putting the finishing touches on Premium features. Stay tuned!</div>'
308
+ : '<div class="premium-modal-price">One-time purchase — yours forever</div>' +
309
+ '<a class="premium-modal-buy" href="https://store.moonco.pro" target="_blank" rel="noopener">Get Uplink Premium</a>' +
310
+ '<button class="premium-modal-key" data-premium-has-key>I already have a key</button>'
311
+ ) +
312
+ '</div>' +
313
+ '</div>';
314
+
315
+ document.body.appendChild(modal);
316
+
317
+ // Animate in
318
+ requestAnimationFrame(() => {
319
+ modal.classList.remove('entering');
320
+ modal.classList.add('visible');
321
+ });
322
+
323
+ // Close handlers
324
+ var closeModal = function() {
325
+ modal.classList.remove('visible');
326
+ setTimeout(function() { modal.remove(); }, 250);
327
+ };
328
+
329
+ // Close on backdrop click
330
+ modal.addEventListener('click', function(e) {
331
+ if (e.target === modal) closeModal();
332
+ });
333
+
334
+ // Close button
335
+ modal.querySelector('.premium-modal-close').addEventListener('click', function(e) {
336
+ e.stopPropagation();
337
+ closeModal();
338
+ });
339
+
340
+ // Escape key
341
+ var escHandler = function(e) {
342
+ if (e.key === 'Escape') {
343
+ closeModal();
344
+ document.removeEventListener('keydown', escHandler);
345
+ }
346
+ };
347
+ document.addEventListener('keydown', escHandler);
348
+
349
+ // "I have a key" → open settings to premium section
350
+ var hasKeyBtn = modal.querySelector('[data-premium-has-key]');
351
+ if (hasKeyBtn) hasKeyBtn.addEventListener('click', function() {
352
+ closeModal();
353
+ // Open settings panel
354
+ var settingsPanel = document.getElementById('settingsPanel');
355
+ if (settingsPanel && !settingsPanel.classList.contains('visible')) {
356
+ if (window.UplinkPanels) {
357
+ window.UplinkPanels.open('settings');
358
+ } else {
359
+ settingsPanel.classList.add('visible');
360
+ }
361
+ }
362
+ // Expand premium section
363
+ setTimeout(function() {
364
+ var premiumHeader = document.querySelector('[data-section="premium"] .settings-section-header');
365
+ var premiumBody = document.getElementById('section-premium');
366
+ if (premiumHeader && premiumBody && premiumBody.classList.contains('collapsed')) {
367
+ premiumHeader.click();
368
+ }
369
+ // Show key entry
370
+ var keyEntry = document.getElementById('premiumKeyEntry');
371
+ if (keyEntry) {
372
+ keyEntry.classList.remove('setting-hidden');
373
+ var input = document.getElementById('premiumKeyInput');
374
+ if (input) input.focus();
375
+ }
376
+ }, 300);
377
+ });
378
+ }
379
+
380
+ // ── Toast (kept as fallback for edge cases) ──
381
+
382
+ function showUpgradeToast(featureName) {
383
+ // Use modal instead of toast for primary gates
384
+ showUpgradeModal(featureName);
385
+ }
386
+
387
+ // ── Init ──
388
+
389
+ async function init() {
390
+ await fetchStatus();
391
+ updateUI();
392
+ setupEvents();
393
+ console.log('Premium: Initialized (' + (premiumStatus.active ? 'active' : 'free') + ')');
394
+ }
395
+
396
+ // Expose API
397
+ export const UplinkPremium = {
398
+ isActive: function() { return premiumStatus.active; },
399
+ hasFeature: function(f) { return premiumStatus.active || !(premiumStatus.features && premiumStatus.features[f] === false); },
400
+ getStatus: function() { return premiumStatus; },
401
+ refresh: async function() { await fetchStatus(); updateUI(); },
402
+ showUpgradeToast: showUpgradeToast,
403
+ showUpgradeModal: showUpgradeModal
404
+ };
405
+
406
+ import { UplinkCore } from './core.js';
407
+
408
+ // Backward compat: assign to window
409
+ window.UplinkPremium = UplinkPremium;
410
+
411
+ // Register and init
412
+ UplinkCore.registerModule('premium', init);