pinokiod 7.1.32 → 7.1.33
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/kernel/api/index.js +67 -2
- package/kernel/bin/bluefairy.js +1 -1
- package/package.json +1 -1
- package/server/index.js +265 -111
- package/server/lib/app_registry.js +11 -43
- package/server/public/common.js +41 -7
- package/server/public/create-launcher.js +20 -4
- package/server/public/plugin-detail.js +34 -17
- package/server/public/task-launcher.css +12 -0
- package/server/public/task-launcher.js +11 -2
- package/server/public/universal-launcher.js +15 -2
- package/server/views/app.ejs +34 -8
- package/server/views/d.ejs +3 -1
- package/server/views/plugin_detail.ejs +1 -0
- package/server/views/terminal.ejs +1 -0
- package/undefined/logs/dev/plugin/amp/pinokio.js/1775428679177 +12 -0
- package/undefined/logs/dev/plugin/amp/pinokio.js/events +2 -0
- package/undefined/logs/dev/plugin/amp/pinokio.js/latest +12 -0
- package/undefined/logs/dev/plugin/pi/pinokio.js/1775428650680 +9 -0
- package/undefined/logs/dev/plugin/pi/pinokio.js/1775428654295 +10 -0
- package/undefined/logs/dev/plugin/pi/pinokio.js/events +10 -0
- package/undefined/logs/dev/plugin/pi/pinokio.js/latest +10 -0
package/kernel/api/index.js
CHANGED
|
@@ -203,6 +203,69 @@ class Api {
|
|
|
203
203
|
}
|
|
204
204
|
return meta
|
|
205
205
|
}
|
|
206
|
+
async listApps() {
|
|
207
|
+
const apps = []
|
|
208
|
+
const apiRoot = this.userdir || this.kernel.path("api")
|
|
209
|
+
let entries
|
|
210
|
+
try {
|
|
211
|
+
entries = await fs.promises.readdir(apiRoot, { withFileTypes: true })
|
|
212
|
+
} catch (enumerationError) {
|
|
213
|
+
console.warn("Failed to enumerate api apps", enumerationError)
|
|
214
|
+
return apps
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
let type
|
|
219
|
+
try {
|
|
220
|
+
type = await Util.file_type(apiRoot, entry)
|
|
221
|
+
} catch (typeError) {
|
|
222
|
+
console.warn("Failed to inspect api entry", entry.name, typeError)
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
if (!type || !type.directory) {
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const workspacePath = path.resolve(apiRoot, entry.name)
|
|
230
|
+
let meta
|
|
231
|
+
try {
|
|
232
|
+
meta = await this.meta(entry.name)
|
|
233
|
+
} catch (metaError) {
|
|
234
|
+
console.warn("Failed to load app metadata", entry.name, metaError)
|
|
235
|
+
meta = null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const launcherPath = meta && meta.path ? meta.path : workspacePath
|
|
239
|
+
const title = meta && meta.title ? meta.title : entry.name
|
|
240
|
+
const description = meta && meta.description ? meta.description : ""
|
|
241
|
+
const icon = meta && meta.icon ? meta.icon : "/pinokio-black.png"
|
|
242
|
+
apps.push({
|
|
243
|
+
id: entry.name,
|
|
244
|
+
name: entry.name,
|
|
245
|
+
title,
|
|
246
|
+
description,
|
|
247
|
+
icon,
|
|
248
|
+
workspace_path: workspacePath,
|
|
249
|
+
launcher_path: launcherPath,
|
|
250
|
+
launcher_root: path.relative(workspacePath, launcherPath),
|
|
251
|
+
meta: meta || {
|
|
252
|
+
title,
|
|
253
|
+
description,
|
|
254
|
+
icon,
|
|
255
|
+
path: launcherPath
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
apps.sort((a, b) => {
|
|
261
|
+
const at = (a.title || a.name || "").toLowerCase()
|
|
262
|
+
const bt = (b.title || b.name || "").toLowerCase()
|
|
263
|
+
if (at < bt) return -1
|
|
264
|
+
if (at > bt) return 1
|
|
265
|
+
return (a.name || "").localeCompare(b.name || "")
|
|
266
|
+
})
|
|
267
|
+
return apps
|
|
268
|
+
}
|
|
206
269
|
get_proxy_url(root, port) {
|
|
207
270
|
if (this.proxies) {
|
|
208
271
|
let proxies = this.proxies[root]
|
|
@@ -795,12 +858,13 @@ class Api {
|
|
|
795
858
|
|
|
796
859
|
let port = await this.kernel.port()
|
|
797
860
|
|
|
798
|
-
let { cwd, script } = await this.resolveScript(request.path)
|
|
861
|
+
let { cwd: scriptDir, script } = await this.resolveScript(request.path)
|
|
799
862
|
const actionKey = request.action || 'run'
|
|
800
863
|
const steps = (script && Array.isArray(script[actionKey])) ? script[actionKey] : []
|
|
801
864
|
const totalSteps = steps.length
|
|
802
865
|
|
|
803
|
-
let name = path.relative(this.kernel.path("api"),
|
|
866
|
+
let name = path.relative(this.kernel.path("api"), scriptDir)
|
|
867
|
+
let cwd = scriptDir
|
|
804
868
|
|
|
805
869
|
if (request.cwd) {
|
|
806
870
|
cwd = request.cwd
|
|
@@ -822,6 +886,7 @@ class Api {
|
|
|
822
886
|
current: i,
|
|
823
887
|
uri: request.uri,
|
|
824
888
|
cwd,
|
|
889
|
+
dirname: scriptDir,
|
|
825
890
|
exists: (...args) => {
|
|
826
891
|
return fs.existsSync(path.resolve(cwd, ...args))
|
|
827
892
|
},
|
package/kernel/bin/bluefairy.js
CHANGED
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -3025,6 +3025,9 @@ class Server {
|
|
|
3025
3025
|
const shellPrefix = "shell/" + unix_item_path + "_"
|
|
3026
3026
|
const matchesShell = (candidate) => {
|
|
3027
3027
|
if (!candidate) return false
|
|
3028
|
+
if (typeof candidate.group === "string" && candidate.group.includes("?cwd=")) {
|
|
3029
|
+
return false
|
|
3030
|
+
}
|
|
3028
3031
|
const idMatches = typeof candidate.id === "string" && candidate.id.startsWith(shellPrefix)
|
|
3029
3032
|
const shellPath = typeof candidate.path === "string" ? path.normalize(candidate.path) : null
|
|
3030
3033
|
const groupPath = typeof candidate.group === "string" ? path.normalize(candidate.group) : null
|
|
@@ -3064,6 +3067,10 @@ class Server {
|
|
|
3064
3067
|
|
|
3065
3068
|
// not only should include the pattern, but also end with it (otherwise can include similar patterns such as /api/qqqa, /api/qqqaaa, etc.
|
|
3066
3069
|
|
|
3070
|
+
if (key.includes("?cwd=")) {
|
|
3071
|
+
continue
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3067
3074
|
let is_running
|
|
3068
3075
|
let api_path = this.kernel.path("api")
|
|
3069
3076
|
if (this.is_subpath(api_path, key)) {
|
|
@@ -5012,78 +5019,228 @@ class Server {
|
|
|
5012
5019
|
}
|
|
5013
5020
|
return shellOptions
|
|
5014
5021
|
}
|
|
5022
|
+
normalizeBundledPluginSpec(value) {
|
|
5023
|
+
if (this.appRegistry && typeof this.appRegistry.normalizeRelativeScriptPath === "function") {
|
|
5024
|
+
return this.appRegistry.normalizeRelativeScriptPath(value)
|
|
5025
|
+
}
|
|
5026
|
+
if (typeof value !== "string") {
|
|
5027
|
+
return ""
|
|
5028
|
+
}
|
|
5029
|
+
const trimmed = value.trim()
|
|
5030
|
+
if (!trimmed) {
|
|
5031
|
+
return ""
|
|
5032
|
+
}
|
|
5033
|
+
const normalized = path.posix.normalize(trimmed.replace(/\\/g, "/"))
|
|
5034
|
+
if (!normalized || normalized === "." || normalized.startsWith("../") || normalized.startsWith("/")) {
|
|
5035
|
+
return ""
|
|
5036
|
+
}
|
|
5037
|
+
return normalized
|
|
5038
|
+
}
|
|
5039
|
+
isValidBundledPluginConfig(pluginConfig) {
|
|
5040
|
+
if (!pluginConfig || !Array.isArray(pluginConfig.run)) {
|
|
5041
|
+
return false
|
|
5042
|
+
}
|
|
5043
|
+
for (const key of Object.keys(pluginConfig)) {
|
|
5044
|
+
if (typeof pluginConfig[key] === "function") {
|
|
5045
|
+
return false
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
return true
|
|
5049
|
+
}
|
|
5050
|
+
isPathInsideRootForBundledPlugin(candidatePath, rootPath) {
|
|
5051
|
+
const relative = path.relative(rootPath, candidatePath)
|
|
5052
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
5053
|
+
}
|
|
5054
|
+
async getBundledPluginAppDescriptor(appName, metaOverride) {
|
|
5055
|
+
if (typeof appName !== "string" || !appName.trim()) {
|
|
5056
|
+
return null
|
|
5057
|
+
}
|
|
5058
|
+
const workspacePath = this.kernel.path("api", appName)
|
|
5059
|
+
let meta = metaOverride
|
|
5060
|
+
if (!meta) {
|
|
5061
|
+
try {
|
|
5062
|
+
meta = await this.kernel.api.meta(appName)
|
|
5063
|
+
} catch (metaError) {
|
|
5064
|
+
console.warn("Failed to load app metadata for bundled plugins", appName, metaError)
|
|
5065
|
+
meta = null
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
const launcherPath = meta && meta.path ? meta.path : workspacePath
|
|
5069
|
+
return {
|
|
5070
|
+
name: appName,
|
|
5071
|
+
title: meta && meta.title ? meta.title : appName,
|
|
5072
|
+
description: meta && meta.description ? meta.description : "",
|
|
5073
|
+
icon: meta && meta.icon ? meta.icon : "/pinokio-black.png",
|
|
5074
|
+
workspacePath,
|
|
5075
|
+
launcherPath,
|
|
5076
|
+
meta: meta || {
|
|
5077
|
+
title: appName,
|
|
5078
|
+
description: "",
|
|
5079
|
+
icon: "/pinokio-black.png",
|
|
5080
|
+
path: launcherPath
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
async getBundledPluginMenuItems(appDescriptor) {
|
|
5085
|
+
if (!appDescriptor || !appDescriptor.meta) {
|
|
5086
|
+
return []
|
|
5087
|
+
}
|
|
5088
|
+
const pluginSpecs = Array.isArray(appDescriptor.meta.plugins) ? appDescriptor.meta.plugins : []
|
|
5089
|
+
if (pluginSpecs.length === 0) {
|
|
5090
|
+
return []
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
const bundledMenu = []
|
|
5094
|
+
for (const pluginSpec of pluginSpecs) {
|
|
5095
|
+
const normalizedSpec = this.normalizeBundledPluginSpec(pluginSpec)
|
|
5096
|
+
if (!normalizedSpec || path.posix.basename(normalizedSpec) !== "pinokio.js") {
|
|
5097
|
+
continue
|
|
5098
|
+
}
|
|
5099
|
+
const pluginAbsolutePath = path.resolve(appDescriptor.launcherPath, normalizedSpec)
|
|
5100
|
+
if (!this.isPathInsideRootForBundledPlugin(pluginAbsolutePath, appDescriptor.launcherPath)) {
|
|
5101
|
+
continue
|
|
5102
|
+
}
|
|
5103
|
+
|
|
5104
|
+
let pluginConfig
|
|
5105
|
+
try {
|
|
5106
|
+
pluginConfig = (await this.kernel.loader.load(pluginAbsolutePath)).resolved
|
|
5107
|
+
} catch (pluginLoadError) {
|
|
5108
|
+
console.warn("Failed to load bundled plugin", pluginAbsolutePath, pluginLoadError)
|
|
5109
|
+
continue
|
|
5110
|
+
}
|
|
5111
|
+
if (!this.isValidBundledPluginConfig(pluginConfig)) {
|
|
5112
|
+
continue
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
const pluginRelativePath = path.relative(appDescriptor.workspacePath, pluginAbsolutePath).split(path.sep).join("/")
|
|
5116
|
+
const menuItem = {
|
|
5117
|
+
...safeStructuredClone(pluginConfig),
|
|
5118
|
+
href: `/run/api/${appDescriptor.name}/${pluginRelativePath}`,
|
|
5119
|
+
src: `/api/${appDescriptor.name}/${pluginRelativePath}`,
|
|
5120
|
+
ownerApp: {
|
|
5121
|
+
name: appDescriptor.name,
|
|
5122
|
+
title: appDescriptor.title,
|
|
5123
|
+
cwd: appDescriptor.workspacePath
|
|
5124
|
+
},
|
|
5125
|
+
defaultCwd: appDescriptor.workspacePath
|
|
5126
|
+
}
|
|
5127
|
+
if (typeof menuItem.text !== "string" || !menuItem.text.trim()) {
|
|
5128
|
+
if (typeof menuItem.title === "string" && menuItem.title.trim()) {
|
|
5129
|
+
menuItem.text = menuItem.title.trim()
|
|
5130
|
+
} else {
|
|
5131
|
+
menuItem.text = path.posix.basename(path.posix.dirname(normalizedSpec))
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
if (typeof pluginConfig.icon === "string" && pluginConfig.icon.trim()) {
|
|
5135
|
+
const iconAbsolutePath = path.resolve(path.dirname(pluginAbsolutePath), pluginConfig.icon)
|
|
5136
|
+
if (this.isPathInsideRootForBundledPlugin(iconAbsolutePath, appDescriptor.workspacePath)) {
|
|
5137
|
+
const iconRelativePath = path.relative(appDescriptor.workspacePath, iconAbsolutePath).split(path.sep).join("/")
|
|
5138
|
+
menuItem.image = `/api/${appDescriptor.name}/${iconRelativePath}?raw=true`
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
bundledMenu.push(menuItem)
|
|
5142
|
+
}
|
|
5143
|
+
return bundledMenu
|
|
5144
|
+
}
|
|
5145
|
+
async getBundledPluginMenuForApp(appName) {
|
|
5146
|
+
const appDescriptor = await this.getBundledPluginAppDescriptor(appName)
|
|
5147
|
+
return this.getBundledPluginMenuItems(appDescriptor)
|
|
5148
|
+
}
|
|
5149
|
+
async getBundledPluginMenu() {
|
|
5150
|
+
let apps = []
|
|
5151
|
+
try {
|
|
5152
|
+
apps = await this.kernel.api.listApps()
|
|
5153
|
+
} catch (error) {
|
|
5154
|
+
console.warn("Failed to enumerate apps for bundled plugins", error)
|
|
5155
|
+
return []
|
|
5156
|
+
}
|
|
5157
|
+
|
|
5158
|
+
const bundledMenu = []
|
|
5159
|
+
for (const app of apps) {
|
|
5160
|
+
const appDescriptor = {
|
|
5161
|
+
name: app.name,
|
|
5162
|
+
title: app.title,
|
|
5163
|
+
description: app.description,
|
|
5164
|
+
icon: app.icon,
|
|
5165
|
+
workspacePath: app.workspace_path,
|
|
5166
|
+
launcherPath: app.launcher_path,
|
|
5167
|
+
meta: app.meta
|
|
5168
|
+
}
|
|
5169
|
+
const menuItems = await this.getBundledPluginMenuItems(appDescriptor)
|
|
5170
|
+
bundledMenu.push(...menuItems)
|
|
5171
|
+
}
|
|
5172
|
+
return bundledMenu
|
|
5173
|
+
}
|
|
5015
5174
|
async getPluginGlobal(req, config, terminal, filepath) {
|
|
5016
5175
|
// if (!this.kernel.plugin.config) {
|
|
5017
5176
|
// await this.kernel.plugin.init()
|
|
5018
5177
|
// }
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5178
|
+
let c = safeStructuredClone(config || { menu: [] })
|
|
5179
|
+
if (!Array.isArray(c.menu)) {
|
|
5180
|
+
c.menu = []
|
|
5181
|
+
}
|
|
5182
|
+
try {
|
|
5183
|
+
const bundledMenu = await this.getBundledPluginMenu()
|
|
5184
|
+
let menu = safeStructuredClone(terminal.menu || [])
|
|
5185
|
+
c.menu = c.menu.concat(bundledMenu, menu)
|
|
5186
|
+
let info = new Info(this.kernel)
|
|
5187
|
+
info.cwd = () => {
|
|
5188
|
+
return filepath
|
|
5189
|
+
}
|
|
5190
|
+
let menuItems = c.menu.map((item) => {
|
|
5191
|
+
return {
|
|
5192
|
+
params: {
|
|
5193
|
+
cwd: filepath
|
|
5194
|
+
},
|
|
5195
|
+
...item
|
|
5028
5196
|
}
|
|
5029
|
-
|
|
5030
|
-
return {
|
|
5031
|
-
params: {
|
|
5032
|
-
cwd: filepath
|
|
5033
|
-
},
|
|
5034
|
-
...item
|
|
5035
|
-
}
|
|
5036
|
-
})
|
|
5197
|
+
})
|
|
5037
5198
|
// let menu = await this.kernel.plugin.config.menu(this.kernel, info)
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5199
|
+
let plugin = { menu: menuItems }
|
|
5200
|
+
let uri = filepath
|
|
5201
|
+
await this.renderMenu(req, uri, filepath, plugin, [])
|
|
5041
5202
|
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
}
|
|
5203
|
+
function setOnlineIfRunning(obj) {
|
|
5204
|
+
if (Array.isArray(obj)) {
|
|
5205
|
+
for (const item of obj) setOnlineIfRunning(item);
|
|
5206
|
+
} else if (obj && typeof obj === 'object') {
|
|
5207
|
+
if (obj.running === true) obj.online = true;
|
|
5208
|
+
for (const key in obj) setOnlineIfRunning(obj[key]);
|
|
5049
5209
|
}
|
|
5210
|
+
}
|
|
5050
5211
|
|
|
5051
|
-
|
|
5212
|
+
setOnlineIfRunning(plugin)
|
|
5052
5213
|
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
return null
|
|
5057
|
-
}
|
|
5058
|
-
} else {
|
|
5214
|
+
return plugin
|
|
5215
|
+
} catch (e) {
|
|
5216
|
+
console.log("getPlugin ERROR", e)
|
|
5059
5217
|
return null
|
|
5060
5218
|
}
|
|
5061
5219
|
}
|
|
5062
5220
|
async getPlugin(req, config, name) {
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
}
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
} else {
|
|
5221
|
+
let c = safeStructuredClone(config || { menu: [] })
|
|
5222
|
+
if (!Array.isArray(c.menu)) {
|
|
5223
|
+
c.menu = []
|
|
5224
|
+
}
|
|
5225
|
+
try {
|
|
5226
|
+
let filepath = this.kernel.path("api", name)
|
|
5227
|
+
let terminal = await this.terminals(filepath)
|
|
5228
|
+
const bundledMenu = await this.getBundledPluginMenu()
|
|
5229
|
+
c.menu = c.menu.concat(bundledMenu, terminal.menu)
|
|
5230
|
+
let menu = c.menu.map((item) => {
|
|
5231
|
+
return {
|
|
5232
|
+
params: {
|
|
5233
|
+
cwd: filepath,
|
|
5234
|
+
},
|
|
5235
|
+
...item
|
|
5236
|
+
}
|
|
5237
|
+
})
|
|
5238
|
+
let plugin = { menu }
|
|
5239
|
+
let uri = this.kernel.path("api")
|
|
5240
|
+
await this.renderMenu(req, uri, name, plugin, [])
|
|
5241
|
+
return plugin
|
|
5242
|
+
} catch (e) {
|
|
5243
|
+
console.log("getPlugin ERROR", e)
|
|
5087
5244
|
return null
|
|
5088
5245
|
}
|
|
5089
5246
|
}
|
|
@@ -6380,6 +6537,7 @@ class Server {
|
|
|
6380
6537
|
? trimmed
|
|
6381
6538
|
: `${trimmed.replace(/\/+$/, "")}/pinokio.js`
|
|
6382
6539
|
}
|
|
6540
|
+
const loadBundledPluginMenu = async () => this.getBundledPluginMenu()
|
|
6383
6541
|
const classifyPluginMenuItem = (pluginItem) => {
|
|
6384
6542
|
const runs = Array.isArray(pluginItem && pluginItem.run) ? pluginItem.run : []
|
|
6385
6543
|
const hasExec = runs.some((step) => step && step.method === "exec")
|
|
@@ -6427,9 +6585,18 @@ class Server {
|
|
|
6427
6585
|
link: pluginItem?.link || "",
|
|
6428
6586
|
image: pluginItem?.image || null,
|
|
6429
6587
|
icon: pluginItem?.icon || null,
|
|
6588
|
+
default: pluginItem?.default === true,
|
|
6430
6589
|
pluginPath: normalizedPluginPath,
|
|
6431
6590
|
pluginKey: normalizePluginLookupKey(normalizedPluginPath),
|
|
6432
6591
|
extraParams,
|
|
6592
|
+
defaultCwd: typeof pluginItem?.defaultCwd === "string" ? pluginItem.defaultCwd : "",
|
|
6593
|
+
ownerApp: pluginItem && pluginItem.ownerApp && typeof pluginItem.ownerApp === "object"
|
|
6594
|
+
? {
|
|
6595
|
+
name: typeof pluginItem.ownerApp.name === "string" ? pluginItem.ownerApp.name : "",
|
|
6596
|
+
title: typeof pluginItem.ownerApp.title === "string" ? pluginItem.ownerApp.title : "",
|
|
6597
|
+
cwd: typeof pluginItem.ownerApp.cwd === "string" ? pluginItem.ownerApp.cwd : "",
|
|
6598
|
+
}
|
|
6599
|
+
: null,
|
|
6433
6600
|
hasInstall: Array.isArray(pluginItem?.install),
|
|
6434
6601
|
hasUninstall: Array.isArray(pluginItem?.uninstall),
|
|
6435
6602
|
hasUpdate: Array.isArray(pluginItem?.update),
|
|
@@ -6455,7 +6622,14 @@ class Server {
|
|
|
6455
6622
|
} catch (err) {
|
|
6456
6623
|
console.warn("Failed to initialize plugins", err)
|
|
6457
6624
|
}
|
|
6458
|
-
|
|
6625
|
+
let bundledPluginMenu = []
|
|
6626
|
+
try {
|
|
6627
|
+
bundledPluginMenu = await loadBundledPluginMenu()
|
|
6628
|
+
} catch (bundledError) {
|
|
6629
|
+
console.warn("Failed to load bundled plugins", bundledError)
|
|
6630
|
+
}
|
|
6631
|
+
const mergedPluginMenu = pluginMenu.concat(bundledPluginMenu)
|
|
6632
|
+
return mergedPluginMenu.map((pluginItem, index) => serializePluginMenuItem(pluginItem, index))
|
|
6459
6633
|
}
|
|
6460
6634
|
const buildPluginCategories = (plugins) => {
|
|
6461
6635
|
const buckets = { ide: [], cli: [] }
|
|
@@ -6539,55 +6713,33 @@ class Server {
|
|
|
6539
6713
|
managedPrefix,
|
|
6540
6714
|
}
|
|
6541
6715
|
}
|
|
6542
|
-
const collectPluginApps = async () => {
|
|
6716
|
+
const collectPluginApps = async (boundAppName = "") => {
|
|
6543
6717
|
const apps = []
|
|
6544
6718
|
try {
|
|
6545
|
-
const
|
|
6546
|
-
const
|
|
6547
|
-
|
|
6548
|
-
let type
|
|
6549
|
-
try {
|
|
6550
|
-
type = await Util.file_type(apipath, entry)
|
|
6551
|
-
} catch (typeErr) {
|
|
6552
|
-
console.warn("Failed to inspect api entry", entry.name, typeErr)
|
|
6553
|
-
continue
|
|
6554
|
-
}
|
|
6555
|
-
if (!type || !type.directory) {
|
|
6719
|
+
const appList = await this.kernel.api.listApps()
|
|
6720
|
+
for (const app of appList) {
|
|
6721
|
+
if (boundAppName && app.name !== boundAppName) {
|
|
6556
6722
|
continue
|
|
6557
6723
|
}
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
if (
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
const normalized = relative.split(path.sep).join("/")
|
|
6568
|
-
displayPath = `~/${normalized}`
|
|
6569
|
-
}
|
|
6724
|
+
const absolutePath = app.workspace_path || this.kernel.path("api", app.name)
|
|
6725
|
+
let displayPath = absolutePath
|
|
6726
|
+
if (this.kernel.homedir && absolutePath.startsWith(this.kernel.homedir)) {
|
|
6727
|
+
const relative = path.relative(this.kernel.homedir, absolutePath)
|
|
6728
|
+
if (!relative || relative === "." || relative === "") {
|
|
6729
|
+
displayPath = "~"
|
|
6730
|
+
} else if (!relative.startsWith("..")) {
|
|
6731
|
+
const normalized = relative.split(path.sep).join("/")
|
|
6732
|
+
displayPath = `~/${normalized}`
|
|
6570
6733
|
}
|
|
6571
|
-
apps.push({
|
|
6572
|
-
name: entry.name,
|
|
6573
|
-
title: meta && meta.title ? meta.title : entry.name,
|
|
6574
|
-
description: meta && meta.description ? meta.description : "",
|
|
6575
|
-
icon: meta && meta.icon ? meta.icon : "/pinokio-black.png",
|
|
6576
|
-
cwd: absolutePath,
|
|
6577
|
-
displayPath
|
|
6578
|
-
})
|
|
6579
|
-
} catch (metaError) {
|
|
6580
|
-
console.warn("Failed to load app metadata", entry.name, metaError)
|
|
6581
|
-
const fallbackPath = this.kernel.path("api", entry.name)
|
|
6582
|
-
apps.push({
|
|
6583
|
-
name: entry.name,
|
|
6584
|
-
title: entry.name,
|
|
6585
|
-
description: "",
|
|
6586
|
-
icon: "/pinokio-black.png",
|
|
6587
|
-
cwd: fallbackPath,
|
|
6588
|
-
displayPath: fallbackPath
|
|
6589
|
-
})
|
|
6590
6734
|
}
|
|
6735
|
+
apps.push({
|
|
6736
|
+
name: app.name,
|
|
6737
|
+
title: app.title || app.name,
|
|
6738
|
+
description: app.description || "",
|
|
6739
|
+
icon: app.icon || "/pinokio-black.png",
|
|
6740
|
+
cwd: absolutePath,
|
|
6741
|
+
displayPath
|
|
6742
|
+
})
|
|
6591
6743
|
}
|
|
6592
6744
|
} catch (enumerationError) {
|
|
6593
6745
|
console.warn("Failed to enumerate api apps for plugin modal", enumerationError)
|
|
@@ -6901,14 +7053,7 @@ class Server {
|
|
|
6901
7053
|
}))
|
|
6902
7054
|
this.app.get("/api/plugin/menu", ex(async (req, res) => {
|
|
6903
7055
|
try {
|
|
6904
|
-
|
|
6905
|
-
await this.kernel.plugin.init()
|
|
6906
|
-
} else {
|
|
6907
|
-
await this.kernel.plugin.setConfig()
|
|
6908
|
-
}
|
|
6909
|
-
const pluginMenu = this.kernel.plugin && this.kernel.plugin.config && Array.isArray(this.kernel.plugin.config.menu)
|
|
6910
|
-
? this.kernel.plugin.config.menu
|
|
6911
|
-
: []
|
|
7056
|
+
const pluginMenu = await loadSerializedPlugins()
|
|
6912
7057
|
res.set("Cache-Control", "no-store")
|
|
6913
7058
|
res.json({ menu: pluginMenu })
|
|
6914
7059
|
} catch (error) {
|
|
@@ -7650,12 +7795,21 @@ class Server {
|
|
|
7650
7795
|
return targetPath
|
|
7651
7796
|
}
|
|
7652
7797
|
const resolveUniversalLauncherPluginHref = (toolValue) => {
|
|
7653
|
-
|
|
7798
|
+
let normalizedTool = typeof toolValue === "string" ? toolValue.trim() : ""
|
|
7799
|
+
normalizedTool = normalizedTool.replace(/^https?:\/\/[^/]+/i, "")
|
|
7800
|
+
normalizedTool = normalizedTool.replace(/^\/+|\/+$/g, "")
|
|
7801
|
+
normalizedTool = normalizedTool.replace(/^run\//, "")
|
|
7654
7802
|
if (!normalizedTool || normalizedTool.includes("..") || !/^[A-Za-z0-9._/-]+$/.test(normalizedTool)) {
|
|
7655
7803
|
const error = new Error("Invalid plugin.")
|
|
7656
7804
|
error.status = 400
|
|
7657
7805
|
throw error
|
|
7658
7806
|
}
|
|
7807
|
+
if (normalizedTool.startsWith("plugin/") || normalizedTool.startsWith("api/")) {
|
|
7808
|
+
const scriptPath = normalizedTool.endsWith(".js")
|
|
7809
|
+
? normalizedTool
|
|
7810
|
+
: `${normalizedTool}/pinokio.js`
|
|
7811
|
+
return `/run/${scriptPath}`
|
|
7812
|
+
}
|
|
7659
7813
|
return `/run/plugin/${normalizedTool}/pinokio.js`
|
|
7660
7814
|
}
|
|
7661
7815
|
const persistLauncherPromptContext = async (targetPath, options = {}) => {
|
|
@@ -371,50 +371,18 @@ class AppRegistryService {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
async listInfoApps() {
|
|
374
|
-
const apps = []
|
|
375
374
|
try {
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
continue
|
|
388
|
-
}
|
|
389
|
-
try {
|
|
390
|
-
const meta = await this.kernel.api.meta(entry.name)
|
|
391
|
-
apps.push({
|
|
392
|
-
name: entry.name,
|
|
393
|
-
title: meta && meta.title ? meta.title : entry.name,
|
|
394
|
-
description: meta && meta.description ? meta.description : '',
|
|
395
|
-
icon: meta && meta.icon ? meta.icon : '/pinokio-black.png'
|
|
396
|
-
})
|
|
397
|
-
} catch (metaError) {
|
|
398
|
-
console.warn('Failed to load app metadata', entry.name, metaError)
|
|
399
|
-
apps.push({
|
|
400
|
-
name: entry.name,
|
|
401
|
-
title: entry.name,
|
|
402
|
-
description: '',
|
|
403
|
-
icon: '/pinokio-black.png'
|
|
404
|
-
})
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
} catch (enumerationError) {
|
|
408
|
-
console.warn('Failed to enumerate api apps for url dropdown', enumerationError)
|
|
409
|
-
}
|
|
410
|
-
apps.sort((a, b) => {
|
|
411
|
-
const at = (a.title || a.name || '').toLowerCase()
|
|
412
|
-
const bt = (b.title || b.name || '').toLowerCase()
|
|
413
|
-
if (at < bt) return -1
|
|
414
|
-
if (at > bt) return 1
|
|
415
|
-
return (a.name || '').localeCompare(b.name || '')
|
|
416
|
-
})
|
|
417
|
-
return apps
|
|
375
|
+
const apps = await this.kernel.api.listApps()
|
|
376
|
+
return apps.map((app) => ({
|
|
377
|
+
name: app.name,
|
|
378
|
+
title: app.title,
|
|
379
|
+
description: app.description,
|
|
380
|
+
icon: app.icon
|
|
381
|
+
}))
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.warn('Failed to enumerate api apps for url dropdown', error)
|
|
384
|
+
return []
|
|
385
|
+
}
|
|
418
386
|
}
|
|
419
387
|
|
|
420
388
|
async buildAppStatus(appId, options = {}) {
|
package/server/public/common.js
CHANGED
|
@@ -3905,6 +3905,33 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
3905
3905
|
return '';
|
|
3906
3906
|
}
|
|
3907
3907
|
|
|
3908
|
+
function isPluginLauncherPath(pathname) {
|
|
3909
|
+
return typeof pathname === 'string'
|
|
3910
|
+
&& (
|
|
3911
|
+
pathname.startsWith('/run/plugin/')
|
|
3912
|
+
|| (pathname.startsWith('/run/api/') && /\/pinokio\.js$/i.test(pathname))
|
|
3913
|
+
);
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
function getPluginToolCategory(plugin) {
|
|
3917
|
+
const explicitCategory = typeof plugin?.category === 'string' ? plugin.category.trim().toLowerCase() : '';
|
|
3918
|
+
if (explicitCategory === 'ide') {
|
|
3919
|
+
return 'IDE';
|
|
3920
|
+
}
|
|
3921
|
+
if (explicitCategory === 'cli') {
|
|
3922
|
+
return 'CLI';
|
|
3923
|
+
}
|
|
3924
|
+
const launchType = typeof plugin?.launch_type === 'string' ? plugin.launch_type.trim().toLowerCase() : '';
|
|
3925
|
+
if (launchType === 'desktop') {
|
|
3926
|
+
return 'IDE';
|
|
3927
|
+
}
|
|
3928
|
+
if (launchType === 'terminal') {
|
|
3929
|
+
return 'CLI';
|
|
3930
|
+
}
|
|
3931
|
+
const runs = Array.isArray(plugin?.run) ? plugin.run : [];
|
|
3932
|
+
return runs.some((step) => step && step.method === 'exec') ? 'IDE' : 'CLI';
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3908
3935
|
function mapPluginMenuToAskAiTools(menu) {
|
|
3909
3936
|
if (!Array.isArray(menu)) {
|
|
3910
3937
|
return [];
|
|
@@ -3914,15 +3941,22 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
3914
3941
|
return null;
|
|
3915
3942
|
}
|
|
3916
3943
|
const href = typeof plugin.href === 'string' ? plugin.href.trim() : '';
|
|
3917
|
-
if (!href
|
|
3944
|
+
if (!href) {
|
|
3945
|
+
return null;
|
|
3946
|
+
}
|
|
3947
|
+
let parsed;
|
|
3948
|
+
try {
|
|
3949
|
+
parsed = new URL(href, window.location.origin);
|
|
3950
|
+
} catch (_) {
|
|
3951
|
+
return null;
|
|
3952
|
+
}
|
|
3953
|
+
if (parsed.origin !== window.location.origin || !isPluginLauncherPath(parsed.pathname)) {
|
|
3918
3954
|
return null;
|
|
3919
3955
|
}
|
|
3920
3956
|
const label = typeof plugin.title === 'string' && plugin.title.trim()
|
|
3921
3957
|
? plugin.title.trim()
|
|
3922
3958
|
: (typeof plugin.text === 'string' && plugin.text.trim() ? plugin.text.trim() : href);
|
|
3923
|
-
const
|
|
3924
|
-
const hasExec = runs.some((step) => step && step.method === 'exec');
|
|
3925
|
-
const normalized = href.replace(/^\/run/, '').replace(/^\/+/, '');
|
|
3959
|
+
const normalized = parsed.pathname.replace(/^\/run/, '').replace(/^\/+/, '');
|
|
3926
3960
|
const parts = normalized.split('/').filter(Boolean);
|
|
3927
3961
|
let value = '';
|
|
3928
3962
|
if (parts[0] === 'plugin' && parts.length >= 3) {
|
|
@@ -3944,7 +3978,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
3944
3978
|
label,
|
|
3945
3979
|
href,
|
|
3946
3980
|
iconSrc: typeof plugin.image === 'string' ? plugin.image : null,
|
|
3947
|
-
category:
|
|
3981
|
+
category: getPluginToolCategory(plugin),
|
|
3948
3982
|
isDefault: plugin.default === true
|
|
3949
3983
|
};
|
|
3950
3984
|
}).filter(Boolean);
|
|
@@ -4017,7 +4051,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
4017
4051
|
if (parsed.origin !== window.location.origin) {
|
|
4018
4052
|
return fallbackCwd;
|
|
4019
4053
|
}
|
|
4020
|
-
if (!parsed.pathname
|
|
4054
|
+
if (!isPluginLauncherPath(parsed.pathname)) {
|
|
4021
4055
|
return '';
|
|
4022
4056
|
}
|
|
4023
4057
|
const launchCwd = normalizeWorkspaceCwdForTerminalsDiscovery(parsed.searchParams.get('cwd') || '');
|
|
@@ -4114,7 +4148,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
4114
4148
|
let next = agentHref || '';
|
|
4115
4149
|
try {
|
|
4116
4150
|
const parsed = new URL(agentHref, window.location.origin);
|
|
4117
|
-
if (parsed.pathname
|
|
4151
|
+
if (isPluginLauncherPath(parsed.pathname)) {
|
|
4118
4152
|
if (workspaceCwd && !parsed.searchParams.has('cwd')) {
|
|
4119
4153
|
parsed.searchParams.set('cwd', workspaceCwd);
|
|
4120
4154
|
}
|
|
@@ -51,6 +51,25 @@
|
|
|
51
51
|
let modalPrevFocus = null;
|
|
52
52
|
let modalPrevInert = null;
|
|
53
53
|
|
|
54
|
+
function getPluginToolCategory(plugin) {
|
|
55
|
+
const explicitCategory = typeof plugin?.category === 'string' ? plugin.category.trim().toLowerCase() : '';
|
|
56
|
+
if (explicitCategory === 'ide') {
|
|
57
|
+
return 'IDE';
|
|
58
|
+
}
|
|
59
|
+
if (explicitCategory === 'cli') {
|
|
60
|
+
return 'CLI';
|
|
61
|
+
}
|
|
62
|
+
const launchType = typeof plugin?.launch_type === 'string' ? plugin.launch_type.trim().toLowerCase() : '';
|
|
63
|
+
if (launchType === 'desktop') {
|
|
64
|
+
return 'IDE';
|
|
65
|
+
}
|
|
66
|
+
if (launchType === 'terminal') {
|
|
67
|
+
return 'CLI';
|
|
68
|
+
}
|
|
69
|
+
const runs = Array.isArray(plugin?.run) ? plugin.run : [];
|
|
70
|
+
return runs.some((step) => step && step.method === 'exec') ? 'IDE' : 'CLI';
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
function mapPluginMenuToCreateLauncherTools(menu) {
|
|
55
74
|
if (!Array.isArray(menu)) return [];
|
|
56
75
|
|
|
@@ -90,16 +109,13 @@
|
|
|
90
109
|
return null;
|
|
91
110
|
}
|
|
92
111
|
const iconSrc = plugin.image || null;
|
|
93
|
-
const runs = Array.isArray(plugin.run) ? plugin.run : [];
|
|
94
|
-
const hasExec = runs.some((step) => step && step.method === 'exec');
|
|
95
|
-
const category = hasExec ? 'IDE' : 'CLI';
|
|
96
112
|
return {
|
|
97
113
|
value,
|
|
98
114
|
label,
|
|
99
115
|
iconSrc,
|
|
100
116
|
isDefault: Boolean(plugin.default === true),
|
|
101
117
|
href: href || null,
|
|
102
|
-
category,
|
|
118
|
+
category: getPluginToolCategory(plugin),
|
|
103
119
|
};
|
|
104
120
|
})
|
|
105
121
|
.filter(Boolean);
|
|
@@ -163,7 +163,12 @@
|
|
|
163
163
|
const normalizedPath = plugin.pluginPath.startsWith("/") ? plugin.pluginPath.slice(1) : plugin.pluginPath;
|
|
164
164
|
if (!normalizedPath) return null;
|
|
165
165
|
const encodedPath = normalizedPath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
166
|
-
|
|
166
|
+
const params = new URLSearchParams();
|
|
167
|
+
if (plugin.defaultCwd) {
|
|
168
|
+
params.set("cwd", plugin.defaultCwd);
|
|
169
|
+
}
|
|
170
|
+
params.set("ts", String(Date.now()));
|
|
171
|
+
return `/action/${encodeURIComponent(actionType)}/${encodedPath}?${params.toString()}`;
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
function showActionModal(actionType) {
|
|
@@ -215,6 +220,29 @@
|
|
|
215
220
|
});
|
|
216
221
|
}
|
|
217
222
|
|
|
223
|
+
function buildPluginLaunchTarget(app) {
|
|
224
|
+
if (!plugin || !plugin.pluginPath || !app || !app.name) {
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
const queryPairs = [];
|
|
228
|
+
const pushPair = (key, value, { rawValue = false } = {}) => {
|
|
229
|
+
if (value === undefined || value === null) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const encodedKey = encodeURIComponent(key);
|
|
233
|
+
const encodedValue = rawValue ? String(value) : encodeURIComponent(String(value));
|
|
234
|
+
queryPairs.push(`${encodedKey}=${encodedValue}`);
|
|
235
|
+
};
|
|
236
|
+
pushPair("plugin", plugin.pluginPath, { rawValue: true });
|
|
237
|
+
if (Array.isArray(plugin.extraParams)) {
|
|
238
|
+
plugin.extraParams.forEach(([key, value]) => {
|
|
239
|
+
pushPair(key, value);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const queryString = queryPairs.join("&");
|
|
243
|
+
return queryPairs.length > 0 ? `/p/${app.name}/dev?${queryString}` : `/p/${app.name}/dev`;
|
|
244
|
+
}
|
|
245
|
+
|
|
218
246
|
function createPluginModal(appList) {
|
|
219
247
|
const overlay = document.createElement("div");
|
|
220
248
|
overlay.className = "modal-overlay url-modal-overlay plugin-modal-overlay";
|
|
@@ -413,23 +441,12 @@
|
|
|
413
441
|
alert("Select a project to continue.");
|
|
414
442
|
return;
|
|
415
443
|
}
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const encodedKey = encodeURIComponent(key);
|
|
422
|
-
const encodedValue = rawValue ? String(value) : encodeURIComponent(String(value));
|
|
423
|
-
queryPairs.push(`${encodedKey}=${encodedValue}`);
|
|
424
|
-
};
|
|
425
|
-
pushPair("plugin", plugin.pluginPath, { rawValue: true });
|
|
426
|
-
if (Array.isArray(plugin.extraParams)) {
|
|
427
|
-
plugin.extraParams.forEach(([key, value]) => {
|
|
428
|
-
pushPair(key, value);
|
|
429
|
-
});
|
|
444
|
+
const target = buildPluginLaunchTarget(app);
|
|
445
|
+
if (!target) {
|
|
446
|
+
closeModal();
|
|
447
|
+
alert("This plugin is missing a launch target.");
|
|
448
|
+
return;
|
|
430
449
|
}
|
|
431
|
-
const queryString = queryPairs.join("&");
|
|
432
|
-
const target = queryPairs.length > 0 ? `/p/${app.name}/dev?${queryString}` : `/p/${app.name}/dev`;
|
|
433
450
|
closeModal();
|
|
434
451
|
location.href = target;
|
|
435
452
|
}
|
|
@@ -1458,6 +1458,18 @@ body.dark .task-badge-warning {
|
|
|
1458
1458
|
color: var(--task-accent-contrast);
|
|
1459
1459
|
}
|
|
1460
1460
|
|
|
1461
|
+
.plugin-detail-page .task-button.primary[data-plugin-open] {
|
|
1462
|
+
background: var(--task-run-button-bg);
|
|
1463
|
+
border-color: var(--task-run-button-bg);
|
|
1464
|
+
color: var(--task-run-button-color);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
.plugin-detail-page .task-button.primary[data-plugin-open]:hover {
|
|
1468
|
+
background: var(--task-run-button-hover);
|
|
1469
|
+
border-color: var(--task-run-button-hover);
|
|
1470
|
+
color: var(--task-run-button-color);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1461
1473
|
.task-button.danger,
|
|
1462
1474
|
.task-link-button.danger {
|
|
1463
1475
|
border-color: rgba(220, 38, 38, 0.18);
|
|
@@ -56,18 +56,27 @@
|
|
|
56
56
|
if (!plugin || typeof plugin !== "object") return null;
|
|
57
57
|
const href = typeof plugin.href === "string" ? plugin.href.trim() : "";
|
|
58
58
|
if (!href) return null;
|
|
59
|
-
|
|
59
|
+
const normalized = href.replace(/^\/run/, "").replace(/^\/+/, "");
|
|
60
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
61
|
+
let value = "";
|
|
62
|
+
if (parts[0] === "plugin" && parts.length >= 3) {
|
|
63
|
+
value = parts.slice(1, -1).join("/");
|
|
64
|
+
} else {
|
|
65
|
+
value = normalized;
|
|
66
|
+
}
|
|
60
67
|
if (value.endsWith("/pinokio.js")) {
|
|
61
68
|
value = value.replace(/\/pinokio\.js$/i, "");
|
|
62
69
|
}
|
|
63
70
|
if (!value) return null;
|
|
71
|
+
const explicitCategory = typeof plugin.category === "string" ? plugin.category.trim().toLowerCase() : "";
|
|
72
|
+
const launchType = typeof plugin.launch_type === "string" ? plugin.launch_type.trim().toLowerCase() : "";
|
|
64
73
|
const runs = Array.isArray(plugin.run) ? plugin.run : [];
|
|
65
74
|
const hasExec = runs.some((step) => step && step.method === "exec");
|
|
66
75
|
return {
|
|
67
76
|
value,
|
|
68
77
|
label: plugin.title || plugin.text || plugin.name || value,
|
|
69
78
|
iconSrc: plugin.image || null,
|
|
70
|
-
category: hasExec ? "IDE" : "CLI",
|
|
79
|
+
category: (explicitCategory === "ide" || launchType === "desktop" || hasExec) ? "IDE" : "CLI",
|
|
71
80
|
isDefault: plugin.default === true
|
|
72
81
|
};
|
|
73
82
|
}).filter(Boolean);
|
|
@@ -210,21 +210,34 @@
|
|
|
210
210
|
const href = typeof plugin.href === 'string' ? plugin.href.trim() : '';
|
|
211
211
|
if (!href) return null;
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
const normalized = href.replace(/^\/run/, '').replace(/^\/+/, '');
|
|
214
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
215
|
+
let value = '';
|
|
216
|
+
if (parts[0] === 'plugin' && parts.length >= 3) {
|
|
217
|
+
value = parts.slice(1, -1).join('/');
|
|
218
|
+
} else {
|
|
219
|
+
value = normalized;
|
|
220
|
+
}
|
|
214
221
|
if (value.endsWith('/pinokio.js')) {
|
|
215
222
|
value = value.replace(/\/pinokio\.js$/i, '');
|
|
216
223
|
}
|
|
217
224
|
if (!value) return null;
|
|
218
225
|
|
|
226
|
+
const explicitCategory = typeof plugin.category === 'string' ? plugin.category.trim().toLowerCase() : '';
|
|
227
|
+
const launchType = typeof plugin.launch_type === 'string' ? plugin.launch_type.trim().toLowerCase() : '';
|
|
219
228
|
const runs = Array.isArray(plugin.run) ? plugin.run : [];
|
|
220
229
|
const hasExec = runs.some((step) => step && step.method === 'exec');
|
|
230
|
+
let category = 'CLI';
|
|
231
|
+
if (explicitCategory === 'ide' || launchType === 'desktop' || hasExec) {
|
|
232
|
+
category = 'IDE';
|
|
233
|
+
}
|
|
221
234
|
|
|
222
235
|
return {
|
|
223
236
|
value,
|
|
224
237
|
label: plugin.title || plugin.text || plugin.name || value,
|
|
225
238
|
iconSrc: plugin.image || null,
|
|
226
239
|
isDefault: plugin.default === true,
|
|
227
|
-
category
|
|
240
|
+
category,
|
|
228
241
|
};
|
|
229
242
|
}).filter(Boolean);
|
|
230
243
|
}
|
package/server/views/app.ejs
CHANGED
|
@@ -608,6 +608,11 @@ body.dark .ask-ai-drawer-location-input {
|
|
|
608
608
|
flex-direction: column;
|
|
609
609
|
}
|
|
610
610
|
.ask-ai-drawer-empty {
|
|
611
|
+
flex: 1 1 auto;
|
|
612
|
+
min-height: 0;
|
|
613
|
+
overflow-y: auto;
|
|
614
|
+
overscroll-behavior: contain;
|
|
615
|
+
-webkit-overflow-scrolling: touch;
|
|
611
616
|
padding: 16px;
|
|
612
617
|
display: flex;
|
|
613
618
|
flex-direction: column;
|
|
@@ -5369,7 +5374,7 @@ header.navheader .mode-selector .community-mode-toggle {
|
|
|
5369
5374
|
renderSelection({ force: true })
|
|
5370
5375
|
}, delay)
|
|
5371
5376
|
}
|
|
5372
|
-
|
|
5377
|
+
let pluginLaunchActive = (() => {
|
|
5373
5378
|
try {
|
|
5374
5379
|
const params = new URLSearchParams(window.location.search)
|
|
5375
5380
|
return params.has('plugin')
|
|
@@ -6026,9 +6031,9 @@ header.navheader .mode-selector .community-mode-toggle {
|
|
|
6026
6031
|
const href = typeof launch.href === "string" ? launch.href : ""
|
|
6027
6032
|
try {
|
|
6028
6033
|
const parsed = new URL(href, window.location.origin)
|
|
6029
|
-
if (parsed.pathname.startsWith("/run/plugin/")) {
|
|
6034
|
+
if (parsed.pathname.startsWith("/run/plugin/") || (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))) {
|
|
6030
6035
|
const parts = parsed.pathname.split("/").filter(Boolean)
|
|
6031
|
-
const pluginSlug = parts.length >=
|
|
6036
|
+
const pluginSlug = parts.length >= 2 ? parts[parts.length - 2] : ""
|
|
6032
6037
|
const pluginLabel = titleCaseSlug(pluginSlug)
|
|
6033
6038
|
if (pluginLabel) {
|
|
6034
6039
|
return pluginLabel
|
|
@@ -6790,6 +6795,10 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6790
6795
|
}
|
|
6791
6796
|
|
|
6792
6797
|
const devTab = document.querySelector('#devtab.frame-link')
|
|
6798
|
+
if (!target && preselected && preselected !== devTab) {
|
|
6799
|
+
target = preselected
|
|
6800
|
+
}
|
|
6801
|
+
|
|
6793
6802
|
if (!triggeredByUser && !resolvedByGlobalSelector && !target && devRouteActive && devTab) {
|
|
6794
6803
|
const defaultCandidate = getDefaultSelection()
|
|
6795
6804
|
if (pluginLaunchActive && defaultCandidate) {
|
|
@@ -7288,7 +7297,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7288
7297
|
try {
|
|
7289
7298
|
const pageUrl = new URL(window.location.href)
|
|
7290
7299
|
const pluginPath = pageUrl.searchParams.get("plugin")
|
|
7291
|
-
if (!pluginPath || !pluginPath.startsWith("/plugin/")) {
|
|
7300
|
+
if (!pluginPath || (!pluginPath.startsWith("/plugin/") && !pluginPath.startsWith("/api/"))) {
|
|
7292
7301
|
return null
|
|
7293
7302
|
}
|
|
7294
7303
|
const launchUrl = new URL(`/run${pluginPath}`, window.location.origin)
|
|
@@ -7334,6 +7343,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7334
7343
|
queryPluginLaunchHandled = true
|
|
7335
7344
|
const launched = await handleLaunchRequest(launchRequest.launch, { persist: false })
|
|
7336
7345
|
if (launched) {
|
|
7346
|
+
pluginLaunchActive = false
|
|
7337
7347
|
ignorePersistedSelection = false
|
|
7338
7348
|
stripSearchParams(launchRequest.consumedKeys)
|
|
7339
7349
|
return true
|
|
@@ -7357,6 +7367,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7357
7367
|
keys.push("plugin_label")
|
|
7358
7368
|
}
|
|
7359
7369
|
if (stripSearchParams(keys)) {
|
|
7370
|
+
pluginLaunchActive = false
|
|
7360
7371
|
resolvedPluginQueryStripped = true
|
|
7361
7372
|
}
|
|
7362
7373
|
}
|
|
@@ -12793,7 +12804,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
12793
12804
|
if (parsed.origin !== window.location.origin) {
|
|
12794
12805
|
return null
|
|
12795
12806
|
}
|
|
12796
|
-
if (
|
|
12807
|
+
if (
|
|
12808
|
+
parsed.pathname.startsWith("/run/plugin/")
|
|
12809
|
+
|| (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))
|
|
12810
|
+
) {
|
|
12797
12811
|
if (workspaceCwd && !parsed.searchParams.has("cwd")) {
|
|
12798
12812
|
parsed.searchParams.set("cwd", workspaceCwd)
|
|
12799
12813
|
}
|
|
@@ -12846,7 +12860,18 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
12846
12860
|
return null
|
|
12847
12861
|
}
|
|
12848
12862
|
const href = typeof plugin.href === "string" ? plugin.href.trim() : ""
|
|
12849
|
-
if (!href
|
|
12863
|
+
if (!href) {
|
|
12864
|
+
return null
|
|
12865
|
+
}
|
|
12866
|
+
let parsed
|
|
12867
|
+
try {
|
|
12868
|
+
parsed = new URL(href, window.location.origin)
|
|
12869
|
+
} catch (_) {
|
|
12870
|
+
return null
|
|
12871
|
+
}
|
|
12872
|
+
const isPluginLauncher = parsed.pathname.startsWith("/run/plugin/")
|
|
12873
|
+
|| (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))
|
|
12874
|
+
if (parsed.origin !== window.location.origin || !isPluginLauncher) {
|
|
12850
12875
|
return null
|
|
12851
12876
|
}
|
|
12852
12877
|
const label = typeof plugin.title === "string" && plugin.title.trim()
|
|
@@ -12855,14 +12880,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
12855
12880
|
const runs = Array.isArray(plugin.run) ? plugin.run : []
|
|
12856
12881
|
const hasExec = runs.some((step) => step && step.method === "exec")
|
|
12857
12882
|
const launchType = typeof plugin.launch_type === "string" ? plugin.launch_type.trim().toLowerCase() : ""
|
|
12883
|
+
const explicitCategory = typeof plugin.category === "string" ? plugin.category.trim().toLowerCase() : ""
|
|
12858
12884
|
const value = normalizeToolValue(href, label)
|
|
12859
12885
|
if (!value) {
|
|
12860
12886
|
return null
|
|
12861
12887
|
}
|
|
12862
12888
|
let category
|
|
12863
|
-
if (launchType === "desktop") {
|
|
12889
|
+
if (explicitCategory === "ide" || launchType === "desktop") {
|
|
12864
12890
|
category = "IDE"
|
|
12865
|
-
} else if (launchType === "terminal") {
|
|
12891
|
+
} else if (explicitCategory === "cli" || launchType === "terminal") {
|
|
12866
12892
|
category = "CLI"
|
|
12867
12893
|
} else {
|
|
12868
12894
|
category = hasExec ? "IDE" : "CLI"
|
package/server/views/d.ejs
CHANGED
|
@@ -894,7 +894,9 @@ const appendWorkspaceCwd = (value, cwd = workspaceCwd) => {
|
|
|
894
894
|
const raw = hasPrefix ? value.slice(1) : value
|
|
895
895
|
try {
|
|
896
896
|
const parsed = new URL(raw, window.location.origin)
|
|
897
|
-
|
|
897
|
+
const isPluginLauncher = parsed.pathname.startsWith("/run/plugin/")
|
|
898
|
+
|| (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))
|
|
899
|
+
if (parsed.origin !== window.location.origin || !isPluginLauncher) {
|
|
898
900
|
return value
|
|
899
901
|
}
|
|
900
902
|
if (!parsed.searchParams.has("cwd")) {
|
|
@@ -275,6 +275,7 @@
|
|
|
275
275
|
link: plugin.link || "",
|
|
276
276
|
pluginPath: plugin.pluginPath || "",
|
|
277
277
|
extraParams: Array.isArray(plugin.extraParams) ? plugin.extraParams : [],
|
|
278
|
+
defaultCwd: plugin.defaultCwd || "",
|
|
278
279
|
hasInstall: !!plugin.hasInstall,
|
|
279
280
|
hasUninstall: !!plugin.hasUninstall,
|
|
280
281
|
hasUpdate: !!plugin.hasUpdate,
|
|
@@ -895,6 +895,7 @@ const createPluginTerminalDiscoveryRefresher = (context = {}) => {
|
|
|
895
895
|
const enabled = (() => {
|
|
896
896
|
try {
|
|
897
897
|
return window.location.pathname.startsWith("/run/plugin/")
|
|
898
|
+
|| (window.location.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(window.location.pathname))
|
|
898
899
|
} catch (_) {
|
|
899
900
|
return false
|
|
900
901
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[api shell.run]
|
|
2
|
+
|
|
3
|
+
The default interactive shell is now zsh.
|
|
4
|
+
To update your account to use zsh, please run `chsh -s /bin/zsh`.
|
|
5
|
+
For more details, please visit https://support.apple.com/kb/HT208050.
|
|
6
|
+
<<PINOKIO_SHELL>>eval "$(conda shell.bash hook)" ; conda deactivate ; conda deactivate ; conda deactivate ; conda activate base && npm install -g @sourcegraph/amp@latest
|
|
7
|
+
|
|
8
|
+
added 3 packages in 3s
|
|
9
|
+
|
|
10
|
+
1 package is looking for funding
|
|
11
|
+
run `npm fund` for details
|
|
12
|
+
(base) <<PINOKIO_SHELL>>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[api shell.run]
|
|
2
|
+
|
|
3
|
+
The default interactive shell is now zsh.
|
|
4
|
+
To update your account to use zsh, please run `chsh -s /bin/zsh`.
|
|
5
|
+
For more details, please visit https://support.apple.com/kb/HT208050.
|
|
6
|
+
<<PINOKIO_SHELL>>eval "$(conda shell.bash hook)" ; conda deactivate ; conda deactivate ; conda deactivate ; conda activate base && npm install -g @sourcegraph/amp@latest
|
|
7
|
+
|
|
8
|
+
added 3 packages in 3s
|
|
9
|
+
|
|
10
|
+
1 package is looking for funding
|
|
11
|
+
run `npm fund` for details
|
|
12
|
+
(base) <<PINOKIO_SHELL>>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
[api shell.run]
|
|
2
|
+
|
|
3
|
+
The default interactive shell is now zsh.
|
|
4
|
+
To update your account to use zsh, please run `chsh -s /bin/zsh`.
|
|
5
|
+
For more details, please visit https://support.apple.com/kb/HT208050.
|
|
6
|
+
<<PINOKIO_SHELL>>eval "$(conda shell.bash hook)" ; conda deactivate ; conda deactivate ; conda deactivate ; conda activate base && npm uninstall -g @mariozechner/pi-coding-agent
|
|
7
|
+
|
|
8
|
+
up to date in 355ms
|
|
9
|
+
(base) <<PINOKIO_SHELL>>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
2026-04-05T22:37:30.680Z [memory] {"ts":1775428650679,"step":0,"input":{"ts":"1775428650149"},"args":{"ts":"1775428650149"},"global":{},"local":{},"port":42003}
|
|
2
|
+
2026-04-05T22:37:30.680Z [memory]
|
|
3
|
+
2026-04-05T22:37:34.296Z [memory] {"ts":1775428654295,"step":1,"input":{"id":"93c7335b-5c6c-4d43-87ae-e573d660d6bb"},"args":{"ts":"1775428650149"},"global":{},"local":{},"port":42003}
|
|
4
|
+
2026-04-05T22:37:34.296Z [memory]
|
|
5
|
+
2026-04-05T22:37:34.311Z [api fs.rm]
|
|
6
|
+
2026-04-05T22:37:34.311Z [api fs.rm] removing:
|
|
7
|
+
2026-04-05T22:37:34.311Z [api fs.rm] /Users/x/pinokio/plugin/pi
|
|
8
|
+
2026-04-05T22:37:34.311Z [api fs.rm]
|
|
9
|
+
2026-04-05T22:37:34.318Z [api fs.rm] done
|
|
10
|
+
2026-04-05T22:37:34.318Z [api fs.rm]
|