@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,381 @@
1
+ // ============================================
2
+ // KEYBOARD SHORTCUTS MODULE
3
+ // Keyboard shortcuts panel and customization
4
+ // ============================================
5
+
6
+ const STORAGE_KEY = 'uplink-shortcuts';
7
+
8
+ // Default shortcuts
9
+ const defaultShortcuts = {
10
+ send: { key: 'Enter', description: 'Send message' },
11
+ newLine: { key: 'Shift+Enter', description: 'New line in message' },
12
+ clearChat: { key: 'Ctrl+Shift+Delete', description: 'Clear chat' },
13
+ toggleVoice: { key: 'Ctrl+Shift+V', description: 'Toggle voice mode' },
14
+ focusInput: { key: 'Ctrl+/', description: 'Focus message input' },
15
+ openSettings: { key: 'Ctrl+,', description: 'Open settings' },
16
+ closePanel: { key: 'Escape', description: 'Close panel/cancel' },
17
+ showShortcuts: { key: 'Ctrl+Shift+/', description: 'Show shortcuts' }
18
+ };
19
+
20
+ // Current shortcuts (may be customized)
21
+ let shortcuts = { ...defaultShortcuts };
22
+
23
+ // Panel element
24
+ let panelEl = null;
25
+
26
+ function init() {
27
+ // Load saved shortcuts
28
+ loadShortcuts();
29
+
30
+ // Create panel
31
+ createPanel();
32
+
33
+ // Add button to settings
34
+ addSettingsButton();
35
+
36
+ // Register global shortcuts
37
+ registerShortcuts();
38
+
39
+ console.log('Shortcuts: Initialized');
40
+ }
41
+
42
+ function loadShortcuts() {
43
+ try {
44
+ const saved = localStorage.getItem(STORAGE_KEY);
45
+ if (saved) {
46
+ const parsed = JSON.parse(saved);
47
+ shortcuts = { ...defaultShortcuts, ...parsed };
48
+ }
49
+ } catch (e) {
50
+ console.error('Shortcuts: Failed to load', e);
51
+ }
52
+ }
53
+
54
+ function saveShortcuts() {
55
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(shortcuts));
56
+ }
57
+
58
+ // Currently editing shortcut
59
+ let editingShortcutId = null;
60
+
61
+ function createPanel() {
62
+ panelEl = document.createElement('div');
63
+ panelEl.className = 'shortcuts-panel';
64
+ panelEl.innerHTML = `
65
+ <div class="shortcuts-panel-inner">
66
+ <div class="shortcuts-panel-header">
67
+ <h3>⌨️ Keyboard Shortcuts</h3>
68
+ <button class="shortcuts-close" title="Close">&times;</button>
69
+ </div>
70
+ <p class="shortcuts-hint">Click a shortcut to edit it</p>
71
+ <div class="shortcuts-list">
72
+ ${Object.entries(shortcuts).map(([id, shortcut]) => `
73
+ <div class="shortcut-row" data-id="${id}">
74
+ <span class="shortcut-desc">${shortcut.description}</span>
75
+ <kbd class="shortcut-key" data-id="${id}" title="Click to edit">${formatKey(shortcut.key)}</kbd>
76
+ </div>
77
+ `).join('')}
78
+ </div>
79
+ <div class="shortcuts-footer">
80
+ <button class="shortcuts-reset">Reset to Defaults</button>
81
+ </div>
82
+ </div>
83
+ `;
84
+
85
+ document.body.appendChild(panelEl);
86
+
87
+ // Close button
88
+ panelEl.querySelector('.shortcuts-close').addEventListener('click', hidePanel);
89
+
90
+ // Reset button
91
+ panelEl.querySelector('.shortcuts-reset').addEventListener('click', resetShortcuts);
92
+
93
+ // Make shortcuts editable
94
+ panelEl.querySelectorAll('.shortcut-key').forEach(kbd => {
95
+ kbd.style.cursor = 'pointer';
96
+ kbd.addEventListener('click', (e) => {
97
+ e.stopPropagation();
98
+ startEditingShortcut(kbd.dataset.id);
99
+ });
100
+ });
101
+
102
+ // Click outside to close
103
+ panelEl.addEventListener('click', (e) => {
104
+ if (e.target === panelEl) hidePanel();
105
+ });
106
+
107
+ // Register with panel manager for mutual exclusivity
108
+ if (window.UplinkPanels) {
109
+ window.UplinkPanels.register('shortcuts', {
110
+ element: panelEl,
111
+ isOpen: () => panelEl?.classList.contains('visible'),
112
+ open: () => panelEl?.classList.add('visible'),
113
+ close: () => panelEl?.classList.remove('visible')
114
+ });
115
+ }
116
+ }
117
+
118
+ function showPanel() {
119
+ if (window.UplinkPanels) {
120
+ window.UplinkPanels.open('shortcuts');
121
+ } else {
122
+ panelEl?.classList.add('visible');
123
+ }
124
+ }
125
+
126
+ function hidePanel() {
127
+ if (window.UplinkPanels) {
128
+ window.UplinkPanels.close('shortcuts');
129
+ } else {
130
+ panelEl?.classList.remove('visible');
131
+ }
132
+ }
133
+
134
+ function togglePanel() {
135
+ if (window.UplinkPanels) {
136
+ window.UplinkPanels.toggle('shortcuts');
137
+ } else {
138
+ panelEl?.classList.toggle('visible');
139
+ }
140
+ }
141
+
142
+ function formatKey(key) {
143
+ return key
144
+ .replace('Ctrl', '⌃')
145
+ .replace('Shift', '⇧')
146
+ .replace('Alt', '⌥')
147
+ .replace('Meta', '⌘')
148
+ .replace('Enter', '↵')
149
+ .replace('Escape', 'Esc')
150
+ .replace('Delete', 'Del')
151
+ .replace(/\+/g, ' ');
152
+ }
153
+
154
+ function startEditingShortcut(shortcutId) {
155
+ // Stop any previous editing
156
+ stopEditingShortcut();
157
+
158
+ editingShortcutId = shortcutId;
159
+ const kbd = panelEl.querySelector(`.shortcut-key[data-id="${shortcutId}"]`);
160
+ if (kbd) {
161
+ kbd.classList.add('editing');
162
+ kbd.textContent = 'Press keys...';
163
+ kbd.title = 'Press new shortcut keys, Escape to cancel';
164
+ }
165
+
166
+ // Add key capture listener
167
+ document.addEventListener('keydown', handleShortcutKeyCapture, true);
168
+ }
169
+
170
+ function stopEditingShortcut() {
171
+ if (!editingShortcutId) return;
172
+
173
+ const kbd = panelEl.querySelector(`.shortcut-key[data-id="${editingShortcutId}"]`);
174
+ if (kbd) {
175
+ kbd.classList.remove('editing');
176
+ kbd.textContent = formatKey(shortcuts[editingShortcutId].key);
177
+ kbd.title = 'Click to edit';
178
+ }
179
+
180
+ editingShortcutId = null;
181
+ document.removeEventListener('keydown', handleShortcutKeyCapture, true);
182
+ }
183
+
184
+ function handleShortcutKeyCapture(e) {
185
+ e.preventDefault();
186
+ e.stopPropagation();
187
+
188
+ // Escape cancels editing
189
+ if (e.key === 'Escape') {
190
+ stopEditingShortcut();
191
+ return;
192
+ }
193
+
194
+ // Build key string
195
+ const parts = [];
196
+ if (e.ctrlKey) parts.push('Ctrl');
197
+ if (e.shiftKey) parts.push('Shift');
198
+ if (e.altKey) parts.push('Alt');
199
+ if (e.metaKey) parts.push('Meta');
200
+
201
+ // Don't save if only modifier keys pressed
202
+ const key = e.key;
203
+ if (['Control', 'Shift', 'Alt', 'Meta'].includes(key)) return;
204
+
205
+ // Normalize key name
206
+ let normalizedKey = key;
207
+ if (key === ' ') normalizedKey = 'Space';
208
+ else if (key.length === 1) normalizedKey = key.toUpperCase();
209
+
210
+ parts.push(normalizedKey);
211
+ const newShortcut = parts.join('+');
212
+
213
+ // Save the new shortcut
214
+ shortcuts[editingShortcutId].key = newShortcut;
215
+ saveShortcuts();
216
+
217
+ // Update display and stop editing
218
+ const kbd = panelEl.querySelector(`.shortcut-key[data-id="${editingShortcutId}"]`);
219
+ if (kbd) {
220
+ kbd.classList.remove('editing');
221
+ kbd.textContent = formatKey(newShortcut);
222
+ kbd.title = 'Click to edit';
223
+ }
224
+
225
+ editingShortcutId = null;
226
+ document.removeEventListener('keydown', handleShortcutKeyCapture, true);
227
+
228
+ console.log(`Shortcuts: Updated ${editingShortcutId} to ${newShortcut}`);
229
+ }
230
+
231
+ function resetShortcuts() {
232
+ shortcuts = { ...defaultShortcuts };
233
+ saveShortcuts();
234
+ updatePanelDisplay();
235
+ }
236
+
237
+ function updatePanelDisplay() {
238
+ if (!panelEl) return;
239
+
240
+ Object.entries(shortcuts).forEach(([id, shortcut]) => {
241
+ const row = panelEl.querySelector(`.shortcut-row[data-id="${id}"]`);
242
+ if (row) {
243
+ row.querySelector('.shortcut-key').textContent = formatKey(shortcut.key);
244
+ }
245
+ });
246
+ }
247
+
248
+ function addSettingsButton() {
249
+ const settingsPanel = document.getElementById('settingsPanel');
250
+ if (!settingsPanel) {
251
+ setTimeout(addSettingsButton, 100);
252
+ return;
253
+ }
254
+
255
+ // Check if already added
256
+ if (document.getElementById('shortcutsRow')) return;
257
+
258
+ const row = document.createElement('div');
259
+ row.className = 'setting-row';
260
+ row.id = 'shortcutsRow';
261
+ row.innerHTML = `
262
+ <div>
263
+ <div class="setting-label">Keyboard shortcuts</div>
264
+ <div class="setting-desc">View and customize shortcuts</div>
265
+ </div>
266
+ <button class="setting-btn" id="shortcutsBtn">View</button>
267
+ `;
268
+
269
+ // Insert before theme row
270
+ const themeRow = document.getElementById('themeSelectorRow');
271
+ if (themeRow) {
272
+ themeRow.before(row);
273
+ } else {
274
+ settingsPanel.appendChild(row);
275
+ }
276
+
277
+ document.getElementById('shortcutsBtn').addEventListener('click', showPanel);
278
+ }
279
+
280
+ function registerShortcuts() {
281
+ document.addEventListener('keydown', (e) => {
282
+ const key = getKeyCombo(e);
283
+
284
+ // Show shortcuts panel (Ctrl+Shift+/ only - removed Shift+? as it conflicts with typing ?)
285
+ if (matchKey(key, shortcuts.showShortcuts.key)) {
286
+ e.preventDefault();
287
+ togglePanel();
288
+ return;
289
+ }
290
+
291
+ // Close panel (any open panel)
292
+ if (matchKey(key, shortcuts.closePanel.key)) {
293
+ // Use panel manager to close any open panel
294
+ if (window.UplinkPanels?.getCurrent()) {
295
+ e.preventDefault();
296
+ window.UplinkPanels.closeAll();
297
+ return;
298
+ }
299
+ }
300
+
301
+ // Focus input
302
+ if (matchKey(key, shortcuts.focusInput.key)) {
303
+ e.preventDefault();
304
+ const input = document.getElementById('textInput');
305
+ input?.focus();
306
+ return;
307
+ }
308
+
309
+ // Open settings
310
+ if (matchKey(key, shortcuts.openSettings.key)) {
311
+ e.preventDefault();
312
+ const settingsPanel = document.getElementById('settingsPanel');
313
+ settingsPanel?.classList.toggle('visible');
314
+ return;
315
+ }
316
+
317
+ // Toggle voice mode
318
+ if (matchKey(key, shortcuts.toggleVoice.key)) {
319
+ e.preventDefault();
320
+ const voiceTab = document.getElementById('voiceModeTab');
321
+ const textTab = document.getElementById('textModeTab');
322
+ const isVoice = voiceTab?.classList.contains('active');
323
+ if (isVoice) {
324
+ textTab?.click();
325
+ } else {
326
+ voiceTab?.click();
327
+ }
328
+ return;
329
+ }
330
+
331
+ // Clear chat (via settings)
332
+ if (matchKey(key, shortcuts.clearChat.key)) {
333
+ e.preventDefault();
334
+ const settingsClearBtn = document.getElementById('settingsClearBtn');
335
+ settingsClearBtn?.click();
336
+ return;
337
+ }
338
+ });
339
+ }
340
+
341
+ function getKeyCombo(e) {
342
+ const parts = [];
343
+ if (e.ctrlKey || e.metaKey) parts.push('Ctrl');
344
+ if (e.shiftKey) parts.push('Shift');
345
+ if (e.altKey) parts.push('Alt');
346
+
347
+ // Get the key name
348
+ let keyName = e.key;
349
+ if (!keyName) return parts.join('+'); // Handle undefined key
350
+ if (keyName === ' ') keyName = 'Space';
351
+ if (keyName.length === 1) keyName = keyName.toUpperCase();
352
+
353
+ // Don't add modifier keys as the key itself
354
+ if (!['Control', 'Shift', 'Alt', 'Meta'].includes(keyName)) {
355
+ parts.push(keyName);
356
+ }
357
+
358
+ return parts.join('+');
359
+ }
360
+
361
+ function matchKey(pressed, shortcut) {
362
+ const normalize = (k) => k.toLowerCase().replace(/\s+/g, '');
363
+ return normalize(pressed) === normalize(shortcut);
364
+ }
365
+
366
+ // Expose API
367
+ export const UplinkShortcuts = {
368
+ show: showPanel,
369
+ hide: hidePanel,
370
+ toggle: togglePanel,
371
+ get: (id) => shortcuts[id],
372
+ getAll: () => ({ ...shortcuts })
373
+ };
374
+
375
+ import { UplinkCore } from './core.js';
376
+
377
+ // Backward compat: assign to window
378
+ window.UplinkShortcuts = UplinkShortcuts;
379
+
380
+ // Register and init
381
+ UplinkCore.registerModule('shortcuts', init);