pinokiod 3.86.0 → 3.88.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 (67) hide show
  1. package/Dockerfile +61 -0
  2. package/docker-entrypoint.sh +75 -0
  3. package/kernel/api/hf/index.js +1 -1
  4. package/kernel/api/index.js +1 -1
  5. package/kernel/api/shell/index.js +6 -0
  6. package/kernel/api/terminal/index.js +166 -0
  7. package/kernel/bin/conda.js +3 -2
  8. package/kernel/bin/index.js +53 -2
  9. package/kernel/bin/setup.js +32 -0
  10. package/kernel/bin/vs.js +11 -2
  11. package/kernel/index.js +42 -2
  12. package/kernel/info.js +36 -0
  13. package/kernel/peer.js +42 -15
  14. package/kernel/router/index.js +23 -15
  15. package/kernel/router/localhost_static_router.js +0 -3
  16. package/kernel/router/pinokio_domain_router.js +333 -0
  17. package/kernel/shells.js +21 -1
  18. package/kernel/util.js +2 -2
  19. package/package.json +2 -1
  20. package/script/install-mode.js +33 -0
  21. package/script/pinokio.json +7 -0
  22. package/server/index.js +513 -173
  23. package/server/public/Socket.js +48 -0
  24. package/server/public/common.js +1441 -276
  25. package/server/public/fseditor.js +71 -12
  26. package/server/public/install.js +1 -1
  27. package/server/public/layout.js +740 -0
  28. package/server/public/modalinput.js +0 -1
  29. package/server/public/style.css +97 -105
  30. package/server/public/tab-idle-notifier.js +629 -0
  31. package/server/public/terminal_input_tracker.js +63 -0
  32. package/server/public/urldropdown.css +319 -53
  33. package/server/public/urldropdown.js +615 -159
  34. package/server/public/window_storage.js +97 -28
  35. package/server/socket.js +40 -9
  36. package/server/views/500.ejs +2 -2
  37. package/server/views/app.ejs +3136 -1367
  38. package/server/views/bookmarklet.ejs +1 -1
  39. package/server/views/bootstrap.ejs +1 -1
  40. package/server/views/columns.ejs +2 -13
  41. package/server/views/connect.ejs +3 -4
  42. package/server/views/container.ejs +1 -2
  43. package/server/views/d.ejs +223 -53
  44. package/server/views/editor.ejs +1 -1
  45. package/server/views/file_explorer.ejs +1 -1
  46. package/server/views/index.ejs +12 -11
  47. package/server/views/index2.ejs +4 -4
  48. package/server/views/init/index.ejs +4 -5
  49. package/server/views/install.ejs +1 -1
  50. package/server/views/layout.ejs +123 -0
  51. package/server/views/net.ejs +39 -7
  52. package/server/views/network.ejs +20 -6
  53. package/server/views/network2.ejs +1 -1
  54. package/server/views/old_network.ejs +2 -2
  55. package/server/views/partials/dynamic.ejs +3 -5
  56. package/server/views/partials/menu.ejs +3 -5
  57. package/server/views/partials/running.ejs +1 -1
  58. package/server/views/pro.ejs +1 -1
  59. package/server/views/prototype/index.ejs +1 -1
  60. package/server/views/review.ejs +11 -23
  61. package/server/views/rows.ejs +2 -13
  62. package/server/views/screenshots.ejs +293 -138
  63. package/server/views/settings.ejs +3 -4
  64. package/server/views/setup.ejs +1 -2
  65. package/server/views/shell.ejs +277 -26
  66. package/server/views/terminal.ejs +322 -49
  67. package/server/views/tools.ejs +448 -4
@@ -13,6 +13,7 @@
13
13
  <script src="/xterm-addon-search-bar.js"></script>
14
14
  <script src="/sweetalert2.js"></script>
15
15
  <script src="/Socket.js"></script>
16
+ <script src="/terminal_input_tracker.js"></script>
16
17
  <script src="/common.js"></script>
17
18
  <script src="/he.js"></script>
18
19
  <script src="/opener.js"></script>
@@ -105,6 +106,28 @@ header {
105
106
  .xterm .xterm-viewport {
106
107
  width: initial !important;
107
108
  }
109
+ #terminal {
110
+ position: relative;
111
+ }
112
+ .terminal-drop-overlay {
113
+ position: absolute;
114
+ inset: 0;
115
+ background: rgba(0,0,0,0.6);
116
+ color: #fff;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ font-size: 14px;
121
+ text-align: center;
122
+ pointer-events: none;
123
+ opacity: 0;
124
+ transition: opacity 0.15s ease-in-out;
125
+ padding: 12px;
126
+ z-index: 5;
127
+ }
128
+ .terminal-drop-overlay.active {
129
+ opacity: 1;
130
+ }
108
131
  .navheader3 {
109
132
  padding: 10px;
110
133
  }
@@ -177,12 +200,113 @@ body.dark #status-window b {
177
200
  <link href="/terminal.css" rel="stylesheet"/>
178
201
  <script>
179
202
  let shell_id
203
+ let lastNotifiedShellId = null
204
+ const notifyShellSession = (id) => {
205
+ if (!id || id === lastNotifiedShellId) {
206
+ return
207
+ }
208
+ lastNotifiedShellId = id
209
+ if (window && window.parent && typeof window.parent.postMessage === "function") {
210
+ window.parent.postMessage({
211
+ type: "shell-session-id",
212
+ frame: window.name || null,
213
+ shellId: id
214
+ }, "*")
215
+ }
216
+ }
217
+ function buildBinaryRpcPayload(rpc, fileEntries) {
218
+ const metadata = {
219
+ rpc,
220
+ buffer_keys: fileEntries.map((entry) => entry.key)
221
+ }
222
+ const metaBytes = new TextEncoder().encode(JSON.stringify(metadata))
223
+ const separator = new Uint8Array([0])
224
+ const parts = [metaBytes, separator]
225
+ for (const entry of fileEntries) {
226
+ const lenBytes = new Uint8Array(4)
227
+ new DataView(lenBytes.buffer).setUint32(0, entry.buffer.byteLength)
228
+ parts.push(lenBytes, entry.buffer)
229
+ }
230
+ const totalLength = parts.reduce((sum, part) => sum + part.length, 0)
231
+ const combined = new Uint8Array(totalLength)
232
+ let offset = 0
233
+ for (const part of parts) {
234
+ combined.set(part, offset)
235
+ offset += part.length
236
+ }
237
+ return combined.buffer
238
+ }
180
239
  document.addEventListener("DOMContentLoaded", async () => {
181
240
  const n = new N()
182
241
  class RPC {
183
242
  constructor() {
184
243
  this.socket = new Socket()
185
244
  this.buffer = []
245
+ this.uploadContext = {
246
+ cwd: "~" + location.pathname
247
+ }
248
+ this.inputBuffer = ""
249
+ this.inputTracker = window.TerminalInputTracker ? new window.TerminalInputTracker({
250
+ getFrameName: () => window.name || null,
251
+ getWindow: () => window
252
+ }) : null
253
+ }
254
+ resetInputBuffer() {
255
+ if (this.inputTracker) {
256
+ this.inputTracker.reset()
257
+ return
258
+ }
259
+ this.inputBuffer = ""
260
+ }
261
+ handleBackspace() {
262
+ if (this.inputTracker) {
263
+ this.inputTracker.handleBackspace()
264
+ return
265
+ }
266
+ if (this.inputBuffer.length > 0) {
267
+ this.inputBuffer = this.inputBuffer.slice(0, -1)
268
+ }
269
+ }
270
+ captureTextInput(text) {
271
+ if (this.inputTracker) {
272
+ this.inputTracker.capture(text)
273
+ return
274
+ }
275
+ if (typeof text !== "string" || text.length === 0) {
276
+ return
277
+ }
278
+ const normalized = text.replace(/\r/g, "\n")
279
+ const segments = normalized.split("\n")
280
+ for (let i = 0; i < segments.length; i++) {
281
+ const segment = segments[i]
282
+ const isLast = (i === segments.length - 1)
283
+ if (!isLast) {
284
+ const line = this.inputBuffer + segment
285
+ this.inputBuffer = ""
286
+ this.notifyLineSubmitted(line)
287
+ } else {
288
+ this.inputBuffer += segment
289
+ }
290
+ }
291
+ }
292
+ notifyLineSubmitted(line) {
293
+ if (this.inputTracker) {
294
+ this.inputTracker.submit(line)
295
+ return
296
+ }
297
+ if (!window || !window.parent || typeof window.parent.postMessage !== "function") {
298
+ return
299
+ }
300
+ const safeLine = (line || "").replace(/[\x00-\x1F\x7F]/g, "")
301
+ const preview = safeLine.trim()
302
+ const limit = 200
303
+ const truncated = preview.length > limit ? preview.slice(0, limit) + "..." : preview
304
+ window.parent.postMessage({
305
+ type: "terminal-input",
306
+ frame: window.name || null,
307
+ line: truncated,
308
+ hasContent: truncated.length > 0
309
+ }, "*")
186
310
  }
187
311
  write(text) {
188
312
  if (text !== "\u0007") {
@@ -317,9 +441,11 @@ document.addEventListener("DOMContentLoaded", async () => {
317
441
  // </div>`
318
442
  }
319
443
  } else if (packet.type === "stream") {
444
+ refreshParent(packet)
320
445
  // set the current shell id
321
446
  if (packet.data.id) {
322
447
  shell_id = packet.data.id
448
+ notifyShellSession(shell_id)
323
449
  }
324
450
  if (packet.data.raw) {
325
451
  // console.log({ packet })
@@ -363,6 +489,7 @@ document.addEventListener("DOMContentLoaded", async () => {
363
489
  if (packet.data) {
364
490
  if (packet.data.shell) {
365
491
  shell_id = packet.data.shell
492
+ notifyShellSession(shell_id)
366
493
  }
367
494
  if (packet.data.state) {
368
495
  this.write(packet.data.state)
@@ -669,6 +796,71 @@ document.addEventListener("DOMContentLoaded", async () => {
669
796
  <% } %>
670
797
  await this.start(mode)
671
798
  }
799
+ async uploadFiles(files, overlay) {
800
+ if (!files || files.length === 0) {
801
+ return
802
+ }
803
+ if (!this.socket || !this.socket.ws || this.socket.ws.readyState !== WebSocket.OPEN) {
804
+ n.Noty({
805
+ text: "Terminal connection is not ready for uploads",
806
+ type: "error"
807
+ })
808
+ return
809
+ }
810
+ const entries = []
811
+ for (let i = 0; i < files.length; i++) {
812
+ const file = files[i]
813
+ if (!file || typeof file.arrayBuffer !== "function") {
814
+ continue
815
+ }
816
+ try {
817
+ const arrayBuffer = await file.arrayBuffer()
818
+ const key = `file_${Date.now()}_${i}_${Math.random().toString(16).slice(2, 8)}`
819
+ entries.push({
820
+ key,
821
+ name: file.name || `upload-${i + 1}`,
822
+ size: file.size,
823
+ type: file.type || "",
824
+ buffer: new Uint8Array(arrayBuffer)
825
+ })
826
+ } catch (error) {
827
+ console.error("Failed to read dropped file", error)
828
+ }
829
+ }
830
+ if (entries.length === 0) {
831
+ n.Noty({
832
+ text: "No readable files were dropped",
833
+ type: "error"
834
+ })
835
+ return
836
+ }
837
+ const rpcPayload = {
838
+ method: "terminal.upload",
839
+ params: {
840
+ id: shell_id,
841
+ cwd: this.uploadContext.cwd,
842
+ files: entries.map(({ key, name, size, type }) => ({ key, name, size, type }))
843
+ }
844
+ }
845
+ try {
846
+ if (overlay) {
847
+ overlay.classList.add("active")
848
+ overlay.textContent = "Uploading..."
849
+ }
850
+ await this.socket.sendBinary(buildBinaryRpcPayload(rpcPayload, entries))
851
+ } catch (error) {
852
+ console.error("Upload failed", error)
853
+ n.Noty({
854
+ text: `Upload failed: ${error.message}`,
855
+ type: "error"
856
+ })
857
+ } finally {
858
+ if (overlay) {
859
+ overlay.classList.remove("active")
860
+ overlay.textContent = "Drop files to upload"
861
+ }
862
+ }
863
+ }
672
864
  async createTerm (_theme) {
673
865
  console.log(xtermTheme)
674
866
  if (!this.term) {
@@ -684,7 +876,7 @@ document.addEventListener("DOMContentLoaded", async () => {
684
876
  let config = {
685
877
  scrollback: 9999999,
686
878
  //fontSize: 12,
687
- fontSize: 14,
879
+ fontSize: 12,
688
880
  theme,
689
881
  //theme: xtermTheme.FrontEndDelight
690
882
  //theme: xtermTheme.Afterglow
@@ -706,8 +898,41 @@ document.addEventListener("DOMContentLoaded", async () => {
706
898
  }
707
899
  const term = new Terminal(config)
708
900
  term.open(document.querySelector("#terminal"))
901
+ const terminalContainer = document.querySelector("#terminal")
902
+ const dropOverlay = document.createElement("div")
903
+ dropOverlay.className = "terminal-drop-overlay"
904
+ dropOverlay.textContent = "Drop files to upload"
905
+ terminalContainer.appendChild(dropOverlay)
906
+ let dragDepth = 0
907
+ const prevent = (event) => {
908
+ event.preventDefault()
909
+ event.stopPropagation()
910
+ }
911
+ terminalContainer.addEventListener("dragenter", (event) => {
912
+ prevent(event)
913
+ dragDepth += 1
914
+ dropOverlay.classList.add("active")
915
+ })
916
+ terminalContainer.addEventListener("dragover", prevent)
917
+ terminalContainer.addEventListener("dragleave", (event) => {
918
+ prevent(event)
919
+ dragDepth = Math.max(0, dragDepth - 1)
920
+ if (dragDepth === 0) {
921
+ dropOverlay.classList.remove("active")
922
+ }
923
+ })
924
+ terminalContainer.addEventListener("drop", async (event) => {
925
+ prevent(event)
926
+ dragDepth = 0
927
+ dropOverlay.classList.remove("active")
928
+ const dropped = Array.from(event.dataTransfer ? event.dataTransfer.files || [] : [])
929
+ if (!dropped.length) {
930
+ return
931
+ }
932
+ await this.uploadFiles(dropped, dropOverlay)
933
+ this.term.focus()
934
+ })
709
935
  term.attachCustomKeyEventHandler(event => {
710
- console.log({ event })
711
936
  if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
712
937
  const selection = term.getSelection();
713
938
  if (selection) {
@@ -717,18 +942,13 @@ document.addEventListener("DOMContentLoaded", async () => {
717
942
  }
718
943
  if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
719
944
  navigator.clipboard.readText().then((text) => {
720
- console.log({ text })
721
- //this.socket.run({
722
- // //key: "\x1b[200~" + text + "\x1b[201~",
723
- // key: text,
724
- // id: shell_id
725
- //})
726
945
  this.socket.run({
727
946
  //key: "\x1b[200~" + text + "\x1b[201~",
728
947
  key: text,
729
948
  id: shell_id,
730
- // paste: true
949
+ paste: true
731
950
  })
951
+ this.captureTextInput(text)
732
952
 
733
953
  })
734
954
  return false
@@ -742,15 +962,48 @@ document.addEventListener("DOMContentLoaded", async () => {
742
962
  ////term.resize(cols, rows-5);
743
963
  //term.resize(cols, rows);
744
964
 
745
- term.onKey(({ key }) => {
746
- console.log({ key, shell_id })
747
- if (this.socket) {
748
- if (shell_id) {
749
- this.socket.run({
750
- key,
751
- id: shell_id
752
- })
965
+ term.onKey(({ key, domEvent }) => {
966
+ if (this.socket && shell_id) {
967
+ this.socket.run({
968
+ key,
969
+ id: shell_id
970
+ })
971
+ }
972
+ if (!domEvent) {
973
+ return
974
+ }
975
+ if (domEvent.key === "Backspace") {
976
+ this.handleBackspace()
977
+ return
978
+ }
979
+ if (domEvent.key === "Enter") {
980
+ this.captureTextInput("\n")
981
+ return
982
+ }
983
+ if (domEvent.key === "Tab") {
984
+ this.captureTextInput("\t")
985
+ return
986
+ }
987
+ if (domEvent.key === "Escape") {
988
+ this.resetInputBuffer()
989
+ return
990
+ }
991
+ if (domEvent.ctrlKey || domEvent.metaKey || domEvent.altKey) {
992
+ if (key === "\u0015" || key === "\u0017" || key === "\u000b" || key === "\u0003" || domEvent.key === "c" || domEvent.key === "C" || domEvent.key === "u" || domEvent.key === "U") {
993
+ this.resetInputBuffer()
753
994
  }
995
+ return
996
+ }
997
+ if (key === "\u0015" || key === "\u0017" || key === "\u000b") {
998
+ this.resetInputBuffer()
999
+ return
1000
+ }
1001
+ if (key === "\u0008") {
1002
+ this.handleBackspace()
1003
+ return
1004
+ }
1005
+ if (typeof key === "string" && key.length === 1 && key >= " ") {
1006
+ this.captureTextInput(key)
754
1007
  }
755
1008
  });
756
1009
 
@@ -838,17 +1091,15 @@ document.addEventListener("DOMContentLoaded", async () => {
838
1091
 
839
1092
  window.addEventListener('message', function(event) {
840
1093
  console.log("Message received from the parent: ", event.data); // Message received from parent
841
- // "foreground" message adds a newline to the terminal
842
- // Needed to fix the issue where the terminal won't write anything when in background
843
- // so when coming back from background, need to refresh by just adding a newline.
1094
+ // "foreground" message triggers a redraw so buffered output appears after backgrounding.
844
1095
  if (event.data && event.data.action === "foreground") {
845
- // if dirty, update
846
- // if not dirty, don't update
847
- const delimiter = "\r\n"
848
- if (rpc.dirty) {
849
- rpc.term.write(delimiter)
1096
+ if (rpc.dirty && rpc.term) {
1097
+ const endRow = Math.max(rpc.term.rows - 1, 0)
1098
+ rpc.term.refresh(0, endRow)
1099
+ if (typeof rpc.term.scrollToBottom === "function") {
1100
+ rpc.term.scrollToBottom()
1101
+ }
850
1102
  rpc.dirty = false
851
- } else {
852
1103
  }
853
1104
  }
854
1105
  });