pinokiod 7.2.12 → 7.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +0 -2
- package/kernel/api/index.js +14 -1
- package/kernel/api/process/index.js +99 -44
- package/kernel/api/uri/index.js +51 -0
- package/kernel/git.js +0 -13
- package/kernel/index.js +4 -0
- package/kernel/plugin.js +6 -58
- package/kernel/plugin_sources.js +236 -0
- package/kernel/util.js +60 -0
- package/package.json +1 -1
- package/server/features/drafts/public/drafts.js +35 -12
- package/server/index.js +110 -142
- package/server/lib/content_validation.js +28 -25
- package/server/public/common.js +95 -29
- package/server/public/create-launcher.js +4 -31
- package/server/public/electron.css +1 -1
- package/server/public/style.css +337 -0
- package/server/public/task-launcher.js +5 -32
- package/server/public/universal-launcher.js +3 -26
- package/server/views/app.ejs +8 -29
- package/server/views/d.ejs +0 -33
- package/server/views/editor.ejs +25 -4
- package/server/views/shell.ejs +11 -3
- package/server/views/terminal.ejs +15 -3
- package/spec/INSTRUCTION_SYNC.md +5 -5
- package/system/plugin/antigravity/antigravity.png +0 -0
- package/system/plugin/antigravity/pinokio.js +37 -0
- package/system/plugin/claude/claude.png +0 -0
- package/system/plugin/claude/pinokio.js +63 -0
- package/system/plugin/claude-auto/claude.png +0 -0
- package/system/plugin/claude-auto/pinokio.js +74 -0
- package/system/plugin/claude-desktop/icon.jpeg +0 -0
- package/system/plugin/claude-desktop/pinokio.js +39 -0
- package/system/plugin/codex/openai.webp +0 -0
- package/system/plugin/codex/pinokio.js +58 -0
- package/system/plugin/codex-auto/openai.webp +0 -0
- package/system/plugin/codex-auto/pinokio.js +65 -0
- package/system/plugin/codex-desktop/icon.png +0 -0
- package/system/plugin/codex-desktop/pinokio.js +39 -0
- package/system/plugin/crush/crush.png +0 -0
- package/system/plugin/crush/pinokio.js +31 -0
- package/system/plugin/cursor/cursor.jpeg +0 -0
- package/system/plugin/cursor/pinokio.js +39 -0
- package/system/plugin/gemini/gemini.jpeg +0 -0
- package/system/plugin/gemini/pinokio.js +40 -0
- package/system/plugin/gemini-auto/gemini.jpeg +0 -0
- package/system/plugin/gemini-auto/pinokio.js +43 -0
- package/system/plugin/qwen/pinokio.js +50 -0
- package/system/plugin/qwen/qwen.png +0 -0
- package/system/plugin/vscode/pinokio.js +36 -0
- package/system/plugin/vscode/vscode.png +0 -0
- package/system/plugin/windsurf/pinokio.js +39 -0
- package/system/plugin/windsurf/windsurf.png +0 -0
package/Dockerfile
CHANGED
|
@@ -37,8 +37,6 @@ RUN mkdir -p /app/.pinokio-seed \
|
|
|
37
37
|
&& rm -rf /app/.pinokio-seed/network/system/.git \
|
|
38
38
|
&& rm -rf /app/.pinokio-seed/plugin \
|
|
39
39
|
&& mkdir -p /app/.pinokio-seed/plugin \
|
|
40
|
-
&& git clone --depth 1 https://github.com/pinokiocomputer/code /app/.pinokio-seed/plugin/code \
|
|
41
|
-
&& rm -rf /app/.pinokio-seed/plugin/code/.git \
|
|
42
40
|
&& rm -rf /app/.pinokio-seed/prototype/system \
|
|
43
41
|
&& mkdir -p /app/.pinokio-seed/prototype \
|
|
44
42
|
&& git clone --depth 1 https://github.com/pinokiocomputer/proto /app/.pinokio-seed/prototype/system \
|
package/kernel/api/index.js
CHANGED
|
@@ -29,6 +29,19 @@ class Api {
|
|
|
29
29
|
this.child_procs = {}
|
|
30
30
|
this.lproxy = new Lproxy()
|
|
31
31
|
}
|
|
32
|
+
startData(rpc) {
|
|
33
|
+
if (rpc && rpc.method === "process.wait" && rpc.params && typeof rpc.params === "object" && !Array.isArray(rpc.params)) {
|
|
34
|
+
let data = rpc
|
|
35
|
+
if (typeof data.title === "undefined" && typeof rpc.params.title !== "undefined") {
|
|
36
|
+
data = { ...data, title: rpc.params.title }
|
|
37
|
+
}
|
|
38
|
+
if (typeof data.description === "undefined" && typeof rpc.params.description !== "undefined") {
|
|
39
|
+
data = { ...data, description: rpc.params.description }
|
|
40
|
+
}
|
|
41
|
+
return data
|
|
42
|
+
}
|
|
43
|
+
return rpc
|
|
44
|
+
}
|
|
32
45
|
async launcher_path(name) {
|
|
33
46
|
let root_path = this.kernel.path("api", name)
|
|
34
47
|
let primary_path = path.resolve(root_path, "pinokio.js")
|
|
@@ -1186,7 +1199,7 @@ class Api {
|
|
|
1186
1199
|
this.ondata({
|
|
1187
1200
|
id: request.id || request.path,
|
|
1188
1201
|
type: "start",
|
|
1189
|
-
data: rpc
|
|
1202
|
+
data: this.startData(rpc)
|
|
1190
1203
|
})
|
|
1191
1204
|
|
|
1192
1205
|
// DEPRECATED APIS
|
|
@@ -34,6 +34,16 @@ class Process {
|
|
|
34
34
|
constructor() {
|
|
35
35
|
this.appApi = new AppAPI()
|
|
36
36
|
}
|
|
37
|
+
async waitIndefinitely(req, kernel) {
|
|
38
|
+
await new Promise((resolve, reject) => {
|
|
39
|
+
this.resolve = resolve
|
|
40
|
+
|
|
41
|
+
// register the process with the root uri so it can be manually resolved (with this.resolve()) later
|
|
42
|
+
if (kernel && kernel.procs && req && req.parent && req.parent.path) {
|
|
43
|
+
kernel.procs[req.parent.path] = this
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
37
47
|
// async start(req, ondata, kernel) {
|
|
38
48
|
// /*
|
|
39
49
|
// req := {
|
|
@@ -159,6 +169,8 @@ class Process {
|
|
|
159
169
|
/*
|
|
160
170
|
params := {
|
|
161
171
|
sec: <SECONDS>,
|
|
172
|
+
title: (optional) Title to display in the footer while waiting,
|
|
173
|
+
description: (optional) Description to display in the footer while waiting,
|
|
162
174
|
message: (optional) Description to display while waiting,
|
|
163
175
|
menu: (optional) menu to display in the modal while waiting,
|
|
164
176
|
// ok: (optional) <ok button text> (if not specified, no ok button),
|
|
@@ -169,6 +181,8 @@ class Process {
|
|
|
169
181
|
|
|
170
182
|
params := {
|
|
171
183
|
min: <MINUTES>,
|
|
184
|
+
title: (optional) Title to display in the footer while waiting,
|
|
185
|
+
description: (optional) Description to display in the footer while waiting,
|
|
172
186
|
message: (optional) Description to display while waiting,
|
|
173
187
|
menu: (optional) menu to display in the modal while waiting,
|
|
174
188
|
// ok: (optional) <ok button text> (if not specified, no ok button),
|
|
@@ -180,6 +194,8 @@ class Process {
|
|
|
180
194
|
params := {
|
|
181
195
|
url: <URL>,
|
|
182
196
|
interval: (optional) how often to retry checking (in seconds)
|
|
197
|
+
title: (optional) Title to display in the footer while waiting,
|
|
198
|
+
description: (optional) Description to display in the footer while waiting,
|
|
183
199
|
message: (optional) the message to display while retrying (default: no message)
|
|
184
200
|
}
|
|
185
201
|
|
|
@@ -198,9 +214,21 @@ class Process {
|
|
|
198
214
|
|
|
199
215
|
or
|
|
200
216
|
|
|
217
|
+
params := {
|
|
218
|
+
title: (optional) Title to display in the footer while waiting,
|
|
219
|
+
description: (optional) Description to display in the footer while waiting,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
This waits indefinitely until the script is stopped or the wait is manually
|
|
223
|
+
resolved.
|
|
224
|
+
|
|
225
|
+
or
|
|
226
|
+
|
|
201
227
|
|
|
202
228
|
params := {
|
|
203
229
|
on: <wait-on condition https://github.com/jeffbski/wait-on>,
|
|
230
|
+
title: (optional) Title to display in the footer while waiting,
|
|
231
|
+
description: (optional) Description to display in the footer while waiting,
|
|
204
232
|
message: (optional) Description to display while waiting,
|
|
205
233
|
menu: (optional) menu to display in the modal while waiting,
|
|
206
234
|
// ok: (optional) <ok button text> (if not specified, no ok button),
|
|
@@ -212,57 +240,84 @@ class Process {
|
|
|
212
240
|
if 'cancel' is pressed before the condition is met, stops the script
|
|
213
241
|
|
|
214
242
|
*/
|
|
215
|
-
let ms
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
243
|
+
let ms
|
|
244
|
+
const waitPath = req && req.parent && req.parent.path
|
|
245
|
+
if (waitPath && kernel) {
|
|
246
|
+
if (!kernel.activeProcessWaits) {
|
|
247
|
+
kernel.activeProcessWaits = {}
|
|
220
248
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
249
|
+
kernel.activeProcessWaits[waitPath] = {
|
|
250
|
+
path: waitPath,
|
|
251
|
+
params: req.params || {},
|
|
252
|
+
title: req.params && req.params.title,
|
|
253
|
+
description: req.params && req.params.description,
|
|
254
|
+
message: req.params && req.params.message,
|
|
255
|
+
started: Date.now()
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const showFooter = req.params && (req.params.title || req.params.description)
|
|
259
|
+
if (showFooter && typeof ondata === "function") {
|
|
260
|
+
ondata(req.params, "process.wait.start")
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
if (req.params) {
|
|
264
|
+
if (req.params.app || req.params.id || req.params.name) {
|
|
265
|
+
await this.appApi.waitForAppPresence(req, ondata, kernel)
|
|
266
|
+
return
|
|
228
267
|
}
|
|
229
|
-
|
|
230
|
-
|
|
268
|
+
// Display modal
|
|
269
|
+
if (req.params.sec || req.params.min) {
|
|
270
|
+
// Wait
|
|
271
|
+
if (req.params.sec) {
|
|
272
|
+
ms = req.params.sec * 1000
|
|
273
|
+
} else if (req.params.min) {
|
|
274
|
+
ms = req.params.min * 60 * 1000
|
|
275
|
+
}
|
|
276
|
+
await new Promise((resolve, reject) => {
|
|
277
|
+
this.resolve = resolve
|
|
231
278
|
|
|
232
|
-
|
|
233
|
-
|
|
279
|
+
// register the process with the root uri so it can be manually resolved (with this.resolve()) later
|
|
280
|
+
kernel.procs[req.parent.path] = this
|
|
234
281
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
})
|
|
239
|
-
} else if (req.params.uri) {
|
|
240
|
-
let interval = req.params.interval ? req.params.interval * 1000 : 1000
|
|
241
|
-
ondata(req.params, "loading.start")
|
|
242
|
-
await waitForUrl(req.params.uri, req.params.message, interval, ondata)
|
|
243
|
-
ondata(req.params, "loading.end")
|
|
244
|
-
} else if (req.params.url) {
|
|
245
|
-
let interval = req.params.interval ? req.params.interval * 1000 : 1000
|
|
246
|
-
ondata(req.params, "loading.start")
|
|
247
|
-
await waitForUrl(req.params.url, req.params.message, interval, ondata)
|
|
248
|
-
ondata(req.params, "loading.end")
|
|
249
|
-
} else if (req.params.on) {
|
|
250
|
-
// Wait
|
|
251
|
-
if (req.params.message) {
|
|
252
|
-
ondata({
|
|
253
|
-
raw: `\r\nWaiting: ${JSON.stringify(req.params.on)}\r\n`
|
|
282
|
+
setTimeout(() => {
|
|
283
|
+
this.resolve()
|
|
284
|
+
}, ms)
|
|
254
285
|
})
|
|
255
|
-
|
|
286
|
+
} else if (req.params.uri) {
|
|
287
|
+
let interval = req.params.interval ? req.params.interval * 1000 : 1000
|
|
288
|
+
ondata(req.params, "loading.start")
|
|
289
|
+
await waitForUrl(req.params.uri, req.params.message, interval, ondata)
|
|
290
|
+
ondata(req.params, "loading.end")
|
|
291
|
+
} else if (req.params.url) {
|
|
292
|
+
let interval = req.params.interval ? req.params.interval * 1000 : 1000
|
|
293
|
+
ondata(req.params, "loading.start")
|
|
294
|
+
await waitForUrl(req.params.url, req.params.message, interval, ondata)
|
|
295
|
+
ondata(req.params, "loading.end")
|
|
296
|
+
} else if (req.params.on) {
|
|
297
|
+
// Wait
|
|
298
|
+
if (req.params.message) {
|
|
299
|
+
ondata({
|
|
300
|
+
raw: `\r\nWaiting: ${JSON.stringify(req.params.on)}\r\n`
|
|
301
|
+
})
|
|
302
|
+
ondata(req.params, "wait")
|
|
303
|
+
}
|
|
304
|
+
console.log("Wait", req.params.on)
|
|
305
|
+
await waitOn(req.params.on)
|
|
306
|
+
console.log("Wait finished")
|
|
307
|
+
ondata(req.params, "wait.end")
|
|
308
|
+
} else {
|
|
309
|
+
await this.waitIndefinitely(req, kernel)
|
|
256
310
|
}
|
|
257
|
-
|
|
258
|
-
await
|
|
259
|
-
|
|
260
|
-
|
|
311
|
+
} else {
|
|
312
|
+
await this.waitIndefinitely(req, kernel)
|
|
313
|
+
}
|
|
314
|
+
} finally {
|
|
315
|
+
if (waitPath && kernel && kernel.activeProcessWaits) {
|
|
316
|
+
delete kernel.activeProcessWaits[waitPath]
|
|
317
|
+
}
|
|
318
|
+
if (showFooter && typeof ondata === "function") {
|
|
319
|
+
ondata(req.params, "process.wait.end")
|
|
261
320
|
}
|
|
262
|
-
} else {
|
|
263
|
-
await new Promise((resolve, reject) => {
|
|
264
|
-
this.resolve = resolve
|
|
265
|
-
})
|
|
266
321
|
}
|
|
267
322
|
}
|
|
268
323
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const Util = require('../../util')
|
|
2
|
+
|
|
3
|
+
const appendQueryParams = (uri, params) => {
|
|
4
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
5
|
+
return uri
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const entries = []
|
|
9
|
+
for (const [key, value] of Object.entries(params)) {
|
|
10
|
+
const values = Array.isArray(value) ? value : [value]
|
|
11
|
+
for (const item of values) {
|
|
12
|
+
if (item === undefined || item === null) {
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
const serialized = typeof item === 'object' ? JSON.stringify(item) : String(item)
|
|
16
|
+
entries.push(`${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (entries.length === 0) {
|
|
21
|
+
return uri
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const hashIndex = uri.indexOf('#')
|
|
25
|
+
const base = hashIndex === -1 ? uri : uri.slice(0, hashIndex)
|
|
26
|
+
const hash = hashIndex === -1 ? '' : uri.slice(hashIndex)
|
|
27
|
+
const separator = base.includes('?')
|
|
28
|
+
? (base.endsWith('?') || base.endsWith('&') ? '' : '&')
|
|
29
|
+
: '?'
|
|
30
|
+
|
|
31
|
+
return `${base}${separator}${entries.join('&')}${hash}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class URI {
|
|
35
|
+
build(params = {}) {
|
|
36
|
+
const uri = typeof params.uri === 'string' ? params.uri.trim() : ''
|
|
37
|
+
if (!uri) {
|
|
38
|
+
throw new Error('uri.open requires params.uri')
|
|
39
|
+
}
|
|
40
|
+
return appendQueryParams(uri, params.params)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async open(req, ondata, kernel) {
|
|
44
|
+
const uri = this.build(req.params)
|
|
45
|
+
ondata({ raw: `\r\nopening uri: ${uri}\r\n` })
|
|
46
|
+
const result = await Util.openURI(uri)
|
|
47
|
+
return { uri, result }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = URI
|
package/kernel/git.js
CHANGED
|
@@ -1378,19 +1378,6 @@ class Git {
|
|
|
1378
1378
|
const absoluteTarget = path.resolve(targetPath)
|
|
1379
1379
|
const home = path.resolve(this.kernel.homedir)
|
|
1380
1380
|
const managedTargets = [
|
|
1381
|
-
{
|
|
1382
|
-
kind: "plugin",
|
|
1383
|
-
root: path.resolve(home, "plugin/code"),
|
|
1384
|
-
matches: (root, target) => target === root || target.startsWith(`${root}${path.sep}`),
|
|
1385
|
-
bootstrap: async () => {
|
|
1386
|
-
await fs.promises.rm(path.resolve(home, "plugin/code"), { recursive: true, force: true })
|
|
1387
|
-
this.dirs.delete(path.resolve(home, "plugin/code"))
|
|
1388
|
-
if (this.kernel.plugin && typeof this.kernel.plugin.init === "function") {
|
|
1389
|
-
await this.kernel.plugin.init()
|
|
1390
|
-
}
|
|
1391
|
-
},
|
|
1392
|
-
exists: async () => this.kernel.exists("plugin/code")
|
|
1393
|
-
},
|
|
1394
1381
|
{
|
|
1395
1382
|
kind: "prototype",
|
|
1396
1383
|
root: path.resolve(home, "prototype/system"),
|
package/kernel/index.js
CHANGED
|
@@ -415,6 +415,9 @@ class Kernel {
|
|
|
415
415
|
path(...args) {
|
|
416
416
|
return path.resolve(this.homedir, ...args)
|
|
417
417
|
}
|
|
418
|
+
systemPath(...args) {
|
|
419
|
+
return path.resolve(__dirname, "..", "system", ...args)
|
|
420
|
+
}
|
|
418
421
|
exists(...args) {
|
|
419
422
|
if (args) {
|
|
420
423
|
let abspath = this.path(...args)
|
|
@@ -1060,6 +1063,7 @@ class Kernel {
|
|
|
1060
1063
|
args: {},
|
|
1061
1064
|
}
|
|
1062
1065
|
this.procs = {}
|
|
1066
|
+
this.activeProcessWaits = {}
|
|
1063
1067
|
this.template = new Template()
|
|
1064
1068
|
try {
|
|
1065
1069
|
if (this.homedir) {
|
package/kernel/plugin.js
CHANGED
|
@@ -1,39 +1,14 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const PluginSources = require("./plugin_sources")
|
|
3
|
+
|
|
4
4
|
class Plugin {
|
|
5
5
|
constructor(kernel) {
|
|
6
6
|
this.kernel = kernel
|
|
7
7
|
}
|
|
8
8
|
async setConfig() {
|
|
9
|
-
let plugin_dir = path.resolve(this.kernel.homedir, "plugin")
|
|
10
9
|
this.cache = {}
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let plugins = []
|
|
15
|
-
for(let plugin_path of plugin_paths) {
|
|
16
|
-
let config = await this.kernel.require(path.resolve(plugin_dir, plugin_path))
|
|
17
|
-
if (config && config.run && Array.isArray(config.run)) {
|
|
18
|
-
let invalid
|
|
19
|
-
for(let key in config) {
|
|
20
|
-
if (typeof config[key] === "function") {
|
|
21
|
-
invalid = true
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if (invalid) {
|
|
25
|
-
continue
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let chunks = plugin_path.split(path.sep)
|
|
29
|
-
let cwd = chunks.slice(0, -1).join("/")
|
|
30
|
-
config.image = "/asset/plugin/" + cwd + "/" + config.icon
|
|
31
|
-
plugins.push({
|
|
32
|
-
href: "/run/plugin/" + chunks.join("/"),
|
|
33
|
-
...config
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
}
|
|
11
|
+
const plugins = await PluginSources.loadPluginMenu(this.kernel)
|
|
37
12
|
|
|
38
13
|
this.config = {
|
|
39
14
|
menu: plugins.map((plugin) => {
|
|
@@ -48,37 +23,10 @@ class Plugin {
|
|
|
48
23
|
if (!exists) {
|
|
49
24
|
await fs.promises.mkdir(this.kernel.path("plugin"), { recursive: true }).catch((e) => {})
|
|
50
25
|
}
|
|
51
|
-
|
|
52
|
-
console.log({ code_exists })
|
|
53
|
-
if (!code_exists) {
|
|
54
|
-
if (this.kernel.bin.installed && this.kernel.bin.installed.conda && this.kernel.bin.installed.conda.has("git")) {
|
|
55
|
-
await this.kernel.exec({
|
|
56
|
-
//message: "git clone https://github.com/peanutcocktail/plugin",
|
|
57
|
-
//message: "git clone https://github.com/pinokiocomputer/plugin",
|
|
58
|
-
message: "git clone https://github.com/pinokiocomputer/code",
|
|
59
|
-
path: this.kernel.path("plugin")
|
|
60
|
-
}, (e) => {
|
|
61
|
-
process.stdout.write(e.raw)
|
|
62
|
-
})
|
|
63
|
-
await this.setConfig()
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
await this.setConfig()
|
|
68
|
-
}
|
|
26
|
+
await this.setConfig()
|
|
69
27
|
}
|
|
70
28
|
async update() {
|
|
71
|
-
|
|
72
|
-
let exists = await this.kernel.exists("plugin")
|
|
73
|
-
if (!exists) {
|
|
74
|
-
await this.kernel.exec({
|
|
75
|
-
message: "git pull",
|
|
76
|
-
path: this.kernel.path("plugin")
|
|
77
|
-
}, (e) => {
|
|
78
|
-
process.stdout.write(e.raw)
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
}
|
|
29
|
+
await this.setConfig()
|
|
82
30
|
}
|
|
83
31
|
}
|
|
84
32
|
module.exports = Plugin
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
const fs = require("fs")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
const { glob } = require("glob")
|
|
4
|
+
|
|
5
|
+
const LOCAL_RUN_PREFIX = "/run"
|
|
6
|
+
const LOCAL_ASSET_PREFIX = "/asset"
|
|
7
|
+
const SYSTEM_RUN_PREFIX = "/pinokio/run"
|
|
8
|
+
const SYSTEM_ASSET_PREFIX = "/pinokio/asset"
|
|
9
|
+
const LOCAL_PLUGIN_RUN_PREFIX = `${LOCAL_RUN_PREFIX}/plugin`
|
|
10
|
+
const LOCAL_PLUGIN_ASSET_PREFIX = `${LOCAL_ASSET_PREFIX}/plugin`
|
|
11
|
+
const SYSTEM_PLUGIN_RUN_PREFIX = `${SYSTEM_RUN_PREFIX}/plugin`
|
|
12
|
+
const SYSTEM_PLUGIN_ASSET_PREFIX = `${SYSTEM_ASSET_PREFIX}/plugin`
|
|
13
|
+
|
|
14
|
+
const toPathname = (value) => {
|
|
15
|
+
const raw = typeof value === "string" ? value.trim() : ""
|
|
16
|
+
if (!raw) return ""
|
|
17
|
+
try {
|
|
18
|
+
if (/^https?:\/\//i.test(raw)) return new URL(raw).pathname
|
|
19
|
+
if (raw.startsWith("/")) return new URL(`http://localhost${raw}`).pathname
|
|
20
|
+
} catch (_) {
|
|
21
|
+
}
|
|
22
|
+
return raw.split("?")[0].split("#")[0]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const normalizeSlashes = (value) => String(value || "").replace(/\\/g, "/").replace(/\/{2,}/g, "/")
|
|
26
|
+
|
|
27
|
+
const systemRoot = (kernel) => {
|
|
28
|
+
if (kernel && typeof kernel.systemPath === "function") {
|
|
29
|
+
return kernel.systemPath()
|
|
30
|
+
}
|
|
31
|
+
return path.resolve(__dirname, "..", "system")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const systemPluginRoot = (kernel) => {
|
|
35
|
+
if (kernel && typeof kernel.systemPath === "function") {
|
|
36
|
+
return kernel.systemPath("plugin")
|
|
37
|
+
}
|
|
38
|
+
return path.resolve(__dirname, "..", "system", "plugin")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isSystemRunPath = (value) => toPathname(value).startsWith(`${SYSTEM_RUN_PREFIX}/`)
|
|
42
|
+
const isLocalRunPath = (value) => toPathname(value).startsWith(`${LOCAL_RUN_PREFIX}/`)
|
|
43
|
+
const isRunPath = (value) => isLocalRunPath(value) || isSystemRunPath(value)
|
|
44
|
+
const isSystemPluginPath = (value) => normalizeSlashes(value).startsWith(`${SYSTEM_PLUGIN_RUN_PREFIX}/`)
|
|
45
|
+
const isLegacyPluginCodePath = (value) => {
|
|
46
|
+
const normalized = normalizeSlashes(value).replace(/^\/+/, "")
|
|
47
|
+
return normalized.startsWith("plugin/code/") || normalized.startsWith("code/")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const normalizePluginPath = (value) => {
|
|
51
|
+
let normalized = toPathname(value)
|
|
52
|
+
if (!normalized) return ""
|
|
53
|
+
normalized = normalizeSlashes(normalized)
|
|
54
|
+
if (!normalized.startsWith(`${SYSTEM_RUN_PREFIX}/`)) {
|
|
55
|
+
normalized = normalized.replace(/^\/run(?=\/)/, "")
|
|
56
|
+
}
|
|
57
|
+
if (!normalized.startsWith("/")) {
|
|
58
|
+
normalized = `/${normalized}`
|
|
59
|
+
}
|
|
60
|
+
normalized = normalized.replace(/\/+$/, "")
|
|
61
|
+
if (!normalized.endsWith("/pinokio.js")) {
|
|
62
|
+
normalized = `${normalized}/pinokio.js`
|
|
63
|
+
}
|
|
64
|
+
return normalized
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const pluginSelectionMatches = (src, selectedValue) => {
|
|
68
|
+
const selectedPlugin = normalizeSlashes(typeof selectedValue === "string" ? selectedValue.trim() : "")
|
|
69
|
+
if (!selectedPlugin || !src) return false
|
|
70
|
+
const selectedRunPath = selectedPlugin.startsWith(`${SYSTEM_RUN_PREFIX}/`)
|
|
71
|
+
? selectedPlugin
|
|
72
|
+
: `${LOCAL_RUN_PREFIX}${selectedPlugin}`
|
|
73
|
+
return src === selectedPlugin || src.startsWith(selectedRunPath)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const resolveRunPath = (kernel, value) => {
|
|
77
|
+
const pathname = toPathname(value)
|
|
78
|
+
if (isSystemRunPath(pathname)) {
|
|
79
|
+
const parts = pathname.split("/").filter(Boolean).slice(2)
|
|
80
|
+
return kernel.systemPath(...parts)
|
|
81
|
+
}
|
|
82
|
+
if (isLocalRunPath(pathname)) {
|
|
83
|
+
const parts = pathname.split("/").filter(Boolean).slice(1)
|
|
84
|
+
return kernel.path(...parts)
|
|
85
|
+
}
|
|
86
|
+
return ""
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const resolvePinokioPath = (kernel, value) => {
|
|
90
|
+
const runPath = resolveRunPath(kernel, value)
|
|
91
|
+
if (runPath) return runPath
|
|
92
|
+
const pathname = toPathname(value)
|
|
93
|
+
return pathname ? kernel.path(pathname.replace(/^\/+/, "")) : ""
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const systemRelativeFromPluginPath = (normalizedPath) => {
|
|
97
|
+
return normalizeSlashes(normalizedPath).replace(/^\/pinokio\/run\/+/, "")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const pluginPathToAbsolute = (kernel, normalizedPath) => {
|
|
101
|
+
if (isSystemPluginPath(normalizedPath)) {
|
|
102
|
+
return kernel.systemPath(systemRelativeFromPluginPath(normalizedPath))
|
|
103
|
+
}
|
|
104
|
+
return kernel.path(normalizeSlashes(normalizedPath).replace(/^\/+/, ""))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const pluginRunHrefForPath = (normalizedPath) => {
|
|
108
|
+
if (typeof normalizedPath !== "string" || !normalizedPath) return ""
|
|
109
|
+
if (isSystemPluginPath(normalizedPath)) return normalizedPath
|
|
110
|
+
return `${LOCAL_RUN_PREFIX}${normalizedPath}`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const pluginAssetHrefForIcon = (normalizedPath, icon) => {
|
|
114
|
+
const trimmedIcon = typeof icon === "string" ? icon.trim() : ""
|
|
115
|
+
if (!trimmedIcon) return ""
|
|
116
|
+
if (isSystemPluginPath(normalizedPath)) {
|
|
117
|
+
const relativeDir = path.posix.dirname(systemRelativeFromPluginPath(normalizedPath))
|
|
118
|
+
return `${SYSTEM_ASSET_PREFIX}/${relativeDir}/${trimmedIcon}`
|
|
119
|
+
}
|
|
120
|
+
if (normalizeSlashes(normalizedPath).startsWith("/plugin/")) {
|
|
121
|
+
const relativeDir = path.posix.dirname(normalizeSlashes(normalizedPath).slice(1))
|
|
122
|
+
return `${LOCAL_ASSET_PREFIX}/${relativeDir}/${trimmedIcon}`
|
|
123
|
+
}
|
|
124
|
+
return ""
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const normalizeLauncherTool = (toolValue) => {
|
|
128
|
+
let normalizedTool = typeof toolValue === "string" ? toolValue.trim() : ""
|
|
129
|
+
normalizedTool = normalizedTool.replace(/^https?:\/\/[^/]+/i, "")
|
|
130
|
+
normalizedTool = normalizedTool.replace(/^\/+|\/+$/g, "")
|
|
131
|
+
if (!normalizedTool || normalizedTool.includes("..") || !/^[A-Za-z0-9._/-]+$/.test(normalizedTool)) {
|
|
132
|
+
const error = new Error("Invalid plugin.")
|
|
133
|
+
error.status = 400
|
|
134
|
+
throw error
|
|
135
|
+
}
|
|
136
|
+
if (!normalizedTool.startsWith("pinokio/run/")) {
|
|
137
|
+
normalizedTool = normalizedTool.replace(/^run\//, "")
|
|
138
|
+
}
|
|
139
|
+
if (isLegacyPluginCodePath(normalizedTool)) {
|
|
140
|
+
const error = new Error("The managed plugin/code path is no longer used.")
|
|
141
|
+
error.status = 400
|
|
142
|
+
throw error
|
|
143
|
+
}
|
|
144
|
+
return normalizedTool
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const resolveLauncherPluginHref = (toolValue) => {
|
|
148
|
+
const normalizedTool = normalizeLauncherTool(toolValue)
|
|
149
|
+
if (normalizedTool.startsWith("pinokio/run/")) {
|
|
150
|
+
const scriptPath = normalizedTool.endsWith(".js")
|
|
151
|
+
? normalizedTool
|
|
152
|
+
: `${normalizedTool}/pinokio.js`
|
|
153
|
+
return `/${scriptPath}`
|
|
154
|
+
}
|
|
155
|
+
if (normalizedTool.startsWith("plugin/") || normalizedTool.startsWith("api/")) {
|
|
156
|
+
const scriptPath = normalizedTool.endsWith(".js")
|
|
157
|
+
? normalizedTool
|
|
158
|
+
: `${normalizedTool}/pinokio.js`
|
|
159
|
+
return `${LOCAL_RUN_PREFIX}/${scriptPath}`
|
|
160
|
+
}
|
|
161
|
+
return `${LOCAL_PLUGIN_RUN_PREFIX}/${normalizedTool}/pinokio.js`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const loadPluginsFromRoot = async ({ kernel, root, runPrefix, assetPrefix, source, ignore = [] }) => {
|
|
165
|
+
const exists = await fs.promises.stat(root).then((stat) => stat.isDirectory()).catch(() => false)
|
|
166
|
+
if (!exists) return []
|
|
167
|
+
|
|
168
|
+
const pluginPaths = await glob("**/pinokio.js", { cwd: root, ignore })
|
|
169
|
+
const plugins = []
|
|
170
|
+
for (const pluginPath of pluginPaths) {
|
|
171
|
+
const normalizedPluginPath = normalizeSlashes(pluginPath)
|
|
172
|
+
const config = await kernel.require(path.resolve(root, pluginPath))
|
|
173
|
+
if (!config || !Array.isArray(config.run)) continue
|
|
174
|
+
const invalid = Object.keys(config).some((key) => typeof config[key] === "function")
|
|
175
|
+
if (invalid) continue
|
|
176
|
+
|
|
177
|
+
const cwd = normalizedPluginPath.split("/").slice(0, -1).join("/")
|
|
178
|
+
const href = `${runPrefix}/${normalizedPluginPath}`
|
|
179
|
+
const image = config.icon ? `${assetPrefix}/${cwd}/${config.icon}` : config.image
|
|
180
|
+
plugins.push({
|
|
181
|
+
...config,
|
|
182
|
+
href,
|
|
183
|
+
src: href,
|
|
184
|
+
image,
|
|
185
|
+
source,
|
|
186
|
+
system: source === "system",
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
return plugins
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const loadPluginMenu = async (kernel) => {
|
|
193
|
+
const systemPlugins = await loadPluginsFromRoot({
|
|
194
|
+
kernel,
|
|
195
|
+
root: systemPluginRoot(kernel),
|
|
196
|
+
runPrefix: SYSTEM_PLUGIN_RUN_PREFIX,
|
|
197
|
+
assetPrefix: SYSTEM_PLUGIN_ASSET_PREFIX,
|
|
198
|
+
source: "system",
|
|
199
|
+
})
|
|
200
|
+
const localPlugins = await loadPluginsFromRoot({
|
|
201
|
+
kernel,
|
|
202
|
+
root: path.resolve(kernel.homedir, "plugin"),
|
|
203
|
+
runPrefix: LOCAL_PLUGIN_RUN_PREFIX,
|
|
204
|
+
assetPrefix: LOCAL_PLUGIN_ASSET_PREFIX,
|
|
205
|
+
source: "local",
|
|
206
|
+
ignore: ["code/**"],
|
|
207
|
+
})
|
|
208
|
+
return systemPlugins.concat(localPlugins)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
LOCAL_RUN_PREFIX,
|
|
213
|
+
LOCAL_ASSET_PREFIX,
|
|
214
|
+
SYSTEM_RUN_PREFIX,
|
|
215
|
+
SYSTEM_ASSET_PREFIX,
|
|
216
|
+
LOCAL_PLUGIN_RUN_PREFIX,
|
|
217
|
+
SYSTEM_PLUGIN_RUN_PREFIX,
|
|
218
|
+
SYSTEM_PLUGIN_ASSET_PREFIX,
|
|
219
|
+
systemRoot,
|
|
220
|
+
systemPluginRoot,
|
|
221
|
+
toPathname,
|
|
222
|
+
isSystemRunPath,
|
|
223
|
+
isLocalRunPath,
|
|
224
|
+
isRunPath,
|
|
225
|
+
isSystemPluginPath,
|
|
226
|
+
isLegacyPluginCodePath,
|
|
227
|
+
normalizePluginPath,
|
|
228
|
+
pluginSelectionMatches,
|
|
229
|
+
resolveRunPath,
|
|
230
|
+
resolvePinokioPath,
|
|
231
|
+
pluginPathToAbsolute,
|
|
232
|
+
pluginRunHrefForPath,
|
|
233
|
+
pluginAssetHrefForIcon,
|
|
234
|
+
resolveLauncherPluginHref,
|
|
235
|
+
loadPluginMenu,
|
|
236
|
+
}
|