pinokiod 7.2.18 → 7.3.0

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 (89) hide show
  1. package/Dockerfile +2 -0
  2. package/kernel/api/index.js +13 -179
  3. package/kernel/api/process/index.js +44 -99
  4. package/kernel/bin/conda-python.js +30 -0
  5. package/kernel/bin/conda.js +22 -3
  6. package/kernel/bin/huggingface.js +1 -1
  7. package/kernel/bin/index.js +11 -1
  8. package/kernel/environment.js +11 -205
  9. package/kernel/git.js +13 -0
  10. package/kernel/index.js +1 -64
  11. package/kernel/plugin.js +58 -6
  12. package/kernel/prototype.js +0 -4
  13. package/kernel/shell.js +2 -23
  14. package/kernel/util.js +0 -60
  15. package/package.json +1 -1
  16. package/server/index.js +171 -229
  17. package/server/lib/content_validation.js +33 -47
  18. package/server/public/common.js +29 -103
  19. package/server/public/create-launcher.js +31 -4
  20. package/server/public/electron.css +6 -0
  21. package/server/public/style.css +0 -337
  22. package/server/public/task-launcher.css +3 -11
  23. package/server/public/task-launcher.js +32 -5
  24. package/server/public/universal-launcher.js +26 -3
  25. package/server/socket.js +11 -22
  26. package/server/views/app.ejs +30 -167
  27. package/server/views/d.ejs +35 -33
  28. package/server/views/editor.ejs +4 -25
  29. package/server/views/partials/main_sidebar.ejs +0 -1
  30. package/server/views/partials/menu.ejs +1 -1
  31. package/server/views/pre.ejs +1 -1
  32. package/server/views/shell.ejs +3 -11
  33. package/server/views/task_launch.ejs +10 -10
  34. package/server/views/terminal.ejs +5 -34
  35. package/spec/INSTRUCTION_SYNC.md +5 -5
  36. package/kernel/agent_instructions.js +0 -166
  37. package/kernel/api/shell_run_template.js +0 -273
  38. package/kernel/api/uri/index.js +0 -51
  39. package/kernel/plugin_sources.js +0 -289
  40. package/kernel/watch/context.js +0 -42
  41. package/kernel/watch/drivers/fs.js +0 -71
  42. package/kernel/watch/drivers/poll.js +0 -33
  43. package/kernel/watch/index.js +0 -185
  44. package/server/features/index.js +0 -13
  45. package/server/features/notes/index.js +0 -41
  46. package/server/features/notes/parser.js +0 -174
  47. package/server/features/notes/public/notes.css +0 -955
  48. package/server/features/notes/public/notes.js +0 -1149
  49. package/server/features/notes/registry_import.js +0 -412
  50. package/server/features/notes/routes.js +0 -156
  51. package/server/features/notes/service.js +0 -326
  52. package/server/features/notes/watcher.js +0 -74
  53. package/server/lib/workspace_catalog.js +0 -151
  54. package/server/lib/workspace_runtime.js +0 -390
  55. package/server/public/tasker.css +0 -336
  56. package/server/public/tasker.js +0 -407
  57. package/server/routes/workspaces.js +0 -44
  58. package/server/views/partials/workspace_row.ejs +0 -61
  59. package/server/views/tasker.ejs +0 -40
  60. package/server/views/workspaces.ejs +0 -813
  61. package/system/plugin/antigravity/antigravity.png +0 -0
  62. package/system/plugin/antigravity/pinokio.js +0 -35
  63. package/system/plugin/claude/claude.png +0 -0
  64. package/system/plugin/claude/pinokio.js +0 -61
  65. package/system/plugin/claude-auto/claude.png +0 -0
  66. package/system/plugin/claude-auto/pinokio.js +0 -72
  67. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  68. package/system/plugin/claude-desktop/pinokio.js +0 -37
  69. package/system/plugin/codex/openai.webp +0 -0
  70. package/system/plugin/codex/pinokio.js +0 -56
  71. package/system/plugin/codex-auto/openai.webp +0 -0
  72. package/system/plugin/codex-auto/pinokio.js +0 -63
  73. package/system/plugin/codex-desktop/icon.png +0 -0
  74. package/system/plugin/codex-desktop/pinokio.js +0 -37
  75. package/system/plugin/crush/crush.png +0 -0
  76. package/system/plugin/crush/pinokio.js +0 -29
  77. package/system/plugin/cursor/cursor.jpeg +0 -0
  78. package/system/plugin/cursor/pinokio.js +0 -37
  79. package/system/plugin/gemini/gemini.jpeg +0 -0
  80. package/system/plugin/gemini/pinokio.js +0 -38
  81. package/system/plugin/gemini-auto/gemini.jpeg +0 -0
  82. package/system/plugin/gemini-auto/pinokio.js +0 -41
  83. package/system/plugin/qwen/pinokio.js +0 -48
  84. package/system/plugin/qwen/qwen.png +0 -0
  85. package/system/plugin/vscode/pinokio.js +0 -34
  86. package/system/plugin/vscode/vscode.png +0 -0
  87. package/system/plugin/windsurf/pinokio.js +0 -37
  88. package/system/plugin/windsurf/windsurf.png +0 -0
  89. package/test/plugin-sources.test.js +0 -45
package/server/index.js CHANGED
@@ -28,8 +28,6 @@ const system = require('systeminformation')
28
28
  const serveIndex = require('./serveIndex')
29
29
  const registerFileRoutes = require('./routes/files')
30
30
  const registerAppRoutes = require('./routes/apps')
31
- const registerWorkspacesRoutes = require('./routes/workspaces')
32
- const { mountFeatures } = require('./features')
33
31
  const Git = require("../kernel/git")
34
32
  const TerminalApi = require('../kernel/api/terminal')
35
33
 
@@ -78,11 +76,8 @@ const { createLauncherInstructionBootstrap } = require("./lib/launcher_instructi
78
76
  const { createTerminalGitResetHandler } = require("./lib/terminal_git_reset")
79
77
  const { createDesktopEventRouter } = require("./lib/desktop_event_router")
80
78
  const { createInjectRouter, resolveInjectList } = require("./lib/inject_router")
81
- const PluginSources = require("../kernel/plugin_sources")
82
79
  const { createTaskPackageService } = require("./lib/task_packages")
83
80
  const { createTaskWorkspaceLinkService } = require("./lib/task_workspace_links")
84
- const { createWorkspaceRuntimeService } = require("./lib/workspace_runtime")
85
- const { createWorkspaceCatalogService } = require("./lib/workspace_catalog")
86
81
  const { createContentValidationService } = require("./lib/content_validation")
87
82
  const { buildSecureRouterDebugSnapshot, createSecureRouterDebugStore } = require("./lib/secure_router_debug")
88
83
  const AppRegistryService = require("./lib/app_registry")
@@ -496,12 +491,14 @@ class Server {
496
491
  assignProjectSlug(obj)
497
492
  running_dynamic.push(obj)
498
493
  }
499
- } else if (PluginSources.isRunPath(href)) {
500
- let filepath = PluginSources.resolveRunPath(this.kernel, href)
494
+ } else if (href.startsWith("/run")) {
495
+ let uri_path = new URL("http://localhost" + href).pathname
496
+ let _filepath = uri_path.split("/").filter(x=>x).slice(1)
497
+ let filepath = this.kernel.path(..._filepath)
501
498
  let id = `${filepath}?cwd=${cwd}`
502
499
  obj.script_id = id
503
500
  //if (this.kernel.api.running[filepath]) {
504
- if (PluginSources.pluginSelectionMatches(obj.src, selected_query && selected_query.plugin)) {
501
+ if (obj.src.startsWith("/run" + selected_query.plugin)) {
505
502
  obj.running = true
506
503
  obj.display = "indent"
507
504
  obj.default = true
@@ -2396,10 +2393,8 @@ class Server {
2396
2393
  filepath = full_filepath
2397
2394
  }
2398
2395
 
2399
- if ((req.action || PluginSources.isRunPath(req.originalUrl)) && this.contentValidation) {
2400
- const validation = await this.contentValidation.validateRunPath(pathComponents, {
2401
- system: req.pinokioSystem === true,
2402
- })
2396
+ if ((req.action || req.originalUrl.startsWith("/run/")) && this.contentValidation) {
2397
+ const validation = await this.contentValidation.validateRunPath(pathComponents)
2403
2398
  if (validation && !validation.valid) {
2404
2399
  await this.renderInvalidContentPage(req, res, validation, {
2405
2400
  sidebarSelected: validation.type === "plugin" ? "plugins" : validation.type === "task" ? "tasks" : "home",
@@ -2758,12 +2753,10 @@ class Server {
2758
2753
  } else {
2759
2754
  resolved = runner(this.kernel, this.kernel.info)
2760
2755
  }
2761
- const action = resolved ? resolved[actionKey] : null
2762
- runnable = typeof action === "function" || (Array.isArray(action) && action.length > 0)
2756
+ runnable = resolved && Array.isArray(resolved[actionKey]) && resolved[actionKey].length > 0
2763
2757
  } else {
2758
+ runnable = runner && Array.isArray(runner[actionKey]) && runner[actionKey].length > 0
2764
2759
  resolved = runner
2765
- const action = resolved ? resolved[actionKey] : null
2766
- runnable = typeof action === "function" || (Array.isArray(action) && action.length > 0)
2767
2760
  }
2768
2761
 
2769
2762
  let template = "terminal"
@@ -2872,27 +2865,11 @@ class Server {
2872
2865
  const protectionPreference = protectionAppId && this.appPreferences && typeof this.appPreferences.getPreference === "function"
2873
2866
  ? await this.appPreferences.getPreference(protectionAppId)
2874
2867
  : null
2875
- const noteWatchEnabled = this.kernel.watch && typeof this.kernel.watch.hasHandler === "function"
2876
- ? this.kernel.watch.hasHandler(resolved, "note")
2877
- : false
2878
- const noteWatchCwd = noteWatchEnabled
2879
- ? (req.query.cwd || path.dirname(filepath))
2880
- : ""
2881
- const activeProcessWait = this.kernel.activeProcessWaits && this.kernel.activeProcessWaits[filepath]
2882
- ? this.kernel.activeProcessWaits[filepath]
2883
- : null
2884
2868
  const result = {
2885
2869
  portal: this.portal,
2886
2870
  projectName: (pathComponents.length > 0 ? pathComponents[0] : ''),
2887
2871
  protection_app_id: protectionAppId,
2888
2872
  protection_enabled: protectionPreference ? protectionPreference.protection_enabled !== false : false,
2889
- note_watch_enabled: noteWatchEnabled,
2890
- note_watch_cwd: noteWatchCwd,
2891
- active_process_wait: activeProcessWait ? {
2892
- title: activeProcessWait.title,
2893
- description: activeProcessWait.description,
2894
- message: activeProcessWait.message
2895
- } : null,
2896
2873
  kill_message,
2897
2874
  callback,
2898
2875
  callback_target,
@@ -2907,7 +2884,6 @@ class Server {
2907
2884
  //run: true, // run mode by default
2908
2885
  run: (req.query && req.query.mode === "source" ? false : true),
2909
2886
  stop: (req.query && req.query.stop ? true : false),
2910
- readonly: req.pinokioSystem === true,
2911
2887
  pinokioPath,
2912
2888
  action: actionKey,
2913
2889
  runnable,
@@ -3124,7 +3100,6 @@ class Server {
3124
3100
  if (pathComponents.length === 0) {
3125
3101
  const normalizedApiRoot = path.normalize(this.kernel.path("api"))
3126
3102
  const normalizedPluginRoot = path.normalize(this.kernel.path("plugin"))
3127
- const normalizedSystemPluginRoot = path.normalize(PluginSources.systemPluginRoot(this.kernel))
3128
3103
  const isPathWithinRoot = (candidatePath, rootPath) => {
3129
3104
  if (typeof candidatePath !== "string" || typeof rootPath !== "string") {
3130
3105
  return false
@@ -3150,9 +3125,6 @@ class Server {
3150
3125
  if (isPathWithinRoot(normalizedCandidate, normalizedPluginRoot)) {
3151
3126
  return true
3152
3127
  }
3153
- if (isPathWithinRoot(normalizedCandidate, normalizedSystemPluginRoot)) {
3154
- return true
3155
- }
3156
3128
  const relativeToApiRoot = path.relative(normalizedApiRoot, normalizedCandidate)
3157
3129
  if (!relativeToApiRoot || relativeToApiRoot.startsWith("..") || path.isAbsolute(relativeToApiRoot)) {
3158
3130
  return false
@@ -3966,7 +3938,8 @@ class Server {
3966
3938
  if (menuitem.href.startsWith("http")) {
3967
3939
  menuitem.src = menuitem.href
3968
3940
  } else if (menuitem.href.startsWith("/")) {
3969
- if (PluginSources.isRunPath(menuitem.href)) {
3941
+ let run_path = "/run"
3942
+ if (menuitem.href.startsWith(run_path)) {
3970
3943
  menuitem.src = menuitem.href
3971
3944
  // u = new URL("http://localhost" + menuitem.href.slice(run_path.length))
3972
3945
  // cwd = u.searchParams.get("cwd")
@@ -3986,14 +3959,7 @@ class Server {
3986
3959
  }
3987
3960
 
3988
3961
  // check running
3989
- let srcPathname = menuitem.src
3990
- try {
3991
- srcPathname = new URL("http://localhost" + menuitem.src).pathname
3992
- } catch (_) {
3993
- }
3994
- let fullpath = PluginSources.isRunPath(srcPathname)
3995
- ? PluginSources.resolveRunPath(this.kernel, srcPathname)
3996
- : this.kernel.path(srcPathname.slice(1))
3962
+ let fullpath = this.kernel.path(menuitem.src.slice(1))
3997
3963
  let relpath = path.relative(this.kernel.homedir, fullpath)
3998
3964
  if (relpath.startsWith("api")) {
3999
3965
  // api script
@@ -5313,7 +5279,15 @@ class Server {
5313
5279
  return normalized
5314
5280
  }
5315
5281
  isValidBundledPluginConfig(pluginConfig) {
5316
- return PluginSources.isValidPluginConfig(pluginConfig)
5282
+ if (!pluginConfig || !Array.isArray(pluginConfig.run)) {
5283
+ return false
5284
+ }
5285
+ for (const key of Object.keys(pluginConfig)) {
5286
+ if (typeof pluginConfig[key] === "function") {
5287
+ return false
5288
+ }
5289
+ }
5290
+ return true
5317
5291
  }
5318
5292
  isPathInsideRootForBundledPlugin(candidatePath, rootPath) {
5319
5293
  const relative = path.relative(rootPath, candidatePath)
@@ -5699,6 +5673,7 @@ class Server {
5699
5673
  "prototype/PTERM.md",
5700
5674
  ]
5701
5675
  const managedRefreshTargets = [
5676
+ "plugin/code",
5702
5677
  "prototype/system",
5703
5678
  "network/system",
5704
5679
  "prototype/PINOKIO.md",
@@ -5720,16 +5695,6 @@ class Server {
5720
5695
 
5721
5696
  needsManagedRefresh = true
5722
5697
  console.log("[TRY] Updating to the new version")
5723
- let envPath = path.resolve(home, "ENVIRONMENT")
5724
- let envExists = await this.kernel.exists(envPath)
5725
- if (!envExists) {
5726
- let str = await Environment.ENV("system", home, this.kernel)
5727
- await fs.promises.writeFile(envPath, str)
5728
- }
5729
- await Environment.ensurePinokioCacheDirs(this.kernel, {
5730
- throwOnFailure: true,
5731
- elevatedRepair: this.kernel.elevatedCacheRepair.bind(this.kernel)
5732
- })
5733
5698
  this.kernel.store.set("version", this.version.pinokiod)
5734
5699
  console.log("[DONE] Updating to the new version")
5735
5700
  console.log("not up to date. update py.")
@@ -5742,6 +5707,9 @@ class Server {
5742
5707
  let p2 = path.resolve(home, "prototype/system")
5743
5708
  await fse.remove(p2)
5744
5709
 
5710
+ let p3 = path.resolve(home, "plugin/code")
5711
+ await fse.remove(p3)
5712
+
5745
5713
  let p4 = path.resolve(home, "network/system")
5746
5714
  await fse.remove(p4)
5747
5715
 
@@ -5810,6 +5778,10 @@ class Server {
5810
5778
  await this.kernel.proto.init()
5811
5779
  }
5812
5780
  if (needsManagedRefresh) {
5781
+ const pluginReady = await this.kernel.exists("plugin/code")
5782
+ if (!pluginReady && this.kernel.plugin && typeof this.kernel.plugin.init === "function") {
5783
+ await this.kernel.plugin.init()
5784
+ }
5813
5785
  const networkReady = await this.kernel.exists("network/system")
5814
5786
  if (!networkReady && this.kernel.router && typeof this.kernel.router.init === "function") {
5815
5787
  await this.kernel.router.init()
@@ -5990,10 +5962,6 @@ class Server {
5990
5962
  })
5991
5963
  this.app.use(express.static(path.resolve(__dirname, 'public')));
5992
5964
  this.app.use("/web", express.static(path.resolve(__dirname, "..", "..", "web")))
5993
- this.app.use(PluginSources.SYSTEM_ASSET_PREFIX, express.static(PluginSources.systemRoot(this.kernel), {
5994
- index: false,
5995
- fallthrough: true,
5996
- }))
5997
5965
  this.app.set('view engine', 'ejs');
5998
5966
  this.app.use((req, res, next) => {
5999
5967
  const peerForwarded = (req.get('X-Pinokio-Peer') || '').trim().toLowerCase()
@@ -6776,7 +6744,24 @@ class Server {
6776
6744
  }
6777
6745
  }
6778
6746
  const normalizePluginPath = (value) => {
6779
- return PluginSources.normalizePluginPath(value)
6747
+ let normalized = typeof value === "string" ? value.trim() : ""
6748
+ if (!normalized) {
6749
+ return ""
6750
+ }
6751
+ normalized = normalized.replace(/\\/g, "/")
6752
+ if (/^https?:\/\//i.test(normalized)) {
6753
+ try {
6754
+ const parsed = new URL(normalized)
6755
+ normalized = parsed.pathname || ""
6756
+ } catch (_) {
6757
+ }
6758
+ }
6759
+ normalized = normalized.replace(/^\/run(?=\/)/, "")
6760
+ if (!normalized.startsWith("/")) {
6761
+ normalized = `/${normalized}`
6762
+ }
6763
+ normalized = normalized.replace(/\/{2,}/g, "/").replace(/\/+$/, "")
6764
+ return normalized
6780
6765
  }
6781
6766
  const normalizePluginLookupKey = (value) => {
6782
6767
  const normalized = normalizePluginPath(value)
@@ -6851,9 +6836,9 @@ class Server {
6851
6836
  cwd: typeof pluginItem.ownerApp.cwd === "string" ? pluginItem.ownerApp.cwd : "",
6852
6837
  }
6853
6838
  : null,
6854
- hasInstall: PluginSources.isAction(pluginItem?.install),
6855
- hasUninstall: PluginSources.isAction(pluginItem?.uninstall),
6856
- hasUpdate: PluginSources.isAction(pluginItem?.update),
6839
+ hasInstall: Array.isArray(pluginItem?.install),
6840
+ hasUninstall: Array.isArray(pluginItem?.uninstall),
6841
+ hasUpdate: Array.isArray(pluginItem?.update),
6857
6842
  category,
6858
6843
  categoryTitle: category === "ide" ? "Desktop Plugin" : "Terminal Plugin",
6859
6844
  categorySubtitle: category === "ide" ? "Launch externally" : "Launch in Pinokio",
@@ -6923,7 +6908,7 @@ class Server {
6923
6908
  index: -1,
6924
6909
  title: context.title || "Plugin",
6925
6910
  description: typeof config.description === "string" ? config.description : "",
6926
- href: PluginSources.pluginRunHrefForPath(normalizedPluginPath),
6911
+ href: `/run${normalizedPluginPath}`,
6927
6912
  link: typeof config.link === "string" ? config.link : "",
6928
6913
  image: context.image || null,
6929
6914
  icon: null,
@@ -6948,31 +6933,24 @@ class Server {
6948
6933
  }
6949
6934
  const resolvePluginLocalState = async (plugin) => {
6950
6935
  const pluginRoot = path.resolve(this.kernel.path("plugin"))
6936
+ const managedRoot = path.resolve(pluginRoot, "code")
6951
6937
  const normalizedPath = normalizePluginPath(plugin && plugin.pluginPath ? plugin.pluginPath : "")
6952
6938
  const emptyState = {
6953
6939
  ownership: "bundled",
6954
6940
  manageable: false,
6955
6941
  canOpenFolder: false,
6956
6942
  pluginRoot,
6943
+ managedRoot,
6957
6944
  pluginFilePath: "",
6958
6945
  pluginDir: "",
6959
6946
  relativeDir: "",
6960
6947
  relativeFile: "",
6961
6948
  localLabel: "",
6962
- }
6963
- if (PluginSources.isSystemPluginPath(normalizedPath)) {
6964
- return {
6965
- ...emptyState,
6966
- ownership: "system",
6967
- localLabel: normalizedPath.replace(/^\/pinokio\/run\//, ""),
6968
- }
6949
+ managedPrefix: "",
6969
6950
  }
6970
6951
  if (!normalizedPath.startsWith("/plugin/")) {
6971
6952
  return emptyState
6972
6953
  }
6973
- if (PluginSources.isLegacyPluginCodePath(normalizedPath)) {
6974
- return emptyState
6975
- }
6976
6954
  const pluginFilePath = path.resolve(this.kernel.path(normalizedPath.slice(1)))
6977
6955
  if (!isPathInsideRoot(pluginFilePath, pluginRoot)) {
6978
6956
  return emptyState
@@ -6992,16 +6970,22 @@ class Server {
6992
6970
  }
6993
6971
  const relativeFile = path.relative(pluginRoot, pluginFilePath).split(path.sep).join("/")
6994
6972
  const relativeDir = path.relative(pluginRoot, pluginDir).split(path.sep).join("/")
6973
+ const isManaged = isPathInsideRoot(pluginFilePath, managedRoot)
6974
+ const managedPrefix = isManaged
6975
+ ? path.relative(managedRoot, pluginDir).split(path.sep).join("/")
6976
+ : ""
6995
6977
  return {
6996
- ownership: "local",
6997
- manageable: Boolean(pluginFileExists && pluginDirExists),
6978
+ ownership: isManaged ? "managed" : "local",
6979
+ manageable: Boolean(pluginFileExists && pluginDirExists && !isManaged),
6998
6980
  canOpenFolder: Boolean(pluginFileExists && pluginDirExists),
6999
6981
  pluginRoot,
6982
+ managedRoot,
7000
6983
  pluginFilePath,
7001
6984
  pluginDir,
7002
6985
  relativeDir,
7003
6986
  relativeFile,
7004
6987
  localLabel: relativeDir ? `plugin/${relativeDir}` : "plugin",
6988
+ managedPrefix,
7005
6989
  }
7006
6990
  }
7007
6991
  const collectPluginApps = async (boundAppName = "") => {
@@ -7045,31 +7029,11 @@ class Server {
7045
7029
  })
7046
7030
  return apps
7047
7031
  }
7048
- const buildPluginShareState = async (plugin) => {
7049
- const localState = await resolvePluginLocalState(plugin)
7050
- if (localState.ownership === "system") {
7032
+ const buildPluginShareState = async (plugin) => {
7033
+ const localState = await resolvePluginLocalState(plugin)
7034
+ if (localState.ownership === "bundled") {
7051
7035
  return {
7052
- ownership: "system",
7053
- manageable: false,
7054
- canOpenFolder: false,
7055
- dir: "",
7056
- localLabel: localState.localLabel || "",
7057
- remoteUrl: "",
7058
- remoteWebUrl: "",
7059
- githubConnected: false,
7060
- gitInitialized: false,
7061
- hasCommit: false,
7062
- changeCount: 0,
7063
- changes: [],
7064
- branch: "HEAD",
7065
- commitUrl: "",
7066
- createUrl: "",
7067
- pushUrl: "",
7068
- }
7069
- }
7070
- if (localState.ownership === "bundled") {
7071
- return {
7072
- ownership: "bundled",
7036
+ ownership: "bundled",
7073
7037
  manageable: false,
7074
7038
  canOpenFolder: false,
7075
7039
  dir: "",
@@ -7089,21 +7053,49 @@ class Server {
7089
7053
  }
7090
7054
 
7091
7055
  if (localState.ownership === "managed") {
7056
+ let changes = []
7057
+ try {
7058
+ const headStatus = await this.getRepoHeadStatusByDir(localState.managedRoot)
7059
+ const allChanges = Array.isArray(headStatus && headStatus.changes) ? headStatus.changes : []
7060
+ const managedPrefix = typeof localState.managedPrefix === "string" ? localState.managedPrefix.trim() : ""
7061
+ changes = allChanges
7062
+ .filter((change) => {
7063
+ const file = change && typeof change.file === "string" ? change.file : ""
7064
+ if (!managedPrefix) {
7065
+ return true
7066
+ }
7067
+ return file === managedPrefix || file.startsWith(`${managedPrefix}/`)
7068
+ })
7069
+ .map((change) => {
7070
+ const file = change && typeof change.file === "string" ? change.file : ""
7071
+ if (!managedPrefix || !file) {
7072
+ return change
7073
+ }
7074
+ const relativeFile = file === managedPrefix
7075
+ ? path.posix.basename(managedPrefix)
7076
+ : file.slice(managedPrefix.length + 1)
7077
+ return {
7078
+ ...change,
7079
+ file: relativeFile || file
7080
+ }
7081
+ })
7082
+ } catch (_) {
7083
+ }
7092
7084
  return {
7093
- ownership: "bundled",
7085
+ ownership: "managed",
7094
7086
  manageable: false,
7095
- canOpenFolder: false,
7096
- dir: "",
7097
- localLabel: "",
7098
- relativeDir: "",
7099
- relativeFile: "",
7087
+ canOpenFolder: Boolean(localState.canOpenFolder),
7088
+ dir: localState.pluginDir,
7089
+ localLabel: localState.localLabel,
7090
+ relativeDir: localState.relativeDir,
7091
+ relativeFile: localState.relativeFile,
7100
7092
  remoteUrl: "",
7101
7093
  remoteWebUrl: "",
7102
7094
  githubConnected: false,
7103
7095
  gitInitialized: false,
7104
7096
  hasCommit: false,
7105
- changeCount: 0,
7106
- changes: [],
7097
+ changeCount: changes.length,
7098
+ changes,
7107
7099
  branch: "HEAD",
7108
7100
  commitUrl: "",
7109
7101
  createUrl: "",
@@ -7179,19 +7171,14 @@ class Server {
7179
7171
  : ""
7180
7172
  const remoteLabel = summarizeTaskRemoteLabel(remoteCandidate)
7181
7173
  const badges = []
7182
- if (ownership === "local") {
7183
- badges.push({
7184
- label: "Local plugin",
7185
- tone: "accent"
7186
- })
7187
- } else if (ownership === "system") {
7174
+ if (ownership === "local") {
7188
7175
  badges.push({
7189
- label: "Built-in plugin",
7190
- tone: "neutral"
7176
+ label: "Local plugin",
7177
+ tone: "accent"
7191
7178
  })
7192
- } else if (ownership === "managed") {
7193
- badges.push({
7194
- label: "Managed by Pinokio",
7179
+ } else if (ownership === "managed") {
7180
+ badges.push({
7181
+ label: "Managed by Pinokio",
7195
7182
  tone: "neutral"
7196
7183
  })
7197
7184
  } else {
@@ -7236,12 +7223,13 @@ class Server {
7236
7223
  : (shareState.gitInitialized ? "No local changes" : "Not version tracked yet")
7237
7224
  githubPanelTitle = "GitHub"
7238
7225
  githubPanelCopy = ""
7239
- } else if (ownership === "system") {
7240
- sourceLabel = "System plugin"
7241
- sourceValue = shareState.localLabel || sourceValue
7242
- statusValue = "Read-only"
7243
- githubPanelTitle = "Built in to Pinokio"
7244
- githubPanelCopy = "This plugin ships with Pinokio and is not editable from the local plugin workspace."
7226
+ } else if (ownership === "managed") {
7227
+ sourceLabel = "Managed folder"
7228
+ sourceValue = shareState.localLabel
7229
+ statusValue = changes.length > 0 ? pluralizeTaskFiles(changes.length) : "Updated with Pinokio"
7230
+ githubPanelTitle = "Managed by Pinokio"
7231
+ 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."
7232
+ localChangesCopy = "These edits live inside Pinokio-managed source and may be overwritten by future Pinokio updates."
7245
7233
  }
7246
7234
  const changePreview = changes.slice(0, 6).map((change) => ({
7247
7235
  file: change && change.file ? change.file : "",
@@ -8106,7 +8094,24 @@ class Server {
8106
8094
  }
8107
8095
  return targetPath
8108
8096
  }
8109
- const resolveUniversalLauncherPluginHref = PluginSources.resolveLauncherPluginHref
8097
+ const resolveUniversalLauncherPluginHref = (toolValue) => {
8098
+ let normalizedTool = typeof toolValue === "string" ? toolValue.trim() : ""
8099
+ normalizedTool = normalizedTool.replace(/^https?:\/\/[^/]+/i, "")
8100
+ normalizedTool = normalizedTool.replace(/^\/+|\/+$/g, "")
8101
+ normalizedTool = normalizedTool.replace(/^run\//, "")
8102
+ if (!normalizedTool || normalizedTool.includes("..") || !/^[A-Za-z0-9._/-]+$/.test(normalizedTool)) {
8103
+ const error = new Error("Invalid plugin.")
8104
+ error.status = 400
8105
+ throw error
8106
+ }
8107
+ if (normalizedTool.startsWith("plugin/") || normalizedTool.startsWith("api/")) {
8108
+ const scriptPath = normalizedTool.endsWith(".js")
8109
+ ? normalizedTool
8110
+ : `${normalizedTool}/pinokio.js`
8111
+ return `/run/${scriptPath}`
8112
+ }
8113
+ return `/run/plugin/${normalizedTool}/pinokio.js`
8114
+ }
8110
8115
  const persistLauncherPromptContext = async (targetPath, options = {}) => {
8111
8116
  const prompt = typeof options.prompt === "string" ? options.prompt.trim() : ""
8112
8117
  if (!prompt) {
@@ -8398,32 +8403,6 @@ class Server {
8398
8403
  const taskWorkspaceLinks = createTaskWorkspaceLinkService({
8399
8404
  kernel: this.kernel
8400
8405
  })
8401
- const workspaceRuntime = createWorkspaceRuntimeService({
8402
- kernel: this.kernel
8403
- })
8404
- this.workspaceRuntime = workspaceRuntime
8405
- const features = await mountFeatures({
8406
- app: this.app,
8407
- kernel: this.kernel,
8408
- taskWorkspaceLinks,
8409
- defaultRegistryUrl: DEFAULT_REGISTRY_URL
8410
- })
8411
- this.features = features
8412
- const notes = features.notes.service
8413
- this.notes = notes
8414
- const workspaceCatalog = createWorkspaceCatalogService({
8415
- kernel: this.kernel,
8416
- workspaceRuntime,
8417
- notes
8418
- })
8419
- registerWorkspacesRoutes(this.app, {
8420
- workspaceCatalog,
8421
- composePeerAccessPayload: () => this.composePeerAccessPayload(),
8422
- getTheme: () => this.theme,
8423
- getPeers: () => this.getPeers(),
8424
- getCurrentHost: () => this.kernel.peer.host,
8425
- getPortal: () => this.portal
8426
- })
8427
8406
  const TASK_INPUT_NAME_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/
8428
8407
  const suggestTaskFolderName = async (rootDir, preferredName) => {
8429
8408
  const normalizedRoot = path.resolve(rootDir)
@@ -9738,13 +9717,6 @@ class Server {
9738
9717
  })
9739
9718
  }))
9740
9719
 
9741
- this.app.get("/tasker", ex(async (req, res) => {
9742
- res.render("tasker", {
9743
- theme: this.theme,
9744
- agent: req.agent,
9745
- })
9746
- }))
9747
-
9748
9720
  this.app.get("/tasks/new", ex(async (req, res) => {
9749
9721
  const sourceWorkspace = normalizeTaskBuilderSourceWorkspace(req.query.sourceWorkspaceCwd)
9750
9722
  const lockTargetSelection = req.query.lockTarget === "1" || req.query.lockTarget === "true"
@@ -10101,8 +10073,7 @@ class Server {
10101
10073
  }
10102
10074
  }
10103
10075
 
10104
- const filledInputValues = filterFilledTaskInputValues(inputValues)
10105
- const prompt = taskPackages.applyTemplateValues(task.template, filledInputValues).trim()
10076
+ const prompt = taskPackages.applyTemplateValues(task.template, filterFilledTaskInputValues(inputValues)).trim()
10106
10077
  if (!prompt) {
10107
10078
  await renderTaskLaunchPage(req, res, task, {
10108
10079
  selectedTool,
@@ -10223,9 +10194,6 @@ class Server {
10223
10194
  params.set("cwd", targetPath)
10224
10195
  params.set("chrome", "full")
10225
10196
  params.set("prompt", prompt)
10226
- if (filledInputValues.url) {
10227
- params.set("url", filledInputValues.url)
10228
- }
10229
10197
  res.redirect(`${pluginHref}?${params.toString()}`)
10230
10198
  })
10231
10199
  this.app.post("/task/start", handleTaskStartRequest)
@@ -10532,8 +10500,7 @@ class Server {
10532
10500
  return
10533
10501
  }
10534
10502
 
10535
- const filledInputValues = filterFilledTaskInputValues(inputValues)
10536
- const prompt = taskPackages.applyTemplateValues(task.template, filledInputValues).trim()
10503
+ const prompt = taskPackages.applyTemplateValues(task.template, filterFilledTaskInputValues(inputValues)).trim()
10537
10504
  if (!prompt) {
10538
10505
  res.status(400).json({
10539
10506
  ok: false,
@@ -10633,9 +10600,6 @@ class Server {
10633
10600
  params.set("chrome", "full")
10634
10601
  params.set("session", createUniversalLauncherSessionId())
10635
10602
  params.set("prompt", prompt)
10636
- if (filledInputValues.url) {
10637
- params.set("url", filledInputValues.url)
10638
- }
10639
10603
 
10640
10604
  res.json({
10641
10605
  ok: true,
@@ -11887,14 +11851,11 @@ class Server {
11887
11851
  // but allow it when the request originates from the local machine
11888
11852
  if (payload.audience === 'device' && typeof payload.device_id === 'string' && payload.device_id) {
11889
11853
  try {
11890
- const remoteAddress = req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : req.ip
11891
- const isLocalRequest = this.socket && typeof this.socket.isLocalAddress === 'function'
11892
- ? this.socket.isLocalAddress(remoteAddress)
11893
- : false
11894
- const isLocalDevice = this.socket && typeof this.socket.isLocalDevice === 'function'
11895
- ? this.socket.isLocalDevice(payload.device_id)
11896
- : false
11897
- payload.host = !!(isLocalRequest || isLocalDevice)
11854
+ if (this.socket && typeof this.socket.isLocalDevice === 'function') {
11855
+ payload.host = !!this.socket.isLocalDevice(payload.device_id)
11856
+ } else {
11857
+ payload.host = false
11858
+ }
11898
11859
  } catch (_) {
11899
11860
  payload.host = false
11900
11861
  }
@@ -12795,6 +12756,23 @@ class Server {
12795
12756
  })
12796
12757
  }
12797
12758
  }))
12759
+ this.app.post("/plugin/update", ex(async (req, res) => {
12760
+ try {
12761
+ await this.kernel.exec({
12762
+ message: "git pull",
12763
+ path: this.kernel.path("plugin/code")
12764
+ }, (e) => {
12765
+ console.log(e)
12766
+ })
12767
+ res.json({
12768
+ success: true
12769
+ })
12770
+ } catch (e) {
12771
+ res.json({
12772
+ error: e.stack
12773
+ })
12774
+ }
12775
+ }))
12798
12776
  this.app.post("/network/reset", ex(async (req, res) => {
12799
12777
  let caddy_path = this.kernel.path("cache/XDG_DATA_HOME/caddy")
12800
12778
  await rimraf(caddy_path)
@@ -13416,21 +13394,9 @@ class Server {
13416
13394
  // }
13417
13395
  // }))
13418
13396
  this.app.post("/env", ex(async (req, res) => {
13419
- const requestFilepath = typeof req.body.filepath === "string" ? req.body.filepath : ""
13420
- const requestRoot = path.resolve(this.kernel.homedir, requestFilepath)
13421
- let fullpath = path.resolve(requestRoot, "ENVIRONMENT")
13422
- if (!(await this.kernel.exists(fullpath))) {
13423
- const normalizedFilepath = requestFilepath.replace(/\\/g, "/")
13424
- const filepathParts = normalizedFilepath.split("/").filter(Boolean)
13425
- if (filepathParts[0] === "api" && filepathParts[1] && filepathParts.length === 2) {
13426
- const launcher = await this.kernel.api.launcher(filepathParts[1])
13427
- if (launcher && launcher.launcher_root) {
13428
- fullpath = path.resolve(launcher.root, launcher.launcher_root, "ENVIRONMENT")
13429
- }
13430
- }
13431
- }
13397
+ let fullpath = path.resolve(this.kernel.homedir, req.body.filepath, "ENVIRONMENT")
13432
13398
  let updated = req.body.vals
13433
- let hosts = req.body.hosts || {}
13399
+ let hosts = req.body.hosts
13434
13400
  await Util.update_env(fullpath, updated)
13435
13401
  const normalizedFilepath = typeof req.body.filepath === "string"
13436
13402
  ? req.body.filepath.replace(/\\/g, "/")
@@ -13583,15 +13549,14 @@ class Server {
13583
13549
  this.app.get("/pre/api/:name", ex(async (req, res) => {
13584
13550
  let launcher = await this.kernel.api.launcher(req.params.name)
13585
13551
  let config = launcher.script
13586
- if (config && Array.isArray(config.pre)) {
13587
- const items = config.pre.filter((item) => item && typeof item === "object")
13588
- items.forEach((item) => {
13589
- if (typeof item.icon === "string" && item.icon) {
13552
+ if (config && config.pre) {
13553
+ config.pre.forEach((item) => {
13554
+ if (item.icon) {
13590
13555
  item.icon = `/api/${req.params.name}/${item.icon}?raw=true`
13591
13556
  } else {
13592
13557
  item.icon = "/pinokio-black.png"
13593
13558
  }
13594
- if (typeof item.href === "string" && item.href && !item.href.startsWith("http")) {
13559
+ if (!item.href.startsWith("http")) {
13595
13560
  item.href = path.resolve(this.kernel.homedir, "api", req.params.name, item.href)
13596
13561
  }
13597
13562
  })
@@ -13602,7 +13567,7 @@ class Server {
13602
13567
  theme: this.theme,
13603
13568
  agent: req.agent,
13604
13569
  name: req.params.name,
13605
- items,
13570
+ items: config.pre,
13606
13571
  env
13607
13572
  })
13608
13573
  } else {
@@ -14088,7 +14053,7 @@ class Server {
14088
14053
  mode = "launch_type.desktop"
14089
14054
  } else if (launchType === "terminal") {
14090
14055
  mode = "launch_type.terminal"
14091
- } else if (Array.isArray(item.run)) {
14056
+ } else {
14092
14057
  for(let step of item.run) {
14093
14058
  if (step.method === "exec") {
14094
14059
  mode = "exec"
@@ -14103,8 +14068,6 @@ class Server {
14103
14068
  break
14104
14069
  }
14105
14070
  }
14106
- } else if (typeof item.run === "function") {
14107
- mode = "shell"
14108
14071
  }
14109
14072
  if (mode === "launch_type.desktop" || mode === "exec" || mode === "launch") {
14110
14073
  item.type = "Open"
@@ -14157,20 +14120,6 @@ class Server {
14157
14120
  spec = await fs.promises.readFile(path.resolve(filepath, "SPEC.md"), "utf8")
14158
14121
  } catch (e) {
14159
14122
  }
14160
- const registryEnabled = await this.isRegistryEnabled().catch(() => false)
14161
- let registry_parent_url = ""
14162
- if (registryEnabled) {
14163
- try {
14164
- registry_parent_url = await git.getConfig({
14165
- fs,
14166
- http,
14167
- dir: filepath,
14168
- path: "remote.origin.url"
14169
- }) || ""
14170
- } catch (_) {
14171
- registry_parent_url = ""
14172
- }
14173
- }
14174
14123
  res.render("d", {
14175
14124
  filepath,
14176
14125
  spec,
@@ -14181,7 +14130,6 @@ class Server {
14181
14130
  install: this.install,
14182
14131
  agent: req.agent,
14183
14132
  theme: this.theme,
14184
- registry_parent_url,
14185
14133
  //dynamic: plugin_menu
14186
14134
  dynamic,
14187
14135
  })
@@ -14256,17 +14204,6 @@ class Server {
14256
14204
  res.status(404).send(e.message)
14257
14205
  }
14258
14206
  }))
14259
- this.app.get(`${PluginSources.SYSTEM_RUN_PREFIX}/*`, ex(async (req, res) => {
14260
- const runPath = typeof req.params[0] === "string" ? req.params[0] : ""
14261
- let pathComponents = runPath.split("/")
14262
- req.base = PluginSources.systemRoot(this.kernel)
14263
- req.pinokioSystem = true
14264
- try {
14265
- await this.render(req, res, pathComponents)
14266
- } catch (e) {
14267
- res.status(404).send(e.message)
14268
- }
14269
- }))
14270
14207
  this.app.get("/run/*", ex(async (req, res) => {
14271
14208
  const runPath = typeof req.params[0] === "string" ? req.params[0] : ""
14272
14209
  let pathComponents = runPath.split("/")
@@ -15692,7 +15629,12 @@ class Server {
15692
15629
  }))
15693
15630
  this.app.get("/bin_ready", ex(async (req, res) => {
15694
15631
  if (this.kernel.bin && !this.kernel.bin.requirements_pending) {
15695
- res.json({ success: true })
15632
+ let code_exists = await this.kernel.exists("plugin/code")
15633
+ if (code_exists) {
15634
+ res.json({ success: true })
15635
+ } else {
15636
+ res.json({ success: false })
15637
+ }
15696
15638
  } else {
15697
15639
  res.json({ success: false })
15698
15640
  }