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
package/server/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const express = require('express');
2
+ const querystring = require("querystring");
2
3
  const diff = require('diff')
3
4
  const kill = require('kill-sync')
4
5
  const { isBinaryFile } = require("isbinaryfile");
@@ -199,39 +200,109 @@ class Server {
199
200
  let id = `${filepath}?cwd=${cwd}`
200
201
  obj.script_id = id
201
202
  //if (this.kernel.api.running[filepath]) {
202
- if (this.kernel.api.running[id] || selected_query.plugin === obj.src) {
203
+ if (obj.src.startsWith("/run" + selected_query.plugin)) {
203
204
  obj.running = true
204
205
  obj.display = "indent"
205
- if (selected_query.plugin === obj.src) {
206
- console.log("selected_query", selected_query)
207
- obj.default = true
208
- for(let key in selected_query) {
209
- if (key !== "plugin") {
210
- obj.href = obj.href + "&" + key + "=" + encodeURIComponent(selected_query[key])
211
- }
206
+ obj.default = true
207
+ for(let key in selected_query) {
208
+ if (key !== "plugin") {
209
+ obj.href = obj.href + "&" + key + "=" + encodeURIComponent(selected_query[key])
212
210
  }
213
211
  }
214
212
  running_dynamic.push(obj)
213
+ } else {
214
+ for(let running_id in this.kernel.api.running) {
215
+ if (running_id.startsWith(id)) {
216
+ let obj2 = structuredClone(obj)
217
+ obj2.running = true
218
+ obj2.display = "indent"
219
+
220
+ const query = running_id.split("?")[1];
221
+ const params = query ? querystring.parse(query) : {};
222
+
223
+ let queryStrippedHref = obj2.href.split("?")[0]
224
+ if (params && Object.keys(params).length > 0) {
225
+ obj2.href = queryStrippedHref + "?" + querystring.stringify(params)
226
+ } else {
227
+ obj2.href = queryStrippedHref
228
+ }
229
+
230
+ obj2.script_id = running_id
231
+ obj2.target = "@" + obj2.href
232
+ obj2.target_full = obj2.href
233
+
234
+ running_dynamic.push(obj2)
235
+ }
236
+ }
215
237
  }
216
238
  }
217
239
  } else if (key === "shell") {
240
+ const appendSession = (value, session) => {
241
+ if (!value || !session) {
242
+ return value
243
+ }
244
+ const hasPrefix = value.startsWith("@")
245
+ const raw = hasPrefix ? value.slice(1) : value
246
+ const [pathPart, queryPart] = raw.split("?")
247
+ const parsed = queryPart ? querystring.parse(queryPart) : {}
248
+ parsed.session = session
249
+ const qs = querystring.stringify(parsed)
250
+ const combined = qs ? `${pathPart}?${qs}` : pathPart
251
+ return hasPrefix ? `@${combined}` : combined
252
+ }
253
+
218
254
  let unix_path = Util.p2u(this.kernel.path("api", name))
219
255
  let shell_id = this.get_shell_id(unix_path, indexPath, obj[key])
220
256
  let decoded_shell_id = decodeURIComponent(shell_id)
221
257
  let id = "shell/" + decoded_shell_id
222
- if (this.kernel.api.running[id] || selected_query.plugin === id) {
258
+
259
+ const originalHref = obj.href
260
+ const originalTarget = obj.target
261
+
262
+ const activeShells = (this.kernel.shell && Array.isArray(this.kernel.shell.shells))
263
+ ? this.kernel.shell.shells.filter((entry) => {
264
+ if (!entry || !entry.id) {
265
+ return false
266
+ }
267
+ return entry.id === id || entry.id.startsWith(`${id}?session=`)
268
+ })
269
+ : []
270
+
271
+ if (activeShells.length > 0 || selected_query.plugin === id) {
223
272
  obj.running = true
224
273
  obj.display = "indent"
225
274
  if (selected_query.plugin === id) {
226
275
  obj.default = true
227
- console.log("selected_query", selected_query)
228
276
  for(let key in selected_query) {
229
277
  if (key !== "plugin") {
230
278
  obj.href = obj.href + "&" + key + "=" + encodeURIComponent(selected_query[key])
231
279
  }
232
280
  }
233
281
  }
234
- running_dynamic.push(obj)
282
+
283
+ if (activeShells.length === 0) {
284
+ running_dynamic.push(obj)
285
+ } else {
286
+ activeShells.forEach((shellEntry) => {
287
+ const clone = structuredClone(obj)
288
+ clone.running = true
289
+ clone.display = "indent"
290
+ clone.shell_id = shellEntry.id
291
+ const sessionMatch = /[?&]session=([^&]+)/.exec(shellEntry.id)
292
+ if (sessionMatch && sessionMatch[1]) {
293
+ const sessionValue = sessionMatch[1]
294
+ clone.href = appendSession(originalHref, sessionValue)
295
+ const baseTarget = originalTarget || `@${originalHref}`
296
+ clone.target = appendSession(baseTarget, sessionValue)
297
+ clone.target_full = clone.href
298
+ } else {
299
+ clone.href = originalHref
300
+ clone.target = originalTarget ? originalTarget : `@${clone.href}`
301
+ clone.target_full = clone.href
302
+ }
303
+ running_dynamic.push(clone)
304
+ })
305
+ }
235
306
  }
236
307
  }
237
308
  traverse(obj[key], indexPath);
@@ -373,7 +444,6 @@ class Server {
373
444
  let p = this.kernel.path("api", name)
374
445
  let html_path = path.resolve(p, "index.html")
375
446
  let html_exists = await this.kernel.exists(html_path)
376
- console.log({ html_path, html_exists })
377
447
  if (html_exists) {
378
448
  return Object.assign({
379
449
  title: name,
@@ -381,7 +451,7 @@ class Server {
381
451
  default: true,
382
452
  icon: "fa-solid fa-link",
383
453
  text: "index.html",
384
- href: "index.html?raw=true",
454
+ href: `/asset/api/${name}/index.html`,
385
455
  }]
386
456
  }, cfg)
387
457
  } else {
@@ -399,6 +469,20 @@ class Server {
399
469
  async getGit(ref, filepath) {
400
470
  let dir = this.kernel.path("api", filepath)
401
471
  let branches = await git.listBranches({ fs, dir });
472
+ // no branch, means no git repo => initialize
473
+ if (branches.length === 0) {
474
+ return {}
475
+ // const defaultBranch = 'main';
476
+ // await git.init({ fs, dir, defaultBranch });
477
+ // branches = await git.listBranches({ fs, dir });
478
+ // const current = await git.currentBranch({ fs, dir, fullname: false }) || defaultBranch;
479
+ // if (!branches.includes(current)) {
480
+ // branches.unshift(current);
481
+ // }
482
+ // if (!ref || ref === 'HEAD') {
483
+ // ref = current;
484
+ // }
485
+ }
402
486
  let log = []
403
487
  try {
404
488
  log = await git.log({ fs, dir, depth: 50, ref: ref }); // fetch last 50 commits
@@ -579,7 +663,6 @@ class Server {
579
663
  }
580
664
  }
581
665
 
582
-
583
666
  let uri = this.kernel.path("api")
584
667
  try {
585
668
  let launcher = await this.kernel.api.launcher(name)
@@ -625,30 +708,30 @@ class Server {
625
708
  }
626
709
  const env = await this.kernel.env("api/" + name)
627
710
 
628
- // profile + feed
629
- const repositoryPath = path.resolve(this.kernel.api.userdir, name)
630
-
631
- try {
632
- await git.resolveRef({ fs, dir: repositoryPath, ref: 'HEAD' });
633
- } catch (err) {
634
- // repo doesn't exist. initialize.
635
- console.log(`repo doesn't exist at ${repositoryPath}. initialize`)
636
- await git.init({ fs, dir: repositoryPath });
637
- }
638
-
639
- let gitRemote = await git.getConfig({ fs, http, dir: repositoryPath, path: 'remote.origin.url' })
640
- let profile
641
- let feed
642
- if (gitRemote) {
643
- gitRemote = gitRemote.replace(/\.git$/i, '')
644
-
645
- let system_env = {}
646
- if (this.kernel.homedir) {
647
- system_env = await Environment.get(this.kernel.homedir, this.kernel)
648
- }
649
- profile = this.profile(gitRemote)
650
- feed = this.newsfeed(gitRemote)
651
- }
711
+ // // profile + feed
712
+ // const repositoryPath = path.resolve(this.kernel.api.userdir, name)
713
+ //
714
+ // try {
715
+ // await git.resolveRef({ fs, dir: repositoryPath, ref: 'HEAD' });
716
+ // } catch (err) {
717
+ // // repo doesn't exist. initialize.
718
+ // console.log(`repo doesn't exist at ${repositoryPath}. initialize`)
719
+ // await git.init({ fs, dir: repositoryPath });
720
+ // }
721
+ //
722
+ // let gitRemote = await git.getConfig({ fs, http, dir: repositoryPath, path: 'remote.origin.url' })
723
+ // let profile
724
+ // let feed
725
+ // if (gitRemote) {
726
+ // gitRemote = gitRemote.replace(/\.git$/i, '')
727
+ //
728
+ // let system_env = {}
729
+ // if (this.kernel.homedir) {
730
+ // system_env = await Environment.get(this.kernel.homedir, this.kernel)
731
+ // }
732
+ // profile = this.profile(gitRemote)
733
+ // feed = this.newsfeed(gitRemote)
734
+ // }
652
735
 
653
736
  // git
654
737
 
@@ -727,8 +810,8 @@ class Server {
727
810
  // dynamic: "/pinokio/dynamic/" + name,
728
811
  dynamic_content: null,
729
812
  name,
730
- profile,
731
- feed,
813
+ // profile,
814
+ // feed,
732
815
  tabs: (this.tabs[name] || []),
733
816
  config,
734
817
  // sidebar_url: "/pinokio/sidebar/" + name,
@@ -1442,9 +1525,51 @@ class Server {
1442
1525
  }
1443
1526
  // check if there is a running process with this folder name
1444
1527
  let runningApps = new Set()
1528
+ const item_path = this.kernel.path("api", items[i].name)
1529
+ const normalizedItemPath = path.normalize(item_path)
1530
+ const itemPathWithSep = normalizedItemPath.endsWith(path.sep)
1531
+ ? normalizedItemPath
1532
+ : normalizedItemPath + path.sep
1533
+ const unix_item_path = Util.p2u(item_path)
1534
+ const shellPrefix = "shell/" + unix_item_path + "_"
1535
+ const matchesShell = (candidate) => {
1536
+ if (!candidate) return false
1537
+ const idMatches = typeof candidate.id === "string" && candidate.id.startsWith(shellPrefix)
1538
+ const shellPath = typeof candidate.path === "string" ? path.normalize(candidate.path) : null
1539
+ const groupPath = typeof candidate.group === "string" ? path.normalize(candidate.group) : null
1540
+ const parentCwd = candidate.params && candidate.params.$parent && typeof candidate.params.$parent.cwd === "string"
1541
+ ? path.normalize(candidate.params.$parent.cwd)
1542
+ : null
1543
+ const paramsCwd = candidate.params && typeof candidate.params.cwd === "string"
1544
+ ? path.normalize(candidate.params.cwd)
1545
+ : null
1546
+ const pathMatches = shellPath && (shellPath === normalizedItemPath || shellPath.startsWith(itemPathWithSep))
1547
+ const groupMatches = groupPath && (groupPath === normalizedItemPath || groupPath.startsWith(itemPathWithSep))
1548
+ const parentMatches = parentCwd && (parentCwd === normalizedItemPath || parentCwd.startsWith(itemPathWithSep))
1549
+ const paramsMatches = paramsCwd && (paramsCwd === normalizedItemPath || paramsCwd.startsWith(itemPathWithSep))
1550
+ return idMatches || pathMatches || groupMatches || parentMatches || paramsMatches
1551
+ }
1552
+ const shellMatches = (this.kernel.shell && typeof this.kernel.shell.find === "function")
1553
+ ? this.kernel.shell.find({ filter: matchesShell })
1554
+ : []
1555
+ const addShellEntries = () => {
1556
+ if (!shellMatches || shellMatches.length === 0) {
1557
+ return
1558
+ }
1559
+ if (!items[i].running_scripts) {
1560
+ items[i].running_scripts = []
1561
+ }
1562
+ for (const sh of shellMatches) {
1563
+ if (!sh || !sh.id) continue
1564
+ const exists = items[i].running_scripts.some((entry) => entry && entry.id === sh.id)
1565
+ if (!exists) {
1566
+ items[i].running_scripts.push({ id: sh.id, name: sh.params.$title || "Shell", type: "shell" })
1567
+ }
1568
+ }
1569
+ }
1445
1570
  for(let key in this.kernel.api.running) {
1446
1571
  //let p = this.kernel.path("api", items[i].name) + path.sep
1447
- let p = this.kernel.path("api", items[i].name)
1572
+ let p = item_path
1448
1573
 
1449
1574
  // not only should include the pattern, but also end with it (otherwise can include similar patterns such as /api/qqqa, /api/qqqaaa, etc.
1450
1575
 
@@ -1468,7 +1593,6 @@ class Server {
1468
1593
  if (chunks.length > 1) {
1469
1594
  let folder = chunks[0]
1470
1595
  /// if the folder name matches, it's running
1471
- let item_path = this.kernel.path("api", items[i].name)
1472
1596
  if (item_path === folder) {
1473
1597
  is_running = true
1474
1598
  }
@@ -1485,12 +1609,15 @@ class Server {
1485
1609
  //if (key.includes(p) && key.endsWith(p)) {
1486
1610
  if (is_running) {
1487
1611
  // add to running
1488
- running.push(items[i])
1612
+ if (!items[i].running) {
1613
+ running.push(items[i])
1614
+ items[i].running = true
1615
+ items[i].index = index
1616
+ index++
1617
+ }
1489
1618
  if (!items[i].running_scripts) {
1490
1619
  items[i].running_scripts = []
1491
1620
  }
1492
- items[i].running = true
1493
- items[i].index = index
1494
1621
 
1495
1622
  // add the running script to running_scripts array
1496
1623
  // 1. normal api script
@@ -1511,24 +1638,17 @@ class Server {
1511
1638
  items[i].running_scripts.push({ id: key, name })
1512
1639
  }
1513
1640
  } else {
1514
- let shell = this.kernel.shell.find({
1515
- filter: (shell) => {
1516
- let item_path = this.kernel.path("api", items[i].name)
1517
- let unix_item_path = Util.p2u(item_path)
1518
- return shell.id.startsWith("shell/" + unix_item_path + "_")
1519
- }
1520
- })
1521
- if (shell.length > 0) {
1522
- items[i].running = true
1523
- items[i].index = index
1524
- for(let sh of shell) {
1525
- items[i].running_scripts.push({ id: sh.id, name: "Terminal", type: "shell" })
1526
- }
1527
- }
1641
+ addShellEntries()
1528
1642
  }
1529
- index++;
1530
1643
  }
1531
1644
  }
1645
+ if (!items[i].running && shellMatches && shellMatches.length > 0) {
1646
+ running.push(items[i])
1647
+ items[i].running = true
1648
+ items[i].index = index
1649
+ addShellEntries()
1650
+ index++
1651
+ }
1532
1652
  if (!items[i].running) {
1533
1653
  items[i].index = index
1534
1654
  index++;
@@ -1874,15 +1994,19 @@ class Server {
1874
1994
  // }
1875
1995
  menuitem.href = "/shell/" + shell_id + "?" + params.toString()
1876
1996
  let decoded_shell_id = decodeURIComponent(shell_id)
1877
- let shell = this.kernel.shell.get(decoded_shell_id)
1878
- menuitem.shell_id = "shell/" + decoded_shell_id
1997
+ const shellPrefixId = "shell/" + decoded_shell_id
1998
+ let shell = this.kernel.shell.get(shellPrefixId)
1999
+ if (!shell && this.kernel.shell && Array.isArray(this.kernel.shell.shells)) {
2000
+ shell = this.kernel.shell.shells.find((entry) => {
2001
+ if (!entry || !entry.id) {
2002
+ return false
2003
+ }
2004
+ return entry.id === shellPrefixId || entry.id.startsWith(`${shellPrefixId}?session=`)
2005
+ })
2006
+ }
2007
+ menuitem.shell_id = shellPrefixId
1879
2008
  if (shell) {
1880
2009
  menuitem.running = true
1881
- } else {
1882
- let shell = this.kernel.shell.get(decoded_shell_id)
1883
- if (shell) {
1884
- menuitem.running = true
1885
- }
1886
2010
  }
1887
2011
  }
1888
2012
  return menuitem
@@ -2001,10 +2125,11 @@ class Server {
2001
2125
  } else if (menuitem.href.startsWith("/")) {
2002
2126
  let run_path = "/run"
2003
2127
  if (menuitem.href.startsWith(run_path)) {
2004
- u = new URL("http://localhost" + menuitem.href.slice(run_path.length))
2005
- cwd = u.searchParams.get("cwd")
2006
- u.search = ""
2007
- menuitem.src = u.pathname
2128
+ menuitem.src = menuitem.href
2129
+ // u = new URL("http://localhost" + menuitem.href.slice(run_path.length))
2130
+ // cwd = u.searchParams.get("cwd")
2131
+ // u.search = ""
2132
+ // menuitem.src = u.pathname
2008
2133
  } else {
2009
2134
  u = new URL("http://localhost" + menuitem.href)
2010
2135
  cwd = u.searchParams.get("cwd")
@@ -2143,16 +2268,19 @@ class Server {
2143
2268
  if (config.menu[i].popout) {
2144
2269
  config.menu[i].target = "_blank"
2145
2270
  } else {
2146
- config.menu[i].target = "@" + (config.menu[i].id || config.menu[i].src)
2147
- }
2148
-
2149
-
2150
- if (config.menu[i].href && config.menu[i].href.startsWith("http")) {
2151
- if (req.agent !== "electron") {
2152
- config.menu[i].target = "_blank"
2271
+ const targetBase = config.menu[i].id || config.menu[i].src || config.menu[i].href
2272
+ config.menu[i].target = targetBase ? "@" + targetBase : undefined
2273
+ if (config.menu[i].href) {
2274
+ config.menu[i].target_full = config.menu[i].href
2153
2275
  }
2154
2276
  }
2155
2277
 
2278
+ //if (config.menu[i].href && config.menu[i].href.startsWith("http")) {
2279
+ // if (req.agent !== "electron") {
2280
+ // config.menu[i].target = "_blank"
2281
+ // }
2282
+ //}
2283
+
2156
2284
  if (menuitem.shell_id) {
2157
2285
  config.menu[i].shell_id = menuitem.shell_id
2158
2286
  }
@@ -2478,7 +2606,7 @@ class Server {
2478
2606
  // }
2479
2607
  }
2480
2608
  async setConfig(config) {
2481
- let home = this.kernel.store.get("home")
2609
+ let home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2482
2610
  let theme = this.kernel.store.get("theme")
2483
2611
  let mode = this.kernel.store.get("mode")
2484
2612
  // let drive = this.kernel.store.get("drive")
@@ -2543,9 +2671,9 @@ class Server {
2543
2671
  // }
2544
2672
 
2545
2673
 
2546
- home = this.kernel.store.get("home")
2674
+ home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2547
2675
  theme = this.kernel.store.get("theme")
2548
- let new_home = this.kernel.store.get("new_home")
2676
+ let new_home = this.kernel.store.get("new_home") || process.env.PINOKIO_HOME
2549
2677
 
2550
2678
  // Handle environment variables
2551
2679
  // HTTP_PROXY
@@ -2618,6 +2746,66 @@ class Server {
2618
2746
  return true
2619
2747
  }
2620
2748
  }
2749
+ add_extra_urls(info) {
2750
+ // get only the parts not from this peer
2751
+ for(let host in this.kernel.peer.info) {
2752
+ let host_info = this.kernel.peer.info[host]
2753
+
2754
+ let host_rewrites = host_info.rewrite_mapping
2755
+ for(let key in host_rewrites) {
2756
+ info.push({
2757
+ online: true,
2758
+ host: {
2759
+ ip: host,
2760
+ local: this.kernel.peer.host === host,
2761
+ name: host_info.name,
2762
+ platform: host_info.platform,
2763
+ arch: host_info.arch
2764
+ },
2765
+ name: `[Files] ${host_rewrites[key].name}`,
2766
+ ip: host_rewrites[key].external_ip
2767
+ })
2768
+ }
2769
+ if (this.kernel.peer.host !== host) {
2770
+ let host_routers = host_info.router_info
2771
+ for(let host_router of host_routers) {
2772
+ let ip
2773
+ // the peer sharing works only if external_ip is available (caddy is installed)
2774
+ if (host_router.external_ip) {
2775
+ ip = host_router.external_ip
2776
+ } else {
2777
+ // if caddy is not turned on, set ip as null, so that it suggests turning on peer sharing
2778
+ ip = null
2779
+ }
2780
+ info.push({
2781
+ online: true,
2782
+ host: {
2783
+ ip: host,
2784
+ name: host_info.name,
2785
+ platform: host_info.platform,
2786
+ arch: host_info.arch
2787
+ },
2788
+ name: host_router.title || host_router.name,
2789
+ ip
2790
+ })
2791
+ }
2792
+ }
2793
+
2794
+ for(let app of host_info.installed) {
2795
+ info.push({
2796
+ host: {
2797
+ ip: host,
2798
+ local: this.kernel.peer.host === host,
2799
+ name: host_info.name,
2800
+ platform: host_info.platform,
2801
+ arch: host_info.arch
2802
+ },
2803
+ name: app.title || app.folder,
2804
+ ip: app.http_href.replace("http://", "")
2805
+ })
2806
+ }
2807
+ }
2808
+ }
2621
2809
  async terminals(filepath) {
2622
2810
  let venvs = await Util.find_venv(filepath)
2623
2811
  let terminal
@@ -2644,14 +2832,14 @@ class Server {
2644
2832
  }
2645
2833
  terminal = {
2646
2834
  icon: "fa-solid fa-terminal",
2647
- title: "Web Terminal",
2835
+ title: "Terminal",
2648
2836
  subtitle: "Open the terminal in the browser",
2649
2837
  menu: terminals
2650
2838
  }
2651
2839
  } else {
2652
2840
  terminal = {
2653
2841
  icon: "fa-solid fa-terminal",
2654
- title: "Web terminal",
2842
+ title: "Terminal",
2655
2843
  subtitle: "Work with the terminal directly in the browser",
2656
2844
  menu: [this.renderShell(filepath, 0, 0, {
2657
2845
  icon: "fa-solid fa-terminal",
@@ -2833,7 +3021,7 @@ class Server {
2833
3021
  await this.syncConfig()
2834
3022
 
2835
3023
  try {
2836
- let _home = this.kernel.store.get("home")
3024
+ let _home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2837
3025
  if (_home) {
2838
3026
  await this.startLogging(_home)
2839
3027
  }
@@ -2869,7 +3057,7 @@ class Server {
2869
3057
  }
2870
3058
 
2871
3059
  let version = this.kernel.store.get("version")
2872
- let home = this.kernel.store.get("home")
3060
+ let home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2873
3061
 
2874
3062
  let needInitHome = false
2875
3063
  if (home) {
@@ -3181,17 +3369,25 @@ class Server {
3181
3369
  })
3182
3370
  }))
3183
3371
  this.app.get("/columns", ex(async (req, res) => {
3372
+ const originSrc = req.query.origin || req.get('Referrer') || '/';
3373
+ const targetSrc = req.query.target || originSrc;
3184
3374
  res.render("columns", {
3185
3375
  theme: this.theme,
3186
3376
  agent: req.agent,
3187
- src: req.get('Referrer')
3377
+ originSrc,
3378
+ targetSrc,
3379
+ src: originSrc
3188
3380
  })
3189
3381
  }))
3190
3382
  this.app.get("/rows", ex(async (req, res) => {
3383
+ const originSrc = req.query.origin || req.get('Referrer') || '/';
3384
+ const targetSrc = req.query.target || originSrc;
3191
3385
  res.render("rows", {
3192
3386
  theme: this.theme,
3193
3387
  agent: req.agent,
3194
- src: req.get('Referrer')
3388
+ originSrc,
3389
+ targetSrc,
3390
+ src: originSrc
3195
3391
  })
3196
3392
  }))
3197
3393
 
@@ -3333,7 +3529,7 @@ class Server {
3333
3529
  link: null
3334
3530
  })
3335
3531
  }))
3336
- this.app.get("/", ex(async (req, res) => {
3532
+ const renderHomePage = ex(async (req, res) => {
3337
3533
  // check bin folder
3338
3534
  // let bin_path = this.kernel.path("bin/miniconda")
3339
3535
  // let bin_exists = await this.exists(bin_path)
@@ -3350,7 +3546,7 @@ class Server {
3350
3546
  // }
3351
3547
 
3352
3548
  if (req.query.mode !== "settings" && !home) {
3353
- res.redirect("/?mode=settings")
3549
+ res.redirect("/home?mode=settings")
3354
3550
  return
3355
3551
  }
3356
3552
  if (req.query.mode === "help") {
@@ -3372,7 +3568,6 @@ class Server {
3372
3568
  return
3373
3569
  }
3374
3570
 
3375
-
3376
3571
  if (req.query.mode === 'settings') {
3377
3572
 
3378
3573
  let platform = os.platform()
@@ -3393,7 +3588,7 @@ class Server {
3393
3588
  "* NO exFAT drives",
3394
3589
  ],
3395
3590
  val: this.kernel.homedir ? this.kernel.homedir : _home,
3396
- placeholder: "Enter the absolute path to use as your Pinokio home folder (D:\\pinokio, /Users/alice/pinokiofs, etc.)"
3591
+ placeholder: "Enter the absolute path to use as your Pinokio home folder (D\\pinokio, /Users/alice/pinokiofs, etc.)"
3397
3592
  // }, {
3398
3593
  // key: "drive",
3399
3594
  // val: path.resolve(this.kernel.homedir, "drive"),
@@ -3466,6 +3661,58 @@ class Server {
3466
3661
  meta[folder] = await this.kernel.api.meta(folder)
3467
3662
  }
3468
3663
  await this.render(req, res, [], meta)
3664
+ })
3665
+
3666
+ this.app.get("/", ex(async (req, res) => {
3667
+ const protocol = (req.$source && req.$source.protocol) || req.protocol || 'http'
3668
+ const host = req.get('host') || `localhost:${this.port}`
3669
+ const baseUrl = `${protocol}://${host}`
3670
+
3671
+ const initialUrl = new URL('/home', baseUrl)
3672
+ const defaultUrl = new URL('/home', baseUrl)
3673
+
3674
+ for (const [key, value] of Object.entries(req.query)) {
3675
+ if (key === 'session') {
3676
+ continue
3677
+ }
3678
+ if (Array.isArray(value)) {
3679
+ value.forEach((val) => {
3680
+ initialUrl.searchParams.append(key, val)
3681
+ })
3682
+ } else if (value != null) {
3683
+ initialUrl.searchParams.set(key, value)
3684
+ }
3685
+ }
3686
+
3687
+ if (!home) {
3688
+ defaultUrl.searchParams.set('mode', 'settings')
3689
+ }
3690
+
3691
+ res.render('layout', {
3692
+ theme: this.theme,
3693
+ agent: req.agent,
3694
+ initialPath: initialUrl.pathname + initialUrl.search + initialUrl.hash,
3695
+ defaultPath: defaultUrl.pathname + defaultUrl.search + defaultUrl.hash,
3696
+ sessionId: typeof req.query.session === 'string' ? req.query.session : null
3697
+ })
3698
+ }))
3699
+
3700
+ this.app.get("/home", renderHomePage)
3701
+
3702
+
3703
+ this.app.get("/bundle/:name", ex(async (req, res) => {
3704
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
3705
+ bin: this.kernel.bin.preset(req.params.name),
3706
+ })
3707
+ if (!requirements_pending && install_required) {
3708
+ res.json({
3709
+ available: false,
3710
+ })
3711
+ } else {
3712
+ res.json({
3713
+ available: true,
3714
+ })
3715
+ }
3469
3716
  }))
3470
3717
 
3471
3718
  this.app.get("/init", ex(async (req, res) => {
@@ -4038,7 +4285,12 @@ class Server {
4038
4285
  GET /shell/:unix_path => shell id: 'shell/:unix_path'
4039
4286
  */
4040
4287
 
4041
- let id = "shell/" + decodeURIComponent(req.params.id)
4288
+ let baseShellId = "shell/" + decodeURIComponent(req.params.id)
4289
+ const sessionId = typeof req.query.session === "string" && req.query.session.length > 0 ? req.query.session : null
4290
+ let id = baseShellId
4291
+ if (sessionId) {
4292
+ id = `${baseShellId}?session=${sessionId}`
4293
+ }
4042
4294
  let target = req.query.target ? req.query.target : null
4043
4295
  let cwd = this.kernel.path(this.kernel.api.filePath(decodeURIComponent(req.query.path)))
4044
4296
  let message = req.query.message ? decodeURIComponent(req.query.message) : null
@@ -4304,7 +4556,11 @@ class Server {
4304
4556
  return serverless_mapping[name]
4305
4557
  })
4306
4558
  let current_urls = await this.current_urls(req.originalUrl.slice(1))
4559
+ let static_routes = Object.keys(this.kernel.router.rewrite_mapping).map((key) => {
4560
+ return this.kernel.router.rewrite_mapping[key]
4561
+ })
4307
4562
  res.render("net", {
4563
+ static_routes,
4308
4564
  selected_name: req.params.name,
4309
4565
  current_urls,
4310
4566
  docs: this.docs,
@@ -4821,6 +5077,7 @@ class Server {
4821
5077
  // }))
4822
5078
  this.app.post("/env", ex(async (req, res) => {
4823
5079
  let fullpath = path.resolve(this.kernel.homedir, req.body.filepath, "ENVIRONMENT")
5080
+ console.log({ fullpath })
4824
5081
  let updated = req.body.vals
4825
5082
  let hosts = req.body.hosts
4826
5083
  await Util.update_env(fullpath, updated)
@@ -5128,7 +5385,7 @@ class Server {
5128
5385
  }
5129
5386
  }
5130
5387
  } catch (err) {
5131
- console.log("git status matrix error", err)
5388
+ // console.log("git status matrix error 1", err)
5132
5389
  }
5133
5390
  } else {
5134
5391
  try {
@@ -5187,7 +5444,7 @@ class Server {
5187
5444
  });
5188
5445
  }
5189
5446
  } catch (err) {
5190
- console.log("git diff error", err);
5447
+ console.log("git diff error 2", err);
5191
5448
  }
5192
5449
  }
5193
5450
  let git_commit_url = `/run/scripts/git/commit.json?cwd=${dir}&callback_target=parent&callback=$location.href`
@@ -5286,22 +5543,15 @@ class Server {
5286
5543
  }
5287
5544
 
5288
5545
 
5289
- let { ref, config, remote, connected, log, branch, branches, dir } = await this.getGit(req.params.ref, req.params[0])
5546
+ let response = await this.getGit(req.params.ref, req.params[0])
5290
5547
 
5291
5548
  res.render("git", {
5292
- config,
5293
- remote,
5294
- connected,
5295
- log,
5296
- branch,
5297
- branches,
5298
- ref,
5299
5549
  path: req.params[0],
5300
5550
  // changes,
5301
- dir,
5302
5551
  theme: this.theme,
5303
5552
  platform: this.kernel.platform,
5304
5553
  agent: req.agent,
5554
+ ...response
5305
5555
  })
5306
5556
  }))
5307
5557
  this.app.get("/d/*", ex(async (req, res) => {
@@ -5363,9 +5613,10 @@ class Server {
5363
5613
  // console.log("online_terminal", online_terminal)
5364
5614
  terminal.menus = href_menus
5365
5615
  let dynamic = [
5616
+ terminal,
5366
5617
  {
5367
5618
  icon: "fa-solid fa-robot",
5368
- title: "AI Engineer",
5619
+ title: "AI Terminal",
5369
5620
  subtitle: "Let AI work on this app",
5370
5621
  menu: shell_menus
5371
5622
  },
@@ -5375,7 +5626,6 @@ class Server {
5375
5626
  subtitle: "Open this project in 3rd party apps",
5376
5627
  menu: exec_menus
5377
5628
  },
5378
- terminal
5379
5629
  ]
5380
5630
  let spec = ""
5381
5631
  try {
@@ -5669,12 +5919,122 @@ class Server {
5669
5919
  // res.json({ success: true })
5670
5920
  // }))
5671
5921
 
5922
+
5923
+ this.app.get("/info/scripts", ex(async (req, res) => {
5924
+ /*
5925
+ returns something like this by using the this.kernel.memory.local variable, extracting the api name and adding all running scripts in each associated array, setting the uri as the script path, and the local variables as the local attribute
5926
+ the api name in the following examples are "comfyui" and "gradio", therefore there are two top level attributes "comfyui" and "gradio", each of which has an array value made up of all scripts running under that api
5927
+ {
5928
+ “comfyui”: [{
5929
+ “uri”: “/data/pinokio/api/comfyui/start.js”,
5930
+ “local”: {
5931
+ "port": 42008,
5932
+ "url": "http://127.0.0.1:42008"
5933
+ }
5934
+ }],
5935
+ “gradio”: [{
5936
+ “uri”: “/data/pinokio/api/gradio/start.js”,
5937
+ “local”: {
5938
+ "port": 7860,
5939
+ "url": "http://127.0.0.1:7860"
5940
+ }
5941
+ }]
5942
+ }
5943
+ */
5944
+ if (!this.kernel || !this.kernel.info || typeof this.kernel.info.scriptsByApi !== 'function') {
5945
+ res.json({})
5946
+ return
5947
+ }
5948
+
5949
+ const scriptsByApi = this.kernel.info.scriptsByApi()
5950
+ res.json(scriptsByApi)
5951
+ }))
5952
+ this.app.get("/info/local", ex(async (req, res) => {
5953
+ if (this.kernel && this.kernel.memory && this.kernel.memory.local) {
5954
+ res.json(this.kernel.memory.local)
5955
+ } else {
5956
+ res.json({})
5957
+ }
5958
+ }))
5959
+
5960
+
5672
5961
  this.app.get("/info/procs", ex(async (req, res) => {
5673
5962
  await this.kernel.processes.refresh()
5674
5963
 
5675
- let info = []
5676
- for(let item of this.kernel.processes.info) {
5677
- info.push({
5964
+ const requestedProtocol = ((req.$source && req.$source.protocol) || req.protocol || '').toLowerCase()
5965
+ const preferHttps = requestedProtocol === 'https'
5966
+
5967
+ const routerInfo = (preferHttps && this.kernel.router && this.kernel.router.info && this.kernel.peer)
5968
+ ? (this.kernel.router.info[this.kernel.peer.host] || {})
5969
+ : null
5970
+
5971
+ const resolveHttpsHosts = (proc) => {
5972
+ if (!routerInfo) {
5973
+ return []
5974
+ }
5975
+ const possibleKeys = new Set()
5976
+ if (proc.ip) {
5977
+ possibleKeys.add(proc.ip)
5978
+ }
5979
+ if (proc.port) {
5980
+ possibleKeys.add(`127.0.0.1:${proc.port}`)
5981
+ possibleKeys.add(`localhost:${proc.port}`)
5982
+ possibleKeys.add(`0.0.0.0:${proc.port}`)
5983
+ }
5984
+
5985
+ const hosts = new Set()
5986
+ for (const key of possibleKeys) {
5987
+ const matches = routerInfo[key]
5988
+ if (!matches || matches.length === 0) {
5989
+ continue
5990
+ }
5991
+ for (const match of matches) {
5992
+ if (typeof match === 'string' && match.trim().length > 0) {
5993
+ hosts.add(match.trim())
5994
+ }
5995
+ }
5996
+ }
5997
+ return Array.from(hosts)
5998
+ }
5999
+
6000
+ const preferFriendlyHost = (hosts) => {
6001
+ if (!hosts || hosts.length === 0) {
6002
+ return null
6003
+ }
6004
+ for (const host of hosts) {
6005
+ if (!/^\d+\.localhost$/i.test(host)) {
6006
+ return host
6007
+ }
6008
+ }
6009
+ return hosts[0]
6010
+ }
6011
+
6012
+ const info = this.kernel.processes.info.map((item) => {
6013
+ const httpUrl = item.ip ? `http://${item.ip}` : null
6014
+ let httpsHosts = []
6015
+ if (preferHttps) {
6016
+ httpsHosts = resolveHttpsHosts(item)
6017
+ }
6018
+ const httpsUrls = httpsHosts.map((host) => {
6019
+ if (!host) {
6020
+ return null
6021
+ }
6022
+ const trimmed = host.trim()
6023
+ if (/^https?:\/\//i.test(trimmed)) {
6024
+ return trimmed.replace(/^http:\/\//i, 'https://')
6025
+ }
6026
+ return `https://${trimmed}`
6027
+ }).filter(Boolean)
6028
+
6029
+ const preferredHttpsUrl = preferFriendlyHost(httpsHosts)
6030
+ const displayHttpsUrl = preferredHttpsUrl
6031
+ ? (preferredHttpsUrl.startsWith('http') ? preferredHttpsUrl.replace(/^http:/i, 'https:') : `https://${preferredHttpsUrl}`)
6032
+ : (httpsUrls[0] || null)
6033
+
6034
+ const selectedUrl = (preferHttps && displayHttpsUrl) ? displayHttpsUrl : httpUrl
6035
+ const protocol = (preferHttps && displayHttpsUrl) ? 'https' : 'http'
6036
+
6037
+ return {
5678
6038
  online: true,
5679
6039
  host: {
5680
6040
  ip: this.kernel.peer.host,
@@ -5684,65 +6044,16 @@ class Server {
5684
6044
  arch: this.kernel.arch,
5685
6045
  },
5686
6046
  ...item,
5687
- })
5688
- }
5689
- // get only the parts not from this peer
5690
- for(let host in this.kernel.peer.info) {
5691
- let host_info = this.kernel.peer.info[host]
5692
- let host_rewrites = host_info.rewrite_mapping
5693
- for(let key in host_rewrites) {
5694
- info.push({
5695
- online: true,
5696
- host: {
5697
- ip: host,
5698
- local: this.kernel.peer.host === host,
5699
- name: host_info.name,
5700
- platform: host_info.platform,
5701
- arch: host_info.arch
5702
- },
5703
- name: `[Files] ${host_rewrites[key].name}`,
5704
- ip: host_rewrites[key].external_ip
5705
- })
5706
- }
5707
- if (this.kernel.peer.host !== host) {
5708
- let host_routers = host_info.router_info
5709
- for(let host_router of host_routers) {
5710
- let ip
5711
- // the peer sharing works only if external_ip is available (caddy is installed)
5712
- if (host_router.external_ip) {
5713
- ip = host_router.external_ip
5714
- } else {
5715
- // if caddy is not turned on, set ip as null, so that it suggests turning on peer sharing
5716
- ip = null
5717
- }
5718
- info.push({
5719
- online: true,
5720
- host: {
5721
- ip: host,
5722
- name: host_info.name,
5723
- platform: host_info.platform,
5724
- arch: host_info.arch
5725
- },
5726
- name: host_router.title || host_router.name,
5727
- ip
5728
- })
6047
+ url: selectedUrl,
6048
+ protocol,
6049
+ urls: {
6050
+ http: httpUrl,
6051
+ https: httpsUrls
5729
6052
  }
5730
6053
  }
6054
+ })
5731
6055
 
5732
- for(let app of host_info.installed) {
5733
- info.push({
5734
- host: {
5735
- ip: host,
5736
- local: this.kernel.peer.host === host,
5737
- name: host_info.name,
5738
- platform: host_info.platform,
5739
- arch: host_info.arch
5740
- },
5741
- name: app.title || app.folder,
5742
- ip: app.http_href.replace("http://", "")
5743
- })
5744
- }
5745
- }
6056
+ // this.add_extra_urls(info)
5746
6057
  res.json({
5747
6058
  info
5748
6059
  })
@@ -5782,6 +6093,10 @@ class Server {
5782
6093
  })
5783
6094
  }
5784
6095
  }))
6096
+ this.app.get("/info/shells", ex(async (req,res) => {
6097
+ let shells = this.kernel.shell.info()
6098
+ res.json(shells)
6099
+ }))
5785
6100
  this.app.get("/info/api/:name", ex(async (req,res) => {
5786
6101
  // api related info
5787
6102
  let c = this.kernel.path("api", req.params.name)
@@ -5865,7 +6180,7 @@ class Server {
5865
6180
  title: name,
5866
6181
  url: gitRemote,
5867
6182
  //redirect_uri: "http://localhost:3001/apps/redirect?git=" + gitRemote,
5868
- redirect_uri: "https://app-7pt7.onrender.com/apps/redirect?git=" + gitRemote,
6183
+ redirect_uri: "https://app.pinokio.co/apps/redirect?git=" + gitRemote,
5869
6184
  platform: this.kernel.platform,
5870
6185
  theme: this.theme,
5871
6186
  agent: req.agent,
@@ -5987,7 +6302,7 @@ class Server {
5987
6302
  }))
5988
6303
  this.app.get("/pinokio/download", ex((req, res) => {
5989
6304
  let queryStr = new URLSearchParams(req.query).toString()
5990
- res.redirect("/?mode=download&" + queryStr)
6305
+ res.redirect("/home?mode=download&" + queryStr)
5991
6306
  }))
5992
6307
  this.app.post("/pinokio/install", ex((req, res) => {
5993
6308
  req.session.requirements = req.body.requirements
@@ -6131,6 +6446,31 @@ class Server {
6131
6446
  res.status(404).send("Missing attribute: path")
6132
6447
  }
6133
6448
  }))
6449
+ const ensureCaptureDir = async () => {
6450
+ await fs.promises.mkdir(this.kernel.path("screenshots"), { recursive: true }).catch(() => {});
6451
+ };
6452
+
6453
+ const saveCaptureFiles = async (files, fallbackExt = '.png') => {
6454
+ await ensureCaptureDir();
6455
+ const saved = [];
6456
+ if (Array.isArray(files)) {
6457
+ for (const file of files) {
6458
+ if (!file || !file.buffer) continue;
6459
+ const origName = file.originalname || '';
6460
+ let ext = path.extname(origName);
6461
+ if (!ext && file.mimetype) {
6462
+ const mapped = mime.extension(file.mimetype);
6463
+ if (mapped) ext = `.${mapped}`;
6464
+ }
6465
+ if (!ext) ext = fallbackExt;
6466
+ const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`;
6467
+ await fs.promises.writeFile(this.kernel.path("screenshots", name), file.buffer);
6468
+ saved.push({ name, url: `/asset/screenshots/${name}` });
6469
+ }
6470
+ }
6471
+ return saved;
6472
+ };
6473
+
6134
6474
  this.app.get("/snapshots", ex(async (req, res) => {
6135
6475
  let files = []
6136
6476
  try {
@@ -6172,13 +6512,13 @@ class Server {
6172
6512
  res.status(500).json({ error: "Failed to delete file: " + e.message })
6173
6513
  }
6174
6514
  }))
6515
+ this.app.post("/capture", this.upload.any(), ex(async (req, res) => {
6516
+ const saved = await saveCaptureFiles(req.files);
6517
+ res.json({ saved });
6518
+ }))
6175
6519
  this.app.post("/screenshot", this.upload.any(), ex(async (req, res) => {
6176
- await fs.promises.mkdir(this.kernel.path("screenshots"), { recursive: true }).catch((e) => { })
6177
- for(let key in req.files) {
6178
- let file = req.files[key]
6179
- let ts = String(Date.now()) + ".png"
6180
- await fs.promises.writeFile(this.kernel.path("screenshots", ts), file.buffer)
6181
- }
6520
+ const saved = await saveCaptureFiles(req.files);
6521
+ res.json({ saved });
6182
6522
  }))
6183
6523
  this.app.post("/pinokio/fs", this.upload.any(), ex(async (req, res) => {
6184
6524
  /*
@@ -6360,7 +6700,7 @@ class Server {
6360
6700
  ready = false;
6361
6701
  }
6362
6702
  if (ready) {
6363
- res.json({ success: true })
6703
+ res.json({ success: true, peer_name: this.kernel.peer.name })
6364
6704
  } else {
6365
6705
  res.json({ success: false })
6366
6706
  }