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.
@@ -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"), cwd)
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
  },
@@ -4,7 +4,7 @@ const semver = require('semver')
4
4
 
5
5
  class Bluefairy {
6
6
  description = "Installs Bluefairy, a standalone package freshness guard."
7
- version = ">=0.0.16"
7
+ version = ">=0.0.17"
8
8
 
9
9
  packageName() {
10
10
  return "bluefairy"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.1.32",
3
+ "version": "7.1.33",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
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
- if (config) {
5020
-
5021
- let c = safeStructuredClone(config)
5022
- let menu = safeStructuredClone(terminal.menu)
5023
- c.menu = c.menu.concat(menu)
5024
- try {
5025
- let info = new Info(this.kernel)
5026
- info.cwd = () => {
5027
- return filepath
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
- let menu = c.menu.map((item) => {
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
- let plugin = { menu }
5039
- let uri = filepath
5040
- await this.renderMenu(req, uri, filepath, plugin, [])
5199
+ let plugin = { menu: menuItems }
5200
+ let uri = filepath
5201
+ await this.renderMenu(req, uri, filepath, plugin, [])
5041
5202
 
5042
- function setOnlineIfRunning(obj) {
5043
- if (Array.isArray(obj)) {
5044
- for (const item of obj) setOnlineIfRunning(item);
5045
- } else if (obj && typeof obj === 'object') {
5046
- if (obj.running === true) obj.online = true;
5047
- for (const key in obj) setOnlineIfRunning(obj[key]);
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
- setOnlineIfRunning(plugin)
5212
+ setOnlineIfRunning(plugin)
5052
5213
 
5053
- return plugin
5054
- } catch (e) {
5055
- console.log("getPlugin ERROR", e)
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
- if (config) {
5064
- let c = safeStructuredClone(config)
5065
- try {
5066
-
5067
- let filepath = this.kernel.path("api", name)
5068
- let terminal = await this.terminals(filepath)
5069
- c.menu = c.menu.concat(terminal.menu)
5070
- let menu = c.menu.map((item) => {
5071
- return {
5072
- params: {
5073
- cwd: filepath,
5074
- },
5075
- ...item
5076
- }
5077
- })
5078
- let plugin = { menu }
5079
- let uri = this.kernel.path("api")
5080
- await this.renderMenu(req, uri, name, plugin, [])
5081
- return plugin
5082
- } catch (e) {
5083
- console.log("getPlugin ERROR", e)
5084
- return null
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
- return pluginMenu.map((pluginItem, index) => serializePluginMenuItem(pluginItem, index))
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 apipath = this.kernel.path("api")
6546
- const entries = await fs.promises.readdir(apipath, { withFileTypes: true })
6547
- for (const entry of entries) {
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
- try {
6559
- const meta = await this.kernel.api.meta(entry.name)
6560
- const absolutePath = meta && meta.path ? meta.path : this.kernel.path("api", entry.name)
6561
- let displayPath = absolutePath
6562
- if (this.kernel.homedir && absolutePath.startsWith(this.kernel.homedir)) {
6563
- const relative = path.relative(this.kernel.homedir, absolutePath)
6564
- if (!relative || relative === "." || relative === "") {
6565
- displayPath = "~"
6566
- } else if (!relative.startsWith("..")) {
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
- if (!this.kernel.plugin.config) {
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
- const normalizedTool = typeof toolValue === "string" ? toolValue.trim().replace(/^\/+|\/+$/g, "") : ""
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 apipath = this.kernel.path('api')
377
- const entries = await fs.promises.readdir(apipath, { withFileTypes: true })
378
- for (const entry of entries) {
379
- let type
380
- try {
381
- type = await Util.file_type(apipath, entry)
382
- } catch (typeErr) {
383
- console.warn('Failed to inspect api entry', entry.name, typeErr)
384
- continue
385
- }
386
- if (!type || !type.directory) {
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 = {}) {
@@ -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 || !href.startsWith('/run/plugin/')) {
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 runs = Array.isArray(plugin.run) ? plugin.run : [];
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: hasExec ? 'IDE' : 'CLI',
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.startsWith('/run/plugin/')) {
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.startsWith('/run/plugin/')) {
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
- return `/action/${encodeURIComponent(actionType)}/${encodedPath}?ts=${Date.now()}`;
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 queryPairs = [];
417
- const pushPair = (key, value, { rawValue = false } = {}) => {
418
- if (value === undefined || value === null) {
419
- return;
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
- let value = href.replace(/^\/run\/plugin\//, "").replace(/^\/+/, "");
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
- let value = href.replace(/^\/run\/plugin\//, '').replace(/^\/+/, '');
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: hasExec ? 'IDE' : 'CLI',
240
+ category,
228
241
  };
229
242
  }).filter(Boolean);
230
243
  }
@@ -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
- const pluginLaunchActive = (() => {
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 >= 3 ? parts[2] : ""
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 (parsed.pathname.startsWith("/run/plugin/")) {
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 || !href.startsWith("/run/plugin/")) {
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"
@@ -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
- if (parsed.origin !== window.location.origin || !parsed.pathname.startsWith("/run/plugin/")) {
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,2 @@
1
+ 2026-04-05T22:37:59.178Z [memory] {"ts":1775428679177,"step":0,"input":{"ts":"1775428678743"},"args":{"ts":"1775428678743"},"global":{},"local":{},"port":42003}
2
+ 2026-04-05T22:37:59.178Z [memory]
@@ -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
+ [memory]
2
+ {"ts":1775428654295,"step":1,"input":{"id":"93c7335b-5c6c-4d43-87ae-e573d660d6bb"},"args":{"ts":"1775428650149"},"global":{},"local":{},"port":42003}
3
+
4
+ [api fs.rm]
5
+
6
+ removing:
7
+ /Users/x/pinokio/plugin/pi
8
+
9
+ [api fs.rm]
10
+ done
@@ -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]
@@ -0,0 +1,10 @@
1
+ [memory]
2
+ {"ts":1775428654295,"step":1,"input":{"id":"93c7335b-5c6c-4d43-87ae-e573d660d6bb"},"args":{"ts":"1775428650149"},"global":{},"local":{},"port":42003}
3
+
4
+ [api fs.rm]
5
+
6
+ removing:
7
+ /Users/x/pinokio/plugin/pi
8
+
9
+ [api fs.rm]
10
+ done