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
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");
@@ -108,10 +109,53 @@ class Server {
108
109
  stop() {
109
110
  this.server.close()
110
111
  }
112
+ killProcessTree(pid, label) {
113
+ const numericPid = typeof pid === 'string' ? parseInt(pid, 10) : pid
114
+ if (!Number.isInteger(numericPid) || numericPid <= 0) {
115
+ return
116
+ }
117
+ if (label) {
118
+ console.log(label, numericPid)
119
+ }
120
+ try {
121
+ kill(numericPid, 'SIGKILL', true)
122
+ } catch (error) {
123
+ if (error && error.code === 'ESRCH') {
124
+ return
125
+ }
126
+ console.error(`Failed to kill pid ${numericPid}`, error)
127
+ }
128
+ }
129
+ killTrackedProcesses() {
130
+ if (this.kernel && this.kernel.processes && this.kernel.processes.map) {
131
+ for (const [pid, name] of Object.entries(this.kernel.processes.map)) {
132
+ if (parseInt(pid, 10) === process.pid) {
133
+ continue
134
+ }
135
+ this.killProcessTree(pid, `kill child ${name}`)
136
+ }
137
+ }
138
+ }
139
+ shutdown(signalLabel) {
140
+ const label = signalLabel || 'Shutdown'
141
+ console.log(`[${label} event] Kill`, process.pid)
142
+ if (this.kernel && this.kernel.shell) {
143
+ try {
144
+ this.kernel.shell.reset()
145
+ } catch (error) {
146
+ console.error('Failed to reset shells', error)
147
+ }
148
+ }
149
+ this.killTrackedProcesses()
150
+ if (this.kernel && this.kernel.processes && this.kernel.processes.caddy_pid) {
151
+ this.killProcessTree(this.kernel.processes.caddy_pid, 'kill caddy')
152
+ }
153
+ this.killProcessTree(process.pid, 'kill self')
154
+ }
111
155
  exists (s) {
112
156
  return new Promise(r=>fs.access(s, fs.constants.F_OK, e => r(!e)))
113
157
  }
114
- running_dynamic (name, menu) {
158
+ running_dynamic (name, menu, selected_query) {
115
159
  let cwd = this.kernel.path("api", name)
116
160
  let running_dynamic = []
117
161
  const traverse = (obj, indexPath) => {
@@ -136,9 +180,17 @@ class Server {
136
180
 
137
181
  let id = `${filepath}?cwd=${cwd}`
138
182
  //if (this.kernel.api.running[filepath]) {
139
- if (this.kernel.api.running[id]) {
183
+ if (this.kernel.api.running[id] || selected_query.plugin === obj.src) {
140
184
  obj.running = true
141
185
  obj.display = "indent"
186
+ if (selected_query.plugin === obj.src) {
187
+ obj.default = true
188
+ for(let key in selected_query) {
189
+ if (key !== "plugin") {
190
+ obj.href = obj.href + "&" + key + "=" + encodeURIComponent(selected_query[key])
191
+ }
192
+ }
193
+ }
142
194
  running_dynamic.push(obj)
143
195
  }
144
196
  } else if (href.startsWith("/run")) {
@@ -148,20 +200,109 @@ class Server {
148
200
  let id = `${filepath}?cwd=${cwd}`
149
201
  obj.script_id = id
150
202
  //if (this.kernel.api.running[filepath]) {
151
- if (this.kernel.api.running[id]) {
203
+ if (obj.src.startsWith("/run" + selected_query.plugin)) {
152
204
  obj.running = true
153
205
  obj.display = "indent"
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])
210
+ }
211
+ }
154
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
+ }
155
237
  }
156
238
  }
157
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
+
158
254
  let unix_path = Util.p2u(this.kernel.path("api", name))
159
255
  let shell_id = this.get_shell_id(unix_path, indexPath, obj[key])
160
256
  let decoded_shell_id = decodeURIComponent(shell_id)
161
- if (this.kernel.api.running["shell/" + decoded_shell_id]) {
257
+ let id = "shell/" + decoded_shell_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) {
162
272
  obj.running = true
163
273
  obj.display = "indent"
164
- running_dynamic.push(obj)
274
+ if (selected_query.plugin === id) {
275
+ obj.default = true
276
+ for(let key in selected_query) {
277
+ if (key !== "plugin") {
278
+ obj.href = obj.href + "&" + key + "=" + encodeURIComponent(selected_query[key])
279
+ }
280
+ }
281
+ }
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
+ }
165
306
  }
166
307
  }
167
308
  traverse(obj[key], indexPath);
@@ -292,30 +433,29 @@ class Server {
292
433
  }
293
434
  }
294
435
  } else {
295
- cfg = await this.renderIndex(name)
436
+ cfg = await this.renderIndex(name, cfg)
296
437
  }
297
438
  } else {
298
- cfg = await this.renderIndex(name)
439
+ cfg = await this.renderIndex(name, cfg)
299
440
  }
300
441
  return cfg
301
442
  }
302
- async renderIndex(name) {
443
+ async renderIndex(name, cfg) {
303
444
  let p = this.kernel.path("api", name)
304
445
  let html_path = path.resolve(p, "index.html")
305
446
  let html_exists = await this.kernel.exists(html_path)
306
- console.log({ html_path, html_exists })
307
447
  if (html_exists) {
308
- return {
448
+ return Object.assign({
309
449
  title: name,
310
450
  menu: [{
311
451
  default: true,
312
452
  icon: "fa-solid fa-link",
313
453
  text: "index.html",
314
- href: "index.html?raw=true",
454
+ href: `/asset/api/${name}/index.html`,
315
455
  }]
316
- }
456
+ }, cfg)
317
457
  } else {
318
- return {
458
+ return Object.assign({
319
459
  title: name,
320
460
  menu: [{
321
461
  default: true,
@@ -323,12 +463,26 @@ class Server {
323
463
  text: "Project Files",
324
464
  href: `/files/api/${name}`,
325
465
  }]
326
- }
466
+ }, cfg)
327
467
  }
328
468
  }
329
469
  async getGit(ref, filepath) {
330
470
  let dir = this.kernel.path("api", filepath)
331
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
+ }
332
486
  let log = []
333
487
  try {
334
488
  log = await git.log({ fs, dir, depth: 50, ref: ref }); // fetch last 50 commits
@@ -509,7 +663,6 @@ class Server {
509
663
  }
510
664
  }
511
665
 
512
-
513
666
  let uri = this.kernel.path("api")
514
667
  try {
515
668
  let launcher = await this.kernel.api.launcher(name)
@@ -520,9 +673,7 @@ class Server {
520
673
  err = e.stack
521
674
  }
522
675
 
523
- console.log("renderMenu before", config)
524
676
  await this.renderMenu(req, uri, name, config, [])
525
- console.log("renderMenu after", config)
526
677
 
527
678
  let platform = os.platform()
528
679
 
@@ -557,30 +708,30 @@ class Server {
557
708
  }
558
709
  const env = await this.kernel.env("api/" + name)
559
710
 
560
- // profile + feed
561
- const repositoryPath = path.resolve(this.kernel.api.userdir, name)
562
-
563
- try {
564
- await git.resolveRef({ fs, dir: repositoryPath, ref: 'HEAD' });
565
- } catch (err) {
566
- // repo doesn't exist. initialize.
567
- console.log(`repo doesn't exist at ${repositoryPath}. initialize`)
568
- await git.init({ fs, dir: repositoryPath });
569
- }
570
-
571
- let gitRemote = await git.getConfig({ fs, http, dir: repositoryPath, path: 'remote.origin.url' })
572
- let profile
573
- let feed
574
- if (gitRemote) {
575
- gitRemote = gitRemote.replace(/\.git$/i, '')
576
-
577
- let system_env = {}
578
- if (this.kernel.homedir) {
579
- system_env = await Environment.get(this.kernel.homedir, this.kernel)
580
- }
581
- profile = this.profile(gitRemote)
582
- feed = this.newsfeed(gitRemote)
583
- }
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
+ // }
584
735
 
585
736
  // git
586
737
 
@@ -602,7 +753,11 @@ class Server {
602
753
  let plugin = await this.getPlugin(req, plugin_config, name)
603
754
  if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
604
755
  plugin = structuredClone(plugin)
605
- plugin_menu = this.running_dynamic(name, plugin.menu)
756
+ let default_plugin_query
757
+ if (req.query) {
758
+ default_plugin_query = req.query
759
+ }
760
+ plugin_menu = this.running_dynamic(name, plugin.menu, default_plugin_query)
606
761
  }
607
762
 
608
763
  let posix_path = Util.p2u(this.kernel.path("api", name))
@@ -617,6 +772,18 @@ class Server {
617
772
  let dev_tab = "/p/" + name + "/dev"
618
773
  let review_tab = "/p/" + name + "/review"
619
774
 
775
+ let dynamic_url = "/pinokio/dynamic/" + name;
776
+ if (Object.values(req.query).length > 0) {
777
+ let index = 0
778
+ for(let key in req.query) {
779
+ if (index === 0) {
780
+ dynamic_url = dynamic_url + `?${key}=${encodeURIComponent(req.query[key])}`
781
+ } else {
782
+ dynamic_url = dynamic_url + `&${key}=${encodeURIComponent(req.query[key])}`
783
+ }
784
+ index++;
785
+ }
786
+ }
620
787
 
621
788
  const result = {
622
789
  dev_link,
@@ -639,12 +806,12 @@ class Server {
639
806
  sidebar: "/pinokio/sidebar/" + name,
640
807
  repos: "/pinokio/repos/" + name,
641
808
  ai: "/pinokio/ai/" + name,
642
- dynamic: "/pinokio/dynamic/" + name,
809
+ dynamic: dynamic_url,
643
810
  // dynamic: "/pinokio/dynamic/" + name,
644
811
  dynamic_content: null,
645
812
  name,
646
- profile,
647
- feed,
813
+ // profile,
814
+ // feed,
648
815
  tabs: (this.tabs[name] || []),
649
816
  config,
650
817
  // sidebar_url: "/pinokio/sidebar/" + name,
@@ -1019,7 +1186,6 @@ class Server {
1019
1186
  template = "editor"
1020
1187
  }
1021
1188
 
1022
- console.log("check requirements")
1023
1189
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
1024
1190
  //bin: this.kernel.bin.preset("ai"),
1025
1191
  bin: this.kernel.bin.preset("dev"),
@@ -1076,7 +1242,6 @@ class Server {
1076
1242
  if (platform === "win32") {
1077
1243
  root_path = root_path.replace(/\\/g, '\\\\')
1078
1244
  }
1079
- console.log({ cwd, api_path, root, root_path })
1080
1245
  res.render("required_env_editor", {
1081
1246
  portal: this.portal,
1082
1247
  agent: req.agent,
@@ -1360,9 +1525,51 @@ class Server {
1360
1525
  }
1361
1526
  // check if there is a running process with this folder name
1362
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
+ }
1363
1570
  for(let key in this.kernel.api.running) {
1364
1571
  //let p = this.kernel.path("api", items[i].name) + path.sep
1365
- let p = this.kernel.path("api", items[i].name)
1572
+ let p = item_path
1366
1573
 
1367
1574
  // not only should include the pattern, but also end with it (otherwise can include similar patterns such as /api/qqqa, /api/qqqaaa, etc.
1368
1575
 
@@ -1386,7 +1593,6 @@ class Server {
1386
1593
  if (chunks.length > 1) {
1387
1594
  let folder = chunks[0]
1388
1595
  /// if the folder name matches, it's running
1389
- let item_path = this.kernel.path("api", items[i].name)
1390
1596
  if (item_path === folder) {
1391
1597
  is_running = true
1392
1598
  }
@@ -1403,12 +1609,15 @@ class Server {
1403
1609
  //if (key.includes(p) && key.endsWith(p)) {
1404
1610
  if (is_running) {
1405
1611
  // add to running
1406
- 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
+ }
1407
1618
  if (!items[i].running_scripts) {
1408
1619
  items[i].running_scripts = []
1409
1620
  }
1410
- items[i].running = true
1411
- items[i].index = index
1412
1621
 
1413
1622
  // add the running script to running_scripts array
1414
1623
  // 1. normal api script
@@ -1429,24 +1638,17 @@ class Server {
1429
1638
  items[i].running_scripts.push({ id: key, name })
1430
1639
  }
1431
1640
  } else {
1432
- let shell = this.kernel.shell.find({
1433
- filter: (shell) => {
1434
- let item_path = this.kernel.path("api", items[i].name)
1435
- let unix_item_path = Util.p2u(item_path)
1436
- return shell.id.startsWith("shell/" + unix_item_path + "_")
1437
- }
1438
- })
1439
- if (shell.length > 0) {
1440
- items[i].running = true
1441
- items[i].index = index
1442
- for(let sh of shell) {
1443
- items[i].running_scripts.push({ id: sh.id, name: "Terminal", type: "shell" })
1444
- }
1445
- }
1641
+ addShellEntries()
1446
1642
  }
1447
- index++;
1448
1643
  }
1449
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
+ }
1450
1652
  if (!items[i].running) {
1451
1653
  items[i].index = index
1452
1654
  index++;
@@ -1613,7 +1815,6 @@ class Server {
1613
1815
  items
1614
1816
  })
1615
1817
  } else {
1616
- console.log("RENDER FILE EXPLORER")
1617
1818
  res.render("file_explorer", {
1618
1819
  docs: this.docs,
1619
1820
  portal: this.portal,
@@ -1793,15 +1994,19 @@ class Server {
1793
1994
  // }
1794
1995
  menuitem.href = "/shell/" + shell_id + "?" + params.toString()
1795
1996
  let decoded_shell_id = decodeURIComponent(shell_id)
1796
- let shell = this.kernel.shell.get(decoded_shell_id)
1797
- 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
1798
2008
  if (shell) {
1799
2009
  menuitem.running = true
1800
- } else {
1801
- let shell = this.kernel.shell.get(decoded_shell_id)
1802
- if (shell) {
1803
- menuitem.running = true
1804
- }
1805
2010
  }
1806
2011
  }
1807
2012
  return menuitem
@@ -1920,10 +2125,11 @@ class Server {
1920
2125
  } else if (menuitem.href.startsWith("/")) {
1921
2126
  let run_path = "/run"
1922
2127
  if (menuitem.href.startsWith(run_path)) {
1923
- u = new URL("http://localhost" + menuitem.href.slice(run_path.length))
1924
- cwd = u.searchParams.get("cwd")
1925
- u.search = ""
1926
- 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
1927
2133
  } else {
1928
2134
  u = new URL("http://localhost" + menuitem.href)
1929
2135
  cwd = u.searchParams.get("cwd")
@@ -2033,7 +2239,6 @@ class Server {
2033
2239
  if (menuitem.image.startsWith("/")) {
2034
2240
  imagePath = menuitem.image
2035
2241
  } else {
2036
- console.log({ launcher_root, name, image: menuitem.image })
2037
2242
  if (launcher_root) {
2038
2243
  imagePath = `/api/${name}/${launcher_root}/${menuitem.image}?raw=true`
2039
2244
  } else {
@@ -2063,16 +2268,19 @@ class Server {
2063
2268
  if (config.menu[i].popout) {
2064
2269
  config.menu[i].target = "_blank"
2065
2270
  } else {
2066
- config.menu[i].target = "@" + (config.menu[i].id || config.menu[i].src)
2067
- }
2068
-
2069
-
2070
- if (config.menu[i].href && config.menu[i].href.startsWith("http")) {
2071
- if (req.agent !== "electron") {
2072
- 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
2073
2275
  }
2074
2276
  }
2075
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
+
2076
2284
  if (menuitem.shell_id) {
2077
2285
  config.menu[i].shell_id = menuitem.shell_id
2078
2286
  }
@@ -2398,7 +2606,7 @@ class Server {
2398
2606
  // }
2399
2607
  }
2400
2608
  async setConfig(config) {
2401
- let home = this.kernel.store.get("home")
2609
+ let home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2402
2610
  let theme = this.kernel.store.get("theme")
2403
2611
  let mode = this.kernel.store.get("mode")
2404
2612
  // let drive = this.kernel.store.get("drive")
@@ -2463,9 +2671,9 @@ class Server {
2463
2671
  // }
2464
2672
 
2465
2673
 
2466
- home = this.kernel.store.get("home")
2674
+ home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2467
2675
  theme = this.kernel.store.get("theme")
2468
- let new_home = this.kernel.store.get("new_home")
2676
+ let new_home = this.kernel.store.get("new_home") || process.env.PINOKIO_HOME
2469
2677
 
2470
2678
  // Handle environment variables
2471
2679
  // HTTP_PROXY
@@ -2538,6 +2746,66 @@ class Server {
2538
2746
  return true
2539
2747
  }
2540
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
+ }
2541
2809
  async terminals(filepath) {
2542
2810
  let venvs = await Util.find_venv(filepath)
2543
2811
  let terminal
@@ -2564,14 +2832,14 @@ class Server {
2564
2832
  }
2565
2833
  terminal = {
2566
2834
  icon: "fa-solid fa-terminal",
2567
- title: "Web Terminal",
2835
+ title: "Terminal",
2568
2836
  subtitle: "Open the terminal in the browser",
2569
2837
  menu: terminals
2570
2838
  }
2571
2839
  } else {
2572
2840
  terminal = {
2573
2841
  icon: "fa-solid fa-terminal",
2574
- title: "Web terminal",
2842
+ title: "Terminal",
2575
2843
  subtitle: "Work with the terminal directly in the browser",
2576
2844
  menu: [this.renderShell(filepath, 0, 0, {
2577
2845
  icon: "fa-solid fa-terminal",
@@ -2753,7 +3021,7 @@ class Server {
2753
3021
  await this.syncConfig()
2754
3022
 
2755
3023
  try {
2756
- let _home = this.kernel.store.get("home")
3024
+ let _home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2757
3025
  if (_home) {
2758
3026
  await this.startLogging(_home)
2759
3027
  }
@@ -2789,7 +3057,7 @@ class Server {
2789
3057
  }
2790
3058
 
2791
3059
  let version = this.kernel.store.get("version")
2792
- let home = this.kernel.store.get("home")
3060
+ let home = this.kernel.store.get("home") || process.env.PINOKIO_HOME
2793
3061
 
2794
3062
  let needInitHome = false
2795
3063
  if (home) {
@@ -3075,7 +3343,6 @@ class Server {
3075
3343
  ...result
3076
3344
  })
3077
3345
  }
3078
- console.log(JSON.stringify(bundles, null, 2))
3079
3346
  res.render("tools", {
3080
3347
  current_host: this.kernel.peer.host,
3081
3348
  pending,
@@ -3102,17 +3369,25 @@ class Server {
3102
3369
  })
3103
3370
  }))
3104
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;
3105
3374
  res.render("columns", {
3106
3375
  theme: this.theme,
3107
3376
  agent: req.agent,
3108
- src: req.get('Referrer')
3377
+ originSrc,
3378
+ targetSrc,
3379
+ src: originSrc
3109
3380
  })
3110
3381
  }))
3111
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;
3112
3385
  res.render("rows", {
3113
3386
  theme: this.theme,
3114
3387
  agent: req.agent,
3115
- src: req.get('Referrer')
3388
+ originSrc,
3389
+ targetSrc,
3390
+ src: originSrc
3116
3391
  })
3117
3392
  }))
3118
3393
 
@@ -3125,6 +3400,23 @@ class Server {
3125
3400
  })
3126
3401
  }))
3127
3402
 
3403
+ this.app.get("/bookmarklet", ex(async (req, res) => {
3404
+ const protocol = (req.$source && req.$source.protocol) || req.protocol || 'http';
3405
+ const host = req.get('host') || `localhost:${this.port}`;
3406
+ const baseUrl = `${protocol}://${host}`;
3407
+ const targetBase = `${baseUrl}/?create=1&prompt=`;
3408
+ const safeTargetBase = targetBase.replace(/'/g, "\\'");
3409
+ const bookmarkletHref = `javascript:(()=>{window.open('${safeTargetBase}'+encodeURIComponent(window.location.href),'_blank');})();`;
3410
+
3411
+ res.render("bookmarklet", {
3412
+ theme: this.theme,
3413
+ agent: req.agent,
3414
+ baseUrl,
3415
+ targetBase,
3416
+ bookmarkletHref
3417
+ });
3418
+ }))
3419
+
3128
3420
  //let home = this.kernel.homedir
3129
3421
  //let home = this.kernel.store.get("home")
3130
3422
  this.app.get("/launch", ex(async (req, res) => {
@@ -3145,7 +3437,6 @@ class Server {
3145
3437
  autolaunch = true
3146
3438
  }
3147
3439
  let chunks = host.split(".")
3148
- console.log("GET /launch", { url, host, chunks })
3149
3440
  if (chunks[chunks.length-1] === "localhost") {
3150
3441
  // if <...>.<kernel.peer.name>.localhost
3151
3442
  let nameChunks
@@ -3172,7 +3463,6 @@ class Server {
3172
3463
  for(let folder of folders) {
3173
3464
  let pattern1 = `${folder}.${this.kernel.peer.name}.localhost`
3174
3465
  let pattern2 = `${folder}.localhost`
3175
- console.log("checking", { pattern1, pattern2, chunks: chunks.join(".") })
3176
3466
  if (pattern1 === chunks.join(".")) {
3177
3467
  matched = true
3178
3468
  nameChunks = chunks.slice(0, -2)
@@ -3193,20 +3483,16 @@ class Server {
3193
3483
  // look for any matching peer names
3194
3484
  // if exists, redirect to that host
3195
3485
  for(let name of peer_names) {
3196
- console.log({ host, name })
3197
3486
  if (host.endsWith(`.${name}.localhost`)) {
3198
- console.log("matched. redirecting")
3199
3487
  res.redirect(`https://pinokio.${name}.localhost/launch?url=${url}`)
3200
3488
  return
3201
3489
  }
3202
3490
  }
3203
3491
  }
3204
3492
  } else {
3205
- console.log("> 3")
3206
3493
  nameChunks = chunks
3207
3494
  }
3208
3495
  let name = nameChunks.join(".")
3209
- console.log({ nameChunks, chunks, name })
3210
3496
  let api_path = this.kernel.path("api", name)
3211
3497
  let exists = await this.exists(api_path)
3212
3498
  if (exists) {
@@ -3243,7 +3529,7 @@ class Server {
3243
3529
  link: null
3244
3530
  })
3245
3531
  }))
3246
- this.app.get("/", ex(async (req, res) => {
3532
+ const renderHomePage = ex(async (req, res) => {
3247
3533
  // check bin folder
3248
3534
  // let bin_path = this.kernel.path("bin/miniconda")
3249
3535
  // let bin_exists = await this.exists(bin_path)
@@ -3260,7 +3546,7 @@ class Server {
3260
3546
  // }
3261
3547
 
3262
3548
  if (req.query.mode !== "settings" && !home) {
3263
- res.redirect("/?mode=settings")
3549
+ res.redirect("/home?mode=settings")
3264
3550
  return
3265
3551
  }
3266
3552
  if (req.query.mode === "help") {
@@ -3282,7 +3568,6 @@ class Server {
3282
3568
  return
3283
3569
  }
3284
3570
 
3285
-
3286
3571
  if (req.query.mode === 'settings') {
3287
3572
 
3288
3573
  let platform = os.platform()
@@ -3303,7 +3588,7 @@ class Server {
3303
3588
  "* NO exFAT drives",
3304
3589
  ],
3305
3590
  val: this.kernel.homedir ? this.kernel.homedir : _home,
3306
- 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.)"
3307
3592
  // }, {
3308
3593
  // key: "drive",
3309
3594
  // val: path.resolve(this.kernel.homedir, "drive"),
@@ -3376,6 +3661,58 @@ class Server {
3376
3661
  meta[folder] = await this.kernel.api.meta(folder)
3377
3662
  }
3378
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
+ }
3379
3716
  }))
3380
3717
 
3381
3718
  this.app.get("/init", ex(async (req, res) => {
@@ -3556,7 +3893,6 @@ class Server {
3556
3893
  } catch (e) {
3557
3894
  console.log(e)
3558
3895
  }
3559
- console.log({ https_running })
3560
3896
  if (!https_running) {
3561
3897
  // res.json({ error: "pinokio.host not yet available" })
3562
3898
  res.redirect("/setup/connect?callback=/connect/" + req.params.provider)
@@ -3574,7 +3910,6 @@ class Server {
3574
3910
  break
3575
3911
  }
3576
3912
  }
3577
- console.log({ router_running })
3578
3913
  if (!router_running) {
3579
3914
  // res.json({ error: "pinokio.localhost not yet available" })
3580
3915
  res.redirect("/setup/connect?callback=/connect/" + req.params.provider)
@@ -3677,7 +4012,6 @@ class Server {
3677
4012
  res.json({ success: true })
3678
4013
  }))
3679
4014
  this.app.post("/go", ex(async (req, res) => {
3680
- console.log("GO", req.body)
3681
4015
  Util.openURL(req.body.url)
3682
4016
  res.json({ success: true })
3683
4017
  }))
@@ -3691,7 +4025,6 @@ class Server {
3691
4025
  // relpath : ...
3692
4026
  let relpath = req.body.asset_path.split("/").filter((x) => { return x }).slice(1).join("/")
3693
4027
  let filepath = this.kernel.path(relpath)
3694
- console.log({ filepath, relpath })
3695
4028
  Util.openfs(filepath, req.body, this.kernel)
3696
4029
  } else if (req.body.path) {
3697
4030
  Util.openfs(req.body.path, req.body, this.kernel)
@@ -3701,7 +4034,6 @@ class Server {
3701
4034
  this.app.post("/keys", ex(async (req, res) => {
3702
4035
  let p = this.kernel.path("key.json")
3703
4036
  let keys = (await this.kernel.loader.load(p)).resolved
3704
- console.log("update", req.body)
3705
4037
  for(let host in req.body) {
3706
4038
  let updated = req.body[host]
3707
4039
  for(let indexStr in updated) {
@@ -3806,8 +4138,6 @@ class Server {
3806
4138
 
3807
4139
  let hosts = await this.get_github_hosts()
3808
4140
 
3809
- console.log("hosts", hosts)
3810
-
3811
4141
  let items
3812
4142
  if (hosts.length > 0) {
3813
4143
  // logged in => display logout
@@ -3955,7 +4285,12 @@ class Server {
3955
4285
  GET /shell/:unix_path => shell id: 'shell/:unix_path'
3956
4286
  */
3957
4287
 
3958
- 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
+ }
3959
4294
  let target = req.query.target ? req.query.target : null
3960
4295
  let cwd = this.kernel.path(this.kernel.api.filePath(decodeURIComponent(req.query.path)))
3961
4296
  let message = req.query.message ? decodeURIComponent(req.query.message) : null
@@ -4018,6 +4353,16 @@ class Server {
4018
4353
  running: (shell ? true : false)
4019
4354
  })
4020
4355
  }))
4356
+ this.app.get("/pro", ex(async (req, res) => {
4357
+ let target = req.query.target ? req.query.target : null
4358
+ let cwd = this.kernel.path("api")
4359
+ res.render("pro", {
4360
+ target,
4361
+ cwd,
4362
+ theme: this.theme,
4363
+ agent: req.agent,
4364
+ })
4365
+ }))
4021
4366
  // this.app.get("/terminal/:api/:id", ex(async (req, res) => {
4022
4367
  // res.render("shell", {
4023
4368
  // theme: this.theme,
@@ -4089,7 +4434,6 @@ class Server {
4089
4434
  if (cr.has("caddy")) {
4090
4435
  wait = "caddy"
4091
4436
  }
4092
- console.log({ wait, cr })
4093
4437
 
4094
4438
  let current = req.query.callback || req.originalUrl
4095
4439
 
@@ -4212,7 +4556,11 @@ class Server {
4212
4556
  return serverless_mapping[name]
4213
4557
  })
4214
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
+ })
4215
4562
  res.render("net", {
4563
+ static_routes,
4216
4564
  selected_name: req.params.name,
4217
4565
  current_urls,
4218
4566
  docs: this.docs,
@@ -4245,9 +4593,7 @@ class Server {
4245
4593
 
4246
4594
  // let list = this.getPeerInfo()
4247
4595
  // console.log("peeerInfo", JSON.stringify(list, null, 2))
4248
- console.time("check peers")
4249
4596
  await this.kernel.peer.check_peers()
4250
- console.timeEnd("check peers")
4251
4597
 
4252
4598
 
4253
4599
  let peers = []
@@ -4731,6 +5077,7 @@ class Server {
4731
5077
  // }))
4732
5078
  this.app.post("/env", ex(async (req, res) => {
4733
5079
  let fullpath = path.resolve(this.kernel.homedir, req.body.filepath, "ENVIRONMENT")
5080
+ console.log({ fullpath })
4734
5081
  let updated = req.body.vals
4735
5082
  let hosts = req.body.hosts
4736
5083
  await Util.update_env(fullpath, updated)
@@ -4828,7 +5175,6 @@ class Server {
4828
5175
  //const repositoryPath = this.kernel.path(pathComponents[0], pathComponents[1])
4829
5176
  //const repositoryPath = this.kernel.path(pathComponents[0])
4830
5177
  const repositoryPath = path.resolve(this.kernel.api.userdir, api_path)
4831
- console.log({ repositoryPath })
4832
5178
  gitRemote = await git.getConfig({
4833
5179
  fs,
4834
5180
  http,
@@ -4974,7 +5320,6 @@ class Server {
4974
5320
  if (config) {
4975
5321
  if (config.xterm) {
4976
5322
  this.xterm = config.xterm
4977
- console.log("this.xterm", this.xterm)
4978
5323
  }
4979
5324
  }
4980
5325
  }
@@ -5040,7 +5385,7 @@ class Server {
5040
5385
  }
5041
5386
  }
5042
5387
  } catch (err) {
5043
- console.log("git status matrix error", err)
5388
+ // console.log("git status matrix error 1", err)
5044
5389
  }
5045
5390
  } else {
5046
5391
  try {
@@ -5099,7 +5444,7 @@ class Server {
5099
5444
  });
5100
5445
  }
5101
5446
  } catch (err) {
5102
- console.log("git diff error", err);
5447
+ console.log("git diff error 2", err);
5103
5448
  }
5104
5449
  }
5105
5450
  let git_commit_url = `/run/scripts/git/commit.json?cwd=${dir}&callback_target=parent&callback=$location.href`
@@ -5198,22 +5543,15 @@ class Server {
5198
5543
  }
5199
5544
 
5200
5545
 
5201
- 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])
5202
5547
 
5203
5548
  res.render("git", {
5204
- config,
5205
- remote,
5206
- connected,
5207
- log,
5208
- branch,
5209
- branches,
5210
- ref,
5211
5549
  path: req.params[0],
5212
5550
  // changes,
5213
- dir,
5214
5551
  theme: this.theme,
5215
5552
  platform: this.kernel.platform,
5216
5553
  agent: req.agent,
5554
+ ...response
5217
5555
  })
5218
5556
  }))
5219
5557
  this.app.get("/d/*", ex(async (req, res) => {
@@ -5275,9 +5613,10 @@ class Server {
5275
5613
  // console.log("online_terminal", online_terminal)
5276
5614
  terminal.menus = href_menus
5277
5615
  let dynamic = [
5616
+ terminal,
5278
5617
  {
5279
5618
  icon: "fa-solid fa-robot",
5280
- title: "AI Engineer",
5619
+ title: "AI Terminal",
5281
5620
  subtitle: "Let AI work on this app",
5282
5621
  menu: shell_menus
5283
5622
  },
@@ -5287,7 +5626,6 @@ class Server {
5287
5626
  subtitle: "Open this project in 3rd party apps",
5288
5627
  menu: exec_menus
5289
5628
  },
5290
- terminal
5291
5629
  ]
5292
5630
  let spec = ""
5293
5631
  try {
@@ -5309,7 +5647,6 @@ class Server {
5309
5647
  })
5310
5648
  }))
5311
5649
  this.app.get("/dev/*", ex(async (req, res) => {
5312
- console.log("GET /dev/*", req.params)
5313
5650
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
5314
5651
  bin: this.kernel.bin.preset("dev"),
5315
5652
  })
@@ -5444,7 +5781,11 @@ class Server {
5444
5781
  if (plugin) {
5445
5782
  if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
5446
5783
  plugin = structuredClone(plugin)
5447
- plugin_menu = this.running_dynamic(req.params.name, plugin.menu)
5784
+ let default_plugin_query
5785
+ if (req.query) {
5786
+ default_plugin_query = req.query
5787
+ }
5788
+ plugin_menu = this.running_dynamic(req.params.name, plugin.menu, default_plugin_query)
5448
5789
  html = await new Promise((resolve, reject) => {
5449
5790
  ejs.renderFile(path.resolve(__dirname, "views/partials/dynamic.ejs"), { dynamic: plugin_menu }, (err, html) => {
5450
5791
  resolve(html)
@@ -5578,12 +5919,122 @@ class Server {
5578
5919
  // res.json({ success: true })
5579
5920
  // }))
5580
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
+
5581
5961
  this.app.get("/info/procs", ex(async (req, res) => {
5582
5962
  await this.kernel.processes.refresh()
5583
5963
 
5584
- let info = []
5585
- for(let item of this.kernel.processes.info) {
5586
- 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 {
5587
6038
  online: true,
5588
6039
  host: {
5589
6040
  ip: this.kernel.peer.host,
@@ -5593,65 +6044,16 @@ class Server {
5593
6044
  arch: this.kernel.arch,
5594
6045
  },
5595
6046
  ...item,
5596
- })
5597
- }
5598
- // get only the parts not from this peer
5599
- for(let host in this.kernel.peer.info) {
5600
- let host_info = this.kernel.peer.info[host]
5601
- let host_rewrites = host_info.rewrite_mapping
5602
- for(let key in host_rewrites) {
5603
- info.push({
5604
- online: true,
5605
- host: {
5606
- ip: host,
5607
- local: this.kernel.peer.host === host,
5608
- name: host_info.name,
5609
- platform: host_info.platform,
5610
- arch: host_info.arch
5611
- },
5612
- name: `[Files] ${host_rewrites[key].name}`,
5613
- ip: host_rewrites[key].external_ip
5614
- })
5615
- }
5616
- if (this.kernel.peer.host !== host) {
5617
- let host_routers = host_info.router_info
5618
- for(let host_router of host_routers) {
5619
- let ip
5620
- // the peer sharing works only if external_ip is available (caddy is installed)
5621
- if (host_router.external_ip) {
5622
- ip = host_router.external_ip
5623
- } else {
5624
- // if caddy is not turned on, set ip as null, so that it suggests turning on peer sharing
5625
- ip = null
5626
- }
5627
- info.push({
5628
- online: true,
5629
- host: {
5630
- ip: host,
5631
- name: host_info.name,
5632
- platform: host_info.platform,
5633
- arch: host_info.arch
5634
- },
5635
- name: host_router.title || host_router.name,
5636
- ip
5637
- })
6047
+ url: selectedUrl,
6048
+ protocol,
6049
+ urls: {
6050
+ http: httpUrl,
6051
+ https: httpsUrls
5638
6052
  }
5639
6053
  }
6054
+ })
5640
6055
 
5641
- for(let app of host_info.installed) {
5642
- info.push({
5643
- host: {
5644
- ip: host,
5645
- local: this.kernel.peer.host === host,
5646
- name: host_info.name,
5647
- platform: host_info.platform,
5648
- arch: host_info.arch
5649
- },
5650
- name: app.title || app.folder,
5651
- ip: app.http_href.replace("http://", "")
5652
- })
5653
- }
5654
- }
6056
+ // this.add_extra_urls(info)
5655
6057
  res.json({
5656
6058
  info
5657
6059
  })
@@ -5691,6 +6093,10 @@ class Server {
5691
6093
  })
5692
6094
  }
5693
6095
  }))
6096
+ this.app.get("/info/shells", ex(async (req,res) => {
6097
+ let shells = this.kernel.shell.info()
6098
+ res.json(shells)
6099
+ }))
5694
6100
  this.app.get("/info/api/:name", ex(async (req,res) => {
5695
6101
  // api related info
5696
6102
  let c = this.kernel.path("api", req.params.name)
@@ -5752,7 +6158,6 @@ class Server {
5752
6158
  let gitRemote = null
5753
6159
  try {
5754
6160
  const repositoryPath = path.resolve(this.kernel.api.userdir, req.params.name)
5755
- console.log({ repositoryPath })
5756
6161
  gitRemote = await git.getConfig({
5757
6162
  fs,
5758
6163
  http,
@@ -5775,7 +6180,7 @@ class Server {
5775
6180
  title: name,
5776
6181
  url: gitRemote,
5777
6182
  //redirect_uri: "http://localhost:3001/apps/redirect?git=" + gitRemote,
5778
- redirect_uri: "https://app-7pt7.onrender.com/apps/redirect?git=" + gitRemote,
6183
+ redirect_uri: "https://app.pinokio.co/apps/redirect?git=" + gitRemote,
5779
6184
  platform: this.kernel.platform,
5780
6185
  theme: this.theme,
5781
6186
  agent: req.agent,
@@ -5897,7 +6302,7 @@ class Server {
5897
6302
  }))
5898
6303
  this.app.get("/pinokio/download", ex((req, res) => {
5899
6304
  let queryStr = new URLSearchParams(req.query).toString()
5900
- res.redirect("/?mode=download&" + queryStr)
6305
+ res.redirect("/home?mode=download&" + queryStr)
5901
6306
  }))
5902
6307
  this.app.post("/pinokio/install", ex((req, res) => {
5903
6308
  req.session.requirements = req.body.requirements
@@ -5905,7 +6310,6 @@ class Server {
5905
6310
  res.redirect("/pinokio/install")
5906
6311
  }))
5907
6312
  this.app.get("/pinokio/install", ex((req, res) => {
5908
- console.log("render /pinokio/install")
5909
6313
  let requirements = req.session.requirements
5910
6314
  let callback = req.session.callback
5911
6315
  req.session.requirements = null
@@ -6042,6 +6446,31 @@ class Server {
6042
6446
  res.status(404).send("Missing attribute: path")
6043
6447
  }
6044
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
+
6045
6474
  this.app.get("/snapshots", ex(async (req, res) => {
6046
6475
  let files = []
6047
6476
  try {
@@ -6083,14 +6512,13 @@ class Server {
6083
6512
  res.status(500).json({ error: "Failed to delete file: " + e.message })
6084
6513
  }
6085
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
+ }))
6086
6519
  this.app.post("/screenshot", this.upload.any(), ex(async (req, res) => {
6087
- await fs.promises.mkdir(this.kernel.path("screenshots"), { recursive: true }).catch((e) => { })
6088
- for(let key in req.files) {
6089
- let file = req.files[key]
6090
- console.log({ file, key })
6091
- let ts = String(Date.now()) + ".png"
6092
- await fs.promises.writeFile(this.kernel.path("screenshots", ts), file.buffer)
6093
- }
6520
+ const saved = await saveCaptureFiles(req.files);
6521
+ res.json({ saved });
6094
6522
  }))
6095
6523
  this.app.post("/pinokio/fs", this.upload.any(), ex(async (req, res) => {
6096
6524
  /*
@@ -6272,13 +6700,12 @@ class Server {
6272
6700
  ready = false;
6273
6701
  }
6274
6702
  if (ready) {
6275
- res.json({ success: true })
6703
+ res.json({ success: true, peer_name: this.kernel.peer.name })
6276
6704
  } else {
6277
6705
  res.json({ success: false })
6278
6706
  }
6279
6707
  } else {
6280
6708
  // if network is not active, return success immediately (just checking if the server is up)
6281
- console.log("this.kernel.router.published()")
6282
6709
  res.json({ success: true })
6283
6710
  }
6284
6711
  }))
@@ -6340,48 +6767,11 @@ class Server {
6340
6767
  })
6341
6768
  });
6342
6769
  process.on('SIGINT', () => {
6343
- //if (this.kernel && this.kernel.shell) {
6344
- // console.log("shell reset")
6345
- // this.kernel.shell.reset(() => {
6346
- // process.exit()
6347
- // })
6348
- //} else {
6349
- // process.exit()
6350
- //}
6351
- console.log("[SigInt event] Kill", process.pid)
6352
- if (this.kernel.processes.caddy_pid) {
6353
- console.log("kill caddy", this.kernel.processes.caddy_pid)
6354
- kill(this.kernel.processes.caddy_pid, "SIGKILL", true)
6355
- }
6356
- console.log("kill self")
6357
- kill(process.pid, 'SIGKILL', true)
6358
- //kill(process.pid, map, 'SIGKILL', () => {
6359
- // console.log("child procs killed for", process.pid)
6360
- // process.exit()
6361
- //});
6770
+ this.shutdown('SigInt')
6362
6771
  })
6363
6772
 
6364
6773
  process.on('SIGTERM', () => {
6365
- // if (this.kernel && this.kernel.shell) {
6366
- // console.log("shell reset")
6367
- // this.kernel.shell.reset(() => {
6368
- // process.exit()
6369
- // })
6370
- // } else {
6371
- // process.exit()
6372
- // }
6373
- console.log("[Sigterm event] Kill", process.pid)
6374
- if (this.kernel.processes.caddy_pid) {
6375
- console.log("kill caddy", this.kernel.processes.caddy_pid)
6376
- kill(this.kernel.processes.caddy_pid, "SIGKILL", true)
6377
- }
6378
- console.log("kill self")
6379
- kill(process.pid, 'SIGKILL', true)
6380
- //let map = this.kernel.processes.map || {}
6381
- //kill(process.pid, map, 'SIGKILL', () => {
6382
- // console.log("child procs killed for", process.pid)
6383
- // process.exit()
6384
- //});
6774
+ this.shutdown('SigTerm')
6385
6775
  })
6386
6776
  // process.on('exit', () => {
6387
6777
  // console.log("[Exit event]")