pinokiod 3.8.1 → 3.8.5

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 (50) hide show
  1. package/kernel/api/fs/index.js +6 -4
  2. package/kernel/api/index.js +102 -31
  3. package/kernel/bin/index.js +143 -4
  4. package/kernel/bin/node.js +9 -0
  5. package/kernel/index.js +32 -1
  6. package/kernel/info.js +1 -0
  7. package/kernel/prototype.js +47 -0
  8. package/kernel/shell.js +110 -54
  9. package/kernel/shells.js +16 -7
  10. package/kernel/store.js +58 -0
  11. package/kernel/util.js +106 -35
  12. package/package.json +1 -1
  13. package/script/index.js +12 -0
  14. package/server/index.js +466 -250
  15. package/server/public/SpaceMono-Bold.ttf +0 -0
  16. package/server/public/SpaceMono-BoldItalic.ttf +0 -0
  17. package/server/public/SpaceMono-Italic.ttf +0 -0
  18. package/server/public/SpaceMono-Regular.ttf +0 -0
  19. package/server/public/filepond-plugin-file-validate-type.min.js +9 -0
  20. package/server/public/filepond-plugin-image-crop.min.js +9 -0
  21. package/server/public/filepond-plugin-image-edit.min.css +8 -0
  22. package/server/public/filepond-plugin-image-edit.min.js +9 -0
  23. package/server/public/filepond-plugin-image-exif-orientation.min.js +9 -0
  24. package/server/public/filepond-plugin-image-preview.min.css +8 -0
  25. package/server/public/filepond-plugin-image-preview.min.js +9 -0
  26. package/server/public/filepond-plugin-image-resize.min.js +9 -0
  27. package/server/public/filepond-plugin-image-transform.min.js +9 -0
  28. package/server/public/filepond.min.css +8 -0
  29. package/server/public/filepond.min.js +9 -0
  30. package/server/public/fscreator.js +160 -0
  31. package/server/public/fseditor.js +189 -0
  32. package/server/public/github.png +0 -0
  33. package/server/public/opener.js +3 -1
  34. package/server/public/pinokio-black.png +0 -0
  35. package/server/public/pinokio-black2.png +0 -0
  36. package/server/public/style.css +131 -9
  37. package/server/socket.js +123 -37
  38. package/server/views/app.ejs +184 -11
  39. package/server/views/editor.ejs +12 -2
  40. package/server/views/explore.ejs +4 -3
  41. package/server/views/help.ejs +4 -2
  42. package/server/views/index.ejs +218 -80
  43. package/server/views/install.ejs +1 -1
  44. package/server/views/local_editor.ejs +282 -0
  45. package/server/views/minterm.ejs +687 -0
  46. package/server/views/partials/menu.ejs +7 -1
  47. package/server/views/prototypes.ejs +463 -0
  48. package/server/views/proxy.ejs +10 -4
  49. package/server/views/settings.ejs +4 -3
  50. package/server/views/terminal.ejs +99 -15
@@ -75,16 +75,18 @@ class FS {
75
75
  /*
76
76
  params := {
77
77
  path: <filepath>,
78
- mode: "view(default)|open"
78
+ command: "view(default)|open|<any command in PATH>"
79
79
  }
80
80
  open finder/file explorer at path
81
81
  */
82
82
  let dirPath = path.resolve(req.cwd, req.params.path)
83
83
  ondata({ raw: `\r\nopening path: ${dirPath}\r\n` })
84
- if (req.params.mode) {
85
- Util.openfs(dirPath, req.params.mode)
84
+ if (req.params.command) {
85
+ Util.openfs(dirPath, req.params, kernel)
86
+ } else if (req.params.mode) {
87
+ Util.openfs(dirPath, req.params, kernel)
86
88
  } else {
87
- Util.openfs(dirPath)
89
+ Util.openfs(dirPath, {}, kernel)
88
90
  }
89
91
  }
90
92
 
@@ -119,21 +119,22 @@ class Api {
119
119
  return result
120
120
  }
121
121
  async stop(req, ondata) {
122
+ console.log(">>>>>>>>>>>>>>>>>> STOP")
122
123
  // 1. set the "stop" flag for the uri, so the next execution in the queue for the uri will NOT queue another task
123
124
  // 2. stream a message closing the socket
125
+ console.time("stop 1")
124
126
 
125
127
  let requestPath = this.filePath(req.params.uri)
126
128
 
127
-
128
-
129
129
  let { cwd, script } = await this.resolveScript(requestPath)
130
- console.log("STOP", { script })
131
130
  if (script.on) {
132
131
  if (script.on.stop) {
133
132
  await this.process(script.on.stop)
134
133
  }
135
134
  }
135
+ console.timeEnd("stop 1")
136
136
 
137
+ console.time("stop 2")
137
138
  // reset modules
138
139
  let modpath = this.resolvePath(cwd, req.params.uri)
139
140
  console.log("STOP modpath", modpath)
@@ -159,10 +160,15 @@ class Api {
159
160
 
160
161
  // stop all proxies
161
162
  this.stopProxy({ script: requestPath })
163
+ console.timeEnd("stop 2")
162
164
 
163
165
  // stop all cloudflare tunnels
164
166
 
167
+ console.time("stop 3")
165
168
  await this.kernel.stopCloudflare({ path: requestPath })
169
+ console.timeEnd("stop 3")
170
+
171
+ console.time("stop 4")
166
172
 
167
173
  // if there are any pending waiters, delete them
168
174
 
@@ -179,6 +185,7 @@ class Api {
179
185
  id: requestPath,
180
186
  type: "disconnect"
181
187
  })
188
+ console.timeEnd("stop 4")
182
189
  return true
183
190
  }
184
191
  async startScripts() {
@@ -352,13 +359,20 @@ class Api {
352
359
  let modpath
353
360
  if (matched_repo.length > 0) {
354
361
  let repo_uri = matched_repo[0]
355
-
356
- let relative_path = uri
357
- .replace(repo_uri, "") // remove the git repo uri
358
- .slice(1) // remove the leading '/' for relative path
362
+ if (!repo_uri.endsWith(".git")) {
363
+ repo_uri = repo_uri + ".git"
364
+ }
359
365
 
360
366
  let repopath = this.gitPath[repo_uri]
361
- modpath = path.resolve(repopath, relative_path)
367
+ if (uri === repo_uri) {
368
+ modpath = path.resolve(repopath)
369
+ } else {
370
+ let relative_path = uri
371
+ .replace(repo_uri, "") // remove the git repo uri
372
+ .slice(1) // remove the leading '/' for relative path
373
+ modpath = path.resolve(repopath, relative_path)
374
+ }
375
+
362
376
  } else {
363
377
  //console.log("no matching gitpath")
364
378
  }
@@ -1121,6 +1135,35 @@ class Api {
1121
1135
  this.process(request, resolve)
1122
1136
  })
1123
1137
  }
1138
+ async get_default(repo_path) {
1139
+ let p = path.resolve(repo_path, "pinokio.js")
1140
+ // call 'process' based on the current status
1141
+ let config = (await this.loader.load(p)).resolved
1142
+ if (config && config.menu) {
1143
+ if (typeof config.menu === "function") {
1144
+ if (config.menu.constructor.name === "AsyncFunction") {
1145
+ config.menu = await config.menu(this.kernel, this.kernel.info)
1146
+ } else {
1147
+ config.menu = config.menu(this.kernel, this.kernel.info)
1148
+ }
1149
+ }
1150
+ // find the default item in the menu
1151
+ let running = config.menu.filter((item) => {
1152
+ return item.default
1153
+ })
1154
+ if (running.length > 0) {
1155
+ let r = running[0]
1156
+ if (r.href) {
1157
+ if (r.href.startsWith("http")) {
1158
+ return r.href
1159
+ } else {
1160
+ let href = path.resolve(repo_path, r.href)
1161
+ return href
1162
+ }
1163
+ }
1164
+ }
1165
+ }
1166
+ }
1124
1167
  async process(request, done) {
1125
1168
  /**************************************************************
1126
1169
  *
@@ -1162,43 +1205,67 @@ class Api {
1162
1205
  // this.kernel.keys = (await this.loader.load(keypath)).resolved
1163
1206
 
1164
1207
  // init shell before running just to make sure the environment variables are fresh
1165
- console.time("shell.init")
1166
1208
  await this.kernel.shell.init()
1167
- console.timeEnd("shell.init")
1168
1209
 
1169
1210
  if (request.uri){
1211
+
1170
1212
  this.counter++;
1171
1213
  // API Call
1172
1214
 
1173
1215
  request.path = this.resolvePath(this.userdir, request.uri)
1174
- let { cwd, script } = await this.resolveScript(request.path)
1175
- request.cwd = cwd
1176
1216
 
1177
- if (!script) {
1178
- this.ondata({
1179
- id: request.path,
1180
- type: "error",
1181
- data: "the endpoint does not exist: " + request.uri,
1182
- })
1183
- } else {
1184
- // 3. Check if the resolved endpoint has the "run" attribute and it's an array
1185
- //if (script.run && Array.isArray(script.run)) {
1186
- if (script.run) {
1187
-
1188
- this.running[request.path] = true
1189
-
1190
- this.done[request.path] = done
1191
-
1192
- this.queue(request, script.run[0], request.input, 0, script.run.length, cwd, request.input)
1193
1217
 
1218
+ // if the path is
1219
+ let relative = path.relative(this.kernel.homedir, request.path)
1220
+ let chunks = relative.split(path.sep)
1221
+ console.log({ chunks })
1222
+ if (chunks.length == 2) {
1223
+ // the script is requesting a uri of the git repo
1224
+ // look for pinokio.js
1225
+ let p = path.resolve(request.path, "pinokio.js")
1226
+ let exists = await this.exists(p)
1227
+ console.log({ p, exists })
1228
+ if (exists) {
1229
+ await this.launch(request, p)
1194
1230
  } else {
1195
1231
  this.ondata({
1196
1232
  id: request.path,
1197
1233
  type: "error",
1198
- data: "missing attribute: run"
1234
+ data: "uri doesn't exist \r\n" + JSON.stringify(request, null, 2)
1199
1235
  })
1200
1236
  }
1237
+ } else {
1238
+ console.log({ request })
1239
+ let { cwd, script } = await this.resolveScript(request.path)
1240
+ request.cwd = cwd
1241
+
1242
+ if (!script) {
1243
+ this.ondata({
1244
+ id: request.path,
1245
+ type: "error",
1246
+ data: "the endpoint does not exist: " + request.uri,
1247
+ })
1248
+ } else {
1249
+ // 3. Check if the resolved endpoint has the "run" attribute and it's an array
1250
+ //if (script.run && Array.isArray(script.run)) {
1251
+ if (script.run) {
1252
+
1253
+ this.running[request.path] = true
1254
+
1255
+ this.done[request.path] = done
1256
+
1257
+ this.queue(request, script.run[0], request.input, 0, script.run.length, cwd, request.input)
1258
+
1259
+ } else {
1260
+ this.ondata({
1261
+ id: request.path,
1262
+ type: "error",
1263
+ data: "missing attribute: run"
1264
+ })
1265
+ }
1266
+ }
1201
1267
  }
1268
+
1202
1269
  } else if (request.method) {
1203
1270
  let pass = 0;
1204
1271
  while(true) {
@@ -1229,12 +1296,16 @@ class Api {
1229
1296
 
1230
1297
  // Kernel Call
1231
1298
  let resolved = await this.resolveMethod(request)
1299
+ let id = request.id ? request.id : request.method
1300
+ if (request.id) {
1301
+ this.running[request.id] = true
1302
+ }
1232
1303
  try {
1233
1304
  resolved.dirname = resolved.dirname
1234
1305
  let result = await this.run(resolved.method, request, (stream, type) => {
1235
1306
  this.ondata({
1236
1307
  kernel: true,
1237
- id: request.method,
1308
+ id,
1238
1309
  type: (type ? type : "stream"),
1239
1310
  data: stream,
1240
1311
  rpc: request,
@@ -1243,7 +1314,7 @@ class Api {
1243
1314
  })
1244
1315
  this.ondata({
1245
1316
  kernel: true,
1246
- id: request.method,
1317
+ id,
1247
1318
  type: "result",
1248
1319
  data: result,
1249
1320
  rpc: request,
@@ -37,12 +37,28 @@ class Bin {
37
37
  this.arch = os.arch()
38
38
  this.platform = os.platform()
39
39
  }
40
+ async shell_kill(params, ondata) {
41
+ console.log("shell_kill", params)
42
+ this.kernel.shell.kill(params.params)
43
+ }
44
+ async shell_start(params, ondata) {
45
+ params.path = params.path || this.path()
46
+ console.log("this.client?", this.client)
47
+ if (this.client) {
48
+ params.cols = this.client.cols
49
+ params.rows = this.client.rows
50
+ }
51
+ console.log({ params })
52
+ let response = await this.kernel.shell.start(params, null, ondata)
53
+ return response
54
+ }
40
55
  async exec(params, ondata) {
41
56
  params.path = params.path || this.path()
42
57
  if (this.client) {
43
58
  params.cols = this.client.cols
44
59
  params.rows = this.client.rows
45
60
  }
61
+ console.log({ params })
46
62
  let response = await this.kernel.shell.run(params, null, ondata)
47
63
  return response
48
64
  }
@@ -646,6 +662,24 @@ class Bin {
646
662
  }
647
663
  // }
648
664
  }
665
+ async path_exists(req, ondata) {
666
+ let abspath = this.kernel.api.resolvePath(this.kernel.api.userdir, req.params.uri)
667
+ if (abspath) {
668
+ console.log({ abspath })
669
+ let exists = await new Promise(r=>fs.access(abspath, fs.constants.F_OK, e => r(!e)))
670
+ return exists
671
+ } else {
672
+ return false
673
+ }
674
+ }
675
+ async install2(req, ondata) {
676
+ let { requirements, install_required, requirements_pending, error } = await this.check_bin()
677
+ req.params = JSON.stringify(requirements)
678
+ if (this.install_required) {
679
+ let res = await this.install(req, ondata)
680
+ return res
681
+ }
682
+ }
649
683
  async install(req, ondata) {
650
684
  /*
651
685
  req.params := {
@@ -665,6 +699,8 @@ class Bin {
665
699
  this.client = null
666
700
  }
667
701
 
702
+ console.log("install", req)
703
+
668
704
  let requirements = JSON.parse(req.params)
669
705
 
670
706
  for(let x=0; x<10; x++) {
@@ -738,17 +774,29 @@ class Bin {
738
774
  //}
739
775
  await this.init()
740
776
  }
777
+ async installed(r) {
778
+ if (Array.isArray(r.name)) {
779
+ for(let name of r.name) {
780
+ let installed = await this._installed(name, r.type)
781
+ if (!installed) return false
782
+ }
783
+ return true
784
+ } else {
785
+ let installed = await this._installed(r.name, r.type)
786
+ return installed
787
+ }
788
+ }
741
789
  async _installed(name, type) {
742
790
  if (type === "conda") {
743
- return this.kernel.bin.installed.conda.has(name)
791
+ return this.installed.conda.has(name)
744
792
  } else if (type === "pip") {
745
- return this.kernel.bin.installed.pip && this.kernel.bin.installed.pip.has(name)
793
+ return this.installed.pip && this.installed.pip.has(name)
746
794
  } else if (type === "brew") {
747
- return this.kernel.bin.installed.brew.has(name)
795
+ return this.installed.brew.has(name)
748
796
  } else {
749
797
  // check kernel/bin/<module>.installed()
750
798
  let filepath = path.resolve(__dirname, "..", "kernel", "bin", name + ".js")
751
- let mod = this.kernel.bin.mod[name]
799
+ let mod = this.mod[name]
752
800
  let installed = false
753
801
  if (mod.installed) {
754
802
  installed = await mod.installed()
@@ -756,6 +804,97 @@ class Bin {
756
804
  return installed
757
805
  }
758
806
  }
807
+ async check_bin() {
808
+ let requirements = [
809
+ { name: "conda", },
810
+ { name: "git", },
811
+ { name: "zip", },
812
+ { name: "node", },
813
+ { name: "ffmpeg", }
814
+ ]
815
+ let platform = os.platform()
816
+ if (platform === "win32") {
817
+ requirements.push({ name: "registry" })
818
+ requirements.push({ name: "vs" })
819
+ }
820
+ if (platform === "darwin") {
821
+ requirements.push({ name: "brew" })
822
+ }
823
+ if (platform === "linux") {
824
+ requirements.push({ name: "gxx" })
825
+ }
826
+ if (this.kernel.gpu === "nvidia") {
827
+ requirements.push({ name: "cuda", })
828
+ }
829
+ requirements = requirements.concat([
830
+ { name: "py" },
831
+ { name: "cloudflared" },
832
+ { name: "playwright" },
833
+ { name: "huggingface" },
834
+ { name: "uv" }
835
+ ])
836
+
837
+ let requirements_pending = !this.installed_initialized
838
+ let install_required = true
839
+ if (!requirements_pending) {
840
+ install_required = false
841
+ for(let i=0; i<requirements.length; i++) {
842
+ let r = requirements[i]
843
+ console.time(r.name)
844
+ let installed = await this.installed(r)
845
+ console.log({ r, installed })
846
+ console.timeEnd(r.name)
847
+ requirements[i].installed = installed
848
+ if (!installed) {
849
+ install_required = true
850
+ }
851
+ }
852
+ }
853
+
854
+ let error = null
855
+ try {
856
+ this.compatible()
857
+ } catch (e) {
858
+ error = e.message
859
+ install_required = true
860
+ }
861
+
862
+ this.install_required = install_required
863
+ this.requirements_pending = requirements_pending
864
+
865
+ console.log("check_bin finished 2")
866
+ return {
867
+ error,
868
+ requirements,
869
+ install_required,
870
+ requirements_pending
871
+ }
872
+
873
+ }
874
+ winBuildNumber() {
875
+ let osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
876
+ let buildNumber = 0;
877
+ if (osVersion && osVersion.length === 4) {
878
+ buildNumber = parseInt(osVersion[3]);
879
+ }
880
+ return buildNumber;
881
+ }
882
+ compatible() {
883
+ if (this.kernel.platform === "win32") {
884
+ let buildNumber = this.winBuildNumber()
885
+ if (buildNumber < 18309) {
886
+ // must use conpty for node-pty, and conpty is only supported in win>=18309
887
+ console.log("Windows buildNumber", buildNumber)
888
+ throw new Error(`Pinokio supports Windows release 18309 and up (current system: ${buildNumber}`)
889
+ }
890
+
891
+ // if (buildNumber > 25000) {
892
+ // console.log("Windows buildNumber", buildNumber)
893
+ // throw new Error(`Pinokio does not currently support Windows Canary (versions 25000 and up). The current system is ${buildNumber}`)
894
+ //
895
+ // }
896
+ }
897
+ }
759
898
  async sh(params, ondata) {
760
899
  let response = await this.kernel.shell.run(params, null, ondata)
761
900
  return response
@@ -20,6 +20,15 @@ class Node {
20
20
  async installed() {
21
21
  return this.kernel.bin.installed.conda.has("nodejs") && this.kernel.bin.installed.conda.has("pnpm")
22
22
  }
23
+ env() {
24
+ return {
25
+ PATH: [this.kernel.path("bin/npm"), this.kernel.path("bin/npm/bin")],
26
+ NPM_CONFIG_PREFIX: this.kernel.path("bin/npm"),
27
+ npm_config_prefix: this.kernel.path("bin/npm"),
28
+ PNPM_HOME: this.kernel.path("bin/npm"),
29
+ pnpm_home: this.kernel.path("bin/npm"),
30
+ }
31
+ }
23
32
  async uninstall(req, ondata) {
24
33
  await this.kernel.bin.exec({
25
34
  message: "conda remove nodejs pnpm",
package/kernel/index.js CHANGED
@@ -7,6 +7,7 @@ const fastq = require('fastq')
7
7
  const fetch = require('cross-fetch');
8
8
  const waitOn = require('wait-on');
9
9
  const system = require('systeminformation');
10
+ const shellPath = require('shell-path');
10
11
  const Sysinfo = require('./sysinfo')
11
12
  const portfinder = require('portfinder-cp');
12
13
  const { execSync } = require('child_process')
@@ -26,6 +27,8 @@ const Config = require("./pinokio.json")
26
27
  const Info = require('./info')
27
28
  const Pipe = require("../pipe")
28
29
  const Cloudflare = require('./api/cloudflare')
30
+ const Store = require('./store')
31
+ const Proto = require('./prototype')
29
32
  const VARS = {
30
33
  pip: {
31
34
  install: {
@@ -42,7 +45,29 @@ class Kernel {
42
45
  schema = "<=3.7.0"
43
46
  constructor(store) {
44
47
  this.fetch = fetch
45
- this.store = store
48
+
49
+ // STORE
50
+ // Previously custom STORE could be passed in. This was to support both electron and server based initialization
51
+ // But this creates complexity because the store location is not fixed
52
+ // Migrating it to ~/.pinokio/config.json
53
+ //
54
+ // 1. does ~/.pinokio exist?
55
+ // - load from that file
56
+ // 2. does ~/.pinokio NOT exist?
57
+ // - save the passed in options to the `~/.pinokio`
58
+ // - load the config
59
+
60
+ this.store = new Store()
61
+ let exists = this.store.exists()
62
+ console.log("Store exists?", exists)
63
+ if (!exists) {
64
+ console.log("doesn't exist")
65
+ // clone the store to the new store
66
+ this.store.clone(store.store)
67
+ // load from the store (this will be the last time this is used, since the next time it loads, it will load from the new store)
68
+ }
69
+ console.log("this.store", this.store.store())
70
+
46
71
  this.arch = os.arch()
47
72
  this.os = os
48
73
  this.platform = os.platform()
@@ -52,7 +77,9 @@ class Kernel {
52
77
  this.envs = {}
53
78
  this.info = new Info(this)
54
79
  this.pipe = new Pipe(this)
80
+ this.proto = new Proto(this)
55
81
  this.cloudflare = new Cloudflare()
82
+ this.shellpath = shellPath.sync()
56
83
  }
57
84
  /*
58
85
  kernel.env() => return the system environment
@@ -513,6 +540,7 @@ class Kernel {
513
540
  console.log("bin init finished")
514
541
  if (this.homedir) {
515
542
  this.shell.init().then(async () => {
543
+ this.bin.check_bin()
516
544
  if (this.envs) {
517
545
  this.template.update({
518
546
  env: this.envs,
@@ -616,6 +644,9 @@ class Kernel {
616
644
  await fs.promises.writeFile(this.path("logs/system.json"), JSON.stringify(this.i, null, 2))
617
645
  let pwpath = this.bin.path("playwright/node_modules/playwright")
618
646
  this.playwright = (await this.loader.load(pwpath)).resolved
647
+
648
+
649
+ await this.proto.init()
619
650
  }
620
651
 
621
652
  } catch (e) {
package/kernel/info.js CHANGED
@@ -45,6 +45,7 @@ class Info {
45
45
  exists(...args) {
46
46
  let cwd = path.dirname(this.caller())
47
47
  let resolved_path = path.resolve(cwd, ...args)
48
+ console.log({ cwd, resolved_path })
48
49
  return fs.existsSync(resolved_path)
49
50
  }
50
51
  local(...args) {
@@ -0,0 +1,47 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ class Proto {
4
+ constructor(kernel) {
5
+ this.kernel = kernel
6
+ }
7
+ async init() {
8
+
9
+ // if ~/pinokio/prototype doesn't exist, clone
10
+ let exists = await this.kernel.exists("prototype")
11
+ if (!exists) {
12
+ console.log("prototype doesn't exist. cloning...")
13
+ await this.kernel.exec({
14
+ message: "git clone https://github.com/peanutcocktail/prototype",
15
+ path: this.kernel.homedir
16
+ }, (e) => {
17
+ process.stdout.write(e.raw)
18
+ })
19
+ }
20
+ // get all prototypes
21
+ let prototype_dir = path.resolve(this.kernel.homedir, "prototype")
22
+ let prototypes = await fs.promises.readdir(prototype_dir, { withFileTypes: true })
23
+ prototypes = prototypes.filter(d => d.isDirectory()).map(d => d.name);
24
+ console.log({ prototypes })
25
+ this.items = []
26
+ this.kv = {}
27
+ for(let proto of prototypes) {
28
+ let pinokiojs = path.resolve(prototype_dir, proto, "pinokio.js")
29
+ let config = await this.kernel.require(pinokiojs)
30
+ console.log({ config })
31
+ if (config && config.type === "prototype") {
32
+ if (config.icon) {
33
+ config.icon = "/prototype/" + proto + "/" + config.icon
34
+ } else {
35
+ config.icon === "/pinokio-black.png"
36
+ }
37
+ let c = {
38
+ id: proto,
39
+ ...config
40
+ }
41
+ this.items.push(c)
42
+ this.kv[proto] = c
43
+ }
44
+ }
45
+ }
46
+ }
47
+ module.exports = Proto