pinokiod 7.2.11 → 7.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/Dockerfile +0 -2
  2. package/kernel/api/index.js +14 -1
  3. package/kernel/api/process/index.js +99 -44
  4. package/kernel/api/uri/index.js +51 -0
  5. package/kernel/environment.js +22 -3
  6. package/kernel/git.js +0 -13
  7. package/kernel/index.js +61 -1
  8. package/kernel/plugin.js +6 -58
  9. package/kernel/plugin_sources.js +236 -0
  10. package/kernel/util.js +60 -0
  11. package/package.json +1 -1
  12. package/server/features/drafts/public/drafts.js +35 -12
  13. package/server/index.js +120 -142
  14. package/server/lib/content_validation.js +28 -25
  15. package/server/public/common.js +95 -29
  16. package/server/public/create-launcher.js +4 -31
  17. package/server/public/electron.css +1 -1
  18. package/server/public/style.css +337 -0
  19. package/server/public/task-launcher.js +5 -32
  20. package/server/public/universal-launcher.js +3 -26
  21. package/server/views/app.ejs +8 -29
  22. package/server/views/d.ejs +0 -33
  23. package/server/views/editor.ejs +25 -4
  24. package/server/views/shell.ejs +11 -3
  25. package/server/views/terminal.ejs +15 -3
  26. package/spec/INSTRUCTION_SYNC.md +5 -5
  27. package/system/plugin/antigravity/antigravity.png +0 -0
  28. package/system/plugin/antigravity/pinokio.js +37 -0
  29. package/system/plugin/claude/claude.png +0 -0
  30. package/system/plugin/claude/pinokio.js +63 -0
  31. package/system/plugin/claude-auto/claude.png +0 -0
  32. package/system/plugin/claude-auto/pinokio.js +74 -0
  33. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  34. package/system/plugin/claude-desktop/pinokio.js +39 -0
  35. package/system/plugin/codex/openai.webp +0 -0
  36. package/system/plugin/codex/pinokio.js +58 -0
  37. package/system/plugin/codex-auto/openai.webp +0 -0
  38. package/system/plugin/codex-auto/pinokio.js +65 -0
  39. package/system/plugin/codex-desktop/icon.png +0 -0
  40. package/system/plugin/codex-desktop/pinokio.js +39 -0
  41. package/system/plugin/crush/crush.png +0 -0
  42. package/system/plugin/crush/pinokio.js +31 -0
  43. package/system/plugin/cursor/cursor.jpeg +0 -0
  44. package/system/plugin/cursor/pinokio.js +39 -0
  45. package/system/plugin/gemini/gemini.jpeg +0 -0
  46. package/system/plugin/gemini/pinokio.js +40 -0
  47. package/system/plugin/gemini-auto/gemini.jpeg +0 -0
  48. package/system/plugin/gemini-auto/pinokio.js +43 -0
  49. package/system/plugin/qwen/pinokio.js +50 -0
  50. package/system/plugin/qwen/qwen.png +0 -0
  51. package/system/plugin/vscode/pinokio.js +36 -0
  52. package/system/plugin/vscode/vscode.png +0 -0
  53. package/system/plugin/windsurf/pinokio.js +39 -0
  54. package/system/plugin/windsurf/windsurf.png +0 -0
package/server/index.js CHANGED
@@ -78,6 +78,7 @@ const { createLauncherInstructionBootstrap } = require("./lib/launcher_instructi
78
78
  const { createTerminalGitResetHandler } = require("./lib/terminal_git_reset")
79
79
  const { createDesktopEventRouter } = require("./lib/desktop_event_router")
80
80
  const { createInjectRouter, resolveInjectList } = require("./lib/inject_router")
81
+ const PluginSources = require("../kernel/plugin_sources")
81
82
  const { createTaskPackageService } = require("./lib/task_packages")
82
83
  const { createTaskWorkspaceLinkService } = require("./lib/task_workspace_links")
83
84
  const { createWorkspaceRuntimeService } = require("./lib/workspace_runtime")
@@ -495,14 +496,12 @@ class Server {
495
496
  assignProjectSlug(obj)
496
497
  running_dynamic.push(obj)
497
498
  }
498
- } else if (href.startsWith("/run")) {
499
- let uri_path = new URL("http://localhost" + href).pathname
500
- let _filepath = uri_path.split("/").filter(x=>x).slice(1)
501
- let filepath = this.kernel.path(..._filepath)
499
+ } else if (PluginSources.isRunPath(href)) {
500
+ let filepath = PluginSources.resolveRunPath(this.kernel, href)
502
501
  let id = `${filepath}?cwd=${cwd}`
503
502
  obj.script_id = id
504
503
  //if (this.kernel.api.running[filepath]) {
505
- if (obj.src.startsWith("/run" + selected_query.plugin)) {
504
+ if (PluginSources.pluginSelectionMatches(obj.src, selected_query && selected_query.plugin)) {
506
505
  obj.running = true
507
506
  obj.display = "indent"
508
507
  obj.default = true
@@ -2397,8 +2396,10 @@ class Server {
2397
2396
  filepath = full_filepath
2398
2397
  }
2399
2398
 
2400
- if ((req.action || req.originalUrl.startsWith("/run/")) && this.contentValidation) {
2401
- const validation = await this.contentValidation.validateRunPath(pathComponents)
2399
+ if ((req.action || PluginSources.isRunPath(req.originalUrl)) && this.contentValidation) {
2400
+ const validation = await this.contentValidation.validateRunPath(pathComponents, {
2401
+ system: req.pinokioSystem === true,
2402
+ })
2402
2403
  if (validation && !validation.valid) {
2403
2404
  await this.renderInvalidContentPage(req, res, validation, {
2404
2405
  sidebarSelected: validation.type === "plugin" ? "plugins" : validation.type === "task" ? "tasks" : "home",
@@ -2875,6 +2876,9 @@ class Server {
2875
2876
  const draftWatchCwd = draftWatchEnabled
2876
2877
  ? (req.query.cwd || path.dirname(filepath))
2877
2878
  : ""
2879
+ const activeProcessWait = this.kernel.activeProcessWaits && this.kernel.activeProcessWaits[filepath]
2880
+ ? this.kernel.activeProcessWaits[filepath]
2881
+ : null
2878
2882
  const result = {
2879
2883
  portal: this.portal,
2880
2884
  projectName: (pathComponents.length > 0 ? pathComponents[0] : ''),
@@ -2882,6 +2886,11 @@ class Server {
2882
2886
  protection_enabled: protectionPreference ? protectionPreference.protection_enabled !== false : false,
2883
2887
  draft_watch_enabled: draftWatchEnabled,
2884
2888
  draft_watch_cwd: draftWatchCwd,
2889
+ active_process_wait: activeProcessWait ? {
2890
+ title: activeProcessWait.title,
2891
+ description: activeProcessWait.description,
2892
+ message: activeProcessWait.message
2893
+ } : null,
2885
2894
  kill_message,
2886
2895
  callback,
2887
2896
  callback_target,
@@ -2896,6 +2905,7 @@ class Server {
2896
2905
  //run: true, // run mode by default
2897
2906
  run: (req.query && req.query.mode === "source" ? false : true),
2898
2907
  stop: (req.query && req.query.stop ? true : false),
2908
+ readonly: req.pinokioSystem === true,
2899
2909
  pinokioPath,
2900
2910
  action: actionKey,
2901
2911
  runnable,
@@ -3112,6 +3122,7 @@ class Server {
3112
3122
  if (pathComponents.length === 0) {
3113
3123
  const normalizedApiRoot = path.normalize(this.kernel.path("api"))
3114
3124
  const normalizedPluginRoot = path.normalize(this.kernel.path("plugin"))
3125
+ const normalizedSystemPluginRoot = path.normalize(PluginSources.systemPluginRoot(this.kernel))
3115
3126
  const isPathWithinRoot = (candidatePath, rootPath) => {
3116
3127
  if (typeof candidatePath !== "string" || typeof rootPath !== "string") {
3117
3128
  return false
@@ -3137,6 +3148,9 @@ class Server {
3137
3148
  if (isPathWithinRoot(normalizedCandidate, normalizedPluginRoot)) {
3138
3149
  return true
3139
3150
  }
3151
+ if (isPathWithinRoot(normalizedCandidate, normalizedSystemPluginRoot)) {
3152
+ return true
3153
+ }
3140
3154
  const relativeToApiRoot = path.relative(normalizedApiRoot, normalizedCandidate)
3141
3155
  if (!relativeToApiRoot || relativeToApiRoot.startsWith("..") || path.isAbsolute(relativeToApiRoot)) {
3142
3156
  return false
@@ -3950,8 +3964,7 @@ class Server {
3950
3964
  if (menuitem.href.startsWith("http")) {
3951
3965
  menuitem.src = menuitem.href
3952
3966
  } else if (menuitem.href.startsWith("/")) {
3953
- let run_path = "/run"
3954
- if (menuitem.href.startsWith(run_path)) {
3967
+ if (PluginSources.isRunPath(menuitem.href)) {
3955
3968
  menuitem.src = menuitem.href
3956
3969
  // u = new URL("http://localhost" + menuitem.href.slice(run_path.length))
3957
3970
  // cwd = u.searchParams.get("cwd")
@@ -3971,7 +3984,14 @@ class Server {
3971
3984
  }
3972
3985
 
3973
3986
  // check running
3974
- let fullpath = this.kernel.path(menuitem.src.slice(1))
3987
+ let srcPathname = menuitem.src
3988
+ try {
3989
+ srcPathname = new URL("http://localhost" + menuitem.src).pathname
3990
+ } catch (_) {
3991
+ }
3992
+ let fullpath = PluginSources.isRunPath(srcPathname)
3993
+ ? PluginSources.resolveRunPath(this.kernel, srcPathname)
3994
+ : this.kernel.path(srcPathname.slice(1))
3975
3995
  let relpath = path.relative(this.kernel.homedir, fullpath)
3976
3996
  if (relpath.startsWith("api")) {
3977
3997
  // api script
@@ -5685,7 +5705,6 @@ class Server {
5685
5705
  "prototype/PTERM.md",
5686
5706
  ]
5687
5707
  const managedRefreshTargets = [
5688
- "plugin/code",
5689
5708
  "prototype/system",
5690
5709
  "network/system",
5691
5710
  "prototype/PINOKIO.md",
@@ -5707,6 +5726,16 @@ class Server {
5707
5726
 
5708
5727
  needsManagedRefresh = true
5709
5728
  console.log("[TRY] Updating to the new version")
5729
+ let envPath = path.resolve(home, "ENVIRONMENT")
5730
+ let envExists = await this.kernel.exists(envPath)
5731
+ if (!envExists) {
5732
+ let str = await Environment.ENV("system", home, this.kernel)
5733
+ await fs.promises.writeFile(envPath, str)
5734
+ }
5735
+ await Environment.ensurePinokioCacheDirs(this.kernel, {
5736
+ throwOnFailure: true,
5737
+ elevatedRepair: this.kernel.elevatedCacheRepair.bind(this.kernel)
5738
+ })
5710
5739
  this.kernel.store.set("version", this.version.pinokiod)
5711
5740
  console.log("[DONE] Updating to the new version")
5712
5741
  console.log("not up to date. update py.")
@@ -5719,9 +5748,6 @@ class Server {
5719
5748
  let p2 = path.resolve(home, "prototype/system")
5720
5749
  await fse.remove(p2)
5721
5750
 
5722
- let p3 = path.resolve(home, "plugin/code")
5723
- await fse.remove(p3)
5724
-
5725
5751
  let p4 = path.resolve(home, "network/system")
5726
5752
  await fse.remove(p4)
5727
5753
 
@@ -5790,10 +5816,6 @@ class Server {
5790
5816
  await this.kernel.proto.init()
5791
5817
  }
5792
5818
  if (needsManagedRefresh) {
5793
- const pluginReady = await this.kernel.exists("plugin/code")
5794
- if (!pluginReady && this.kernel.plugin && typeof this.kernel.plugin.init === "function") {
5795
- await this.kernel.plugin.init()
5796
- }
5797
5819
  const networkReady = await this.kernel.exists("network/system")
5798
5820
  if (!networkReady && this.kernel.router && typeof this.kernel.router.init === "function") {
5799
5821
  await this.kernel.router.init()
@@ -5974,6 +5996,10 @@ class Server {
5974
5996
  })
5975
5997
  this.app.use(express.static(path.resolve(__dirname, 'public')));
5976
5998
  this.app.use("/web", express.static(path.resolve(__dirname, "..", "..", "web")))
5999
+ this.app.use(PluginSources.SYSTEM_ASSET_PREFIX, express.static(PluginSources.systemRoot(this.kernel), {
6000
+ index: false,
6001
+ fallthrough: true,
6002
+ }))
5977
6003
  this.app.set('view engine', 'ejs');
5978
6004
  this.app.use((req, res, next) => {
5979
6005
  const peerForwarded = (req.get('X-Pinokio-Peer') || '').trim().toLowerCase()
@@ -6756,24 +6782,7 @@ class Server {
6756
6782
  }
6757
6783
  }
6758
6784
  const normalizePluginPath = (value) => {
6759
- let normalized = typeof value === "string" ? value.trim() : ""
6760
- if (!normalized) {
6761
- return ""
6762
- }
6763
- normalized = normalized.replace(/\\/g, "/")
6764
- if (/^https?:\/\//i.test(normalized)) {
6765
- try {
6766
- const parsed = new URL(normalized)
6767
- normalized = parsed.pathname || ""
6768
- } catch (_) {
6769
- }
6770
- }
6771
- normalized = normalized.replace(/^\/run(?=\/)/, "")
6772
- if (!normalized.startsWith("/")) {
6773
- normalized = `/${normalized}`
6774
- }
6775
- normalized = normalized.replace(/\/{2,}/g, "/").replace(/\/+$/, "")
6776
- return normalized
6785
+ return PluginSources.normalizePluginPath(value)
6777
6786
  }
6778
6787
  const normalizePluginLookupKey = (value) => {
6779
6788
  const normalized = normalizePluginPath(value)
@@ -6920,7 +6929,7 @@ class Server {
6920
6929
  index: -1,
6921
6930
  title: context.title || "Plugin",
6922
6931
  description: typeof config.description === "string" ? config.description : "",
6923
- href: `/run${normalizedPluginPath}`,
6932
+ href: PluginSources.pluginRunHrefForPath(normalizedPluginPath),
6924
6933
  link: typeof config.link === "string" ? config.link : "",
6925
6934
  image: context.image || null,
6926
6935
  icon: null,
@@ -6945,24 +6954,31 @@ class Server {
6945
6954
  }
6946
6955
  const resolvePluginLocalState = async (plugin) => {
6947
6956
  const pluginRoot = path.resolve(this.kernel.path("plugin"))
6948
- const managedRoot = path.resolve(pluginRoot, "code")
6949
6957
  const normalizedPath = normalizePluginPath(plugin && plugin.pluginPath ? plugin.pluginPath : "")
6950
6958
  const emptyState = {
6951
6959
  ownership: "bundled",
6952
6960
  manageable: false,
6953
6961
  canOpenFolder: false,
6954
6962
  pluginRoot,
6955
- managedRoot,
6956
6963
  pluginFilePath: "",
6957
6964
  pluginDir: "",
6958
6965
  relativeDir: "",
6959
6966
  relativeFile: "",
6960
6967
  localLabel: "",
6961
- managedPrefix: "",
6968
+ }
6969
+ if (PluginSources.isSystemPluginPath(normalizedPath)) {
6970
+ return {
6971
+ ...emptyState,
6972
+ ownership: "system",
6973
+ localLabel: normalizedPath.replace(/^\/pinokio\/run\//, ""),
6974
+ }
6962
6975
  }
6963
6976
  if (!normalizedPath.startsWith("/plugin/")) {
6964
6977
  return emptyState
6965
6978
  }
6979
+ if (PluginSources.isLegacyPluginCodePath(normalizedPath)) {
6980
+ return emptyState
6981
+ }
6966
6982
  const pluginFilePath = path.resolve(this.kernel.path(normalizedPath.slice(1)))
6967
6983
  if (!isPathInsideRoot(pluginFilePath, pluginRoot)) {
6968
6984
  return emptyState
@@ -6982,22 +6998,16 @@ class Server {
6982
6998
  }
6983
6999
  const relativeFile = path.relative(pluginRoot, pluginFilePath).split(path.sep).join("/")
6984
7000
  const relativeDir = path.relative(pluginRoot, pluginDir).split(path.sep).join("/")
6985
- const isManaged = isPathInsideRoot(pluginFilePath, managedRoot)
6986
- const managedPrefix = isManaged
6987
- ? path.relative(managedRoot, pluginDir).split(path.sep).join("/")
6988
- : ""
6989
7001
  return {
6990
- ownership: isManaged ? "managed" : "local",
6991
- manageable: Boolean(pluginFileExists && pluginDirExists && !isManaged),
7002
+ ownership: "local",
7003
+ manageable: Boolean(pluginFileExists && pluginDirExists),
6992
7004
  canOpenFolder: Boolean(pluginFileExists && pluginDirExists),
6993
7005
  pluginRoot,
6994
- managedRoot,
6995
7006
  pluginFilePath,
6996
7007
  pluginDir,
6997
7008
  relativeDir,
6998
7009
  relativeFile,
6999
7010
  localLabel: relativeDir ? `plugin/${relativeDir}` : "plugin",
7000
- managedPrefix,
7001
7011
  }
7002
7012
  }
7003
7013
  const collectPluginApps = async (boundAppName = "") => {
@@ -7041,11 +7051,31 @@ class Server {
7041
7051
  })
7042
7052
  return apps
7043
7053
  }
7044
- const buildPluginShareState = async (plugin) => {
7045
- const localState = await resolvePluginLocalState(plugin)
7046
- if (localState.ownership === "bundled") {
7054
+ const buildPluginShareState = async (plugin) => {
7055
+ const localState = await resolvePluginLocalState(plugin)
7056
+ if (localState.ownership === "system") {
7047
7057
  return {
7048
- ownership: "bundled",
7058
+ ownership: "system",
7059
+ manageable: false,
7060
+ canOpenFolder: false,
7061
+ dir: "",
7062
+ localLabel: localState.localLabel || "",
7063
+ remoteUrl: "",
7064
+ remoteWebUrl: "",
7065
+ githubConnected: false,
7066
+ gitInitialized: false,
7067
+ hasCommit: false,
7068
+ changeCount: 0,
7069
+ changes: [],
7070
+ branch: "HEAD",
7071
+ commitUrl: "",
7072
+ createUrl: "",
7073
+ pushUrl: "",
7074
+ }
7075
+ }
7076
+ if (localState.ownership === "bundled") {
7077
+ return {
7078
+ ownership: "bundled",
7049
7079
  manageable: false,
7050
7080
  canOpenFolder: false,
7051
7081
  dir: "",
@@ -7065,49 +7095,21 @@ class Server {
7065
7095
  }
7066
7096
 
7067
7097
  if (localState.ownership === "managed") {
7068
- let changes = []
7069
- try {
7070
- const headStatus = await this.getRepoHeadStatusByDir(localState.managedRoot)
7071
- const allChanges = Array.isArray(headStatus && headStatus.changes) ? headStatus.changes : []
7072
- const managedPrefix = typeof localState.managedPrefix === "string" ? localState.managedPrefix.trim() : ""
7073
- changes = allChanges
7074
- .filter((change) => {
7075
- const file = change && typeof change.file === "string" ? change.file : ""
7076
- if (!managedPrefix) {
7077
- return true
7078
- }
7079
- return file === managedPrefix || file.startsWith(`${managedPrefix}/`)
7080
- })
7081
- .map((change) => {
7082
- const file = change && typeof change.file === "string" ? change.file : ""
7083
- if (!managedPrefix || !file) {
7084
- return change
7085
- }
7086
- const relativeFile = file === managedPrefix
7087
- ? path.posix.basename(managedPrefix)
7088
- : file.slice(managedPrefix.length + 1)
7089
- return {
7090
- ...change,
7091
- file: relativeFile || file
7092
- }
7093
- })
7094
- } catch (_) {
7095
- }
7096
7098
  return {
7097
- ownership: "managed",
7099
+ ownership: "bundled",
7098
7100
  manageable: false,
7099
- canOpenFolder: Boolean(localState.canOpenFolder),
7100
- dir: localState.pluginDir,
7101
- localLabel: localState.localLabel,
7102
- relativeDir: localState.relativeDir,
7103
- relativeFile: localState.relativeFile,
7101
+ canOpenFolder: false,
7102
+ dir: "",
7103
+ localLabel: "",
7104
+ relativeDir: "",
7105
+ relativeFile: "",
7104
7106
  remoteUrl: "",
7105
7107
  remoteWebUrl: "",
7106
7108
  githubConnected: false,
7107
7109
  gitInitialized: false,
7108
7110
  hasCommit: false,
7109
- changeCount: changes.length,
7110
- changes,
7111
+ changeCount: 0,
7112
+ changes: [],
7111
7113
  branch: "HEAD",
7112
7114
  commitUrl: "",
7113
7115
  createUrl: "",
@@ -7183,14 +7185,19 @@ class Server {
7183
7185
  : ""
7184
7186
  const remoteLabel = summarizeTaskRemoteLabel(remoteCandidate)
7185
7187
  const badges = []
7186
- if (ownership === "local") {
7188
+ if (ownership === "local") {
7189
+ badges.push({
7190
+ label: "Local plugin",
7191
+ tone: "accent"
7192
+ })
7193
+ } else if (ownership === "system") {
7187
7194
  badges.push({
7188
- label: "Local plugin",
7189
- tone: "accent"
7195
+ label: "Built-in plugin",
7196
+ tone: "neutral"
7190
7197
  })
7191
- } else if (ownership === "managed") {
7192
- badges.push({
7193
- label: "Managed by Pinokio",
7198
+ } else if (ownership === "managed") {
7199
+ badges.push({
7200
+ label: "Managed by Pinokio",
7194
7201
  tone: "neutral"
7195
7202
  })
7196
7203
  } else {
@@ -7235,13 +7242,12 @@ class Server {
7235
7242
  : (shareState.gitInitialized ? "No local changes" : "Not version tracked yet")
7236
7243
  githubPanelTitle = "GitHub"
7237
7244
  githubPanelCopy = ""
7238
- } else if (ownership === "managed") {
7239
- sourceLabel = "Managed folder"
7240
- sourceValue = shareState.localLabel
7241
- statusValue = changes.length > 0 ? pluralizeTaskFiles(changes.length) : "Updated with Pinokio"
7242
- githubPanelTitle = "Managed by Pinokio"
7243
- githubPanelCopy = "This plugin lives inside <code>plugin/code</code>, which Pinokio refreshes as part of its managed source. Open the folder if you need to inspect it, but don&apos;t treat it as your own publishable repo."
7244
- localChangesCopy = "These edits live inside Pinokio-managed source and may be overwritten by future Pinokio updates."
7245
+ } else if (ownership === "system") {
7246
+ sourceLabel = "System plugin"
7247
+ sourceValue = shareState.localLabel || sourceValue
7248
+ statusValue = "Read-only"
7249
+ githubPanelTitle = "Built in to Pinokio"
7250
+ githubPanelCopy = "This plugin ships with Pinokio and is not editable from the local plugin workspace."
7245
7251
  }
7246
7252
  const changePreview = changes.slice(0, 6).map((change) => ({
7247
7253
  file: change && change.file ? change.file : "",
@@ -8106,24 +8112,7 @@ class Server {
8106
8112
  }
8107
8113
  return targetPath
8108
8114
  }
8109
- const resolveUniversalLauncherPluginHref = (toolValue) => {
8110
- let normalizedTool = typeof toolValue === "string" ? toolValue.trim() : ""
8111
- normalizedTool = normalizedTool.replace(/^https?:\/\/[^/]+/i, "")
8112
- normalizedTool = normalizedTool.replace(/^\/+|\/+$/g, "")
8113
- normalizedTool = normalizedTool.replace(/^run\//, "")
8114
- if (!normalizedTool || normalizedTool.includes("..") || !/^[A-Za-z0-9._/-]+$/.test(normalizedTool)) {
8115
- const error = new Error("Invalid plugin.")
8116
- error.status = 400
8117
- throw error
8118
- }
8119
- if (normalizedTool.startsWith("plugin/") || normalizedTool.startsWith("api/")) {
8120
- const scriptPath = normalizedTool.endsWith(".js")
8121
- ? normalizedTool
8122
- : `${normalizedTool}/pinokio.js`
8123
- return `/run/${scriptPath}`
8124
- }
8125
- return `/run/plugin/${normalizedTool}/pinokio.js`
8126
- }
8115
+ const resolveUniversalLauncherPluginHref = PluginSources.resolveLauncherPluginHref
8127
8116
  const persistLauncherPromptContext = async (targetPath, options = {}) => {
8128
8117
  const prompt = typeof options.prompt === "string" ? options.prompt.trim() : ""
8129
8118
  if (!prompt) {
@@ -12805,23 +12794,6 @@ class Server {
12805
12794
  })
12806
12795
  }
12807
12796
  }))
12808
- this.app.post("/plugin/update", ex(async (req, res) => {
12809
- try {
12810
- await this.kernel.exec({
12811
- message: "git pull",
12812
- path: this.kernel.path("plugin/code")
12813
- }, (e) => {
12814
- console.log(e)
12815
- })
12816
- res.json({
12817
- success: true
12818
- })
12819
- } catch (e) {
12820
- res.json({
12821
- error: e.stack
12822
- })
12823
- }
12824
- }))
12825
12797
  this.app.post("/network/reset", ex(async (req, res) => {
12826
12798
  let caddy_path = this.kernel.path("cache/XDG_DATA_HOME/caddy")
12827
12799
  await rimraf(caddy_path)
@@ -14253,6 +14225,17 @@ class Server {
14253
14225
  res.status(404).send(e.message)
14254
14226
  }
14255
14227
  }))
14228
+ this.app.get(`${PluginSources.SYSTEM_RUN_PREFIX}/*`, ex(async (req, res) => {
14229
+ const runPath = typeof req.params[0] === "string" ? req.params[0] : ""
14230
+ let pathComponents = runPath.split("/")
14231
+ req.base = PluginSources.systemRoot(this.kernel)
14232
+ req.pinokioSystem = true
14233
+ try {
14234
+ await this.render(req, res, pathComponents)
14235
+ } catch (e) {
14236
+ res.status(404).send(e.message)
14237
+ }
14238
+ }))
14256
14239
  this.app.get("/run/*", ex(async (req, res) => {
14257
14240
  const runPath = typeof req.params[0] === "string" ? req.params[0] : ""
14258
14241
  let pathComponents = runPath.split("/")
@@ -15678,12 +15661,7 @@ class Server {
15678
15661
  }))
15679
15662
  this.app.get("/bin_ready", ex(async (req, res) => {
15680
15663
  if (this.kernel.bin && !this.kernel.bin.requirements_pending) {
15681
- let code_exists = await this.kernel.exists("plugin/code")
15682
- if (code_exists) {
15683
- res.json({ success: true })
15684
- } else {
15685
- res.json({ success: false })
15686
- }
15664
+ res.json({ success: true })
15687
15665
  } else {
15688
15666
  res.json({ success: false })
15689
15667
  }
@@ -1,6 +1,7 @@
1
1
  const fs = require("fs")
2
2
  const path = require("path")
3
3
  const clearModule = require("clear-module")
4
+ const PluginSources = require("../../kernel/plugin_sources")
4
5
 
5
6
  const {
6
7
  TASK_CONFIG_FILENAME,
@@ -117,23 +118,7 @@ function createContentValidationService({ kernel }) {
117
118
  }
118
119
 
119
120
  const normalizePluginPath = (value) => {
120
- let normalized = typeof value === "string" ? value.trim() : ""
121
- if (!normalized) {
122
- return ""
123
- }
124
- normalized = normalized.replace(/\\/g, "/")
125
- normalized = normalized.replace(/^\/run(?=\/)/, "")
126
- if (!normalized.startsWith("/")) {
127
- normalized = `/${normalized}`
128
- }
129
- normalized = normalized.replace(/\/{2,}/g, "/").replace(/\/+$/, "")
130
- if (!normalized) {
131
- return ""
132
- }
133
- if (!normalized.endsWith("/pinokio.js")) {
134
- normalized = `${normalized}/pinokio.js`
135
- }
136
- return normalized
121
+ return PluginSources.normalizePluginPath(value)
137
122
  }
138
123
 
139
124
  const normalizeBundledPluginSpec = (value) => {
@@ -169,9 +154,9 @@ function createContentValidationService({ kernel }) {
169
154
  }
170
155
 
171
156
  if (config && typeof config.icon === "string" && config.icon.trim()) {
172
- if (normalizedPath.startsWith("/plugin/")) {
173
- const relativeDir = path.posix.dirname(normalizedPath.slice(1))
174
- context.image = `/asset/${relativeDir}/${config.icon.trim()}`
157
+ const pluginImage = PluginSources.pluginAssetHrefForIcon(normalizedPath, config.icon)
158
+ if (pluginImage) {
159
+ context.image = pluginImage
175
160
  } else if (normalizedPath.startsWith("/api/")) {
176
161
  const segments = normalizedPath.replace(/^\/+/, "").split("/")
177
162
  const appName = segments[1] || ""
@@ -192,7 +177,10 @@ function createContentValidationService({ kernel }) {
192
177
  const fallbackTitle = normalizedPath
193
178
  ? path.basename(path.posix.dirname(normalizedPath))
194
179
  : "Plugin"
195
- if (!normalizedPath || (!normalizedPath.startsWith("/plugin/") && !normalizedPath.startsWith("/api/"))) {
180
+ const isSystemPlugin = PluginSources.isSystemPluginPath(normalizedPath)
181
+ const isHomePlugin = normalizedPath.startsWith("/plugin/")
182
+ const isApiPlugin = normalizedPath.startsWith("/api/")
183
+ if (!normalizedPath || (!isSystemPlugin && !isHomePlugin && !isApiPlugin)) {
196
184
  return buildInvalid({
197
185
  type: "plugin",
198
186
  subjectTitle: fallbackTitle,
@@ -205,8 +193,21 @@ function createContentValidationService({ kernel }) {
205
193
  detailUrl: "/plugins",
206
194
  })
207
195
  }
196
+ if (PluginSources.isLegacyPluginCodePath(normalizedPath)) {
197
+ return buildInvalid({
198
+ type: "plugin",
199
+ subjectTitle: fallbackTitle,
200
+ errors: [
201
+ buildError(
202
+ "This managed plugin path is no longer used.",
203
+ "Open the built-in Pinokio plugin instead."
204
+ ),
205
+ ],
206
+ detailUrl: "/plugins",
207
+ })
208
+ }
208
209
 
209
- const absolutePath = kernel.path(normalizedPath.replace(/^\/+/, ""))
210
+ const absolutePath = PluginSources.pluginPathToAbsolute(kernel, normalizedPath)
210
211
  const folderPath = path.dirname(absolutePath)
211
212
  const loaded = await loadJsFile(absolutePath)
212
213
  if (!loaded.exists) {
@@ -267,9 +268,8 @@ function createContentValidationService({ kernel }) {
267
268
  ))
268
269
  }
269
270
  if (normalizedPath.startsWith("/plugin/")) {
270
- const isManagedDefaultPlugin = normalizedPath.startsWith("/plugin/code/")
271
271
  const declaredPath = typeof config.path === "string" ? config.path.trim() : ""
272
- if (!isManagedDefaultPlugin && declaredPath !== "plugin") {
272
+ if (declaredPath !== "plugin") {
273
273
  errors.push(buildError(
274
274
  'Standalone plugins must set `path: "plugin"`.',
275
275
  'Add `path: "plugin"` to pinokio.js.',
@@ -567,7 +567,7 @@ function createContentValidationService({ kernel }) {
567
567
  })
568
568
  }
569
569
 
570
- const validateRunPath = async (pathComponents) => {
570
+ const validateRunPath = async (pathComponents, options = {}) => {
571
571
  if (!Array.isArray(pathComponents) || pathComponents.length === 0) {
572
572
  return buildValid()
573
573
  }
@@ -576,6 +576,9 @@ function createContentValidationService({ kernel }) {
576
576
  return buildValid()
577
577
  }
578
578
  if (normalized[0] === "plugin") {
579
+ if (options && options.system) {
580
+ return validatePluginByPath(`${PluginSources.SYSTEM_RUN_PREFIX}/${normalized.join("/")}`)
581
+ }
579
582
  return validatePluginByPath(`/${normalized.join("/")}`)
580
583
  }
581
584
  if (normalized[0] === "api" && normalized.length > 1) {