pinokiod 3.180.0 → 3.182.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/kernel/favicon.js +91 -34
  2. package/kernel/peer.js +73 -0
  3. package/kernel/util.js +28 -4
  4. package/package.json +1 -1
  5. package/server/index.js +237 -35
  6. package/server/public/common.js +677 -240
  7. package/server/public/files-app/app.css +64 -0
  8. package/server/public/files-app/app.js +87 -0
  9. package/server/public/install.js +8 -1
  10. package/server/public/layout.js +124 -0
  11. package/server/public/nav.js +227 -64
  12. package/server/public/sound/beep.mp3 +0 -0
  13. package/server/public/sound/bell.mp3 +0 -0
  14. package/server/public/sound/bright-ring.mp3 +0 -0
  15. package/server/public/sound/clap.mp3 +0 -0
  16. package/server/public/sound/deep-ring.mp3 +0 -0
  17. package/server/public/sound/gasp.mp3 +0 -0
  18. package/server/public/sound/hehe.mp3 +0 -0
  19. package/server/public/sound/levelup.mp3 +0 -0
  20. package/server/public/sound/light-pop.mp3 +0 -0
  21. package/server/public/sound/light-ring.mp3 +0 -0
  22. package/server/public/sound/meow.mp3 +0 -0
  23. package/server/public/sound/piano.mp3 +0 -0
  24. package/server/public/sound/pop.mp3 +0 -0
  25. package/server/public/sound/uhoh.mp3 +0 -0
  26. package/server/public/sound/whistle.mp3 +0 -0
  27. package/server/public/style.css +195 -4
  28. package/server/public/tab-idle-notifier.js +700 -4
  29. package/server/public/terminal-settings.js +1131 -0
  30. package/server/public/urldropdown.css +28 -1
  31. package/server/socket.js +71 -4
  32. package/server/views/{terminals.ejs → agents.ejs} +108 -32
  33. package/server/views/app.ejs +321 -104
  34. package/server/views/bootstrap.ejs +8 -0
  35. package/server/views/connect.ejs +10 -1
  36. package/server/views/d.ejs +172 -18
  37. package/server/views/editor.ejs +8 -0
  38. package/server/views/file_browser.ejs +4 -0
  39. package/server/views/index.ejs +10 -1
  40. package/server/views/init/index.ejs +18 -3
  41. package/server/views/install.ejs +8 -0
  42. package/server/views/layout.ejs +2 -0
  43. package/server/views/net.ejs +10 -1
  44. package/server/views/network.ejs +10 -1
  45. package/server/views/pro.ejs +8 -0
  46. package/server/views/prototype/index.ejs +8 -0
  47. package/server/views/screenshots.ejs +10 -2
  48. package/server/views/settings.ejs +10 -2
  49. package/server/views/shell.ejs +8 -0
  50. package/server/views/terminal.ejs +8 -0
  51. package/server/views/tools.ejs +10 -2
@@ -167,6 +167,70 @@
167
167
  display: flex;
168
168
  flex-direction: column;
169
169
  background: var(--surface-color);
170
+ position: relative;
171
+ overflow: visible;
172
+ }
173
+
174
+ .files-app__sidebar-toggle {
175
+ position: absolute;
176
+ top: 20px;
177
+ left: 0;
178
+ transform: translateX(-50%);
179
+ display: inline-flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ width: 32px;
183
+ height: 36px;
184
+ border: 1px solid var(--border-color);
185
+ border-left: none;
186
+ border-radius: 0 8px 8px 0;
187
+ background: var(--surface-color);
188
+ color: var(--muted-color);
189
+ cursor: pointer;
190
+ transition: transform 0.2s ease, color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
191
+ box-shadow: 0 2px 6px rgba(15, 23, 42, 0.08);
192
+ z-index: 10;
193
+ }
194
+
195
+ .files-app__sidebar-toggle:hover,
196
+ .files-app__sidebar-toggle:focus-visible {
197
+ background: rgba(127, 91, 243, 0.14);
198
+ color: var(--accent-color);
199
+ }
200
+
201
+ .files-app__sidebar-toggle--collapsed {
202
+ top: 36px;
203
+ left: 8px;
204
+ transform: translateX(0);
205
+ border-left: 1px solid var(--border-color);
206
+ border-radius: 8px;
207
+ background: rgba(127, 91, 243, 0.12);
208
+ color: var(--accent-color);
209
+ box-shadow: 0 4px 12px rgba(15, 23, 42, 0.18);
210
+ }
211
+
212
+ .files-app__sidebar-toggle--collapsed:hover,
213
+ .files-app__sidebar-toggle--collapsed:focus-visible {
214
+ background: rgba(127, 91, 243, 0.18);
215
+ }
216
+
217
+ .files-app__sidebar-toggle:focus-visible {
218
+ outline: 2px solid var(--accent-color);
219
+ outline-offset: 2px;
220
+ }
221
+
222
+ .files-app--sidebar-collapsed .files-app__sidebar {
223
+ flex: 0 0 0;
224
+ width: 0;
225
+ min-width: 0;
226
+ max-width: 0;
227
+ opacity: 0;
228
+ pointer-events: none;
229
+ border-right: none;
230
+ }
231
+
232
+ .files-app--sidebar-collapsed .files-app__sidebar-scroll {
233
+ padding: 0;
170
234
  }
171
235
 
172
236
  .files-app__tabs:empty {
@@ -28,6 +28,8 @@
28
28
  }
29
29
  };
30
30
 
31
+ const SIDEBAR_STORAGE_PREFIX = 'files-app.sidebar-collapsed.';
32
+
31
33
  const FilesApp = {
32
34
  init(config) {
33
35
  if (this._initialized) {
@@ -48,14 +50,19 @@
48
50
  activePath: null,
49
51
  selectedTreePath: null,
50
52
  statusTimer: null,
53
+ sidebarCollapsed: false,
51
54
  };
52
55
 
53
56
  this.dom = {
57
+ root: document.querySelector('.files-app'),
54
58
  treeRoot: document.getElementById('files-app-tree'),
55
59
  tabs: document.getElementById('files-app-tabs'),
56
60
  editorContainer: document.getElementById('files-app-editor'),
57
61
  status: document.getElementById('files-app-status'),
58
62
  saveBtn: document.getElementById('files-app-save'),
63
+ sidebar: document.querySelector('.files-app__sidebar'),
64
+ main: document.querySelector('.files-app__main'),
65
+ sidebarToggle: document.getElementById('files-app-toggle-sidebar'),
59
66
  };
60
67
 
61
68
  this.api = createApi(config.workspace, this.state.workspaceRoot);
@@ -63,6 +70,8 @@
63
70
  this.modelist = ace.require('ace/ext/modelist');
64
71
  this.undoManagerCtor = ace.require('ace/undomanager').UndoManager;
65
72
 
73
+ setupSidebarToggle.call(this);
74
+
66
75
  this.dom.saveBtn.addEventListener('click', (event) => {
67
76
  event.preventDefault();
68
77
  this.saveActiveFile();
@@ -227,6 +236,84 @@
227
236
  },
228
237
  };
229
238
 
239
+ function setupSidebarToggle() {
240
+ if (!this.dom.sidebarToggle || !this.dom.root) {
241
+ return;
242
+ }
243
+
244
+ const storageKey = getSidebarStorageKey(this.state.workspace);
245
+ const initialCollapsed = readSidebarPreference(storageKey);
246
+ applySidebarCollapsed.call(this, initialCollapsed);
247
+
248
+ this.dom.sidebarToggle.addEventListener('click', () => {
249
+ const nextState = !this.state.sidebarCollapsed;
250
+ applySidebarCollapsed.call(this, nextState);
251
+ persistSidebarPreference(storageKey, nextState);
252
+ });
253
+
254
+ window.addEventListener('storage', (event) => {
255
+ if (event.key === storageKey) {
256
+ const newValue = event.newValue === '1';
257
+ if (newValue !== this.state.sidebarCollapsed) {
258
+ applySidebarCollapsed.call(this, newValue);
259
+ }
260
+ }
261
+ });
262
+ }
263
+
264
+ function readSidebarPreference(storageKey) {
265
+ try {
266
+ const storedValue = window.localStorage.getItem(storageKey);
267
+ return storedValue === '1';
268
+ } catch (error) {
269
+ return false;
270
+ }
271
+ }
272
+
273
+ function persistSidebarPreference(storageKey, collapsed) {
274
+ try {
275
+ window.localStorage.setItem(storageKey, collapsed ? '1' : '0');
276
+ } catch (error) {
277
+ /* ignore */
278
+ }
279
+ }
280
+
281
+ function applySidebarCollapsed(collapsed) {
282
+ this.state.sidebarCollapsed = collapsed;
283
+ if (this.dom.root) {
284
+ this.dom.root.classList.toggle('files-app--sidebar-collapsed', collapsed);
285
+ }
286
+ if (this.dom.sidebar) {
287
+ if (collapsed) {
288
+ this.dom.sidebar.setAttribute('aria-hidden', 'true');
289
+ } else {
290
+ this.dom.sidebar.removeAttribute('aria-hidden');
291
+ }
292
+ }
293
+ if (!this.dom.sidebarToggle) {
294
+ return;
295
+ }
296
+ this.dom.sidebarToggle.setAttribute('aria-expanded', String(!collapsed));
297
+ const label = collapsed ? 'Show files' : 'Hide files';
298
+ this.dom.sidebarToggle.setAttribute('aria-label', label);
299
+ this.dom.sidebarToggle.title = label;
300
+ const srText = this.dom.sidebarToggle.querySelector('.sr-only');
301
+ if (srText) {
302
+ srText.textContent = label;
303
+ }
304
+ this.dom.sidebarToggle.classList.toggle('files-app__sidebar-toggle--collapsed', collapsed);
305
+ const icon = this.dom.sidebarToggle.querySelector('i');
306
+ if (icon) {
307
+ icon.classList.toggle('fa-chevron-left', !collapsed);
308
+ icon.classList.toggle('fa-chevron-right', collapsed);
309
+ }
310
+ }
311
+
312
+ function getSidebarStorageKey(workspace) {
313
+ const scope = workspace ? String(workspace) : 'default';
314
+ return `${SIDEBAR_STORAGE_PREFIX}${scope}`;
315
+ }
316
+
230
317
  function createApi(workspace, workspaceRoot) {
231
318
  const list = async (pathPosix) => {
232
319
  const params = new URLSearchParams({ workspace });
@@ -146,7 +146,7 @@ const install = async (name, url, term, socket, options) => {
146
146
  text: `Downloaded to ~/${normalizedPath}/${name}`,
147
147
  timeout: 4000
148
148
  })
149
- location.href = "/terminals"
149
+ location.href = "/agents"
150
150
  }
151
151
  }
152
152
  }
@@ -166,7 +166,14 @@ const createTerm = async (_theme) => {
166
166
  if (res && res.config) {
167
167
  config = res.config
168
168
  }
169
+ const baseConfig = Object.assign({}, config)
170
+ if (window.PinokioTerminalSettings && typeof window.PinokioTerminalSettings.applyToConfig === 'function') {
171
+ config = window.PinokioTerminalSettings.applyToConfig(config)
172
+ }
169
173
  const term = new Terminal(config)
174
+ if (window.PinokioTerminalSettings && typeof window.PinokioTerminalSettings.register === 'function') {
175
+ window.PinokioTerminalSettings.register(term, { baseConfig })
176
+ }
170
177
  const fitAddon = new FitAddon.FitAddon();
171
178
  term.loadAddon(fitAddon);
172
179
  term.loadAddon(new WebLinksAddon.WebLinksAddon());
@@ -790,4 +790,128 @@
790
790
  };
791
791
 
792
792
  window.PinokioLayout = api;
793
+ // Mobile "Tap to connect" curtain is centralized in common.js to avoid duplicates
794
+
795
+ // Top-level notification listener (indicator + optional chime) for mobile
796
+ (function initTopLevelNotificationListener() {
797
+ try { if (window.top && window.top !== window) return; } catch (_) { return; }
798
+ if (window.__pinokioTopNotifyListener) {
799
+ return;
800
+ }
801
+ window.__pinokioTopNotifyListener = true;
802
+
803
+ const ensureIndicator = (() => {
804
+ let el = null;
805
+ let styleInjected = false;
806
+ return () => {
807
+ if (!styleInjected) {
808
+ const style = document.createElement('style');
809
+ style.textContent = `
810
+ .pinokio-notify-indicator{position:fixed;top:12px;right:12px;z-index:2147483647;display:none;align-items:center;gap:8px;padding:8px 10px;border-radius:999px;background:rgba(15,23,42,0.92);color:#fff;font:600 12px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;box-shadow:0 10px 30px rgba(0,0,0,0.35)}
811
+ .pinokio-notify-indicator .bell{font-size:14px}
812
+ .pinokio-notify-indicator.show{display:inline-flex;animation:pinokioNotifyPop 160ms ease-out, pinokioNotifyFade 1600ms ease-in 700ms forwards}
813
+ @keyframes pinokioNotifyPop{from{transform:translateY(-6px) scale(.98);opacity:0}to{transform:translateY(0) scale(1);opacity:1}}
814
+ @keyframes pinokioNotifyFade{to{opacity:0;transform:translateY(-4px)}}
815
+ @media (max-width: 768px){.pinokio-notify-indicator{top:10px;right:10px;padding:7px 9px;font-size:12px}}
816
+ `;
817
+ document.head.appendChild(style);
818
+ styleInjected = true;
819
+ }
820
+ if (!el) {
821
+ el = document.createElement('div');
822
+ el.className = 'pinokio-notify-indicator';
823
+ const icon = document.createElement('span');
824
+ icon.className = 'bell';
825
+ icon.textContent = '🔔';
826
+ const text = document.createElement('span');
827
+ text.className = 'text';
828
+ text.textContent = 'Notification received';
829
+ el.appendChild(icon);
830
+ el.appendChild(text);
831
+ document.body.appendChild(el);
832
+ }
833
+ return el;
834
+ };
835
+ })();
836
+
837
+ const flashIndicator = (message) => {
838
+ const node = ensureIndicator();
839
+ const text = node.querySelector('.text');
840
+ if (text) {
841
+ const msg = (message && typeof message === 'string' && message.trim()) ? message.trim() : 'Notification received';
842
+ text.textContent = msg.length > 80 ? (msg.slice(0,77) + '…') : msg;
843
+ }
844
+ node.classList.remove('show');
845
+ void node.offsetWidth;
846
+ node.classList.add('show');
847
+ setTimeout(() => node.classList.remove('show'), 2400);
848
+ };
849
+
850
+ const isFalseyString = (value) => {
851
+ return typeof value === 'string' && ['false', '0', 'no', 'off'].includes(value.trim().toLowerCase());
852
+ };
853
+
854
+ const tryPlay = (url) => {
855
+ if (!url || url === false || isFalseyString(url)) {
856
+ return;
857
+ }
858
+ try {
859
+ const isString = typeof url === 'string';
860
+ const trimmed = isString ? url.trim() : '';
861
+ const hasCustom = isString && trimmed.length > 0 && trimmed.toLowerCase() !== 'true';
862
+ const src = hasCustom ? url : '/chime.mp3';
863
+ let a = window.__pinokioChimeAudio;
864
+ if (!a) {
865
+ a = new Audio(src);
866
+ a.preload = 'auto';
867
+ a.loop = false;
868
+ a.muted = false;
869
+ window.__pinokioChimeAudio = a;
870
+ } else {
871
+ try { if (a.src && !a.src.endsWith(src)) a.src = src; } catch (_) {}
872
+ }
873
+ try { a.currentTime = 0; } catch (_) {}
874
+ const p = a.play();
875
+ if (p && typeof p.catch === 'function') { p.catch(() => {}); }
876
+ if (typeof navigator !== 'undefined' && typeof navigator.vibrate === 'function') {
877
+ try { navigator.vibrate(80); } catch (_) {}
878
+ }
879
+ } catch (_) {}
880
+ };
881
+
882
+ const listen = () => {
883
+ const SocketCtor = typeof window.Socket === 'function' ? window.Socket : (typeof Socket === 'function' ? Socket : null);
884
+ if (!SocketCtor || typeof WebSocket === 'undefined') {
885
+ return;
886
+ }
887
+ const socket = new SocketCtor();
888
+ try {
889
+ socket.run({ method: 'kernel.notifications', mode: 'listen', device_id: (typeof window.PinokioGetDeviceId === 'function') ? window.PinokioGetDeviceId() : undefined }, (packet) => {
890
+ if (!packet || packet.id !== 'kernel.notifications' || packet.type !== 'notification') {
891
+ return;
892
+ }
893
+ const payload = packet.data || {};
894
+ // If targeted to a specific device, ignore only when our id exists and mismatches
895
+ try {
896
+ const targetId = (typeof payload.device_id === 'string' && payload.device_id.trim()) ? payload.device_id.trim() : null;
897
+ if (targetId) {
898
+ const myId = (typeof window.PinokioGetDeviceId === 'function') ? window.PinokioGetDeviceId() : null;
899
+ if (myId && myId !== targetId) return;
900
+ }
901
+ } catch (_) {}
902
+ flashIndicator(payload.message);
903
+ tryPlay(payload.sound);
904
+ }).then(() => {
905
+ // socket closed; ignore
906
+ }).catch(() => {});
907
+ window.__pinokioTopNotifySocket = socket;
908
+ } catch (_) {}
909
+ };
910
+
911
+ if (document.readyState === 'loading') {
912
+ document.addEventListener('DOMContentLoaded', listen, { once: true });
913
+ } else {
914
+ listen();
915
+ }
916
+ })();
793
917
  })();