pinokiod 3.85.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 (88) 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 +8 -1
  5. package/kernel/api/shell/index.js +6 -0
  6. package/kernel/api/terminal/index.js +166 -0
  7. package/kernel/bin/caddy.js +10 -4
  8. package/kernel/bin/conda.js +3 -2
  9. package/kernel/bin/index.js +53 -2
  10. package/kernel/bin/setup.js +32 -0
  11. package/kernel/bin/vs.js +11 -2
  12. package/kernel/index.js +42 -2
  13. package/kernel/info.js +36 -0
  14. package/kernel/peer.js +42 -18
  15. package/kernel/prototype.js +1 -0
  16. package/kernel/router/index.js +23 -15
  17. package/kernel/router/localhost_static_router.js +0 -3
  18. package/kernel/router/pinokio_domain_router.js +333 -0
  19. package/kernel/shell.js +43 -2
  20. package/kernel/shells.js +21 -1
  21. package/kernel/util.js +4 -2
  22. package/package.json +2 -1
  23. package/pipe/views/login.ejs +1 -1
  24. package/script/install-mode.js +33 -0
  25. package/script/pinokio.json +7 -0
  26. package/server/index.js +636 -246
  27. package/server/public/Socket.js +48 -0
  28. package/server/public/common.js +1956 -257
  29. package/server/public/fseditor.js +71 -12
  30. package/server/public/install.js +1 -1
  31. package/server/public/layout.js +740 -0
  32. package/server/public/modalinput.js +0 -1
  33. package/server/public/opener.js +12 -11
  34. package/server/public/serve/style.css +1 -1
  35. package/server/public/style.css +122 -129
  36. package/server/public/tab-idle-notifier.js +629 -0
  37. package/server/public/terminal_input_tracker.js +63 -0
  38. package/server/public/urldropdown.css +780 -45
  39. package/server/public/urldropdown.js +806 -156
  40. package/server/public/window_storage.js +97 -28
  41. package/server/socket.js +40 -9
  42. package/server/views/404.ejs +1 -1
  43. package/server/views/500.ejs +3 -3
  44. package/server/views/app.ejs +3146 -1381
  45. package/server/views/bookmarklet.ejs +197 -0
  46. package/server/views/bootstrap.ejs +1 -1
  47. package/server/views/columns.ejs +2 -13
  48. package/server/views/connect/x.ejs +4 -4
  49. package/server/views/connect.ejs +13 -14
  50. package/server/views/container.ejs +3 -4
  51. package/server/views/d.ejs +225 -55
  52. package/server/views/download.ejs +1 -1
  53. package/server/views/editor.ejs +2 -2
  54. package/server/views/env_editor.ejs +3 -3
  55. package/server/views/explore.ejs +2 -2
  56. package/server/views/file_explorer.ejs +3 -3
  57. package/server/views/git.ejs +7 -7
  58. package/server/views/github.ejs +3 -3
  59. package/server/views/help.ejs +2 -2
  60. package/server/views/index.ejs +17 -16
  61. package/server/views/index2.ejs +7 -7
  62. package/server/views/init/index.ejs +15 -79
  63. package/server/views/install.ejs +4 -4
  64. package/server/views/keys.ejs +2 -2
  65. package/server/views/layout.ejs +105 -0
  66. package/server/views/mini.ejs +2 -2
  67. package/server/views/net.ejs +45 -13
  68. package/server/views/network.ejs +41 -27
  69. package/server/views/network2.ejs +11 -11
  70. package/server/views/old_network.ejs +10 -10
  71. package/server/views/partials/dynamic.ejs +3 -5
  72. package/server/views/partials/menu.ejs +3 -5
  73. package/server/views/partials/running.ejs +1 -1
  74. package/server/views/pro.ejs +369 -0
  75. package/server/views/prototype/index.ejs +3 -3
  76. package/server/views/required_env_editor.ejs +2 -2
  77. package/server/views/review.ejs +15 -27
  78. package/server/views/rows.ejs +2 -13
  79. package/server/views/screenshots.ejs +298 -142
  80. package/server/views/settings.ejs +6 -7
  81. package/server/views/setup.ejs +3 -4
  82. package/server/views/setup_home.ejs +2 -2
  83. package/server/views/share_editor.ejs +4 -4
  84. package/server/views/shell.ejs +280 -29
  85. package/server/views/start.ejs +2 -2
  86. package/server/views/task.ejs +2 -2
  87. package/server/views/terminal.ejs +326 -52
  88. package/server/views/tools.ejs +461 -17
@@ -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);
@@ -102,7 +125,7 @@ header {
102
125
  */
103
126
  /*
104
127
  .btn {
105
- background: royalblue;
128
+ background: rgba(127, 91, 243, 0.9);
106
129
  }
107
130
  */
108
131
  #status-window {
@@ -114,7 +137,7 @@ header {
114
137
  font-size: 12px;
115
138
  }
116
139
  #status-window strong {
117
- color: royalblue;
140
+ color: rgba(127, 91, 243, 0.9);
118
141
  }
119
142
  #status-window b {
120
143
  color: black;
@@ -136,11 +159,11 @@ body.dark #status-window b {
136
159
  #progress-bar {
137
160
  width: 0%;
138
161
  height: 100%;
139
- background: royalblue;
162
+ background: rgba(127, 91, 243, 0.9);
140
163
  transition: width 0.2s;
141
164
  }
142
165
  #del-bin {
143
- color: royalblue;
166
+ color: rgba(127, 91, 243, 0.9);
144
167
  cursor: pointer;
145
168
  font-weight: bold;
146
169
  padding: 0 5px;
@@ -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,30 +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))
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
251
369
 
252
- this.socket.run({
253
- //uri: location.pathname.slice(1).replace("api/", ""),
254
- <% if (script_id) { %>
255
- id: "<%-JSON.stringify(script_id).slice(1, -1)%>",
256
- <% } %>
257
- <% if (cwd) { %>
258
- cwd: "<%-JSON.stringify(cwd).slice(1, -1)%>",
259
- <% } %>
260
- <% if (script_path) { %>
261
- uri: "<%-JSON.stringify(script_path).slice(1, -1)%>",
262
- <% } else { %>
263
- uri: "~" + location.pathname,
264
- <% } %>
265
- //uri: "<%-uri%>",
370
+ const payload = {
371
+ uri: scriptUri || ("~" + location.pathname),
266
372
  mode,
267
373
  input: query,
268
374
  client: {
269
375
  cols: this.term.cols,
270
376
  rows: this.term.rows,
271
377
  }
272
- }, async (packet) => {
273
- 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) => {
274
387
  if (packet.type === 'start') {
275
388
  refreshParent(packet)
276
389
  reloadMemory()
@@ -298,6 +411,7 @@ document.addEventListener("DOMContentLoaded", async () => {
298
411
  </b>`
299
412
  }
300
413
  } else if (packet.type === "stream") {
414
+ refreshParent(packet)
301
415
  // set the current shell id
302
416
  if (packet.data.id) {
303
417
  shell_id = packet.data.id
@@ -614,6 +728,36 @@ document.addEventListener("DOMContentLoaded", async () => {
614
728
  n.Noty(payload)
615
729
  }
616
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
+ }
617
761
  refreshParent(packet)
618
762
  reloadMemory()
619
763
  } else if (packet.type === "info") {
@@ -734,6 +878,71 @@ document.addEventListener("DOMContentLoaded", async () => {
734
878
  <% } %>
735
879
  await this.start(mode)
736
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
+ }
737
946
  async createTerm (_theme) {
738
947
  if (!this.term) {
739
948
  const theme = Object.assign({ }, _theme, {
@@ -745,7 +954,7 @@ document.addEventListener("DOMContentLoaded", async () => {
745
954
  <% } %>
746
955
  let config = {
747
956
  scrollback: 9999999,
748
- //fontSize: 12,
957
+ fontSize: 12,
749
958
  //fontSize: 14,
750
959
  theme,
751
960
  //theme: xtermTheme.FrontEndDelight
@@ -768,6 +977,40 @@ document.addEventListener("DOMContentLoaded", async () => {
768
977
  }
769
978
  const term = new Terminal(config)
770
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
+ })
771
1014
  term.attachCustomKeyEventHandler(event => {
772
1015
  if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
773
1016
  const selection = term.getSelection();
@@ -784,6 +1027,7 @@ document.addEventListener("DOMContentLoaded", async () => {
784
1027
  id: shell_id,
785
1028
  paste: true
786
1029
  })
1030
+ this.captureTextInput(text)
787
1031
 
788
1032
 
789
1033
  // this.socket.run({
@@ -803,14 +1047,48 @@ document.addEventListener("DOMContentLoaded", async () => {
803
1047
  ////term.resize(cols, rows-5);
804
1048
  //term.resize(cols, rows);
805
1049
 
806
- term.onKey(({ key }) => {
807
- if (this.socket) {
808
- if (shell_id) {
809
- this.socket.run({
810
- key,
811
- id: shell_id
812
- })
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()
813
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)
814
1092
  }
815
1093
  });
816
1094
 
@@ -859,19 +1137,15 @@ document.addEventListener("DOMContentLoaded", async () => {
859
1137
  <% if (mod && runnable) { %>
860
1138
  window.addEventListener('message', function(event) {
861
1139
  console.log("Message received from the parent: ", event.data); // Message received from parent
862
- // "foreground" message adds a newline to the terminal
863
- // Needed to fix the issue where the terminal won't write anything when in background
864
- // 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.
865
1141
  if (event.data && event.data.action === "foreground") {
866
-
867
-
868
- // if dirty, update
869
- // if not dirty, don't update
870
- const delimiter = "\r\n"
871
- if (rpc.dirty) {
872
- 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
+ }
873
1148
  rpc.dirty = false
874
- } else {
875
1149
  }
876
1150
 
877
1151
  //console.log("buffer", rpc.term.buffer)