pinokiod 3.40.0 → 3.42.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 (82) hide show
  1. package/kernel/api/browser/index.js +3 -1
  2. package/kernel/api/cloudflare/index.js +3 -3
  3. package/kernel/api/index.js +187 -51
  4. package/kernel/api/loading/index.js +15 -0
  5. package/kernel/api/process/index.js +7 -0
  6. package/kernel/api/shell/index.js +0 -2
  7. package/kernel/bin/browserless.js +22 -0
  8. package/kernel/bin/caddy.js +36 -4
  9. package/kernel/bin/index.js +4 -1
  10. package/kernel/bin/setup.js +38 -5
  11. package/kernel/connect/backend.js +110 -0
  12. package/kernel/connect/config.js +171 -0
  13. package/kernel/connect/index.js +18 -7
  14. package/kernel/connect/providers/huggingface/index.js +98 -0
  15. package/kernel/connect/providers/x/index.js +0 -1
  16. package/kernel/environment.js +91 -19
  17. package/kernel/git.js +46 -3
  18. package/kernel/index.js +119 -39
  19. package/kernel/peer.js +40 -5
  20. package/kernel/plugin.js +3 -2
  21. package/kernel/procs.js +27 -20
  22. package/kernel/prototype.js +30 -16
  23. package/kernel/router/common.js +1 -1
  24. package/kernel/router/connector.js +1 -3
  25. package/kernel/router/index.js +38 -4
  26. package/kernel/router/localhost_home_router.js +5 -1
  27. package/kernel/router/localhost_port_router.js +27 -1
  28. package/kernel/router/localhost_static_router.js +93 -0
  29. package/kernel/router/localhost_variable_router.js +14 -9
  30. package/kernel/router/peer_peer_router.js +3 -0
  31. package/kernel/router/peer_static_router.js +43 -0
  32. package/kernel/router/peer_variable_router.js +15 -14
  33. package/kernel/router/processor.js +26 -1
  34. package/kernel/router/rewriter.js +59 -0
  35. package/kernel/scripts/git/commit +11 -1
  36. package/kernel/shell.js +8 -3
  37. package/kernel/util.js +65 -6
  38. package/package.json +2 -1
  39. package/server/index.js +1048 -970
  40. package/server/public/common.js +382 -1
  41. package/server/public/fscreator.js +0 -1
  42. package/server/public/loading.js +17 -0
  43. package/server/public/notifyinput.js +0 -1
  44. package/server/public/opener.js +4 -2
  45. package/server/public/style.css +310 -11
  46. package/server/socket.js +7 -1
  47. package/server/views/app.ejs +1747 -351
  48. package/server/views/columns.ejs +338 -0
  49. package/server/views/connect/huggingface.ejs +353 -0
  50. package/server/views/connect/index.ejs +410 -0
  51. package/server/views/connect/x.ejs +43 -9
  52. package/server/views/connect.ejs +709 -49
  53. package/server/views/container.ejs +357 -0
  54. package/server/views/d.ejs +251 -62
  55. package/server/views/download.ejs +54 -10
  56. package/server/views/editor.ejs +11 -0
  57. package/server/views/explore.ejs +40 -15
  58. package/server/views/file_explorer.ejs +25 -246
  59. package/server/views/form.ejs +44 -1
  60. package/server/views/frame.ejs +39 -1
  61. package/server/views/github.ejs +48 -11
  62. package/server/views/help.ejs +48 -7
  63. package/server/views/index.ejs +119 -58
  64. package/server/views/index2.ejs +3 -4
  65. package/server/views/init/index.ejs +651 -197
  66. package/server/views/install.ejs +1 -1
  67. package/server/views/mini.ejs +47 -18
  68. package/server/views/net.ejs +199 -67
  69. package/server/views/network.ejs +229 -93
  70. package/server/views/network2.ejs +3 -4
  71. package/server/views/old_network.ejs +3 -3
  72. package/server/views/prototype/index.ejs +48 -11
  73. package/server/views/review.ejs +1005 -0
  74. package/server/views/rows.ejs +341 -0
  75. package/server/views/screenshots.ejs +1020 -0
  76. package/server/views/settings.ejs +160 -23
  77. package/server/views/setup.ejs +49 -7
  78. package/server/views/setup_home.ejs +43 -10
  79. package/server/views/shell.ejs +7 -1
  80. package/server/views/start.ejs +14 -9
  81. package/server/views/terminal.ejs +13 -2
  82. package/server/views/tools.ejs +1015 -0
package/server/index.js CHANGED
@@ -23,6 +23,7 @@ const fse = require('fs-extra')
23
23
  const QRCode = require('qrcode')
24
24
  const axios = require('axios')
25
25
  const crypto = require('crypto')
26
+ const serveIndex = require('serve-index')
26
27
 
27
28
  const git = require('isomorphic-git')
28
29
  const http = require('isomorphic-git/http/node')
@@ -60,8 +61,6 @@ function normalize(str) {
60
61
 
61
62
  class Server {
62
63
  constructor(config) {
63
- this.menu_hidden = {}
64
- this.selected = {}
65
64
  this.tabs = {}
66
65
  this.agent = config.agent
67
66
  this.port = DEFAULT_PORT
@@ -112,38 +111,6 @@ class Server {
112
111
  exists (s) {
113
112
  return new Promise(r=>fs.access(s, fs.constants.F_OK, e => r(!e)))
114
113
  }
115
- async updateMeta(formData, app_path) {
116
- // write title/description to pinokio.json
117
- let dirty
118
- let meta_path = this.kernel.path("api", app_path, "pinokio.json")
119
- let meta = (await this.kernel.loader.load(meta_path)).resolved
120
- if (!meta) meta = {}
121
- if (formData.title) {
122
- meta.title = formData.title
123
- dirty = true
124
- }
125
- if (formData.description) {
126
- meta.description = formData.description
127
- dirty = true
128
- }
129
- if (!meta.plugin) {
130
- meta.plugin = {
131
- menu: []
132
- }
133
- }
134
-
135
- if (formData.icon_dirty) {
136
- //
137
- // write icon file
138
- let icon_path = this.kernel.path("api", formData.new_path, formData.icon_path)
139
- await fs.promises.writeFile(icon_path, formData.avatar)
140
- meta.icon = formData.icon_path
141
- dirty = true
142
- }
143
- if (dirty) {
144
- await fs.promises.writeFile(meta_path, JSON.stringify(meta, null, 2))
145
- }
146
- }
147
114
  running_dynamic (name, menu) {
148
115
  let cwd = this.kernel.path("api", name)
149
116
  let running_dynamic = []
@@ -204,24 +171,6 @@ class Server {
204
171
  traverse(menu)
205
172
  return running_dynamic
206
173
  }
207
- async createMeta(formData) {
208
- let _path = this.kernel.path("api", formData.path)
209
- await fs.promises.mkdir(_path, { recursive: true }).catch((e) => {})
210
- let icon_path = this.kernel.path("api", formData.path, "icon.png")
211
- await fs.promises.writeFile(icon_path, formData.avatar)
212
-
213
- // write title/description to pinokio.json
214
- let meta_path = this.kernel.path("api", formData.path, "pinokio.json")
215
- let meta = {
216
- title: formData.title,
217
- description: formData.description,
218
- icon: "icon.png",
219
- plugin: {
220
- menu: []
221
- }
222
- }
223
- await fs.promises.writeFile(meta_path, JSON.stringify(meta, null, 2))
224
- }
225
174
  getMemory(filepath) {
226
175
  let localMem = this.kernel.memory.local[filepath]
227
176
  let globalMem = this.kernel.memory.global[filepath]
@@ -287,14 +236,22 @@ class Server {
287
236
  name = x.name
288
237
  description = ""
289
238
  }
239
+
240
+
290
241
  let browser_url
242
+ let target
243
+
291
244
  if (x.run) {
292
245
  browser_url = "/env/api/" + x.name
293
246
  } else {
294
247
  //browser_url = "/pinokio/browser/" + x.name
295
248
  browser_url = "/p/" + x.name
296
249
  }
297
- let browser_browse_url = browser_url + "/dev"
250
+ let dev_url = browser_url + "/dev"
251
+ let review_url = browser_url + "/review"
252
+
253
+ let dns = this.kernel.pinokio_configs[x.name].dns
254
+ let routes = dns["@"]
298
255
  return {
299
256
  filepath: this.kernel.path("api", x.name),
300
257
  icon,
@@ -313,12 +270,86 @@ class Server {
313
270
  description,
314
271
  url: p + "/" + x.name,
315
272
  browser_url,
273
+ target,
316
274
  url: browser_url,
317
275
  path: uri,
318
- browse_url: browser_browse_url,
276
+ dev_url,
277
+ review_url,
319
278
  }
320
279
  })
321
280
  }
281
+ async getGit(ref, filepath) {
282
+ let dir = this.kernel.path("api", filepath)
283
+ let branches = await git.listBranches({ fs, dir });
284
+ let log = []
285
+ try {
286
+ log = await git.log({ fs, dir, depth: 50, ref: ref }); // fetch last 50 commits
287
+ log.forEach((item) => {
288
+ item.info = `/gitcommit/${item.oid}/${filepath}`
289
+ })
290
+ } catch (e) {
291
+ console.log("Log error", e)
292
+ }
293
+
294
+ let config = await this.kernel.git.config(dir)
295
+
296
+ let hosts = ""
297
+ let hosts_file = this.kernel.path("config/gh/hosts.yml")
298
+ let e = await this.exists(hosts_file)
299
+ if (e) {
300
+ hosts = await fs.promises.readFile(hosts_file, "utf8")
301
+ if (hosts.startsWith("{}")) {
302
+ hosts = ""
303
+ }
304
+ }
305
+ let connected = (hosts.length > 0)
306
+ let remote = null
307
+ if (config["remote \"origin\""]) {
308
+ remote = config["remote \"origin\""].url
309
+ }
310
+
311
+ let branch = await git.currentBranch({ fs, dir, fullname: false });
312
+
313
+ const remote2 = await git.getConfig({
314
+ fs,
315
+ dir,
316
+ path: `branch.${branch}.remote`
317
+ });
318
+
319
+ // if current branch exitss => currengt branch is selected
320
+ // if current branch does not exist => get logs[0].oid
321
+ if (branch) {
322
+ branches = branches.map((b) => {
323
+ if (b === branch) {
324
+ return {
325
+ branch: b,
326
+ selected: true
327
+ }
328
+ } else {
329
+ return {
330
+ branch: b,
331
+ selected: false
332
+ }
333
+ }
334
+ })
335
+ } else {
336
+ branches.push(log[0].oid)
337
+ branches = branches.map((b) => {
338
+ if (b === log[0].oid) {
339
+ return {
340
+ branch: b,
341
+ selected: true
342
+ }
343
+ } else {
344
+ return {
345
+ branch: b,
346
+ selected: false
347
+ }
348
+ }
349
+ })
350
+ }
351
+ return { ref, config, remote, connected, log, branch, branches, dir }
352
+ }
322
353
  async init_env(env_dir_path, options) {
323
354
  let current = this.kernel.path(env_dir_path, "ENVIRONMENT")
324
355
  // if environment.json doesn't exist,
@@ -338,7 +369,7 @@ class Server {
338
369
  await fs.promises.writeFile(current, _environmentStr)
339
370
  }
340
371
  } else {
341
- let content = await Environment.ENV("app", this.kernel.homedir)
372
+ let content = await Environment.ENV("app", this.kernel.homedir, this.kernel)
342
373
  if (_exists) {
343
374
  let _environmentStr = await fs.promises.readFile(_environment, "utf8")
344
375
  await fs.promises.writeFile(current, _environmentStr + "\n\n\n" + content)
@@ -348,31 +379,45 @@ class Server {
348
379
  }
349
380
  }
350
381
  }
351
- async current_urls(current_path) {
352
- let router_running = await this.check_router_up()
353
- let u = new URL("http://localhost:42000")
354
-
355
- let current_urls = {}
356
-
357
- // http
358
- if (current_path) {
359
- u.pathname = current_path
360
- }
361
- current_urls.http = u.toString()
362
-
363
- // https
364
- if (router_running.success) {
365
- let u = new URL("https://pinokio.localhost")
366
- if (current_path) {
367
- u.pathname = current_path
382
+ async get_github_hosts() {
383
+ let hosts = ""
384
+ let hosts_file = this.kernel.path("config/gh/hosts.yml")
385
+ let e = await this.exists(hosts_file)
386
+ if (e) {
387
+ hosts = await fs.promises.readFile(hosts_file, "utf8")
388
+ if (hosts.startsWith("{}")) {
389
+ hosts = ""
368
390
  }
369
- current_urls.https = u.toString()
370
391
  }
371
-
372
- return current_urls
392
+ return hosts
393
+ }
394
+ async current_urls(current_path) {
395
+ return {}
396
+ // let router_running = await this.check_router_up()
397
+ // let u = new URL("http://localhost:42000")
398
+ //
399
+ // let current_urls = {}
400
+ //
401
+ // // http
402
+ // if (current_path) {
403
+ // u.pathname = current_path
404
+ // }
405
+ // current_urls.http = u.toString()
406
+ //
407
+ // // https
408
+ // if (router_running.success) {
409
+ // let u = new URL("https://pinokio.localhost")
410
+ // if (current_path) {
411
+ // u.pathname = current_path
412
+ // }
413
+ // current_urls.https = u.toString()
414
+ // }
415
+ //
416
+ // return current_urls
373
417
  }
374
418
 
375
419
  async chrome(req, res, type) {
420
+
376
421
  let d = Date.now()
377
422
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
378
423
  bin: this.kernel.bin.preset("dev"),
@@ -408,32 +453,6 @@ class Server {
408
453
  }
409
454
  }
410
455
 
411
-
412
-
413
- // let requires_instantiation = false
414
- // console.log("## CONFIG", config)
415
- // if (config && config.pre) {
416
- // let env = await Environment.get2(app_path, this.kernel)
417
- // for(let item of config.pre) {
418
- // console.log("ITEM" , item)
419
- // if (item.env) {
420
- // if (env[item.env]) {
421
- //
422
- // } else {
423
- // requires_instantiation = true
424
- // break;
425
- // }
426
- // }
427
- // }
428
- // }
429
- // console.log({ requires_instantiation })
430
- // if (requires_instantiation) {
431
- // // redirect to pre
432
- // res.redirect("/required_env/api/" + name)
433
- // return
434
- // }
435
-
436
-
437
456
  let menu = config.menu || []
438
457
  try {
439
458
  if (typeof config.menu === "function") {
@@ -448,80 +467,53 @@ class Server {
448
467
  config.menu = []
449
468
  }
450
469
 
470
+
451
471
  let uri = this.kernel.path("api")
452
472
  try {
473
+ let launcher = await this.kernel.api.launcher(name)
474
+ req.launcher_root = launcher.launcher_root
453
475
  await this.renderMenu(req, uri, name, config, [])
454
476
  } catch(e) {
455
477
  config.menu = []
456
478
  err = e.stack
457
479
  }
458
480
 
459
-
460
481
  let platform = os.platform()
461
482
 
462
- // if (config.icon) {
463
- // //config.iconpath = this.kernel.path("api", name, config.icon)
464
- // config.iconpath = config.icon
465
- // config.icon = `${rawpath}/${config.icon}?raw=true`
466
- // } else {
467
- // //config.iconpath = this.kernel.path("api", name, "pinokio_icon.png")
468
- // config.iconpath = "pinokio.png"
469
- // config.icon = "/pinokio-black.png"
470
- // }
471
-
472
-
473
- // // get all memory variable stied to the current repository
474
- // let api_path = this.kernel.path("api", name)
475
- // let mem = {}
476
- // for(let type in this.kernel.memory) {
477
- // // type := local|global
478
- // let vars = this.kernel.memory[type]
479
- // for(let k in vars) {
480
- // if (k.includes(api_path)) {
481
- // if (mem[k]) {
482
- // mem[k][type] = vars[k]
483
- // } else {
484
- // mem[k] = {
485
- // [type]: vars[k]
486
- // }
487
- // }
488
- // }
489
- // }
490
- // }
491
-
492
- // console.time("2 chrome " + d)
493
- await this.init_env("api/" + name)
483
+ await Environment.init({ name }, this.kernel)
494
484
 
495
485
  // copy gitignore from ~pinokio/prototype/system/gitignore if it doesn't exist
496
486
 
497
487
 
498
488
  let gitignore_path = this.kernel.path("api/" + name + "/.gitignore")
489
+ let dot_path = this.kernel.path("api", name, "pinokio")
499
490
  let gitignore_template_path = this.kernel.path("prototype/system/gitignore")
500
491
  let template_exists = await this.exists(gitignore_template_path)
501
492
  if (template_exists) {
502
- await Util.mergeLines(
503
- gitignore_path, // existing path
504
- gitignore_template_path // overwrite with template
505
- )
493
+ let exists = await this.exists(dot_path)
494
+ if (exists) {
495
+ // 1. when importing existing projects (.pinokio exists), don't mess with .gitignore
496
+ } else {
497
+ // 2. otherwise, merge gitignore
498
+ await Util.mergeLines(
499
+ gitignore_path, // existing path
500
+ gitignore_template_path // overwrite with template
501
+ )
502
+ }
506
503
  }
507
504
 
508
505
 
509
506
 
510
507
 
511
- // console.timeEnd("2 chrome " + d)
512
-
513
- // console.time("3 chrome " + d)
514
508
  let mode = "run"
515
509
  if (req.query && req.query.mode) {
516
510
  mode = req.query.mode
517
511
  }
518
512
  const env = await this.kernel.env("api/" + name)
519
- // console.timeEnd("3 chrome " + d)
520
513
 
521
514
  // profile + feed
522
515
  const repositoryPath = path.resolve(this.kernel.api.userdir, name)
523
516
 
524
- // console.time("4 chrome " + d)
525
517
  try {
526
518
  await git.resolveRef({ fs, dir: repositoryPath, ref: 'HEAD' });
527
519
  } catch (err) {
@@ -530,9 +522,6 @@ class Server {
530
522
  await git.init({ fs, dir: repositoryPath });
531
523
  }
532
524
 
533
- // console.timeEnd("4 chrome " + d)
534
-
535
- // console.time("5 chrome " + d)
536
525
  let gitRemote = await git.getConfig({ fs, http, dir: repositoryPath, path: 'remote.origin.url' })
537
526
  let profile
538
527
  let feed
@@ -541,24 +530,18 @@ class Server {
541
530
 
542
531
  let system_env = {}
543
532
  if (this.kernel.homedir) {
544
- system_env = await Environment.get(this.kernel.homedir)
533
+ system_env = await Environment.get(this.kernel.homedir, this.kernel)
545
534
  }
546
535
  profile = this.profile(gitRemote)
547
536
  feed = this.newsfeed(gitRemote)
548
537
  }
549
- // console.timeEnd("5 chrome " + d)
550
538
 
551
539
  // git
552
540
 
553
541
  let c = this.kernel.path("api", name)
554
542
 
555
- // console.time("6 chrome " + d)
556
543
  // await this.kernel.plugin.init()
557
- // console.timeEnd("6 chrome " + d)
558
- // console.time("7 chrome " + d)
559
544
  // let plugin = await this.getPlugin(name)
560
- // console.timeEnd("7 chrome " + d)
561
- // console.time("8 chrome " + d)
562
545
  // let plugin_menu = null
563
546
  // if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
564
547
  // let running_dynamic = this.running_dynamic(name, plugin.menu)
@@ -569,15 +552,12 @@ class Server {
569
552
  let current_urls = await this.current_urls(req.originalUrl.slice(1))
570
553
 
571
554
  let plugin_menu = null
572
- let plugin = await this.getPlugin(req, name)
555
+ let plugin_config = structuredClone(this.kernel.plugin.config)
556
+ let plugin = await this.getPlugin(req, plugin_config, name)
573
557
  if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
574
558
  plugin = structuredClone(plugin)
575
559
  plugin_menu = this.running_dynamic(name, plugin.menu)
576
560
  }
577
- let menu_hidden = false
578
- if (this.menu_hidden[name] && this.menu_hidden[name][type]) {
579
- menu_hidden = true
580
- }
581
561
 
582
562
  let posix_path = Util.p2u(this.kernel.path("api", name))
583
563
  let dev_link
@@ -587,9 +567,13 @@ class Server {
587
567
  dev_link = "/d/" + posix_path
588
568
  }
589
569
 
570
+ let run_tab = "/p/" + name
571
+ let dev_tab = "/p/" + name + "/dev"
572
+ let review_tab = "/p/" + name + "/review"
573
+
574
+
590
575
  const result = {
591
576
  dev_link,
592
- minimized: menu_hidden,
593
577
  // repos,
594
578
  current_urls,
595
579
  path: this.kernel.path("api", name),
@@ -619,20 +603,22 @@ class Server {
619
603
  config,
620
604
  // sidebar_url: "/pinokio/sidebar/" + name,
621
605
  home: req.originalUrl,
606
+ run_tab,
607
+ dev_tab,
608
+ review_tab,
622
609
  // paths,
623
610
  theme: this.theme,
624
611
  agent: this.agent,
625
612
  src: "/_api/" + name,
626
613
  logs: "/_api/" + name + "/logs",
627
614
  execUrl: "/api/" + name,
615
+ git_monitor_url: `/gitcommit/HEAD/${name}`,
616
+ git_history_url: `/info/git/HEAD/${name}`,
628
617
  // rawpath,
629
618
  }
630
- // console.timeEnd("8 chrome " + d)
631
- // console.time("9 chrome " + d)
632
619
  // if (!this.kernel.proto.config) {
633
620
  // await this.kernel.proto.init()
634
621
  // }
635
- // console.timeEnd("9 chrome " + d)
636
622
  res.render("app", result)
637
623
  }
638
624
  getVariationUrls(req) {
@@ -651,12 +637,13 @@ class Server {
651
637
  return { editorUrl, prevUrl }
652
638
  }
653
639
  get_shell_id(name, i, rendered) {
654
- let hash = crypto.createHash('md5').update(JSON.stringify(rendered)).digest('hex')
655
640
  let shell_id
656
641
  if (rendered.id) {
657
642
  shell_id = encodeURIComponent(`${name}_${rendered.id}`)
658
643
  } else {
659
- shell_id = encodeURIComponent(`${name}_${i}_session_${hash}`)
644
+ let hash = crypto.createHash('md5').update(JSON.stringify(rendered)).digest('hex')
645
+ //shell_id = encodeURIComponent(`${name}_${i}_session_${hash}`)
646
+ shell_id = encodeURIComponent(`${name}_session_${hash}`)
660
647
  }
661
648
  return shell_id
662
649
  }
@@ -728,50 +715,11 @@ class Server {
728
715
 
729
716
  }
730
717
 
731
- // if (pathComponents.length > 1) {
732
- // if (pathComponents[1] === 'web') {
733
- // let filepath = this.kernel.path("api", ...pathComponents)
734
- // console.log("filepath", filepath)
735
- // try {
736
- // console.log("testing")
737
- // let stat = await fs.promises.stat(filepath)
738
- // console.log("stat", stat)
739
- // // if it's a folder
740
- // if (stat.isDirectory()) {
741
- // // if the current folder has "index.html", send that file
742
- // // otherwise 404
743
- // let indexFile = path.resolve(filepath, "index.html")
744
- // let exists = await this.exists(indexFile)
745
- // if (exists) {
746
- // res.sendFile(indexFile)
747
- // } else {
748
- // // res.redirect("/api/" + pathComponents[0])
749
- // res.status(404).render("404", {
750
- // message: "index.html not found"
751
- // })
752
- // }
753
- // } else if (stat.isFile()) {
754
- // res.sendFile(filepath)
755
- // }
756
- // return
757
- // } catch (e) {
758
- // console.log("E", e)
759
- // res.redirect("/api/" + pathComponents[0])
760
- // return
761
- // /*
762
- // res.status(404).render("404", {
763
- // message: e.message
764
- // })
765
- // */
766
- // }
767
- // }
768
- // }
769
-
770
718
  if (path.basename(filepath) === "ENVIRONMENT") {
771
719
  // if environment.json doesn't exist,
772
720
  let exists = await this.exists(filepath)
773
721
  if (!exists) {
774
- let content = await Environment.ENV("app", this.kernel.homedir)
722
+ let content = await Environment.ENV("app", this.kernel.homedir, this.kernel)
775
723
  await fs.promises.writeFile(filepath, content)
776
724
  }
777
725
  }
@@ -816,7 +764,7 @@ class Server {
816
764
  } else if (pathComponents.length === 0 && req.query.mode === "settings") {
817
765
  let system_env = {}
818
766
  if (this.kernel.homedir) {
819
- system_env = await Environment.get(this.kernel.homedir)
767
+ system_env = await Environment.get(this.kernel.homedir, this.kernel)
820
768
  }
821
769
  let configArray = [{
822
770
  key: "home",
@@ -837,7 +785,7 @@ class Server {
837
785
  }, {
838
786
  key: "mode",
839
787
  val: this.mode,
840
- options: ["full", "minimal"]
788
+ options: ["desktop", "background"]
841
789
  }, {
842
790
  key: "HTTP_PROXY",
843
791
  val: (system_env.HTTP_PROXY || ""),
@@ -862,7 +810,10 @@ class Server {
862
810
  drive: path.resolve(this.kernel.homedir, "drive"),
863
811
  }
864
812
  }
813
+ let list = this.getPeers()
865
814
  res.render("settings", {
815
+ list,
816
+ current_host: this.kernel.peer.host,
866
817
  platform,
867
818
  version: this.version,
868
819
  logo: this.logo,
@@ -1068,23 +1019,23 @@ class Server {
1068
1019
  let env_requirements = await Environment.requirements(resolved, cwd, this.kernel)
1069
1020
  if (env_requirements.requires_instantiation) {
1070
1021
  //let p = Util.api_path(filepath, this.kernel)
1071
- let p = Util.api_path(cwd, this.kernel)
1022
+ let api_path = Util.api_path(cwd, this.kernel)
1023
+ let root = await Environment.get_root({ path: api_path }, this.kernel)
1024
+ let root_path = root.root
1072
1025
  let platform = os.platform()
1073
1026
  if (platform === "win32") {
1074
- p = p.replace(/\\/g, '\\\\')
1027
+ root_path = root_path.replace(/\\/g, '\\\\')
1075
1028
  }
1076
- console.log({ p, cwd })
1029
+ console.log({ cwd, api_path, root, root_path })
1077
1030
  res.render("required_env_editor", {
1078
1031
  portal: this.portal,
1079
1032
  agent: this.agent,
1080
1033
  theme: this.theme,
1081
1034
  filename,
1082
- filepath: p,
1035
+ filepath: root_path,
1083
1036
  items: env_requirements.items
1084
1037
  })
1085
1038
  } else {
1086
- console.log("req.query.callback", req.query.callback)
1087
-
1088
1039
  // check if it's a prototype script
1089
1040
  let kill_message
1090
1041
  let callback
@@ -1162,7 +1113,6 @@ class Server {
1162
1113
  })
1163
1114
  }
1164
1115
  } else if (stat.isDirectory()) {
1165
-
1166
1116
  if (req.query && req.query.mode === "browser") {
1167
1117
  return
1168
1118
  }
@@ -1185,12 +1135,11 @@ class Server {
1185
1135
  }
1186
1136
 
1187
1137
  for(let file of files) {
1188
- if (!file.name.startsWith(".")) {
1189
- if (file.isDirectory()) {
1190
- f.folders.push(file)
1191
- } else {
1192
- f.files.push(file)
1193
- }
1138
+ let type = await Util.file_type(filepath, file)
1139
+ if (type.directory) {
1140
+ f.folders.push(file)
1141
+ } else {
1142
+ f.files.push(file)
1194
1143
  }
1195
1144
  }
1196
1145
 
@@ -1210,8 +1159,6 @@ class Server {
1210
1159
  let p = path.resolve(filepath, file.name)
1211
1160
  config = (await this.kernel.loader.load(p)).resolved
1212
1161
 
1213
-
1214
-
1215
1162
  if (config && config.menu) {
1216
1163
  if (typeof config.menu === "function") {
1217
1164
  if (config.menu.constructor.name === "AsyncFunction") {
@@ -1272,8 +1219,13 @@ class Server {
1272
1219
 
1273
1220
 
1274
1221
  // let folder = pathComponents[pathComponents.length - 1]
1275
-
1276
- items = f.folders.concat(f.files)
1222
+ if (meta) {
1223
+ // home => only show the folders
1224
+ items = f.folders
1225
+ } else {
1226
+ // app view file explorer => show all files and folders
1227
+ items = f.folders.concat(f.files)
1228
+ }
1277
1229
  // }
1278
1230
  let display = pathComponents.length === 0 ? ["form", "explore"] : []
1279
1231
  //let display = ["form"]
@@ -1329,9 +1281,16 @@ class Server {
1329
1281
  let index = 0
1330
1282
  for(let i=0; i<items.length; i++) {
1331
1283
  let item = items[i]
1332
- let p = path.resolve(uri, item.name, "pinokio.js")
1333
- let config = (await this.kernel.loader.load(p)).resolved
1284
+ let launcher = await this.kernel.api.launcher(item.name)
1285
+ let config = launcher.script
1286
+ await this.kernel.dns({
1287
+ name: item.name,
1288
+ config
1289
+ })
1290
+
1291
+
1334
1292
  if (config) {
1293
+
1335
1294
  if (config.shortcuts) {
1336
1295
  if (typeof config.shortcuts === "function") {
1337
1296
  if (config.shortcuts.constructor.name === "AsyncFunction") {
@@ -1414,8 +1373,9 @@ class Server {
1414
1373
  // other global scripts
1415
1374
  let chunks = key.split("?")
1416
1375
  let dev = chunks[0]
1417
- let name_chunks = dev.split(path.sep)
1418
- let name = name_chunks[name_chunks.length-1]
1376
+ let relpath = path.relative(this.kernel.homedir, dev)
1377
+ let name_chunks = relpath.split(path.sep)
1378
+ let name = "/" + relpath
1419
1379
  items[i].running_scripts.push({ id: key, name })
1420
1380
  }
1421
1381
  } else {
@@ -1603,6 +1563,7 @@ class Server {
1603
1563
  items
1604
1564
  })
1605
1565
  } else {
1566
+ console.log("RENDER FILE EXPLORER")
1606
1567
  res.render("file_explorer", {
1607
1568
  docs: this.docs,
1608
1569
  portal: this.portal,
@@ -1819,9 +1780,10 @@ class Server {
1819
1780
  //// icon: "fa-solid fa-gear"
1820
1781
  // }].concat(config.menu)
1821
1782
 
1783
+ let launcher_root = req.launcher_root || ""
1784
+
1822
1785
  for(let i=0; i<config.menu.length; i++) {
1823
1786
  let menuitem = config.menu[i]
1824
-
1825
1787
  if (menuitem.menu) {
1826
1788
  let newIndexPath
1827
1789
  if (indexPath) {
@@ -1841,10 +1803,10 @@ class Server {
1841
1803
  // href resolution
1842
1804
  if (menuitem.fs) {
1843
1805
  // file explorer
1844
- config.menu[i].href = path.resolve(this.kernel.homedir, "api", name, menuitem.href)
1806
+ config.menu[i].href = path.resolve(this.kernel.homedir, "api", name, launcher_root, menuitem.href)
1845
1807
  } else if (menuitem.command) {
1846
1808
  // file explorer
1847
- config.menu[i].href = path.resolve(this.kernel.homedir, "api", name, menuitem.href)
1809
+ config.menu[i].href = path.resolve(this.kernel.homedir, "api", name, launcher_root, menuitem.href)
1848
1810
  } else {
1849
1811
  if (menuitem.href.startsWith("/")) {
1850
1812
  config.menu[i].href = menuitem.href
@@ -1853,7 +1815,11 @@ class Server {
1853
1815
  let seed = path.resolve(__dirname)
1854
1816
  let p = absolute.replace(seed, "")
1855
1817
  let link = p.split(/[\/\\]/).filter((x) => { return x }).join("/")
1856
- config.menu[i].href = "/api/" + name + "/" + link
1818
+ if (launcher_root) {
1819
+ config.menu[i].href = "/api/" + name + "/" + launcher_root + "/" + link
1820
+ } else {
1821
+ config.menu[i].href = "/api/" + name + "/" + link
1822
+ }
1857
1823
  }
1858
1824
  }
1859
1825
  } else if (menuitem.run) {
@@ -1862,12 +1828,21 @@ class Server {
1862
1828
  if (typeof rendered.run === "object") {
1863
1829
  let run = rendered.run
1864
1830
  config.menu[i].run = run.message
1865
- config.menu[i].cwd = run.path ? path.resolve(this.kernel.homedir, "api", name, run.path) : path.resolve(this.kernel.homedir, "api", name)
1866
- config.menu[i].href = "/api/" + name
1831
+ if (launcher_root) {
1832
+ config.menu[i].cwd = run.path ? path.resolve(this.kernel.homedir, "api", name, launcher_root, run.path) : path.resolve(this.kernel.homedir, "api", name, launcher_root)
1833
+ config.menu[i].href = "/api/" + name + "/" + launcher_root
1834
+ } else {
1835
+ config.menu[i].cwd = run.path ? path.resolve(this.kernel.homedir, "api", name, run.path) : path.resolve(this.kernel.homedir, "api", name)
1836
+ config.menu[i].href = "/api/" + name
1837
+ }
1867
1838
  } else {
1868
1839
  config.menu[i].run = rendered.run
1869
- config.menu[i].cwd = path.resolve(this.kernel.homedir, "api", name)
1870
- config.menu[i].href = "/api/" + name
1840
+ config.menu[i].cwd = path.resolve(this.kernel.homedir, "api", launcher_root, name)
1841
+ if (launcher_root) {
1842
+ config.menu[i].href = "/api/" + name + "/" + launcher_root
1843
+ } else {
1844
+ config.menu[i].href = "/api/" + name
1845
+ }
1871
1846
  }
1872
1847
  }
1873
1848
  }
@@ -1878,8 +1853,13 @@ class Server {
1878
1853
 
1879
1854
 
1880
1855
  if (menuitem.shell) {
1881
- let basePath = this.kernel.path("api", name)
1882
- this.renderShell(basePath, indexPath, i, menuitem)
1856
+ if (launcher_root) {
1857
+ let basePath = this.kernel.path("api", name, launcher_root)
1858
+ this.renderShell(basePath, indexPath, i, menuitem)
1859
+ } else {
1860
+ let basePath = this.kernel.path("api", name)
1861
+ this.renderShell(basePath, indexPath, i, menuitem)
1862
+ }
1883
1863
  }
1884
1864
 
1885
1865
  if (menuitem.href) {
@@ -1917,7 +1897,12 @@ class Server {
1917
1897
  }
1918
1898
  } else {
1919
1899
  // prototype script
1920
- let api_path = this.kernel.path("api", name)
1900
+ let api_path
1901
+ if (launcher_root) {
1902
+ api_path = this.kernel.path("api", name, launcher_root)
1903
+ } else {
1904
+ api_path = this.kernel.path("api", name)
1905
+ }
1921
1906
  let id = `${fullpath}?cwd=${api_path}`
1922
1907
  if (this.kernel.api.running[id]) {
1923
1908
  menuitem.running = true
@@ -1934,7 +1919,6 @@ class Server {
1934
1919
  let p = absolute.replace(seed, "")
1935
1920
  let link = p.split(/[\/\\]/).filter((x) => { return x }).join("/")
1936
1921
  let uri = "~/api/" + name + "/" + link
1937
-
1938
1922
  config.menu[i].action.uri = uri
1939
1923
  }
1940
1924
  }
@@ -1999,7 +1983,12 @@ class Server {
1999
1983
  if (menuitem.image.startsWith("/")) {
2000
1984
  imagePath = menuitem.image
2001
1985
  } else {
2002
- imagePath = `/api/${name}/${menuitem.image}?raw=true`
1986
+ console.log({ launcher_root, name, image: menuitem.image })
1987
+ if (launcher_root) {
1988
+ imagePath = `/api/${name}/${launcher_root}/${menuitem.image}?raw=true`
1989
+ } else {
1990
+ imagePath = `/api/${name}/${menuitem.image}?raw=true`
1991
+ }
2003
1992
  }
2004
1993
  menuitem.html = `<img class='menu-item-image' src='${imagePath}' /> ${menuitem.text}`
2005
1994
  } else if (menuitem.hasOwnProperty("icon")) {
@@ -2077,10 +2066,7 @@ class Server {
2077
2066
  // config.icon = "/pinokio-white.png"
2078
2067
  // }
2079
2068
  // }
2080
- console.log("############## config", JSON.stringify(config, null, 2))
2081
-
2082
2069
  config = Util.rewrite_localhost(this.kernel, config, req.$source)
2083
-
2084
2070
  return config
2085
2071
  } else {
2086
2072
  return config
@@ -2260,12 +2246,12 @@ class Server {
2260
2246
 
2261
2247
  // 1. THEME
2262
2248
  this.theme = this.kernel.store.get("theme") || "light"
2263
- this.mode = this.kernel.store.get("mode") || "full"
2249
+ this.mode = this.kernel.store.get("mode") || "desktop"
2264
2250
 
2265
2251
  // when loaded in electron but in minimal mode,
2266
2252
  // the app is loaded in the web so the agent should be "web"
2267
2253
  if (this.agent === "electron") {
2268
- if (this.mode === "minimal") {
2254
+ if (this.mode === "minimal" || this.mode === "background") {
2269
2255
  this.agent = "web"
2270
2256
  }
2271
2257
  }
@@ -2504,17 +2490,70 @@ class Server {
2504
2490
  return true
2505
2491
  }
2506
2492
  }
2507
- async getPluginGlobal(req, filepath) {
2493
+ async terminals(filepath) {
2494
+ let venvs = await Util.find_venv(filepath)
2495
+ let terminal
2496
+ if (venvs.length > 0) {
2497
+ let terminals = []
2498
+ try {
2499
+ for(let i=0; i<venvs.length; i++) {
2500
+ let venv = venvs[i]
2501
+ let parsed = path.parse(venv)
2502
+ terminals.push(this.renderShell(filepath, i, 0, {
2503
+ icon: "fa-brands fa-python",
2504
+ title: "Python virtual environment",
2505
+ subtitle: this.kernel.path("api", parsed.name),
2506
+ text: `[venv] ${parsed.name}`,
2507
+ type: "Start",
2508
+ shell: {
2509
+ venv: venv,
2510
+ input: true,
2511
+ }
2512
+ }))
2513
+ }
2514
+ } catch (e) {
2515
+ console.log(e)
2516
+ }
2517
+ terminal = {
2518
+ icon: "fa-solid fa-terminal",
2519
+ title: "Web Terminal",
2520
+ subtitle: "Open the terminal in the browser",
2521
+ menu: terminals
2522
+ }
2523
+ } else {
2524
+ terminal = {
2525
+ icon: "fa-solid fa-terminal",
2526
+ title: "Web terminal",
2527
+ subtitle: "Work with the terminal directly in the browser",
2528
+ menu: [this.renderShell(filepath, 0, 0, {
2529
+ icon: "fa-solid fa-terminal",
2530
+ title: "Terminal",
2531
+ subtitle: filepath,
2532
+ text: `Terminal`,
2533
+ type: "Start",
2534
+ shell: {
2535
+ input: true
2536
+ }
2537
+ })]
2538
+ }
2539
+ }
2540
+ return terminal
2541
+ }
2542
+ async getPluginGlobal(req, config, terminal, filepath) {
2508
2543
  // if (!this.kernel.plugin.config) {
2509
2544
  // await this.kernel.plugin.init()
2510
2545
  // }
2511
- if (this.kernel.plugin.config) {
2546
+ if (config) {
2547
+
2548
+ let c = structuredClone(config)
2549
+ let menu = structuredClone(terminal.menu)
2550
+ c.menu = c.menu.concat(menu)
2512
2551
  try {
2513
2552
  let info = new Info(this.kernel)
2514
2553
  info.cwd = () => {
2515
2554
  return filepath
2516
2555
  }
2517
- let menu = this.kernel.plugin.config.menu.map((item) => {
2556
+ let menu = c.menu.map((item) => {
2518
2557
  return {
2519
2558
  params: {
2520
2559
  cwd: filepath
@@ -2547,34 +2586,26 @@ class Server {
2547
2586
  return null
2548
2587
  }
2549
2588
  }
2550
- async getPlugin(req, name) {
2551
- if (this.kernel.plugin.config) {
2589
+ async getPlugin(req, config, name) {
2590
+ if (config) {
2591
+ let c = structuredClone(config)
2552
2592
  try {
2553
- if (this.kernel.plugin.cache[name]) {
2554
- let cached = this.kernel.plugin.cache[name]
2555
- return cached
2556
- } else {
2557
- // let info = new Info(this.kernel)
2558
- // info.caller = () => {
2559
- // return this.kernel.path("api", name, "pinokio.js")
2560
- // }
2561
- // let menu = await this.kernel.plugin.config.menu(this.kernel, info)
2562
2593
 
2563
- let menu = this.kernel.plugin.config.menu.map((item) => {
2564
- return {
2565
- params: {
2566
- //cwd: this.kernel.path("api", name, "pinokio.js")
2567
- cwd: this.kernel.path("api", name)
2568
- },
2569
- ...item
2570
- }
2571
- })
2572
- let plugin = { menu }
2573
- let uri = this.kernel.path("api")
2574
- await this.renderMenu(req, uri, name, plugin, [])
2575
- this.kernel.plugin.cache[name] = plugin
2576
- return plugin
2577
- }
2594
+ let filepath = this.kernel.path("api", name)
2595
+ let terminal = await this.terminals(filepath)
2596
+ c.menu = c.menu.concat(terminal.menu)
2597
+ let menu = c.menu.map((item) => {
2598
+ return {
2599
+ params: {
2600
+ cwd: filepath,
2601
+ },
2602
+ ...item
2603
+ }
2604
+ })
2605
+ let plugin = { menu }
2606
+ let uri = this.kernel.path("api")
2607
+ await this.renderMenu(req, uri, name, plugin, [])
2608
+ return plugin
2578
2609
  } catch (e) {
2579
2610
  console.log("getPlugin ERROR", e)
2580
2611
  return null
@@ -2767,7 +2798,7 @@ class Server {
2767
2798
  if (this.kernel.homedir) {
2768
2799
  let ex = await this.kernel.exists(this.kernel.homedir, "ENVIRONMENT")
2769
2800
  if (!ex) {
2770
- let str = await Environment.ENV("system", this.kernel.homedir)
2801
+ let str = await Environment.ENV("system", this.kernel.homedir, this.kernel)
2771
2802
  await fs.promises.writeFile(path.resolve(this.kernel.homedir, "ENVIRONMENT"), str)
2772
2803
  }
2773
2804
  }
@@ -2869,11 +2900,45 @@ class Server {
2869
2900
  this.app.use(express.static(path.resolve(__dirname, 'public')));
2870
2901
  this.app.use("/web", express.static(path.resolve(__dirname, "..", "..", "web")))
2871
2902
  this.app.set('view engine', 'ejs');
2903
+ this.app.use((req, res, next) => {
2904
+ let protocol = req.get('X-Forwarded-Proto') || "http"
2905
+ req.$source = {
2906
+ protocol,
2907
+ host: req.get("host")
2908
+ }
2909
+ next()
2910
+ })
2872
2911
  if (this.kernel.homedir) {
2873
2912
  this.app.set("views", [
2874
2913
  this.kernel.path("web/views"),
2875
2914
  path.resolve(__dirname, "views")
2876
2915
  ])
2916
+ let serve = express.static(this.kernel.homedir, { fallthrough: true })
2917
+ let http_serve = express.static(this.kernel.homedir, {
2918
+ redirect: true,
2919
+ })
2920
+ let https_serve = express.static(this.kernel.homedir, {
2921
+ redirect: false,
2922
+ })
2923
+ this.app.use('/asset', serve, serveIndex(this.kernel.homedir, {'icons': true}))
2924
+ this.app.use('/asset', (req, res, next) => {
2925
+ if (req.path.match(/\.(png|jpg|jpeg|gif|ico|svg)$/)) {
2926
+ res.sendFile(path.resolve(__dirname, 'public', 'pinokio-black.png'));
2927
+ } else {
2928
+ next();
2929
+ }
2930
+ });
2931
+ this.app.use("/asset", async (req, res, next) => {
2932
+ let asset_path = this.kernel.path(req.path.slice(1), "index.html")
2933
+ let exists = await this.exists(asset_path)
2934
+ if (exists) {
2935
+ return res.sendFile(asset_path)
2936
+ } else {
2937
+ let chunks = req.path.slice(1).split("/")
2938
+ let parent_path = chunks.slice(0, -1).join("/")
2939
+ res.redirect("/asset/" + parent_path)
2940
+ }
2941
+ })
2877
2942
  } else {
2878
2943
  this.app.set("views", [
2879
2944
  path.resolve(__dirname, "views")
@@ -2891,16 +2956,106 @@ class Server {
2891
2956
  };
2892
2957
  next();
2893
2958
  });
2894
- this.app.use((req, res, next) => {
2895
- let protocol = req.get('X-Forwarded-Proto') || "http"
2896
- req.$source = {
2897
- protocol,
2898
- host: req.get("host")
2959
+ /*
2960
+ this.app.get("/asset/*", ex((req, res) => {
2961
+ let pathComponents = req.params[0].split("/")
2962
+ let filepath = this.kernel.path(...pathComponents)
2963
+ console.log("req.originalUrl", req.originalUrl)
2964
+ console.log("pathComponents", pathComponents)
2965
+ // if (pathComponents.length === 2 && pathComponents[0] === "api") {
2966
+ // // ex: /asset/api/comfy.git
2967
+ // filepath = path.resolve(filepath, "index.html")
2968
+ // }
2969
+ try {
2970
+ if (req.query.frame) {
2971
+ let m = mime.lookup(filepath)
2972
+ res.type("text/plain")
2973
+ }
2974
+ //res.setHeader('Content-Disposition', 'inline');
2975
+ res.sendFile(filepath)
2976
+ } catch (e) {
2977
+ res.status(404).send(e.message);
2899
2978
  }
2900
- next()
2901
- })
2979
+ }))
2980
+ */
2981
+ this.app.get("/tools", ex(async (req, res) => {
2982
+ let list = this.getPeers()
2983
+ let installs = []
2984
+ for(let key in this.kernel.bin.installed) {
2985
+ let installed = this.kernel.bin.installed[key]
2986
+ let modules = Array.from(installed)
2987
+ if (modules.length > 0) {
2988
+ installs.push({
2989
+ package_manager: key,
2990
+ modules,
2991
+ })
2992
+ }
2993
+ }
2994
+ // add minimal
2995
+ const bundle_names = ["dev", "advanced_dev", "ai", "network"]
2996
+ let bundles = []
2997
+ let pending
2998
+ for(let bundle_name of bundle_names) {
2999
+ let result = await this.kernel.bin.check({
3000
+ bin: this.kernel.bin.preset(bundle_name)
3001
+ })
3002
+ if (result.requirements_pending) {
3003
+ pending = true
3004
+ }
3005
+ bundles.push({
3006
+ name: bundle_name,
3007
+ setup: "/setup/" + bundle_name + "?callback=/tools",
3008
+ ...result
3009
+ })
3010
+ }
3011
+ console.log(JSON.stringify(bundles, null, 2))
3012
+ res.render("tools", {
3013
+ pending,
3014
+ installs,
3015
+ bundles,
3016
+ version: this.version,
3017
+ portal: this.portal,
3018
+ logo: this.logo,
3019
+ theme: this.theme,
3020
+ agent: this.agent,
3021
+ list,
3022
+ })
3023
+ }))
3024
+ this.app.get("/screenshots", ex(async (req, res) => {
3025
+ let list = this.getPeers()
3026
+ res.render("screenshots", {
3027
+ version: this.version,
3028
+ portal: this.portal,
3029
+ logo: this.logo,
3030
+ theme: this.theme,
3031
+ agent: this.agent,
3032
+ list,
3033
+ })
3034
+ }))
3035
+ this.app.get("/columns", ex(async (req, res) => {
3036
+ res.render("columns", {
3037
+ theme: this.theme,
3038
+ agent: this.agent,
3039
+ src: req.get('Referrer')
3040
+ })
3041
+ }))
3042
+ this.app.get("/rows", ex(async (req, res) => {
3043
+ res.render("rows", {
3044
+ theme: this.theme,
3045
+ agent: this.agent,
3046
+ src: req.get('Referrer')
3047
+ })
3048
+ }))
2902
3049
 
2903
3050
 
3051
+ this.app.get("/container", ex(async (req, res) => {
3052
+ res.render("container", {
3053
+ theme: this.theme,
3054
+ agent: this.agent,
3055
+ src: req.query.url
3056
+ })
3057
+ }))
3058
+
2904
3059
  //let home = this.kernel.homedir
2905
3060
  //let home = this.kernel.store.get("home")
2906
3061
  this.app.get("/launch", ex(async (req, res) => {
@@ -2915,7 +3070,7 @@ class Server {
2915
3070
  let url = req.query.url
2916
3071
  let u = new URL(url)
2917
3072
  let host = u.host
2918
- let env = await Environment.get(this.kernel.homedir)
3073
+ let env = await Environment.get(this.kernel.homedir, this.kernel)
2919
3074
  let autolaunch = false
2920
3075
  if (env && env.PINOKIO_ONDEMAND_AUTOLAUNCH === "1") {
2921
3076
  autolaunch = true
@@ -2932,18 +3087,17 @@ class Server {
2932
3087
  // otherwise => redirect
2933
3088
 
2934
3089
 
2935
- if (chunks.length > 2) {
3090
+ if (chunks.length >= 2) {
2936
3091
 
2937
3092
  let apipath = this.kernel.path("api")
2938
3093
  let files = await fs.promises.readdir(apipath, { withFileTypes: true })
2939
- let folders = files.filter((f) => {
2940
- return f.isDirectory()
2941
- }).map((x) => {
2942
- return x.name
2943
- })
2944
-
2945
- console.log({ folders })
2946
-
3094
+ let folders = []
3095
+ for(let file of files) {
3096
+ let type = await Util.file_type(apipath, file)
3097
+ if (type.directory) {
3098
+ folders.push(file.name)
3099
+ }
3100
+ }
2947
3101
 
2948
3102
  let matched = false
2949
3103
  for(let folder of folders) {
@@ -2988,7 +3142,15 @@ class Server {
2988
3142
  let exists = await this.exists(api_path)
2989
3143
  if (exists) {
2990
3144
  let meta = await this.kernel.api.meta(name)
3145
+ let launcher = await this.kernel.api.launcher(name)
3146
+ let pinokio = launcher.script
3147
+ let launchable = false
3148
+ if (pinokio && pinokio.menu && pinokio.menu.length > 0) {
3149
+ launchable = true
3150
+ }
2991
3151
  res.render("start", {
3152
+ url,
3153
+ launchable,
2992
3154
  autolaunch,
2993
3155
  logo: this.logo,
2994
3156
  theme: this.theme,
@@ -3001,6 +3163,8 @@ class Server {
3001
3163
  }
3002
3164
  }
3003
3165
  res.render("start", {
3166
+ url,
3167
+ launchable: false,
3004
3168
  autolaunch,
3005
3169
  logo: this.logo,
3006
3170
  theme: this.theme,
@@ -3061,7 +3225,7 @@ class Server {
3061
3225
  }
3062
3226
  let system_env = {}
3063
3227
  if (this.kernel.homedir) {
3064
- system_env = await Environment.get(this.kernel.homedir)
3228
+ system_env = await Environment.get(this.kernel.homedir, this.kernel)
3065
3229
  }
3066
3230
  let configArray = [{
3067
3231
  key: "home",
@@ -3083,7 +3247,7 @@ class Server {
3083
3247
  }, {
3084
3248
  key: "mode",
3085
3249
  val: this.mode,
3086
- options: ["full", "minimal"]
3250
+ options: ["desktop", "background"]
3087
3251
  }, {
3088
3252
  key: "HTTP_PROXY",
3089
3253
  val: (system_env.HTTP_PROXY || ""),
@@ -3109,7 +3273,10 @@ class Server {
3109
3273
  drive: path.resolve(this.kernel.homedir, "drive"),
3110
3274
  }
3111
3275
  }
3276
+ let list = this.getPeers()
3112
3277
  res.render("settings", {
3278
+ current_host: this.kernel.peer.host,
3279
+ list,
3113
3280
  platform,
3114
3281
  version: this.version,
3115
3282
  portal: this.portal,
@@ -3127,75 +3294,21 @@ class Server {
3127
3294
 
3128
3295
  let apipath = this.kernel.path("api")
3129
3296
  let files = await fs.promises.readdir(apipath, { withFileTypes: true })
3130
- let folders = files.filter((f) => {
3131
- return f.isDirectory()
3132
- }).map((x) => {
3133
- return x.name
3134
- })
3297
+
3298
+ let folders = []
3299
+ for(let file of files) {
3300
+ let type = await Util.file_type(apipath, file)
3301
+ if (type.directory) {
3302
+ folders.push(file.name)
3303
+ }
3304
+ }
3135
3305
  let meta = {}
3136
3306
  for(let folder of folders) {
3137
3307
  meta[folder] = await this.kernel.api.meta(folder)
3138
- // let p = path.resolve(apipath, folder, "pinokio.js")
3139
- // let pinokio = (await this.kernel.loader.load(p)).resolved
3140
- // let p2 = path.resolve(apipath, folder, "pinokio_meta.json")
3141
- // let pinokio2 = (await this.kernel.loader.load(p2)).resolved
3142
- //
3143
- // meta[folder] = Object.assign({}, pinokio, pinokio2)
3144
- // meta[folder].iconpath = meta[folder].icon ? path.resolve(apipath, folder, meta[folder].icon) : null
3145
- // meta[folder].icon = meta[folder].icon ? `/api/${folder}/${meta[folder].icon}?raw=true` : null
3146
- // meta[folder].path = path.resolve(apipath, folder)
3147
-
3148
-
3149
- // if (pinokio) {
3150
- // meta[folder] = {
3151
- // title: pinokio.title,
3152
- // description: pinokio.description,
3153
- // icon: pinokio.icon ? `/api/${folder}/${pinokio.icon}?raw=true` : null
3154
- // }
3155
- // }
3156
- // if (pinokio2) {
3157
- // if (pinokio2.title) meta[folder].title = pinokio2.title
3158
- // if (pinokio2.description) meta[folder].description = pinokio2.description
3159
- // if (pinokio2.icon) meta[folder].icon = pinokio2.icon
3160
- // }
3161
3308
  }
3162
3309
  await this.render(req, res, [], meta)
3163
- // if (this.kernel.bin.all_installed) {
3164
- // this.started = true
3165
- // let apipath = this.kernel.path("api")
3166
- // let files = await fs.promises.readdir(apipath, { withFileTypes: true })
3167
- // let folders = files.filter((f) => {
3168
- // return f.isDirectory()
3169
- // }).map((x) => {
3170
- // return x.name
3171
- // })
3172
- // let meta = {}
3173
- // for(let folder of folders) {
3174
- // let p = path.resolve(apipath, folder, "pinokio.js")
3175
- // let pinokio = (await this.kernel.loader.load(p)).resolved
3176
- // if (pinokio) {
3177
- // meta[folder] = {
3178
- // title: pinokio.title,
3179
- // description: pinokio.description,
3180
- // icon: pinokio.icon ? `/api/${folder}/${pinokio.icon}?raw=true` : null
3181
- // }
3182
- // }
3183
- // }
3184
- // await this.render(req, res, [], meta)
3185
- // } else {
3186
- // // get all the "start" scripts from pinokio.json
3187
- // // render installer page
3188
- // this.started = true
3189
- // let home = this.kernel.homedir ? this.kernel.homedir : path.resolve(os.homedir(), "pinokio")
3190
- // res.render("bootstrap", {
3191
- // home,
3192
- // agent: this.agent,
3193
- // })
3194
- // }
3195
3310
  }))
3196
3311
 
3197
- // this.app.get("/init/:name", ex(async (req, res) => {
3198
- // console.log("Rnder init", req.params.name)
3199
3312
  this.app.get("/init", ex(async (req, res) => {
3200
3313
  /*
3201
3314
  option 1: new vs. clone
@@ -3222,17 +3335,15 @@ class Server {
3222
3335
  return
3223
3336
  }
3224
3337
 
3225
- // console.log("this.kernel.proto.init")
3226
- // await this.kernel.proto.init()
3227
- let list = this.getPeerInfo()
3338
+ let list = this.getPeers()
3228
3339
  let ai = await this.kernel.proto.ai()
3229
- ai.push({
3340
+ ai = [{
3230
3341
  title: "Use your own AI recipe",
3231
3342
  description: "Enter your own markdown instruction for AI",
3343
+ placeholder: "(example: 'build a launcher for https://github.com/comfyanonymous/ComfyUI)",
3232
3344
  meta: {},
3233
3345
  content: ""
3234
- })
3235
- console.log("ai", ai)
3346
+ }].concat(ai)
3236
3347
  res.render("init/index", {
3237
3348
  list,
3238
3349
  ai,
@@ -3281,22 +3392,71 @@ class Server {
3281
3392
  - x
3282
3393
  */
3283
3394
  this.app.get("/connect", ex(async (req, res) => {
3395
+ let list = this.getPeers()
3396
+ let current_urls = await this.current_urls(req.originalUrl.slice(1))
3397
+ let items = [{
3398
+ image: "/pinokio-black.png",
3399
+ name: "pinokio",
3400
+ title: "pinokio.co",
3401
+ description: "Connect with pinokio.co",
3402
+ url: "/connect/pinokio"
3403
+ }, {
3404
+ icon: "fa-brands fa-square-x-twitter",
3405
+ name: "x",
3406
+ title: "x.com",
3407
+ description: "Connect with X.com",
3408
+ url: "/connect/x"
3409
+ }, {
3410
+ emoji: "🤗",
3411
+ name: "huggingface",
3412
+ title: "huggingface.co",
3413
+ description: "Connect with huggingface.co",
3414
+ url: "/connect/huggingface"
3415
+ }, {
3416
+ icon: "fa-brands fa-github",
3417
+ name: "github",
3418
+ title: "github.com",
3419
+ description: "Connect with GitHub.com",
3420
+ url: "/github"
3421
+ }]
3422
+ let github_hosts = await this.get_github_hosts()
3423
+ for(let i=0; i<items.length; i++) {
3424
+ try {
3425
+ if (items[i].name === "github") {
3426
+ if (github_hosts.length > 0) {
3427
+ items[i].profile = {
3428
+ icon: "fa-brands fa-github",
3429
+ items: [{
3430
+ key: "config",
3431
+ val: github_hosts
3432
+ }]
3433
+ }
3434
+ items[i].description = `<i class="fa-solid fa-circle-check"></i> Connected with ${items[i].title}`
3435
+ items[i].connected = true
3436
+ }
3437
+ } else {
3438
+ const config = this.kernel.connect.config[items[i].name]
3439
+ if (config) {
3440
+ let profile = await this.kernel.connect.profile(items[i].name)
3441
+ if (profile) {
3442
+ items[i].profile = profile
3443
+ items[i].description = `<i class="fa-solid fa-circle-check"></i> Connected with ${items[i].title}`
3444
+ items[i].connected = true
3445
+ }
3446
+ }
3447
+ }
3448
+ } catch (e) {
3449
+ }
3450
+ }
3284
3451
  res.render(`connect`, {
3452
+ current_urls,
3453
+ current_host: this.kernel.peer.host,
3454
+ list,
3285
3455
  portal: this.portal,
3286
3456
  logo: this.logo,
3287
3457
  theme: this.theme,
3288
3458
  agent: this.agent,
3289
- items: [{
3290
- icon: "fa-brands fa-square-x-twitter",
3291
- title: "X",
3292
- description: "Connect with X.com",
3293
- url: "/connect/x"
3294
- }, {
3295
- icon: "fa-brands fa-github",
3296
- title: "GitHub",
3297
- description: "Connect with GitHub.com",
3298
- url: "/github"
3299
- }]
3459
+ items,
3300
3460
  })
3301
3461
  }))
3302
3462
  /*
@@ -3353,17 +3513,32 @@ class Server {
3353
3513
  }
3354
3514
 
3355
3515
 
3356
- let readme = await this.kernel.connect[req.params.provider].readme()
3357
- res.render(`connect/${req.params.provider}`, {
3516
+ let readme = ""
3517
+ let id = ""
3518
+ try {
3519
+ readme = await this.kernel.connect[req.params.provider].readme()
3520
+ id = this.kernel.connect[req.params.provider].id
3521
+ } catch (e) {
3522
+ }
3523
+ //res.render(`connect/${req.params.provider}`, {
3524
+ const config = this.kernel.connect.config[req.params.provider]
3525
+ res.render(`connect/index`, {
3526
+ protocol: req.$source.protocol,
3527
+ name: req.params.provider,
3528
+ config,
3358
3529
  portal: this.portal,
3359
3530
  logo: this.logo,
3360
3531
  theme: this.theme,
3361
3532
  agent: this.agent,
3362
- id: this.kernel.connect[req.params.provider].id,
3533
+ id,
3363
3534
  readme
3364
3535
  })
3365
3536
  }))
3366
3537
 
3538
+ this.app.get("/connect/:provider/profile", ex(async (req, res) => {
3539
+ let response = await this.kernel.connect.profile(req.params.provider, req.body)
3540
+ res.send(response)
3541
+ }))
3367
3542
  /*
3368
3543
  * POST /connect/x/login => login and acquire auth token
3369
3544
  * POST /connect/x/logout => loout
@@ -3548,17 +3723,8 @@ class Server {
3548
3723
  let md = await fs.promises.readFile(path.resolve(__dirname, "..", "kernel/connect/providers/github/README.md"), "utf8")
3549
3724
  let readme = marked.parse(md)
3550
3725
 
3551
- let hosts = ""
3552
- let hosts_file = this.kernel.path("config/gh/hosts.yml")
3553
- let e = await this.exists(hosts_file)
3554
- console.log({ hosts_file, e })
3555
- if (e) {
3556
- hosts = await fs.promises.readFile(hosts_file, "utf8")
3557
- console.log( { hosts: `#${hosts}#` })
3558
- if (hosts.startsWith("{}")) {
3559
- hosts = ""
3560
- }
3561
- }
3726
+ let hosts = await this.get_github_hosts()
3727
+
3562
3728
  console.log("hosts", hosts)
3563
3729
 
3564
3730
  let items
@@ -3865,8 +4031,22 @@ class Server {
3865
4031
  agent: this.agent,
3866
4032
  })
3867
4033
  }))
4034
+ this.app.post("/plugin/update_spec", ex(async (req, res) => {
4035
+ try {
4036
+ let filepath = req.body.filepath
4037
+ let content = req.body.spec
4038
+ let spec_path = path.resolve(filepath, "SPEC.md")
4039
+ await fs.promises.writeFile(spec_path, content)
4040
+ res.json({
4041
+ success: true
4042
+ })
4043
+ } catch (e) {
4044
+ res.error({
4045
+ error: e.stack
4046
+ })
4047
+ }
4048
+ }))
3868
4049
  this.app.post("/plugin/update", ex(async (req, res) => {
3869
- console.time("/plugin/update")
3870
4050
  try {
3871
4051
  await this.kernel.exec({
3872
4052
  message: "git pull",
@@ -3874,7 +4054,6 @@ class Server {
3874
4054
  }, (e) => {
3875
4055
  console.log(e)
3876
4056
  })
3877
- console.timeEnd("/plugin/update")
3878
4057
  res.json({
3879
4058
  success: true
3880
4059
  })
@@ -3946,8 +4125,13 @@ class Server {
3946
4125
  }
3947
4126
  } catch (e) {
3948
4127
  }
4128
+ console.log("PEER INFO", JSON.stringify(this.kernel.peer.info[host], null, 2))
3949
4129
 
3950
4130
  let installed = this.kernel.peer.info[host].installed
4131
+ let serverless_mapping = this.kernel.peer.info[host].rewrite_mapping
4132
+ let serverless = Object.keys(serverless_mapping).map((name) => {
4133
+ return serverless_mapping[name]
4134
+ })
3951
4135
  let current_urls = await this.current_urls(req.originalUrl.slice(1))
3952
4136
  res.render("net", {
3953
4137
  selected_name: req.params.name,
@@ -3959,6 +4143,7 @@ class Server {
3959
4143
  theme: this.theme,
3960
4144
  processes,
3961
4145
  installed,
4146
+ serverless,
3962
4147
  error: null,
3963
4148
  list,
3964
4149
  host,
@@ -3996,11 +4181,11 @@ class Server {
3996
4181
  })
3997
4182
  }
3998
4183
 
3999
- if (peers.length === 0) {
4000
- console.log("network not yet ready")
4001
- res.redirect("/")
4002
- return
4003
- }
4184
+ // if (peers.length === 0) {
4185
+ // console.log("network not yet ready")
4186
+ // res.redirect("/")
4187
+ // return
4188
+ // }
4004
4189
 
4005
4190
 
4006
4191
  let live_proxies = this.kernel.api.proxies["/proxy"]
@@ -4050,11 +4235,13 @@ class Server {
4050
4235
  // App sharing
4051
4236
  let apipath = this.kernel.path("api")
4052
4237
  let files = await fs.promises.readdir(apipath, { withFileTypes: true })
4053
- let folders = files.filter((f) => {
4054
- return f.isDirectory()
4055
- }).map((x) => {
4056
- return x.name
4057
- })
4238
+ let folders = []
4239
+ for(let file of files) {
4240
+ let type = await Util.file_type(apipath, file)
4241
+ if (type.directory) {
4242
+ folders.push(file.name)
4243
+ }
4244
+ }
4058
4245
  let apps = []
4059
4246
  for(let folder of folders) {
4060
4247
  let meta = await this.kernel.api.meta(folder)
@@ -4066,20 +4253,25 @@ class Server {
4066
4253
 
4067
4254
 
4068
4255
  let current_urls = await this.current_urls(req.originalUrl.slice(1))
4069
- let current_peer = this.kernel.peer.info[this.kernel.peer.host]
4070
- let host = current_peer.host
4256
+ let current_peer = this.kernel.peer.info ? this.kernel.peer.info[this.kernel.peer.host] : null
4257
+ let host = null
4258
+ if (current_peer) {
4259
+ host = current_peer.host
4260
+ }
4071
4261
  let peer = current_peer
4072
4262
 
4073
4263
  let processes = []
4074
4264
  try {
4075
- processes = current_peer.router_info
4076
- for(let i=0; i<processes.length; i++) {
4077
- if (!processes[i].icon) {
4078
- if (protocol === "https") {
4079
- processes[i].icon = processes[i].https_icon
4080
- } else {
4081
- // http
4082
- processes[i].icon = processes[i].http_icon
4265
+ if (current_peer) {
4266
+ processes = current_peer.router_info
4267
+ for(let i=0; i<processes.length; i++) {
4268
+ if (!processes[i].icon) {
4269
+ if (protocol === "https") {
4270
+ processes[i].icon = processes[i].https_icon
4271
+ } else {
4272
+ // http
4273
+ processes[i].icon = processes[i].http_icon
4274
+ }
4083
4275
  }
4084
4276
  }
4085
4277
  }
@@ -4095,9 +4287,13 @@ class Server {
4095
4287
 
4096
4288
 
4097
4289
  let list = this.getPeers()
4098
- let installed = this.kernel.peer.info[host].installed
4099
- res.render("network", {
4290
+ let installed = this.kernel.peer.info && this.kernel.peer.info[host] ? this.kernel.peer.info[host].installed : []
4100
4291
 
4292
+ let static_routes = Object.keys(this.kernel.router.rewrite_mapping).map((key) => {
4293
+ return this.kernel.router.rewrite_mapping[key]
4294
+ })
4295
+ res.render("network", {
4296
+ static_routes,
4101
4297
  host,
4102
4298
  favicons,
4103
4299
  titles,
@@ -4137,78 +4333,16 @@ class Server {
4137
4333
  let str = await fs.promises.readFile(req.query.logpath, "utf8")
4138
4334
  res.send(str)
4139
4335
  }))
4140
- this.app.get("/state/:type/:name", ex(async (req,res) => {
4141
- let selected = null
4142
- try {
4143
- selected = this.selected[req.params.name][req.params.type]
4144
- } catch (e) {
4145
- }
4146
- res.json({
4147
- selected,
4148
- })
4149
- }))
4150
- this.app.post("/state", ex(async (req, res) => {
4151
- /*
4152
- req.body := {
4153
- name: <name>,
4154
- type: "browse"|"run",
4155
- method: "toggleMenu"|"select",
4156
- params: {
4157
- <url>,
4158
- }
4159
- }
4160
- */
4161
- if (req.body.method === "select") {
4162
- if (!this.selected[req.body.name]) {
4163
- this.selected[req.body.name] = {}
4164
- }
4165
- this.selected[req.body.name][req.body.type] = req.body.params.url
4166
- } else if (req.body.method === "toggleMenu") {
4167
- if (!this.menu_hidden[req.body.name]) {
4168
- this.menu_hidden[req.body.name] = {}
4169
- }
4170
- if (this.menu_hidden[req.body.name][req.body.type]) {
4171
- this.menu_hidden[req.body.name][req.body.type] = false
4172
- } else {
4173
- this.menu_hidden[req.body.name][req.body.type] = true
4174
- }
4175
- }
4176
- res.json({
4177
- success: true
4178
- })
4179
- }))
4180
4336
  this.app.post("/mkdir", ex(async (req, res) => {
4181
4337
  let folder = req.body.folder
4182
4338
  let folder_path = path.resolve(this.kernel.api.userdir, req.body.folder)
4183
4339
  try {
4184
4340
  // mkdir
4185
4341
  await fs.promises.mkdir(folder_path)
4186
-
4187
- // create basic pinokio.json
4188
-
4189
- // add default icon
4190
-
4191
4342
  let default_icon_path = path.resolve(__dirname, "public/pinokio-black.png")
4192
4343
  let icon_path = path.resolve(folder_path, "icon.png")
4193
4344
  await fs.promises.cp(default_icon_path, icon_path)
4194
-
4195
-
4196
- // write title/description to pinokio.json
4197
- // let meta_path = path.resolve(folder_path, "pinokio.json")
4198
- // let meta = {
4199
- // title: "No title",
4200
- // description: "",
4201
- // icon: "icon.png",
4202
- // plugin: {
4203
- // menu: []
4204
- // }
4205
- // }
4206
- // console.log({ folder_path, default_icon_path, icon_path, meta_path, meta })
4207
- // await fs.promises.writeFile(meta_path, JSON.stringify(meta, null, 2))
4208
-
4209
4345
  res.json({
4210
- //success: "/pinokio/browser/"+folder
4211
- //success: "/p/"+folder
4212
4346
  success: "/init/"+folder
4213
4347
  })
4214
4348
  } catch (e) {
@@ -4291,7 +4425,7 @@ class Server {
4291
4425
  */
4292
4426
  if (req.body.type) {
4293
4427
  if (req.body.type === "local") {
4294
- let env = await Environment.get(this.kernel.homedir)
4428
+ let env = await Environment.get(this.kernel.homedir, this.kernel)
4295
4429
  if (env && env.PINOKIO_SHARE_LOCAL_PORT) {
4296
4430
  let port = env.PINOKIO_SHARE_LOCAL_PORT.trim()
4297
4431
  if (port.length > 0) {
@@ -4320,244 +4454,217 @@ class Server {
4320
4454
  res.json({ error: "type must be 'local' or 'cloudflare'" })
4321
4455
  }
4322
4456
  }))
4323
- this.app.get("/prototype/run/*", ex(async (req, res) => {
4324
- let pathComponents = req.params[0].split("/").concat("pinokio.js")
4325
- let config = await this.kernel.api.meta({ path: req.query.path })
4326
- let pinokiojson_path = path.resolve(req.query.path, "pinokio.json")
4327
- let pinokiojson = await this.kernel.require(pinokiojson_path)
4328
- if (pinokiojson) {
4329
- if (pinokiojson.plugin) {
4330
- if (pinokiojson.plugin.menu) {
4331
- } else {
4332
- pinokiojson.plugin.menu = []
4333
- await fs.promises.writeFile(pinokiojson_path, JSON.stringify(pinokiojson, null, 2))
4334
- }
4335
- } else {
4336
- pinokiojson.plugin = { menu: [] }
4337
- await fs.promises.writeFile(pinokiojson_path, JSON.stringify(pinokiojson, null, 2))
4338
- }
4339
- } else {
4340
- pinokiojson = {
4341
- plugin: {
4342
- menu: []
4343
- }
4344
- }
4345
- await fs.promises.writeFile(pinokiojson_path, JSON.stringify(pinokiojson, null, 2))
4346
- }
4347
- req.base = this.kernel.path("prototype")
4348
- req.query.callback = config.ui
4349
- //req.query.callback = config.browse
4350
- req.query.cwd = req.query.path
4351
- await this.render(req, res, pathComponents, null)
4352
- }))
4353
- this.app.get("/prototype/show/*", ex(async (req, res) => {
4354
- let name = req.params[0].split("/").filter((x) => { return x }).join("/")
4355
-
4356
- // print readme
4357
-
4358
-
4359
-
4360
-
4361
- let paths = req.params[0].split("/")
4362
- let item
4363
- let config = this.kernel.proto.config
4364
- for(let key of paths) {
4365
- config = config.menu[key]
4366
- }
4367
- console.log("config.shell", config.shell)
4368
- if (config.shell) {
4369
-
4370
- let rendered = this.kernel.template.render(config.shell, {})
4371
- let params = new URLSearchParams()
4372
- if (rendered.path) params.set("path", encodeURIComponent(rendered.path))
4373
- if (rendered.message) params.set("message", encodeURIComponent(rendered.message))
4374
- if (rendered.venv) params.set("venv", encodeURIComponent(rendered.venv))
4375
- if (rendered.input) params.set("input", true)
4376
- if (rendered.callback) params.set("callback", encodeURIComponent(rendered.callback))
4377
- if (rendered.kill) params.set("kill", encodeURIComponent(rendered.kill))
4378
- if (rendered.done) params.set("done", encodeURIComponent(rendered.done))
4379
- if (rendered.env) {
4380
- for(let key in rendered.env) {
4381
- let env_key = "env." + key
4382
- params.set(env_key, rendered.env[key])
4383
- }
4384
- }
4385
- if (rendered.conda) {
4386
- for(let key in rendered.conda) {
4387
- let conda_key = "conda." + key
4388
- params.set(conda_key, rendered.conda[key])
4389
- }
4390
- }
4391
- let shell_id = Math.floor("SH_" + 1000000000000 * Math.random())
4392
- let href = "/shell/" + shell_id + "?" + params.toString()
4393
- res.redirect(href)
4394
- } else {
4395
- let run_path = "/run/prototype/system/" + config.href + "?cwd=" + req.query.path
4396
- let readme_path = this.kernel.path("prototype/system", config.readme)
4397
- let md = await fs.promises.readFile(readme_path, "utf8")
4398
- let baseUrl = "/asset/prototype/system/" + (config.readme.split("/").slice(0, -1).join("/")) + "/"
4399
- let readme = marked.parse(md, {
4400
- baseUrl
4401
- })
4402
- res.render("prototype/show", {
4403
- run_path,
4404
- portal: this.portal,
4405
- readme,
4406
- logo: this.logo,
4407
- theme: this.theme,
4408
- agent: this.agent,
4409
- kernel: this.kernel,
4410
- })
4411
- }
4412
-
4413
- }))
4414
- this.app.get("/prototype", ex(async (req, res) => {
4415
- // load meta
4457
+ // this.app.get("/prototype/run/*", ex(async (req, res) => {
4458
+ // let pathComponents = req.params[0].split("/").concat("pinokio.js")
4416
4459
  // let config = await this.kernel.api.meta({ path: req.query.path })
4417
- // let items = this.kernel.proto.items
4418
- // if (req.query.type) {
4419
- // items = this.kernel.proto.items.filter(item => item.type === req.query.type)
4460
+ // let pinokiojson_path = path.resolve(req.query.path, "pinokio.json")
4461
+ // let pinokiojson = await this.kernel.require(pinokiojson_path)
4462
+ // if (pinokiojson) {
4463
+ // if (pinokiojson.plugin) {
4464
+ // if (pinokiojson.plugin.menu) {
4465
+ // } else {
4466
+ // pinokiojson.plugin.menu = []
4467
+ // await fs.promises.writeFile(pinokiojson_path, JSON.stringify(pinokiojson, null, 2))
4468
+ // }
4469
+ // } else {
4470
+ // pinokiojson.plugin = { menu: [] }
4471
+ // await fs.promises.writeFile(pinokiojson_path, JSON.stringify(pinokiojson, null, 2))
4472
+ // }
4473
+ // } else {
4474
+ // pinokiojson = {
4475
+ // plugin: {
4476
+ // menu: []
4477
+ // }
4478
+ // }
4479
+ // await fs.promises.writeFile(pinokiojson_path, JSON.stringify(pinokiojson, null, 2))
4420
4480
  // }
4421
- let title
4422
- let description
4423
- if (req.query.type === "init") {
4424
- title = "Initialize"
4425
- description = "Select an option to intitialize the project with. This may overwrite the folder if you already have existing files"
4426
- } else if (req.query.type === "extension") {
4427
- title = "Extensions"
4428
- description = "Add extension modules to the current folder"
4429
- }
4430
-
4431
- let config = structuredClone(this.kernel.proto.config)
4432
- config = this.renderMenu2(config, {
4433
- cwd: req.query.path,
4434
- href: "/prototype/show",
4435
- path: this.kernel.path("prototype/system"),
4436
- web_path: "/asset/prototype/system"
4437
- })
4438
-
4439
- // {
4440
- // "icon": "fa-solid fa-power-off",
4441
- // "text": "Run Default",
4442
- // "href": "/api/facefusion-pinokio.git/run.js?run=true&fullscreen=true&mode=Default",
4443
- // "params": {
4444
- // "run": true,
4445
- // "fullscreen": true,
4446
- // "mode": "Default"
4447
- // },
4448
- // "src": "/api/facefusion-pinokio.git/run.js",
4449
- // "html": "<i class=\"fa-solid fa-power-off\"></i> Run Default",
4450
- // "btn": "<i class=\"fa-solid fa-power-off\"></i> Run Default",
4451
- // "target": "@/api/facefusion-pinokio.git/run.js"
4452
- // },
4481
+ // req.base = this.kernel.path("prototype")
4482
+ // req.query.callback = config.ui
4483
+ // //req.query.callback = config.browse
4484
+ // req.query.cwd = req.query.path
4485
+ // await this.render(req, res, pathComponents, null)
4486
+ // }))
4487
+ // this.app.get("/prototype/show/*", ex(async (req, res) => {
4488
+ // let name = req.params[0].split("/").filter((x) => { return x }).join("/")
4453
4489
  //
4454
- res.render("prototype/index", {
4455
- title,
4456
- description,
4457
- config,
4458
- path: req.query.path,
4459
- portal: this.portal,
4460
- // items,
4461
- logo: this.logo,
4462
- platform: this.kernel.platform,
4463
- theme: this.theme,
4464
- agent: this.agent,
4465
- kernel: this.kernel,
4466
- })
4467
- }))
4468
- this.app.post("/prototype", this.upload.any(), ex(async (req, res) => {
4469
- try {
4470
- /*
4471
- {
4472
- title,
4473
- description,
4474
- path,
4475
- id
4476
- }
4477
- */
4478
- let formData = req.body
4479
- for(let key in req.files) {
4480
- let file = req.files[key]
4481
- formData[file.fieldname] = file.buffer
4482
- }
4483
- console.log({ formData })
4484
-
4485
-
4486
- // check if the path exists. if it does, return error
4487
- let api_path = this.kernel.path("api", formData.path)
4488
- let e = await this.exists(api_path)
4489
- if (e) {
4490
- console.log("e", e)
4491
- console.log("e.message", e.message)
4492
- res.status(500).json({ error: `The path ${api_path} already exists` })
4493
- } else {
4494
- await this.createMeta(formData)
4495
-
4496
- // run
4497
-
4498
- res.json({ success: true })
4499
- }
4500
- } catch (e) {
4501
- console.log("e", e)
4502
- console.log("e.message", e.message)
4503
- res.status(500).json({ error: e.message })
4504
- }
4505
- }))
4506
- this.app.post("/new", this.upload.any(), ex(async (req, res) => {
4507
- try {
4508
- /*
4509
- {
4510
- title,
4511
- description,
4512
- path,
4513
- id
4514
- }
4515
- */
4516
- let formData = req.body
4517
- for(let key in req.files) {
4518
- let file = req.files[key]
4519
- formData[file.fieldname] = file.buffer
4520
- }
4521
- console.log({ formData })
4522
-
4523
-
4524
- // check if the path exists. if it does, return error
4525
- let api_path = this.kernel.path("api", formData.path)
4526
- let e = await this.exists(api_path)
4527
- if (e) {
4528
- console.log("e", e)
4529
- console.log("e.message", e.message)
4530
- res.status(500).json({ error: `The path ${api_path} already exists` })
4531
- } else {
4532
- await this.createMeta(formData)
4533
- res.json({ success: true })
4534
- }
4535
- } catch (e) {
4536
- console.log("e", e)
4537
- console.log("e.message", e.message)
4538
- res.status(500).json({ error: e.message })
4539
- }
4540
- }))
4490
+ // // print readme
4491
+ //
4492
+ //
4493
+ //
4494
+ //
4495
+ // let paths = req.params[0].split("/")
4496
+ // let item
4497
+ // let config = this.kernel.proto.config
4498
+ // for(let key of paths) {
4499
+ // config = config.menu[key]
4500
+ // }
4501
+ // console.log("config.shell", config.shell)
4502
+ // if (config.shell) {
4503
+ //
4504
+ // let rendered = this.kernel.template.render(config.shell, {})
4505
+ // let params = new URLSearchParams()
4506
+ // if (rendered.path) params.set("path", encodeURIComponent(rendered.path))
4507
+ // if (rendered.message) params.set("message", encodeURIComponent(rendered.message))
4508
+ // if (rendered.venv) params.set("venv", encodeURIComponent(rendered.venv))
4509
+ // if (rendered.input) params.set("input", true)
4510
+ // if (rendered.callback) params.set("callback", encodeURIComponent(rendered.callback))
4511
+ // if (rendered.kill) params.set("kill", encodeURIComponent(rendered.kill))
4512
+ // if (rendered.done) params.set("done", encodeURIComponent(rendered.done))
4513
+ // if (rendered.env) {
4514
+ // for(let key in rendered.env) {
4515
+ // let env_key = "env." + key
4516
+ // params.set(env_key, rendered.env[key])
4517
+ // }
4518
+ // }
4519
+ // if (rendered.conda) {
4520
+ // for(let key in rendered.conda) {
4521
+ // let conda_key = "conda." + key
4522
+ // params.set(conda_key, rendered.conda[key])
4523
+ // }
4524
+ // }
4525
+ // let shell_id = Math.floor("SH_" + 1000000000000 * Math.random())
4526
+ // let href = "/shell/" + shell_id + "?" + params.toString()
4527
+ // res.redirect(href)
4528
+ // } else {
4529
+ // let run_path = "/run/prototype/system/" + config.href + "?cwd=" + req.query.path
4530
+ // let readme_path = this.kernel.path("prototype/system", config.readme)
4531
+ // let md = await fs.promises.readFile(readme_path, "utf8")
4532
+ // let baseUrl = "/asset/prototype/system/" + (config.readme.split("/").slice(0, -1).join("/")) + "/"
4533
+ // let readme = marked.parse(md, {
4534
+ // baseUrl
4535
+ // })
4536
+ // res.render("prototype/show", {
4537
+ // run_path,
4538
+ // portal: this.portal,
4539
+ // readme,
4540
+ // logo: this.logo,
4541
+ // theme: this.theme,
4542
+ // agent: this.agent,
4543
+ // kernel: this.kernel,
4544
+ // })
4545
+ // }
4546
+ //
4547
+ // }))
4548
+ // this.app.get("/prototype", ex(async (req, res) => {
4549
+ // let title
4550
+ // let description
4551
+ // if (req.query.type === "init") {
4552
+ // title = "Initialize"
4553
+ // description = "Select an option to intitialize the project with. This may overwrite the folder if you already have existing files"
4554
+ // } else if (req.query.type === "extension") {
4555
+ // title = "Extensions"
4556
+ // description = "Add extension modules to the current folder"
4557
+ // }
4558
+ //
4559
+ // let config = structuredClone(this.kernel.proto.config)
4560
+ // config = this.renderMenu2(config, {
4561
+ // cwd: req.query.path,
4562
+ // href: "/prototype/show",
4563
+ // path: this.kernel.path("prototype/system"),
4564
+ // web_path: "/asset/prototype/system"
4565
+ // })
4566
+ // res.render("prototype/index", {
4567
+ // title,
4568
+ // description,
4569
+ // config,
4570
+ // path: req.query.path,
4571
+ // portal: this.portal,
4572
+ // logo: this.logo,
4573
+ // platform: this.kernel.platform,
4574
+ // theme: this.theme,
4575
+ // agent: this.agent,
4576
+ // kernel: this.kernel,
4577
+ // })
4578
+ // }))
4579
+ // this.app.post("/prototype", this.upload.any(), ex(async (req, res) => {
4580
+ // try {
4581
+ // /*
4582
+ // {
4583
+ // title,
4584
+ // description,
4585
+ // path,
4586
+ // id
4587
+ // }
4588
+ // */
4589
+ // let formData = req.body
4590
+ // for(let key in req.files) {
4591
+ // let file = req.files[key]
4592
+ // formData[file.fieldname] = file.buffer
4593
+ // }
4594
+ // console.log({ formData })
4595
+ //
4596
+ //
4597
+ // // check if the path exists. if it does, return error
4598
+ // let api_path = this.kernel.path("api", formData.path)
4599
+ // let e = await this.exists(api_path)
4600
+ // if (e) {
4601
+ // console.log("e", e)
4602
+ // console.log("e.message", e.message)
4603
+ // res.status(500).json({ error: `The path ${api_path} already exists` })
4604
+ // } else {
4605
+ // await this.kernel.api.createMeta(formData)
4606
+ //
4607
+ // // run
4608
+ //
4609
+ // res.json({ success: true })
4610
+ // }
4611
+ // } catch (e) {
4612
+ // console.log("e", e)
4613
+ // console.log("e.message", e.message)
4614
+ // res.status(500).json({ error: e.message })
4615
+ // }
4616
+ // }))
4617
+ // this.app.post("/new", this.upload.any(), ex(async (req, res) => {
4618
+ // try {
4619
+ // /*
4620
+ // {
4621
+ // title,
4622
+ // description,
4623
+ // path,
4624
+ // id
4625
+ // }
4626
+ // */
4627
+ // let formData = req.body
4628
+ // for(let key in req.files) {
4629
+ // let file = req.files[key]
4630
+ // formData[file.fieldname] = file.buffer
4631
+ // }
4632
+ // console.log({ formData })
4633
+ //
4634
+ //
4635
+ // // check if the path exists. if it does, return error
4636
+ // let api_path = this.kernel.path("api", formData.path)
4637
+ // let e = await this.exists(api_path)
4638
+ // if (e) {
4639
+ // console.log("e", e)
4640
+ // console.log("e.message", e.message)
4641
+ // res.status(500).json({ error: `The path ${api_path} already exists` })
4642
+ // } else {
4643
+ // await this.kernel.api.createMeta(formData)
4644
+ // res.json({ success: true })
4645
+ // }
4646
+ // } catch (e) {
4647
+ // console.log("e", e)
4648
+ // console.log("e.message", e.message)
4649
+ // res.status(500).json({ error: e.message })
4650
+ // }
4651
+ // }))
4541
4652
  this.app.post("/env", ex(async (req, res) => {
4542
4653
  let fullpath = path.resolve(this.kernel.homedir, req.body.filepath, "ENVIRONMENT")
4543
4654
  let updated = req.body.vals
4544
4655
  let hosts = req.body.hosts
4545
- console.log("Util.update_env", { fullpath, filepath: req.body.filepath, updated })
4546
4656
  await Util.update_env(fullpath, updated)
4547
4657
  // for all environment variables that have hosts, save the key as well
4548
4658
  // hosts := { env_key: host }
4549
4659
  for(let env in hosts) {
4550
4660
  let host = hosts[env]
4551
4661
  let val = updated[env]
4552
- console.log({ hosts, updated, host, val })
4553
4662
  await this.kernel.kv.set(host.value, val, host.index)
4554
4663
  }
4555
4664
  res.json({})
4556
4665
  }))
4557
4666
  this.app.get("/env", ex(async (req, res) => {
4558
- let env_path = path.resolve(this.kernel.homedir)
4559
- await this.init_env(env_path)
4560
-
4667
+ await Environment.init({}, this.kernel)
4561
4668
  let filepath = path.resolve(this.kernel.homedir, "ENVIRONMENT")
4562
4669
  let editorpath = "/edit/ENVIRONMENT"
4563
4670
 
@@ -4576,41 +4683,47 @@ class Server {
4576
4683
  })
4577
4684
  }))
4578
4685
  this.app.get("/env/*", ex(async (req, res) => {
4579
-
4580
4686
  let env_path = req.params[0]
4581
- // let p = path.resolve(this.kernel.homedir, env_path, "pinokio.js")
4582
- // let config = (await this.kernel.loader.load(p)).resolved
4583
4687
  let api_path
4584
4688
  if (env_path.startsWith("api/")) {
4585
4689
  api_path = env_path.slice(4)
4586
4690
  }
4587
4691
  let config = await this.kernel.api.meta(api_path)
4692
+ let env_result
4588
4693
  if (config.run) {
4589
- await this.init_env(env_path, { no_inherit: true })
4694
+ env_result = await Environment.init({
4695
+ name: api_path,
4696
+ no_inherit: true
4697
+ }, this.kernel)
4590
4698
  } else {
4591
- await this.init_env(env_path)
4699
+ env_result = await Environment.init({
4700
+ name: api_path,
4701
+ }, this.kernel)
4592
4702
  }
4593
4703
 
4594
- let pathComponents = req.params[0].split("/")
4595
- let filepath = path.resolve(this.kernel.homedir, req.params[0], "ENVIRONMENT")
4704
+
4705
+ let filepath = env_result.env_path
4706
+
4707
+ // let pathComponents = req.params[0].split("/")
4708
+ // let filepath = path.resolve(this.kernel.homedir, req.params[0], "ENVIRONMENT")
4596
4709
 
4597
4710
  let items = []
4598
4711
  let e = await this.exists(filepath)
4599
4712
  if (e) {
4600
4713
  items = await Util.parse_env_detail(filepath)
4601
4714
  }
4602
- // if (config.icon) {
4603
- // config.icon = `/${env_path}/${config.icon}?raw=true`
4604
- // } else {
4605
- // config.icon = "/pinokio-black.png"
4606
- // }
4607
4715
 
4608
4716
  let name
4609
4717
  if (env_path.startsWith("api")) {
4610
4718
  name = env_path.split("/")[1]
4611
4719
  }
4612
- let editorpath = "/edit/" + req.params[0] + "/ENVIRONMENT"
4613
4720
 
4721
+ let editorpath
4722
+ if (env_result.relpath) {
4723
+ editorpath = "/edit/" + req.params[0] + "/" + env_result.relpath + "/ENVIRONMENT"
4724
+ } else {
4725
+ editorpath = "/edit/" + req.params[0] + "/ENVIRONMENT"
4726
+ }
4614
4727
  if (config.run) {
4615
4728
  let configStr = await fs.promises.readFile(p, "utf8")
4616
4729
  res.render("task", {
@@ -4671,9 +4784,8 @@ class Server {
4671
4784
  //})
4672
4785
  }))
4673
4786
  this.app.get("/pre/api/:name", ex(async (req, res) => {
4674
- let p = path.resolve(this.kernel.homedir, "api", req.params.name, "pinokio.js")
4675
- let p2 = path.resolve(this.kernel.homedir, "api", req.params.name)
4676
- let config = (await this.kernel.loader.load(p)).resolved
4787
+ let launcher = await this.kernel.api.launcher(req.params.name)
4788
+ let config = launcher.script
4677
4789
  if (config && config.pre) {
4678
4790
  config.pre.forEach((item) => {
4679
4791
  if (item.icon) {
@@ -4685,6 +4797,7 @@ class Server {
4685
4797
  item.href = path.resolve(this.kernel.homedir, "api", req.params.name, item.href)
4686
4798
  }
4687
4799
  })
4800
+ let p2 = launcher.root
4688
4801
  let env = await Environment.get2(p2, this.kernel)
4689
4802
  res.render("pre", {
4690
4803
  name: req.params.name,
@@ -4699,8 +4812,8 @@ class Server {
4699
4812
  }
4700
4813
  }))
4701
4814
  this.app.get("/initialize/:name", ex(async (req, res) => {
4702
- let p = path.resolve(this.kernel.homedir, "api", req.params.name, "pinokio.js")
4703
- let config = (await this.kernel.loader.load(p)).resolved
4815
+ let launcher = await this.kernel.api.launcher(req.params.name)
4816
+ let config = launcher.script
4704
4817
  if (config) {
4705
4818
  // if pinokio.js exists
4706
4819
  if (config.pre && Array.isArray(config.pre)) {
@@ -4817,8 +4930,8 @@ class Server {
4817
4930
  this.app.get("/gitcommit/:ref/*", ex(async (req, res) => {
4818
4931
  // return git log
4819
4932
  let dir = this.kernel.path("api", req.params[0])
4820
- let changes = []
4821
4933
  let d = Date.now()
4934
+ let changes = []
4822
4935
  if (req.params.ref === "HEAD") {
4823
4936
  try {
4824
4937
  let statusMatrix = await git.statusMatrix({ dir, fs });
@@ -4841,6 +4954,7 @@ class Server {
4841
4954
  webpath,
4842
4955
  file: filepath,
4843
4956
  path: fullPath,
4957
+ diffpath: `/gitdiff/${req.params.ref}/${req.params[0]}/${filepath}`,
4844
4958
  status: Util.classifyChange(head, workdir, stage),
4845
4959
  });
4846
4960
  }
@@ -4900,6 +5014,7 @@ class Server {
4900
5014
  webpath,
4901
5015
  file: filepath,
4902
5016
  path: fullPath,
5017
+ diffpath: `/gitdiff/${req.params.ref}/${req.params[0]}/${filepath}`,
4903
5018
  status: type,
4904
5019
  });
4905
5020
  }
@@ -4907,7 +5022,8 @@ class Server {
4907
5022
  console.log("git diff error", err);
4908
5023
  }
4909
5024
  }
4910
- res.json({ changes })
5025
+ let git_commit_url = `/run/scripts/git/commit.json?cwd=${dir}&callback_target=parent&callback=$location.href`
5026
+ res.json({ git_commit_url, changes })
4911
5027
  }))
4912
5028
  this.app.get("/gitdiff/:ref/*", ex(async (req, res) => {
4913
5029
  let fullpath = this.kernel.path("api", req.params[0])
@@ -4987,6 +5103,10 @@ class Server {
4987
5103
  }
4988
5104
  res.json(response)
4989
5105
  }))
5106
+ this.app.get("/info/git/:ref/*", ex(async (req, res) => {
5107
+ let response = await this.getGit(req.params.ref, req.params[0])
5108
+ res.json(response)
5109
+ }))
4990
5110
  this.app.get("/git/:ref/*", ex(async (req, res) => {
4991
5111
 
4992
5112
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
@@ -4998,95 +5118,28 @@ class Server {
4998
5118
  }
4999
5119
 
5000
5120
 
5001
-
5002
- let dir = this.kernel.path("api", req.params[0])
5003
- let branches = await git.listBranches({ fs, dir });
5004
- let log = []
5005
- try {
5006
- log = await git.log({ fs, dir, depth: 50, ref: req.params.ref }); // fetch last 50 commits
5007
- } catch (e) {
5008
- console.log("Log error", e)
5009
- }
5010
-
5011
- let config = await this.kernel.git.config(dir)
5012
-
5013
- let hosts = ""
5014
- let hosts_file = this.kernel.path("config/gh/hosts.yml")
5015
- let e = await this.exists(hosts_file)
5016
- if (e) {
5017
- hosts = await fs.promises.readFile(hosts_file, "utf8")
5018
- if (hosts.startsWith("{}")) {
5019
- hosts = ""
5020
- }
5021
- }
5022
- let connected = (hosts.length > 0)
5023
- let remote = null
5024
- if (config["remote \"origin\""]) {
5025
- remote = config["remote \"origin\""].url
5026
- }
5027
-
5028
- let branch = await git.currentBranch({ fs, dir, fullname: false });
5029
-
5030
- const remote2 = await git.getConfig({
5031
- fs,
5032
- dir,
5033
- path: `branch.${branch}.remote`
5034
- });
5035
-
5036
- // if current branch exitss => currengt branch is selected
5037
- // if current branch does not exist => get logs[0].oid
5038
- if (branch) {
5039
- branches = branches.map((b) => {
5040
- if (b === branch) {
5041
- return {
5042
- branch: b,
5043
- selected: true
5044
- }
5045
- } else {
5046
- return {
5047
- branch: b,
5048
- selected: false
5049
- }
5050
- }
5051
- })
5052
- } else {
5053
- branches.push(log[0].oid)
5054
- branches = branches.map((b) => {
5055
- if (b === log[0].oid) {
5056
- return {
5057
- branch: b,
5058
- selected: true
5059
- }
5060
- } else {
5061
- return {
5062
- branch: b,
5063
- selected: false
5064
- }
5065
- }
5066
- })
5067
- }
5121
+ let { ref, config, remote, connected, log, branch, branches, dir } = await this.getGit(req.params.ref, req.params[0])
5068
5122
 
5069
5123
  res.render("git", {
5124
+ config,
5125
+ remote,
5126
+ connected,
5127
+ log,
5070
5128
  branch,
5071
5129
  branches,
5072
- ref: req.params.ref,
5130
+ ref,
5073
5131
  path: req.params[0],
5074
- log,
5075
- connected,
5076
5132
  // changes,
5077
5133
  dir,
5078
- config,
5079
- remote,
5080
5134
  theme: this.theme,
5081
5135
  platform: this.kernel.platform,
5082
5136
  agent: this.agent,
5083
5137
  })
5084
5138
  }))
5085
5139
  this.app.get("/d/*", ex(async (req, res) => {
5086
- console.log("> 1")
5087
5140
  let filepath = Util.u2p(req.params[0])
5088
- let plugin = await this.getPluginGlobal(req, filepath)
5089
- console.log("> 2")
5141
+ let terminal = await this.terminals(filepath)
5142
+ let plugin = await this.getPluginGlobal(req, this.kernel.plugin.config, terminal, filepath)
5090
5143
  let html = ""
5091
5144
  let plugin_menu
5092
5145
  try {
@@ -5096,105 +5149,74 @@ class Server {
5096
5149
  plugin_menu = []
5097
5150
  }
5098
5151
  let current_urls = await this.current_urls(req.originalUrl.slice(1))
5099
- console.log("> 3")
5100
5152
  let retry = false
5101
5153
  // if plugin_menu is empty, try again in 1 sec
5102
5154
  if (plugin_menu.length === 0) {
5103
5155
  retry = true
5104
5156
  }
5105
- let venvs = await Util.find_venv(filepath)
5106
- console.log("> 4")
5107
- let terminal
5108
- if (venvs.length > 0) {
5109
- let terminals = []
5110
- try {
5111
- for(let i=0; i<venvs.length; i++) {
5112
- let venv = venvs[i]
5113
- let parsed = path.parse(venv)
5114
- terminals.push(this.renderShell(filepath, i, 0, {
5115
- icon: "fa-brands fa-python",
5116
- title: "Python virtual environment",
5117
- subtitle: this.kernel.path("api", parsed.name),
5118
- type: "Start",
5119
- shell: {
5120
- venv: venv,
5121
- input: true,
5122
- }
5123
- }))
5124
- }
5125
- } catch (e) {
5126
- console.log(e)
5127
- }
5128
- terminal = {
5129
- icon: "fa-solid fa-terminal",
5130
- title: "Open web terminal",
5131
- subtitle: "Open the terminal in the browser",
5132
- menu: terminals
5133
- }
5134
- } else {
5135
- terminal = {
5136
- icon: "fa-solid fa-terminal",
5137
- title: "Open web terminal",
5138
- subtitle: "Work with the terminal directly in the browser",
5139
- menu: [this.renderShell(filepath, 0, 0, {
5140
- icon: "fa-solid fa-terminal",
5141
- title: "Terminal",
5142
- subtitle: filepath,
5143
- type: "Start",
5144
- shell: {
5145
- input: true
5146
- }
5147
- })]
5148
- }
5149
- }
5150
- console.log("> 5")
5151
5157
 
5152
5158
  let exec_menus = []
5153
5159
  let shell_menus = []
5160
+ let href_menus = []
5154
5161
  if (plugin_menu.length > 0) {
5155
5162
  for(let item of plugin_menu) {
5156
5163
  // if shell.run method exists
5157
5164
  // if exec method exists
5158
5165
  let mode
5159
- for(let step of item.run) {
5160
- if (step.method === "exec") {
5161
- mode = "exec"
5162
- break
5166
+ if (item.run) {
5167
+ for(let step of item.run) {
5168
+ if (step.method === "exec") {
5169
+ mode = "exec"
5170
+ break
5171
+ }
5172
+ if (step.method === "shell.run") {
5173
+ mode = "shell"
5174
+ break
5175
+ }
5163
5176
  }
5164
- if (step.method === "shell.run") {
5165
- mode = "shell"
5166
- break
5177
+ if (mode === "exec") {
5178
+ item.type = "Open"
5179
+ exec_menus.push(item)
5180
+ } else if (mode === "shell") {
5181
+ item.type = "Start"
5182
+ shell_menus.push(item)
5167
5183
  }
5168
- }
5169
- if (mode === "exec") {
5170
- item.type = "Open"
5171
- exec_menus.push(item)
5172
- } else if (mode === "shell") {
5173
- item.type = "Start"
5174
- shell_menus.push(item)
5184
+ } else {
5185
+ href_menus.push(item)
5175
5186
  }
5176
5187
  }
5177
5188
  exec_menus.sort((a, b) => { return a > b })
5178
5189
  shell_menus.sort((a, b) => { return a > b })
5190
+ href_menus.sort((a, b) => { return a > b })
5179
5191
  }
5180
- console.log("> 6")
5192
+
5193
+ // let terminal = await this.terminals(filepath)
5194
+ // let online_terminal = await this.getPluginGlobal(req, terminal, filepath)
5195
+ // console.log("online_terminal", online_terminal)
5196
+ terminal.menus = href_menus
5181
5197
  let dynamic = [
5182
5198
  {
5183
5199
  icon: "fa-solid fa-robot",
5184
- title: "Get started building with AI",
5185
- subtitle: "Start making changes to this project using AI",
5200
+ title: "AI Engineer",
5201
+ subtitle: "Let AI work on this app",
5186
5202
  menu: shell_menus
5187
5203
  },
5188
5204
  {
5189
5205
  icon: "fa-solid fa-arrow-up-right-from-square",
5190
- title: "Open in external apps",
5206
+ title: "External apps",
5191
5207
  subtitle: "Open this project in 3rd party apps",
5192
5208
  menu: exec_menus
5193
5209
  },
5194
- terminal,
5210
+ terminal
5195
5211
  ]
5196
- console.log("> 7")
5212
+ let spec = ""
5213
+ try {
5214
+ spec = await fs.promises.readFile(path.resolve(filepath, "SPEC.md"), "utf8")
5215
+ } catch (e) {
5216
+ }
5197
5217
  res.render("d", {
5218
+ filepath,
5219
+ spec,
5198
5220
  retry,
5199
5221
  current_urls,
5200
5222
  docs: this.docs,
@@ -5243,20 +5265,6 @@ class Server {
5243
5265
  }
5244
5266
  res.render("mini", result)
5245
5267
  }))
5246
- this.app.get("/asset/*", ex((req, res) => {
5247
- let pathComponents = req.params[0].split("/")
5248
- let filepath = this.kernel.path(...pathComponents)
5249
- try {
5250
- if (req.query.frame) {
5251
- let m = mime.lookup(filepath)
5252
- res.type("text/plain")
5253
- }
5254
- //res.setHeader('Content-Disposition', 'inline');
5255
- res.sendFile(filepath)
5256
- } catch (e) {
5257
- res.status(404).send(e.message);
5258
- }
5259
- }))
5260
5268
  this.app.get("/raw/*", ex((req, res) => {
5261
5269
  let pathComponents = req.params[0].split("/")
5262
5270
  let filepath = this.kernel.path("api", ...pathComponents)
@@ -5324,7 +5332,9 @@ class Server {
5324
5332
  }
5325
5333
  }))
5326
5334
  this.app.get("/pinokio/dynamic_global/*", ex(async (req, res) => {
5327
- let plugin = await this.getPluginGlobal(req, "/" + req.params[0])
5335
+ let filepath = Util.u2p(req.params[0])
5336
+ let terminal = await this.terminals(filepath)
5337
+ let plugin = await this.getPluginGlobal(req, this.kernel.plugin.config, terminal, filepath)
5328
5338
  if (plugin) {
5329
5339
  let html = ""
5330
5340
  if (plugin && plugin.menu) {
@@ -5347,7 +5357,8 @@ class Server {
5347
5357
  }))
5348
5358
  this.app.get("/pinokio/dynamic/:name", ex(async (req, res) => {
5349
5359
  // await this.kernel.plugin.init()
5350
- let plugin = await this.getPlugin(req, req.params.name)
5360
+
5361
+ let plugin = await this.getPlugin(req, this.kernel.plugin.config, req.params.name)
5351
5362
  let html = ""
5352
5363
  let plugin_menu
5353
5364
  if (plugin) {
@@ -5399,6 +5410,15 @@ class Server {
5399
5410
  })
5400
5411
  res.send(html)
5401
5412
  }))
5413
+ this.app.get("/repos/:name", ex(async (req, res) => {
5414
+ // await this.kernel.plugin.init()
5415
+ let c = this.kernel.path("api", req.params.name)
5416
+ let repos = await this.kernel.git.repos(c)
5417
+ let repos_with_remote = repos.filter((repo) => {
5418
+ return repo.url
5419
+ })
5420
+ res.json(repos_with_remote)
5421
+ }))
5402
5422
  this.app.get("/pinokio/repos/:name", ex(async (req, res) => {
5403
5423
  // await this.kernel.plugin.init()
5404
5424
  let c = this.kernel.path("api", req.params.name)
@@ -5420,9 +5440,10 @@ class Server {
5420
5440
  }))
5421
5441
  this.app.get("/pinokio/sidebar/:name", ex(async (req, res) => {
5422
5442
  let name = req.params.name
5423
- let app_path = this.kernel.path("api", name, "pinokio.js")
5443
+ let launcher = await this.kernel.api.launcher(name)
5424
5444
  let rawpath = "/api/" + name
5425
- let config = (await this.kernel.loader.load(app_path)).resolved
5445
+ let config = launcher.script
5446
+ req.launcher_root = launcher.launcher_root
5426
5447
  if (config && config.menu) {
5427
5448
  if (typeof config.menu === "function") {
5428
5449
  if (config.menu.constructor.name === "AsyncFunction") {
@@ -5500,81 +5521,72 @@ class Server {
5500
5521
  // await this.kernel.refresh()
5501
5522
  // res.json({ success: true })
5502
5523
  // }))
5503
- this.app.get("/pinokio/peer", ex(async (req, res) => {
5504
- // await this.kernel.refresh()
5524
+
5525
+
5526
+ this.app.get("/info/system", ex(async (req,res) => {
5505
5527
  let current_peer_info = await this.kernel.peer.current_host()
5506
5528
  res.json(current_peer_info)
5507
- /*
5529
+ }))
5530
+ this.app.get("/info/api", ex(async (req,res) => {
5531
+ // api related info
5532
+ let repo = this.kernel.git.find(req.query.git)
5533
+ if (repo) {
5534
+ let repos = await this.kernel.git.repos(repo.path)
5535
+ repos = repos.filter((r) => {
5536
+ return r.main
5537
+ })
5538
+ let repos_with_remote = []
5539
+ let main_repo
5540
+ for(let repo of repos) {
5541
+ if (repo.url) {
5542
+ repo.commit = await this.kernel.git.getHead(repo.gitParentPath)
5543
+ let du = await Util.du(repo.gitParentPath)
5544
+ repo.du = du
5545
+ repos_with_remote.push(repo)
5546
+ }
5547
+ if (repo.main) {
5548
+ main_repo = repo
5549
+ }
5550
+ }
5551
+ res.json({
5552
+ repos: repos_with_remote
5553
+ })
5554
+ } else {
5555
+ res.json({
5556
+ repos: []
5557
+ })
5558
+ }
5559
+ }))
5560
+ this.app.get("/info/api/:name", ex(async (req,res) => {
5561
+ // api related info
5562
+ let c = this.kernel.path("api", req.params.name)
5563
+ let repos = await this.kernel.git.repos(c)
5564
+ let repos_with_remote = []
5565
+ let main_repo
5566
+ for(let repo of repos) {
5567
+ if (repo.url) {
5568
+ repo.commit = await this.kernel.git.getHead(repo.gitParentPath)
5569
+ repos_with_remote.push(repo)
5570
+ }
5571
+ if (repo.main) {
5572
+ main_repo = repo
5573
+ }
5574
+ }
5508
5575
  res.json({
5509
- home: this.kernel.homedir,
5510
- arch: this.kernel.arch,
5511
- platform: this.kernel.platform,
5512
- name: this.kernel.peer.name,
5513
- host: this.kernel.peer.host,
5514
- port_mapping: this.kernel.router.port_mapping,
5515
- //router: this.kernel.router.info(),
5516
- proc: this.kernel.processes.info,
5517
- router: this.kernel.router.published(),
5518
- memory: this.kernel.memory
5576
+ repos: repos_with_remote
5519
5577
  })
5520
- */
5578
+ }))
5579
+
5580
+
5581
+ this.app.get("/pinokio/peer", ex(async (req, res) => {
5582
+ let current_peer_info = await this.kernel.peer.current_host()
5583
+ res.json(current_peer_info)
5521
5584
  }))
5522
5585
  this.app.get("/pinokio/memory", ex((req, res) => {
5523
5586
  let filepath = req.query.filepath
5524
5587
  let mem = this.getMemory(filepath)
5525
5588
  res.json(mem)
5526
5589
  }))
5527
- // this.app.post("/pinokio/tunnel", async (req, res) => {
5528
- // let port
5529
- // let local_host
5530
- // try {
5531
- // let u = new URL(req.body.url)
5532
- // port = u.port
5533
- // local_host = u.hostname
5534
- // console.log({ local_host, port })
5535
- // if (req.body.action === "start") {
5536
- // // Output ngrok url to console
5537
- //
5538
- // let url = req.body.url
5539
- // console.log("tunnel", req.body)
5540
- // const tunnel = await ngrok.forward({ addr: port, authtoken: req.body.token });
5541
- // console.log("created", tunnel)
5542
- // console.log("url", tunnel.url())
5543
- // this.tunnels[url] = tunnel
5544
- // res.json({ url: tunnel.url() })
5545
- //
5546
- //
5547
- // // localtunnel
5548
- // //const tunnel = await localtunnel({ local_host, port: parseInt(port) });
5549
- // //const tunnel = await localtunnel({ local_host: "127.0.0.1", port: parseInt(port) });
5550
- //
5551
- // //const tunnel = await localtunnel({ port: parseInt(port) });
5552
- // //this.tunnels[url] = tunnel
5553
- // //tunnel.on('error', (err) => {
5554
- // // console.log(err)
5555
- // // delete this.tunnels[url]
5556
- // //})
5557
- // //tunnel.on('close', () => {
5558
- // // // tunnels are closed
5559
- // // console.log("tunnel closed", { url, tunnel_url: tunnel.url })
5560
- // // delete this.tunnels[url]
5561
- // //});
5562
- // //res.json({ url: tunnel.url })
5563
- // } else if (req.body.action === "stop") {
5564
- // let url = req.body.url
5565
- // await this.tunnels[url].close()
5566
- // delete this.tunnels[url]
5567
- // res.json({ url })
5568
- //// let url = req.body.url
5569
- //// console.log({ tunnels: this.tunnels, url })
5570
- //// this.tunnels[url].close()
5571
- //// res.json({ url })
5572
- // }
5573
- // } catch (e) {
5574
- // console.log("ERROR", e)
5575
- // res.json({ error: e.message })
5576
- // }
5577
- // })
5578
5590
  this.app.post("/pinokio/tabs", ex(async (req, res) => {
5579
5591
  this.tabs[req.body.name] = req.body.tabs
5580
5592
  res.json({ success: true })
@@ -5600,6 +5612,41 @@ class Server {
5600
5612
  this.app.get("/pinokio/browser/:name", ex(async (req, res) => {
5601
5613
  await this.chrome(req, res, "run")
5602
5614
  }))
5615
+
5616
+
5617
+ this.app.get("/p/:name/review", ex(async (req, res) => {
5618
+ let gitRemote = null
5619
+ try {
5620
+ const repositoryPath = path.resolve(this.kernel.api.userdir, req.params.name)
5621
+ console.log({ repositoryPath })
5622
+ gitRemote = await git.getConfig({
5623
+ fs,
5624
+ http,
5625
+ dir: repositoryPath,
5626
+ path: 'remote.origin.url'
5627
+ })
5628
+ } catch (e) {
5629
+ console.log("ERROR", e)
5630
+ }
5631
+ let name = req.params.name
5632
+ let run_tab = "/p/" + name
5633
+ let dev_tab = "/p/" + name + "/dev"
5634
+ let review_tab = "/p/" + name + "/review"
5635
+ res.render("review", {
5636
+ run_tab,
5637
+ dev_tab,
5638
+ review_tab,
5639
+ name: req.params.name,
5640
+ type: "review",
5641
+ title: name,
5642
+ url: gitRemote,
5643
+ //redirect_uri: "http://localhost:3001/apps/redirect?git=" + gitRemote,
5644
+ redirect_uri: "https://app-7pt7.onrender.com/apps/redirect?git=" + gitRemote,
5645
+ platform: this.kernel.platform,
5646
+ theme: this.theme,
5647
+ agent: this.agent,
5648
+ })
5649
+ }))
5603
5650
  this.app.get("/p/:name/dev", ex(async (req, res) => {
5604
5651
  await this.chrome(req, res, "browse")
5605
5652
  }))
@@ -5623,7 +5670,7 @@ class Server {
5623
5670
  res.json({ success: true })
5624
5671
  } else if (req.body.type === 'env') {
5625
5672
  let envpath = this.kernel.path("ENVIRONMENT")
5626
- let str = await Environment.ENV("system", this.kernel.homedir)
5673
+ let str = await Environment.ENV("system", this.kernel.homedir, this.kernel)
5627
5674
  await fs.promises.writeFile(path.resolve(this.kernel.homedir, "ENVIRONMENT"), str)
5628
5675
  res.json({ success: true })
5629
5676
  } else if (req.body.type === 'browser-cache') {
@@ -5646,35 +5693,8 @@ class Server {
5646
5693
  res.json({ error: err.stack })
5647
5694
  }
5648
5695
  }))
5649
- // this.app.get("/pinokio/shell_state", (req, res) => {
5650
- // let states = this.kernel.shell.shells.map((s) => {
5651
- // return {
5652
- // state: s.state,
5653
- // id: s.id,
5654
- // group: s.group,
5655
- // env: s.env,
5656
- // path: s.path,
5657
- // cmd: s.cmd,
5658
- // done: s.done,
5659
- // ready: s.ready,
5660
- // }
5661
- // })
5662
- //
5663
- // let info = {
5664
- // platform: this.kernel.platform,
5665
- // arch: this.kernel.arch,
5666
- // running: this.kernel.api.running,
5667
- // home: this.kernel.homedir,
5668
- // vars: this.kernel.vars,
5669
- // memory: this.kernel.memory,
5670
- // procs: this.kernel.procs,
5671
- // gpu: this.kernel.gpu,
5672
- // gpus: this.kernel.gpus
5673
- // }
5674
- // })
5675
5696
  this.app.get("/pinokio/logs.zip", ex((req, res) => {
5676
5697
  let zipPath = this.kernel.path("logs.zip")
5677
- console.log("sendFile", zipPath)
5678
5698
  res.download(zipPath)
5679
5699
  }))
5680
5700
  this.app.post("/pinokio/log", ex(async (req, res) => {
@@ -5807,7 +5827,7 @@ class Server {
5807
5827
  await fs.promises.cp(old_path, new_path, { recursive: true })
5808
5828
 
5809
5829
  // 2. edit meta in the new_path
5810
- await this.updateMeta(formData, formData.new_path)
5830
+ await this.kernel.api.updateMeta(formData, formData.new_path)
5811
5831
 
5812
5832
  }
5813
5833
  } else if (formData.move) {
@@ -5820,11 +5840,11 @@ class Server {
5820
5840
  }
5821
5841
 
5822
5842
  // 2. edit meta in the new_path
5823
- await this.updateMeta(formData, formData.new_path)
5843
+ await this.kernel.api.updateMeta(formData, formData.new_path)
5824
5844
  } else {
5825
5845
  // 1. edit only
5826
5846
  if (formData.old_path === formData.new_path) {
5827
- await this.updateMeta(formData, formData.new_path)
5847
+ await this.kernel.api.updateMeta(formData, formData.new_path)
5828
5848
  }
5829
5849
  }
5830
5850
  } else {
@@ -5888,6 +5908,56 @@ class Server {
5888
5908
  res.status(404).send("Missing attribute: path")
5889
5909
  }
5890
5910
  }))
5911
+ this.app.get("/snapshots", ex(async (req, res) => {
5912
+ let files = []
5913
+ try {
5914
+ files = await fs.promises.readdir(this.kernel.path("screenshots"))
5915
+ files = files.map((file) => {
5916
+ return `/asset/screenshots/${file}`
5917
+ })
5918
+ } catch (e) {
5919
+ }
5920
+ res.json({ files })
5921
+ }))
5922
+
5923
+ this.app.post("/snapshots", ex(async (req, res) => {
5924
+ const { filename } = req.body
5925
+
5926
+ if (!filename) {
5927
+ return res.status(400).json({ error: "Missing filename parameter" })
5928
+ }
5929
+
5930
+ try {
5931
+ // Extract just the filename from the path (security measure)
5932
+ const baseFilename = path.basename(filename.replace('/asset/screenshots/', ''))
5933
+ const fullPath = this.kernel.path("screenshots", baseFilename)
5934
+
5935
+ // Check if file exists before attempting to delete
5936
+ try {
5937
+ await fs.promises.access(fullPath)
5938
+ } catch (e) {
5939
+ return res.status(404).json({ error: "File not found" })
5940
+ }
5941
+
5942
+ // Delete the file
5943
+ await fs.promises.unlink(fullPath)
5944
+
5945
+ res.json({ success: true, message: "File deleted successfully" })
5946
+
5947
+ } catch (e) {
5948
+ console.log("Error deleting screenshot:", e)
5949
+ res.status(500).json({ error: "Failed to delete file: " + e.message })
5950
+ }
5951
+ }))
5952
+ this.app.post("/screenshot", this.upload.any(), ex(async (req, res) => {
5953
+ await fs.promises.mkdir(this.kernel.path("screenshots"), { recursive: true }).catch((e) => { })
5954
+ for(let key in req.files) {
5955
+ let file = req.files[key]
5956
+ console.log({ file, key })
5957
+ let ts = String(Date.now()) + ".png"
5958
+ await fs.promises.writeFile(this.kernel.path("screenshots", ts), file.buffer)
5959
+ }
5960
+ }))
5891
5961
  this.app.post("/pinokio/fs", this.upload.any(), ex(async (req, res) => {
5892
5962
  /*
5893
5963
  Packet format:
@@ -6079,6 +6149,14 @@ class Server {
6079
6149
  res.json({ success: true })
6080
6150
  }
6081
6151
  }))
6152
+ this.app.get("/bin_ready", ex((req, res) => {
6153
+ if (this.kernel.bin && !this.kernel.bin.requirements_pending) {
6154
+ res.json({ success: true })
6155
+ } else {
6156
+ res.json({ success: false })
6157
+ }
6158
+ }))
6159
+
6082
6160
  this.app.get("/check", ex((req, res) => {
6083
6161
  res.json({ success: true })
6084
6162
  }))