pinokiod 3.23.0 → 3.25.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.23.0",
3
+ "version": "3.25.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -36,6 +36,7 @@
36
36
  "glob": "^10.3.3",
37
37
  "glob-gitignore": "^1.0.14",
38
38
  "go-get-folder-size": "^0.5.5",
39
+ "gray-matter": "^4.0.3",
39
40
  "http-proxy": "^1.18.1",
40
41
  "http-proxy-middleware": "^3.0.0",
41
42
  "http-terminator": "^3.2.0",
package/server/index.js CHANGED
@@ -188,8 +188,10 @@ class Server {
188
188
  }
189
189
  }
190
190
  } else if (key === "shell") {
191
- let shell_id = this.get_shell_id(name, indexPath, obj[key])
192
- if (this.kernel.api.running[shell_id]) {
191
+ let unix_path = Util.p2u(this.kernel.path("api", name))
192
+ let shell_id = this.get_shell_id(unix_path, indexPath, obj[key])
193
+ let decoded_shell_id = decodeURIComponent(shell_id)
194
+ if (this.kernel.api.running["shell/" + decoded_shell_id]) {
193
195
  obj.running = true
194
196
  obj.display = "indent"
195
197
  running_dynamic.push(obj)
@@ -422,34 +424,8 @@ class Server {
422
424
  // }
423
425
 
424
426
 
425
- if (config.init_required) {
426
- res.redirect("/init/" + name)
427
- return
428
-
429
- // // none of the pinokio.js, pinokio.json, pinokio_meta.json exists => need to initialize
430
- // // if there is no menu, display all files
431
- // let p = this.kernel.path("api", name)
432
- // let files = await fs.promises.readdir(p, { withFileTypes: true })
433
- // files = files.filter((file) => {
434
- // return file.name.endsWith(".json") || file.name.endsWith(".js")
435
- // }).filter((file) => {
436
- // return file.name !== "pinokio.js" && file.name !== "pinokio.json" && file.name !== "pinokio_meta.json"
437
- // })
438
- // config = {
439
- // init_required: true,
440
- // icon: config.icon,
441
- // title: name,
442
- // menu: files.map((file) => {
443
- // return {
444
- // text: file.name,
445
- // href: file.name
446
- // }
447
- // })
448
- // }
449
- // let uri = this.kernel.path("api")
450
- // await this.renderMenu(uri, name, config, [])
451
- } else {
452
- let menu = config.menu || []
427
+ let menu = config.menu || []
428
+ try {
453
429
  if (typeof config.menu === "function") {
454
430
  if (config.menu.constructor.name === "AsyncFunction") {
455
431
  config.menu = await config.menu(this.kernel, this.kernel.info)
@@ -457,11 +433,14 @@ class Server {
457
433
  config.menu = config.menu(this.kernel, this.kernel.info)
458
434
  }
459
435
  }
436
+ } catch (e) {
437
+ err = e.stack
438
+ config.menu = []
439
+ }
460
440
 
461
- let uri = this.kernel.path("api")
462
- await this.renderMenu(uri, name, config, [])
441
+ let uri = this.kernel.path("api")
442
+ await this.renderMenu(uri, name, config, [])
463
443
 
464
- }
465
444
 
466
445
  let platform = os.platform()
467
446
 
@@ -578,16 +557,23 @@ class Server {
578
557
  let plugin = await this.getPlugin(name)
579
558
  if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
580
559
  plugin = structuredClone(plugin)
581
- this.running_dynamic(name, plugin.menu)
582
- plugin_menu = plugin.menu
560
+ plugin_menu = this.running_dynamic(name, plugin.menu)
583
561
  }
584
-
585
-
586
562
  let menu_hidden = false
587
563
  if (this.menu_hidden[name] && this.menu_hidden[name][type]) {
588
564
  menu_hidden = true
589
565
  }
566
+
567
+ let posix_path = Util.p2u(this.kernel.path("api", name))
568
+ let dev_link
569
+ if (posix_path.startsWith("/")) {
570
+ dev_link = "/d" + posix_path
571
+ } else {
572
+ dev_link = "/d/" + posix_path
573
+ }
574
+
590
575
  const result = {
576
+ dev_link,
591
577
  minimized: menu_hidden,
592
578
  // repos,
593
579
  current_urls,
@@ -662,7 +648,6 @@ class Server {
662
648
  is_subpath(parent, child) {
663
649
  const relative = path.relative(parent, child);
664
650
  let check = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
665
- console.log({ relative, parent, child, check })
666
651
  return check
667
652
  }
668
653
  async render(req, res, pathComponents, meta) {
@@ -1360,23 +1345,28 @@ class Server {
1360
1345
  let is_running
1361
1346
  let api_path = this.kernel.path("api")
1362
1347
  if (this.is_subpath(api_path, key)) {
1348
+ // normal api script at path p
1363
1349
  if (this.is_subpath(p, key)) {
1364
1350
  is_running = true
1365
1351
  }
1366
1352
  } else {
1367
1353
  if (key.endsWith(p)) {
1354
+ // global scripts that run in the path p
1368
1355
  is_running = true
1369
1356
  } else {
1370
- if (!path.isAbsolute(key)) {
1371
- let chunks = key.split("_")
1357
+ // shell sessions
1358
+ if (key.startsWith("shell/")) {
1359
+ let unix_path = key.slice(6)
1360
+ let native_path = Util.u2p(unix_path)
1361
+ let chunks = native_path.split("_")
1372
1362
  if (chunks.length > 1) {
1373
1363
  let folder = chunks[0]
1374
1364
  /// if the folder name matches, it's running
1375
- if (folder === items[i].name) {
1365
+ let item_path = this.kernel.path("api", items[i].name)
1366
+ if (item_path === folder) {
1376
1367
  is_running = true
1377
1368
  }
1378
1369
  }
1379
-
1380
1370
  }
1381
1371
  }
1382
1372
  }
@@ -1416,7 +1406,9 @@ class Server {
1416
1406
  } else {
1417
1407
  let shell = this.kernel.shell.find({
1418
1408
  filter: (shell) => {
1419
- return shell.id.startsWith(items[i].name + "_")
1409
+ let item_path = this.kernel.path("api", items[i].name)
1410
+ let unix_item_path = Util.p2u(item_path)
1411
+ return shell.id.startsWith("shell/" + unix_item_path + "_")
1420
1412
  }
1421
1413
  })
1422
1414
  if (shell.length > 0) {
@@ -1706,6 +1698,89 @@ class Server {
1706
1698
  }
1707
1699
  return config
1708
1700
  }
1701
+ renderShell(cwd, indexPath, subIndexPath, menuitem) {
1702
+ if (menuitem.shell) {
1703
+ /*
1704
+ shell :- {
1705
+ id (optional),
1706
+ path (required), // api, bin, quick, network, api/
1707
+ message (optional), // if not specified, start an empty shell
1708
+ venv,
1709
+ input, // input mode if true
1710
+ callback, // callback url after shutting down
1711
+ kill, // when to kill (regular expression)
1712
+ }
1713
+ */
1714
+
1715
+ let rendered = this.kernel.template.render(menuitem.shell, {})
1716
+ let params = new URLSearchParams()
1717
+ // if (rendered.id) {
1718
+ // params.set("id", encodeURIComponent(rendered.id))
1719
+ // } else {
1720
+ // let shell_id = "sh_" + name + "_" + i
1721
+ // params.set("id", encodeURIComponent(shell_id))
1722
+ // }
1723
+ if (rendered.path) {
1724
+ params.set("path", encodeURIComponent(this.kernel.api.filePath(rendered.path, cwd)))
1725
+ } else {
1726
+ params.set("path", encodeURIComponent(cwd))
1727
+ }
1728
+ if (rendered.message) params.set("message", encodeURIComponent(rendered.message))
1729
+ if (rendered.venv) params.set("venv", encodeURIComponent(rendered.venv))
1730
+ if (rendered.input) params.set("input", true)
1731
+ if (rendered.callback) params.set("callback", encodeURIComponent(rendered.callback))
1732
+ if (rendered.callback_target) params.set("callback_target", rendered_callback_target)
1733
+ if (rendered.kill) params.set("kill", encodeURIComponent(rendered.kill))
1734
+ if (rendered.done) params.set("done", encodeURIComponent(rendered.done))
1735
+ if (rendered.env) {
1736
+ for(let key in rendered.env) {
1737
+ let env_key = "env." + key
1738
+ params.set(env_key, rendered.env[key])
1739
+ }
1740
+ }
1741
+ if (rendered.conda) {
1742
+ for(let key in rendered.conda) {
1743
+ let conda_key = "conda." + key
1744
+ params.set(conda_key, rendered.conda[key])
1745
+ }
1746
+ }
1747
+
1748
+ // deterministic shell id generation
1749
+ // `${api_path}_${i}_${hash}`
1750
+ let currentIndexPath
1751
+ if (indexPath) {
1752
+ currentIndexPath = indexPath + "." + subIndexPath
1753
+ } else {
1754
+ currentIndexPath = "" + subIndexPath
1755
+ }
1756
+ let unix_path = Util.p2u(cwd)
1757
+ let shell_id = this.get_shell_id(unix_path, currentIndexPath, rendered)
1758
+
1759
+ console.log("SHELL ID 1", shell_id)
1760
+
1761
+ // let hash = crypto.createHash('md5').update(JSON.stringify(rendered)).digest('hex')
1762
+ // let shell_id
1763
+ // if (rendered.id) {
1764
+ // shell_id = encodeURIComponent(`${name}_${rendered.id}`)
1765
+ // } else {
1766
+ // shell_id = encodeURIComponent(`${name}_${i}_${hash}`)
1767
+ // }
1768
+ menuitem.href = "/shell/" + shell_id + "?" + params.toString()
1769
+ let decoded_shell_id = decodeURIComponent(shell_id)
1770
+ let shell = this.kernel.shell.get(decoded_shell_id)
1771
+ menuitem.shell_id = "shell/" + decoded_shell_id
1772
+ if (shell) {
1773
+ menuitem.running = true
1774
+ } else {
1775
+ let shell = this.kernel.shell.get(decoded_shell_id)
1776
+ if (shell) {
1777
+ menuitem.running = true
1778
+ }
1779
+ }
1780
+ }
1781
+ console.log("renderShell", menuitem)
1782
+ return menuitem
1783
+ }
1709
1784
 
1710
1785
  async renderMenu(uri, name, config, pathComponents, indexPath) {
1711
1786
  if (config.menu) {
@@ -1789,81 +1864,8 @@ class Server {
1789
1864
 
1790
1865
 
1791
1866
  if (menuitem.shell) {
1792
- /*
1793
- shell :- {
1794
- id (optional),
1795
- path (required), // api, bin, quick, network, api/
1796
- message (optional), // if not specified, start an empty shell
1797
- venv,
1798
- input, // input mode if true
1799
- callback, // callback url after shutting down
1800
- kill, // when to kill (regular expression)
1801
- }
1802
- */
1803
-
1804
- let rendered = this.kernel.template.render(menuitem.shell, {})
1805
- let params = new URLSearchParams()
1806
- // if (rendered.id) {
1807
- // params.set("id", encodeURIComponent(rendered.id))
1808
- // } else {
1809
- // let shell_id = "sh_" + name + "_" + i
1810
- // params.set("id", encodeURIComponent(shell_id))
1811
- // }
1812
1867
  let basePath = this.kernel.path("api", name)
1813
- if (rendered.path) {
1814
- params.set("path", encodeURIComponent(this.kernel.api.filePath(rendered.path, basePath)))
1815
- } else {
1816
- params.set("path", encodeURIComponent(basePath))
1817
- }
1818
- if (rendered.message) params.set("message", encodeURIComponent(rendered.message))
1819
- if (rendered.venv) params.set("venv", encodeURIComponent(rendered.venv))
1820
- if (rendered.input) params.set("input", true)
1821
- if (rendered.callback) params.set("callback", encodeURIComponent(rendered.callback))
1822
- if (rendered.callback_target) params.set("callback_target", rendered_callback_target)
1823
- if (rendered.kill) params.set("kill", encodeURIComponent(rendered.kill))
1824
- if (rendered.done) params.set("done", encodeURIComponent(rendered.done))
1825
- if (rendered.env) {
1826
- for(let key in rendered.env) {
1827
- let env_key = "env." + key
1828
- params.set(env_key, rendered.env[key])
1829
- }
1830
- }
1831
- if (rendered.conda) {
1832
- for(let key in rendered.conda) {
1833
- let conda_key = "conda." + key
1834
- params.set(conda_key, rendered.conda[key])
1835
- }
1836
- }
1837
-
1838
- // deterministic shell id generation
1839
- // `${api_path}_${i}_${hash}`
1840
- let currentIndexPath
1841
- if (indexPath) {
1842
- currentIndexPath = indexPath + "." + i
1843
- } else {
1844
- currentIndexPath = "" + i
1845
- }
1846
- let shell_id = this.get_shell_id(name, currentIndexPath, rendered)
1847
-
1848
- // let hash = crypto.createHash('md5').update(JSON.stringify(rendered)).digest('hex')
1849
- // let shell_id
1850
- // if (rendered.id) {
1851
- // shell_id = encodeURIComponent(`${name}_${rendered.id}`)
1852
- // } else {
1853
- // shell_id = encodeURIComponent(`${name}_${i}_${hash}`)
1854
- // }
1855
- menuitem.href = "/shell/" + shell_id + "?" + params.toString()
1856
- menuitem.shell_id = shell_id
1857
- let shell = this.kernel.shell.get(shell_id)
1858
- if (shell) {
1859
- menuitem.running = true
1860
- } else {
1861
- let decoded_shell_id = decodeURIComponent(shell_id)
1862
- let shell = this.kernel.shell.get(decoded_shell_id)
1863
- if (shell) {
1864
- menuitem.running = true
1865
- }
1866
- }
1868
+ this.renderShell(basePath, indexPath, i, menuitem)
1867
1869
  }
1868
1870
 
1869
1871
  if (menuitem.href) {
@@ -1978,9 +1980,7 @@ class Server {
1978
1980
  config.menu[i].btn = menuitem.html
1979
1981
  }
1980
1982
  } else if (menuitem.hasOwnProperty("text")) {
1981
- if (menuitem.hasOwnProperty("icon")) {
1982
- menuitem.html = `<i class="${menuitem.icon}"></i> ${menuitem.text}`
1983
- } else if (menuitem.hasOwnProperty("image")) {
1983
+ if (menuitem.hasOwnProperty("image")) {
1984
1984
  let imagePath
1985
1985
  if (menuitem.image.startsWith("/")) {
1986
1986
  imagePath = menuitem.image
@@ -1988,6 +1988,8 @@ class Server {
1988
1988
  imagePath = `/api/${name}/${menuitem.image}?raw=true`
1989
1989
  }
1990
1990
  menuitem.html = `<img class='menu-item-image' src='${imagePath}' /> ${menuitem.text}`
1991
+ } else if (menuitem.hasOwnProperty("icon")) {
1992
+ menuitem.html = `<i class="${menuitem.icon}"></i> ${menuitem.text}`
1991
1993
  } else {
1992
1994
  menuitem.html = `${menuitem.text}`
1993
1995
  }
@@ -2494,7 +2496,15 @@ class Server {
2494
2496
  info.cwd = () => {
2495
2497
  return filepath
2496
2498
  }
2497
- let menu = await this.kernel.plugin.config.menu(this.kernel, info)
2499
+ let menu = this.kernel.plugin.config.menu.map((item) => {
2500
+ return {
2501
+ params: {
2502
+ cwd: filepath
2503
+ },
2504
+ ...item
2505
+ }
2506
+ })
2507
+ // let menu = await this.kernel.plugin.config.menu(this.kernel, info)
2498
2508
  let plugin = { menu }
2499
2509
  let uri = filepath
2500
2510
  await this.renderMenu(uri, filepath, plugin, [])
@@ -2526,11 +2536,21 @@ class Server {
2526
2536
  let cached = this.kernel.plugin.cache[name]
2527
2537
  return cached
2528
2538
  } else {
2529
- let info = new Info(this.kernel)
2530
- info.caller = () => {
2531
- return this.kernel.path("api", name, "pinokio.js")
2532
- }
2533
- let menu = await this.kernel.plugin.config.menu(this.kernel, info)
2539
+ // let info = new Info(this.kernel)
2540
+ // info.caller = () => {
2541
+ // return this.kernel.path("api", name, "pinokio.js")
2542
+ // }
2543
+ // let menu = await this.kernel.plugin.config.menu(this.kernel, info)
2544
+
2545
+ let menu = this.kernel.plugin.config.menu.map((item) => {
2546
+ return {
2547
+ params: {
2548
+ //cwd: this.kernel.path("api", name, "pinokio.js")
2549
+ cwd: this.kernel.path("api", name)
2550
+ },
2551
+ ...item
2552
+ }
2553
+ })
2534
2554
  let plugin = { menu }
2535
2555
  let uri = this.kernel.path("api")
2536
2556
  await this.renderMenu(uri, name, plugin, [])
@@ -2847,6 +2867,44 @@ class Server {
2847
2867
 
2848
2868
  //let home = this.kernel.homedir
2849
2869
  //let home = this.kernel.store.get("home")
2870
+ this.app.get("/launch", ex(async (req, res) => {
2871
+ // parse the url
2872
+ /*
2873
+ is it https://<name>.localhost ?
2874
+ - is <name> already installed?
2875
+ - yes: display
2876
+ - no: 404
2877
+ else: 404
2878
+ */
2879
+ let url = req.query.url
2880
+ let u = new URL(url)
2881
+ let host = u.host
2882
+ if (host.endsWith(".localhost")) {
2883
+ let name = host.replace(/\.localhost$/, '')
2884
+ let api_path = this.kernel.path("api", name)
2885
+ let exists = await this.exists(api_path)
2886
+ if (exists) {
2887
+ let meta = await this.kernel.api.meta(name)
2888
+ res.render("start", {
2889
+ logo: this.logo,
2890
+ theme: this.theme,
2891
+ agent: this.agent,
2892
+ name: meta.title,
2893
+ image: meta.icon,
2894
+ link: "/p/" + name
2895
+ })
2896
+ return
2897
+ }
2898
+ }
2899
+ res.render("start", {
2900
+ logo: this.logo,
2901
+ theme: this.theme,
2902
+ agent: this.agent,
2903
+ name: "Does not exist",
2904
+ image: "/pinokio-black.png",
2905
+ link: null
2906
+ })
2907
+ }))
2850
2908
  this.app.get("/", ex(async (req, res) => {
2851
2909
  // check bin folder
2852
2910
  // let bin_path = this.kernel.path("bin/miniconda")
@@ -3061,7 +3119,13 @@ class Server {
3061
3119
 
3062
3120
  // console.log("this.kernel.proto.init")
3063
3121
  // await this.kernel.proto.init()
3122
+ let list = this.getPeerInfo()
3123
+ let ai = await this.kernel.proto.ai()
3124
+ console.log("ai", ai)
3064
3125
  res.render("init/index", {
3126
+ list,
3127
+ ai,
3128
+ current_host: this.kernel.peer.host,
3065
3129
  cwd: this.kernel.path("api"),
3066
3130
  name: null,
3067
3131
  // name: req.params.name,
@@ -3527,7 +3591,12 @@ class Server {
3527
3591
  */
3528
3592
 
3529
3593
  // create a new term from cwd
3530
- let id = decodeURIComponent(req.params.id)
3594
+
3595
+ /*
3596
+ GET /shell/:unix_path => shell id: 'shell/:unix_path'
3597
+ */
3598
+
3599
+ let id = "shell/" + decodeURIComponent(req.params.id)
3531
3600
  let target = req.query.target ? req.query.target : null
3532
3601
  let cwd = this.kernel.path(this.kernel.api.filePath(decodeURIComponent(req.query.path)))
3533
3602
  let message = req.query.message ? decodeURIComponent(req.query.message) : null
@@ -3724,7 +3793,6 @@ class Server {
3724
3793
  if (item.name === req.params.name) {
3725
3794
  processes = item.processes
3726
3795
  host = item.host
3727
- console.log("matched", processes)
3728
3796
  peer = item
3729
3797
  }
3730
3798
  }
@@ -4368,7 +4436,6 @@ class Server {
4368
4436
 
4369
4437
  const items = await Util.parse_env_detail(filepath)
4370
4438
 
4371
-
4372
4439
  res.render("env_editor", {
4373
4440
  home: true,
4374
4441
  config: null,
@@ -4436,8 +4503,23 @@ class Server {
4436
4503
  })
4437
4504
  } else {
4438
4505
 
4439
-
4506
+ let gitRemote = null
4507
+ try {
4508
+ //const repositoryPath = this.kernel.path(pathComponents[0], pathComponents[1])
4509
+ //const repositoryPath = this.kernel.path(pathComponents[0])
4510
+ const repositoryPath = path.resolve(this.kernel.api.userdir, api_path)
4511
+ console.log({ repositoryPath })
4512
+ gitRemote = await git.getConfig({
4513
+ fs,
4514
+ http,
4515
+ dir: repositoryPath,
4516
+ path: 'remote.origin.url'
4517
+ })
4518
+ } catch (e) {
4519
+ console.log("ERROR", e)
4520
+ }
4440
4521
  res.render("env_editor", {
4522
+ gitRemote,
4441
4523
  home: null,
4442
4524
  config,
4443
4525
  name,
@@ -4873,6 +4955,123 @@ class Server {
4873
4955
  agent: this.agent,
4874
4956
  })
4875
4957
  }))
4958
+ this.app.get("/d/*", ex(async (req, res) => {
4959
+ let filepath = Util.u2p(req.params[0])
4960
+ let plugin = await this.getPluginGlobal(filepath)
4961
+ let html = ""
4962
+ let plugin_menu
4963
+ try {
4964
+ plugin_menu = plugin.menu
4965
+ //plugin_menu = plugin.menu[0].menu
4966
+ } catch (e) {
4967
+ plugin_menu = []
4968
+ }
4969
+ let current_urls = await this.current_urls(req.originalUrl.slice(1))
4970
+ let retry = false
4971
+ // if plugin_menu is empty, try again in 1 sec
4972
+ if (plugin_menu.length === 0) {
4973
+ retry = true
4974
+ }
4975
+ let venvs = await Util.find_venv(filepath)
4976
+ let terminal
4977
+ if (venvs.length > 0) {
4978
+ let terminals = []
4979
+ try {
4980
+ for(let i=0; i<venvs.length; i++) {
4981
+ let venv = venvs[i]
4982
+ let parsed = path.parse(venv)
4983
+ terminals.push(this.renderShell(filepath, i, 0, {
4984
+ icon: "fa-brands fa-python",
4985
+ title: "Python virtual environment",
4986
+ subtitle: this.kernel.path("api", parsed.name),
4987
+ type: "Start",
4988
+ shell: {
4989
+ venv: venv,
4990
+ input: true,
4991
+ }
4992
+ }))
4993
+ }
4994
+ } catch (e) {
4995
+ console.log(e)
4996
+ }
4997
+ terminal = {
4998
+ icon: "fa-solid fa-terminal",
4999
+ title: "Open web terminal",
5000
+ subtitle: "Open the terminal in the browser",
5001
+ menu: terminals
5002
+ }
5003
+ } else {
5004
+ terminal = {
5005
+ icon: "fa-solid fa-terminal",
5006
+ title: "Open web terminal",
5007
+ subtitle: "Work with the terminal directly in the browser",
5008
+ menu: [this.renderShell(filepath, 0, 0, {
5009
+ icon: "fa-solid fa-terminal",
5010
+ title: "Terminal",
5011
+ subtitle: filepath,
5012
+ type: "Start",
5013
+ shell: {
5014
+ input: true
5015
+ }
5016
+ })]
5017
+ }
5018
+ }
5019
+
5020
+ let exec_menus = []
5021
+ let shell_menus = []
5022
+ if (plugin_menu.length > 0) {
5023
+ for(let item of plugin_menu) {
5024
+ // if shell.run method exists
5025
+ // if exec method exists
5026
+ let mode
5027
+ for(let step of item.run) {
5028
+ if (step.method === "exec") {
5029
+ mode = "exec"
5030
+ break
5031
+ }
5032
+ if (step.method === "shell.run") {
5033
+ mode = "shell"
5034
+ break
5035
+ }
5036
+ }
5037
+ if (mode === "exec") {
5038
+ item.type = "Open"
5039
+ exec_menus.push(item)
5040
+ } else if (mode === "shell") {
5041
+ item.type = "Start"
5042
+ shell_menus.push(item)
5043
+ }
5044
+ }
5045
+ exec_menus.sort((a, b) => { return a > b })
5046
+ shell_menus.sort((a, b) => { return a > b })
5047
+ }
5048
+ let dynamic = [
5049
+ {
5050
+ icon: "fa-solid fa-robot",
5051
+ title: "Get started building with AI",
5052
+ subtitle: "Start making changes to this project using AI",
5053
+ menu: shell_menus
5054
+ },
5055
+ {
5056
+ icon: "fa-solid fa-arrow-up-right-from-square",
5057
+ title: "Open in external apps",
5058
+ subtitle: "Open this project in 3rd party apps",
5059
+ menu: exec_menus
5060
+ },
5061
+ terminal,
5062
+ ]
5063
+ res.render("d", {
5064
+ retry,
5065
+ current_urls,
5066
+ docs: this.docs,
5067
+ portal: this.portal,
5068
+ install: this.install,
5069
+ agent: this.agent,
5070
+ theme: this.theme,
5071
+ //dynamic: plugin_menu
5072
+ dynamic,
5073
+ })
5074
+ }))
4876
5075
  this.app.get("/dev/*", ex(async (req, res) => {
4877
5076
  console.log("GET /dev/*", req.params)
4878
5077
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
@@ -5015,21 +5214,21 @@ class Server {
5015
5214
  this.app.get("/pinokio/dynamic/:name", ex(async (req, res) => {
5016
5215
  // await this.kernel.plugin.init()
5017
5216
  let plugin = await this.getPlugin(req.params.name)
5217
+ let html = ""
5218
+ console.log("plugin", JSON.stringify(plugin, null, 2))
5219
+ let plugin_menu
5018
5220
  if (plugin) {
5019
- let html = ""
5020
5221
  if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
5021
5222
  plugin = structuredClone(plugin)
5022
- this.running_dynamic(req.params.name, plugin.menu)
5223
+ plugin_menu = this.running_dynamic(req.params.name, plugin.menu)
5023
5224
  html = await new Promise((resolve, reject) => {
5024
- ejs.renderFile(path.resolve(__dirname, "views/partials/dynamic.ejs"), { dynamic: plugin.menu }, (err, html) => {
5225
+ ejs.renderFile(path.resolve(__dirname, "views/partials/dynamic.ejs"), { dynamic: plugin_menu }, (err, html) => {
5025
5226
  resolve(html)
5026
5227
  })
5027
5228
  })
5028
5229
  }
5029
- res.send(html)
5030
- } else {
5031
- res.send("")
5032
5230
  }
5231
+ res.send(html)
5033
5232
  }))
5034
5233
  this.app.get("/pinokio/ai/:name", ex(async (req, res) => {
5035
5234
  /*