pinokiod 3.86.0 → 3.87.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 +105 -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>
@@ -95,6 +96,28 @@ header {
95
96
  .xterm .xterm-viewport {
96
97
  width: initial !important;
97
98
  }
99
+ #terminal {
100
+ position: relative;
101
+ }
102
+ .terminal-drop-overlay {
103
+ position: absolute;
104
+ inset: 0;
105
+ background: rgba(0,0,0,0.6);
106
+ color: #fff;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ font-size: 14px;
111
+ text-align: center;
112
+ pointer-events: none;
113
+ opacity: 0;
114
+ transition: opacity 0.15s ease-in-out;
115
+ padding: 12px;
116
+ z-index: 5;
117
+ }
118
+ .terminal-drop-overlay.active {
119
+ opacity: 1;
120
+ }
98
121
  /*
99
122
  .navheader {
100
123
  background: var(--dark-bg);
@@ -154,6 +177,28 @@ body.frozen {
154
177
  <script>
155
178
  let shell_id
156
179
  Dropzone.autoDiscover = false;
180
+ function buildBinaryRpcPayload(rpc, fileEntries) {
181
+ const metadata = {
182
+ rpc,
183
+ buffer_keys: fileEntries.map((entry) => entry.key)
184
+ }
185
+ const metaBytes = new TextEncoder().encode(JSON.stringify(metadata))
186
+ const separator = new Uint8Array([0])
187
+ const parts = [metaBytes, separator]
188
+ for (const entry of fileEntries) {
189
+ const lenBytes = new Uint8Array(4)
190
+ new DataView(lenBytes.buffer).setUint32(0, entry.buffer.byteLength)
191
+ parts.push(lenBytes, entry.buffer)
192
+ }
193
+ const totalLength = parts.reduce((sum, part) => sum + part.length, 0)
194
+ const combined = new Uint8Array(totalLength)
195
+ let offset = 0
196
+ for (const part of parts) {
197
+ combined.set(part, offset)
198
+ offset += part.length
199
+ }
200
+ return combined.buffer
201
+ }
157
202
  document.addEventListener("DOMContentLoaded", async () => {
158
203
  <% if (error) { %>
159
204
  document.querySelector(".requirements .content").innerHTML = '<div class="loading"><i class="fa-solid fa-circle-exclamation"></i> <%=error%></div>'
@@ -177,10 +222,80 @@ document.addEventListener("DOMContentLoaded", async () => {
177
222
  location.href = location.href
178
223
  <% } %>
179
224
  const n = new N()
225
+ const baseScriptId = <% if (script_id) { %><%- JSON.stringify(script_id) %><% } else { %>null<% } %>
226
+ const scriptUri = <% if (script_path) { %><%- JSON.stringify(script_path) %><% } else { %>null<% } %>
227
+ const scriptCwd = <% if (cwd) { %><%- JSON.stringify(cwd) %><% } else { %>null<% } %>
180
228
  class RPC {
181
229
  constructor() {
182
230
  this.socket = new Socket()
183
231
  this.buffer = []
232
+ this.uploadContext = {
233
+ cwd: "~" + location.pathname
234
+ }
235
+ this.baseScriptId = baseScriptId
236
+ this.currentId = baseScriptId
237
+ this.inputBuffer = ""
238
+ this.inputTracker = window.TerminalInputTracker ? new window.TerminalInputTracker({
239
+ getFrameName: () => window.name || null,
240
+ getWindow: () => window
241
+ }) : null
242
+ }
243
+ resetInputBuffer() {
244
+ if (this.inputTracker) {
245
+ this.inputTracker.reset()
246
+ return
247
+ }
248
+ this.inputBuffer = ""
249
+ }
250
+ handleBackspace() {
251
+ if (this.inputTracker) {
252
+ this.inputTracker.handleBackspace()
253
+ return
254
+ }
255
+ if (this.inputBuffer.length > 0) {
256
+ this.inputBuffer = this.inputBuffer.slice(0, -1)
257
+ }
258
+ }
259
+ captureTextInput(text) {
260
+ if (this.inputTracker) {
261
+ this.inputTracker.capture(text)
262
+ return
263
+ }
264
+ if (typeof text !== "string" || text.length === 0) {
265
+ return
266
+ }
267
+ const normalized = text.replace(/\r/g, "\n")
268
+ const segments = normalized.split("\n")
269
+ for (let i = 0; i < segments.length; i++) {
270
+ const segment = segments[i]
271
+ const isLast = (i === segments.length - 1)
272
+ if (!isLast) {
273
+ const line = this.inputBuffer + segment
274
+ this.inputBuffer = ""
275
+ this.notifyLineSubmitted(line)
276
+ } else {
277
+ this.inputBuffer += segment
278
+ }
279
+ }
280
+ }
281
+ notifyLineSubmitted(line) {
282
+ if (this.inputTracker) {
283
+ this.inputTracker.submit(line)
284
+ return
285
+ }
286
+ if (!window || !window.parent || typeof window.parent.postMessage !== "function") {
287
+ return
288
+ }
289
+ const safeLine = (line || "").replace(/[\x00-\x1F\x7F]/g, "")
290
+ const preview = safeLine.trim()
291
+ const limit = 200
292
+ const truncated = preview.length > limit ? preview.slice(0, limit) + "..." : preview
293
+ window.parent.postMessage({
294
+ type: "terminal-input",
295
+ frame: window.name || null,
296
+ line: truncated,
297
+ hasContent: truncated.length > 0
298
+ }, "*")
184
299
  }
185
300
  write(text) {
186
301
  if (text !== "\u0007") {
@@ -199,20 +314,16 @@ document.addEventListener("DOMContentLoaded", async () => {
199
314
  document.querySelector(".run .starting").classList.add("hidden")
200
315
  }
201
316
  stop() {
317
+ const params = {
318
+ uri: scriptUri || ("~" + location.pathname)
319
+ }
320
+ const runId = this.currentId || this.baseScriptId
321
+ if (runId) {
322
+ params.id = runId
323
+ }
202
324
  this.socket.run({
203
325
  method: "kernel.api.stop",
204
- params: {
205
- <% if (script_id) { %>
206
- id: "<%-JSON.stringify(script_id).slice(1, -1)%>",
207
- <% } %>
208
- //uri: "<%-uri%>",
209
- //uri: location.pathname.slice(1).replace("api/", ""),
210
- <% if (script_path) { %>
211
- uri: "<%-JSON.stringify(script_path).slice(1, -1)%>",
212
- <% } else { %>
213
- uri: "~" + location.pathname,
214
- <% } %>
215
- }
326
+ params
216
327
  }, (stream) => {
217
328
  })
218
329
  }
@@ -247,31 +358,32 @@ document.addEventListener("DOMContentLoaded", async () => {
247
358
  // await this.save()
248
359
  await this.socket.close()
249
360
 
250
- let query = Object.fromEntries(new URLSearchParams(location.search))
251
- console.log("query", query)
361
+ const searchParams = new URLSearchParams(location.search)
362
+ const query = Object.fromEntries(searchParams)
363
+ let runId = this.baseScriptId
364
+ if (query.session) {
365
+ const baseId = this.baseScriptId || (scriptUri || ("~" + location.pathname))
366
+ runId = `${baseId}&session=${query.session}`
367
+ }
368
+ this.currentId = runId || null
252
369
 
253
- this.socket.run({
254
- //uri: location.pathname.slice(1).replace("api/", ""),
255
- <% if (script_id) { %>
256
- id: "<%-JSON.stringify(script_id).slice(1, -1)%>",
257
- <% } %>
258
- <% if (cwd) { %>
259
- cwd: "<%-JSON.stringify(cwd).slice(1, -1)%>",
260
- <% } %>
261
- <% if (script_path) { %>
262
- uri: "<%-JSON.stringify(script_path).slice(1, -1)%>",
263
- <% } else { %>
264
- uri: "~" + location.pathname,
265
- <% } %>
266
- //uri: "<%-uri%>",
370
+ const payload = {
371
+ uri: scriptUri || ("~" + location.pathname),
267
372
  mode,
268
373
  input: query,
269
374
  client: {
270
375
  cols: this.term.cols,
271
376
  rows: this.term.rows,
272
377
  }
273
- }, async (packet) => {
274
- console.log({ packet })
378
+ }
379
+ if (scriptCwd) {
380
+ payload.cwd = scriptCwd
381
+ }
382
+ if (runId) {
383
+ payload.id = runId
384
+ payload.useId = true
385
+ }
386
+ this.socket.run(payload, async (packet) => {
275
387
  if (packet.type === 'start') {
276
388
  refreshParent(packet)
277
389
  reloadMemory()
@@ -299,6 +411,7 @@ document.addEventListener("DOMContentLoaded", async () => {
299
411
  </b>`
300
412
  }
301
413
  } else if (packet.type === "stream") {
414
+ refreshParent(packet)
302
415
  // set the current shell id
303
416
  if (packet.data.id) {
304
417
  shell_id = packet.data.id
@@ -615,6 +728,36 @@ document.addEventListener("DOMContentLoaded", async () => {
615
728
  n.Noty(payload)
616
729
  }
617
730
  } else if (packet.type === "result") {
731
+ if (packet.id === "terminal.upload") {
732
+ const uploaded = Array.isArray(packet.data && packet.data.files) ? packet.data.files : []
733
+ if (uploaded.length > 0) {
734
+ const mappedFiles = uploaded.map((file) => {
735
+ const displayPath = file.displayPath || file.homeRelativePath ? `~/${(file.homeRelativePath || '').replace(/^\/+/, '')}` : (file.path || '')
736
+ return {
737
+ originalName: file.originalName || file.name || file.storedAs,
738
+ storedAs: file.storedAs,
739
+ path: file.path,
740
+ displayPath,
741
+ homeRelativePath: file.homeRelativePath || '',
742
+ cliPath: file.cliPath || null,
743
+ cliRelativePath: file.cliRelativePath || null
744
+ }
745
+ })
746
+ refreshParent({
747
+ type: "terminal.upload",
748
+ files: mappedFiles
749
+ })
750
+ n.Noty({
751
+ text: mappedFiles.length > 1 ? `${mappedFiles.length} files attached` : `File attached`,
752
+ timeout: 4000
753
+ })
754
+ } else {
755
+ refreshParent({
756
+ type: "terminal.upload",
757
+ files: []
758
+ })
759
+ }
760
+ }
618
761
  refreshParent(packet)
619
762
  reloadMemory()
620
763
  } else if (packet.type === "info") {
@@ -735,6 +878,71 @@ document.addEventListener("DOMContentLoaded", async () => {
735
878
  <% } %>
736
879
  await this.start(mode)
737
880
  }
881
+ async uploadFiles(files, overlay) {
882
+ if (!files || files.length === 0) {
883
+ return
884
+ }
885
+ if (!this.socket || !this.socket.ws || this.socket.ws.readyState !== WebSocket.OPEN) {
886
+ n.Noty({
887
+ text: "Terminal connection is not ready for uploads",
888
+ type: "error"
889
+ })
890
+ return
891
+ }
892
+ const entries = []
893
+ for (let i = 0; i < files.length; i++) {
894
+ const file = files[i]
895
+ if (!file || typeof file.arrayBuffer !== "function") {
896
+ continue
897
+ }
898
+ try {
899
+ const arrayBuffer = await file.arrayBuffer()
900
+ const key = `file_${Date.now()}_${i}_${Math.random().toString(16).slice(2, 8)}`
901
+ entries.push({
902
+ key,
903
+ name: file.name || `upload-${i + 1}`,
904
+ size: file.size,
905
+ type: file.type || "",
906
+ buffer: new Uint8Array(arrayBuffer)
907
+ })
908
+ } catch (error) {
909
+ console.error("Failed to read dropped file", error)
910
+ }
911
+ }
912
+ if (entries.length === 0) {
913
+ n.Noty({
914
+ text: "No readable files were dropped",
915
+ type: "error"
916
+ })
917
+ return
918
+ }
919
+ const rpcPayload = {
920
+ method: "terminal.upload",
921
+ params: {
922
+ id: shell_id,
923
+ cwd: this.uploadContext.cwd,
924
+ files: entries.map(({ key, name, size, type }) => ({ key, name, size, type }))
925
+ }
926
+ }
927
+ try {
928
+ if (overlay) {
929
+ overlay.classList.add("active")
930
+ overlay.textContent = "Uploading..."
931
+ }
932
+ await this.socket.sendBinary(buildBinaryRpcPayload(rpcPayload, entries))
933
+ } catch (error) {
934
+ console.error("Upload failed", error)
935
+ n.Noty({
936
+ text: `Upload failed: ${error.message}`,
937
+ type: "error"
938
+ })
939
+ } finally {
940
+ if (overlay) {
941
+ overlay.classList.remove("active")
942
+ overlay.textContent = "Drop files to upload"
943
+ }
944
+ }
945
+ }
738
946
  async createTerm (_theme) {
739
947
  if (!this.term) {
740
948
  const theme = Object.assign({ }, _theme, {
@@ -746,7 +954,7 @@ document.addEventListener("DOMContentLoaded", async () => {
746
954
  <% } %>
747
955
  let config = {
748
956
  scrollback: 9999999,
749
- //fontSize: 12,
957
+ fontSize: 12,
750
958
  //fontSize: 14,
751
959
  theme,
752
960
  //theme: xtermTheme.FrontEndDelight
@@ -769,6 +977,40 @@ document.addEventListener("DOMContentLoaded", async () => {
769
977
  }
770
978
  const term = new Terminal(config)
771
979
  term.open(document.querySelector("#terminal"))
980
+ const terminalContainer = document.querySelector("#terminal")
981
+ const dropOverlay = document.createElement("div")
982
+ dropOverlay.className = "terminal-drop-overlay"
983
+ dropOverlay.textContent = "Drop files to upload"
984
+ terminalContainer.appendChild(dropOverlay)
985
+ let dragDepth = 0
986
+ const prevent = (event) => {
987
+ event.preventDefault()
988
+ event.stopPropagation()
989
+ }
990
+ terminalContainer.addEventListener("dragenter", (event) => {
991
+ prevent(event)
992
+ dragDepth += 1
993
+ dropOverlay.classList.add("active")
994
+ })
995
+ terminalContainer.addEventListener("dragover", prevent)
996
+ terminalContainer.addEventListener("dragleave", (event) => {
997
+ prevent(event)
998
+ dragDepth = Math.max(0, dragDepth - 1)
999
+ if (dragDepth === 0) {
1000
+ dropOverlay.classList.remove("active")
1001
+ }
1002
+ })
1003
+ terminalContainer.addEventListener("drop", async (event) => {
1004
+ prevent(event)
1005
+ dragDepth = 0
1006
+ dropOverlay.classList.remove("active")
1007
+ const dropped = Array.from(event.dataTransfer ? event.dataTransfer.files || [] : [])
1008
+ if (!dropped.length) {
1009
+ return
1010
+ }
1011
+ await this.uploadFiles(dropped, dropOverlay)
1012
+ this.term.focus()
1013
+ })
772
1014
  term.attachCustomKeyEventHandler(event => {
773
1015
  if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
774
1016
  const selection = term.getSelection();
@@ -785,6 +1027,7 @@ document.addEventListener("DOMContentLoaded", async () => {
785
1027
  id: shell_id,
786
1028
  paste: true
787
1029
  })
1030
+ this.captureTextInput(text)
788
1031
 
789
1032
 
790
1033
  // this.socket.run({
@@ -804,14 +1047,48 @@ document.addEventListener("DOMContentLoaded", async () => {
804
1047
  ////term.resize(cols, rows-5);
805
1048
  //term.resize(cols, rows);
806
1049
 
807
- term.onKey(({ key }) => {
808
- if (this.socket) {
809
- if (shell_id) {
810
- this.socket.run({
811
- key,
812
- id: shell_id
813
- })
1050
+ term.onKey(({ key, domEvent }) => {
1051
+ if (this.socket && shell_id) {
1052
+ this.socket.run({
1053
+ key,
1054
+ id: shell_id
1055
+ })
1056
+ }
1057
+ if (!domEvent) {
1058
+ return
1059
+ }
1060
+ if (domEvent.key === "Backspace") {
1061
+ this.handleBackspace()
1062
+ return
1063
+ }
1064
+ if (domEvent.key === "Enter") {
1065
+ this.captureTextInput("\n")
1066
+ return
1067
+ }
1068
+ if (domEvent.key === "Tab") {
1069
+ this.captureTextInput("\t")
1070
+ return
1071
+ }
1072
+ if (domEvent.key === "Escape") {
1073
+ this.resetInputBuffer()
1074
+ return
1075
+ }
1076
+ if (domEvent.ctrlKey || domEvent.metaKey || domEvent.altKey) {
1077
+ if (key === "\u0015" || key === "\u0017" || key === "\u000b" || key === "\u0003" || domEvent.key === "c" || domEvent.key === "C" || domEvent.key === "u" || domEvent.key === "U") {
1078
+ this.resetInputBuffer()
814
1079
  }
1080
+ return
1081
+ }
1082
+ if (key === "\u0015" || key === "\u0017" || key === "\u000b") {
1083
+ this.resetInputBuffer()
1084
+ return
1085
+ }
1086
+ if (key === "\u0008") {
1087
+ this.handleBackspace()
1088
+ return
1089
+ }
1090
+ if (typeof key === "string" && key.length === 1 && key >= " ") {
1091
+ this.captureTextInput(key)
815
1092
  }
816
1093
  });
817
1094
 
@@ -860,19 +1137,15 @@ document.addEventListener("DOMContentLoaded", async () => {
860
1137
  <% if (mod && runnable) { %>
861
1138
  window.addEventListener('message', function(event) {
862
1139
  console.log("Message received from the parent: ", event.data); // Message received from parent
863
- // "foreground" message adds a newline to the terminal
864
- // Needed to fix the issue where the terminal won't write anything when in background
865
- // so when coming back from background, need to refresh by just adding a newline.
1140
+ // "foreground" message triggers a redraw so buffered output appears after backgrounding.
866
1141
  if (event.data && event.data.action === "foreground") {
867
-
868
-
869
- // if dirty, update
870
- // if not dirty, don't update
871
- const delimiter = "\r\n"
872
- if (rpc.dirty) {
873
- rpc.term.write(delimiter)
1142
+ if (rpc.dirty && rpc.term) {
1143
+ const endRow = Math.max(rpc.term.rows - 1, 0)
1144
+ rpc.term.refresh(0, endRow)
1145
+ if (typeof rpc.term.scrollToBottom === "function") {
1146
+ rpc.term.scrollToBottom()
1147
+ }
874
1148
  rpc.dirty = false
875
- } else {
876
1149
  }
877
1150
 
878
1151
  //console.log("buffer", rpc.term.buffer)