pinokiod 7.3.1 → 7.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kernel/api/github/index.js +444 -0
- package/kernel/api/index.js +199 -11
- package/kernel/api/process/index.js +124 -44
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/api/uri/index.js +51 -0
- package/kernel/bin/git.js +9 -10
- package/kernel/bin/huggingface.js +1 -1
- package/kernel/bin/zip.js +9 -1
- package/kernel/connect/providers/github/README.md +5 -4
- package/kernel/environment.js +195 -92
- package/kernel/git.js +98 -19
- package/kernel/gitconfig_template +7 -0
- package/kernel/gpu/amd.js +72 -0
- package/kernel/gpu/apple.js +8 -0
- package/kernel/gpu/common.js +12 -0
- package/kernel/gpu/intel.js +47 -0
- package/kernel/gpu/nvidia.js +8 -0
- package/kernel/index.js +11 -1
- package/kernel/managed_skills.js +871 -0
- package/kernel/plugin.js +6 -58
- package/kernel/plugin_sources.js +316 -0
- package/kernel/resource_usage/gpu.js +349 -0
- package/kernel/resource_usage/index.js +322 -0
- package/kernel/resource_usage/macos_footprint.js +197 -0
- package/kernel/resource_usage/preferences.js +92 -0
- package/kernel/resource_usage/process_tree.js +303 -0
- package/kernel/scripts/git/create +4 -4
- package/kernel/scripts/git/fork +7 -8
- package/kernel/shell.js +23 -2
- package/kernel/shells.js +41 -0
- package/kernel/sysinfo.js +62 -9
- package/kernel/util.js +60 -0
- package/package.json +1 -1
- package/server/index.js +984 -156
- package/server/lib/app_log_report.js +543 -0
- package/server/lib/content_validation.js +55 -33
- package/server/lib/launcher_instruction_bootstrap.js +4 -96
- package/server/lib/terminal_session_helpers.js +0 -3
- package/server/public/common.js +77 -31
- package/server/public/create-launcher.js +4 -32
- package/server/public/logs.js +1428 -0
- package/server/public/nav.js +7 -0
- package/server/public/plugin-detail.js +93 -10
- package/server/public/privacy_filter_worker.js +391 -0
- package/server/public/style.css +1104 -154
- package/server/public/task-launcher.js +8 -29
- package/server/public/universal-launcher.css +8 -6
- package/server/public/universal-launcher.js +3 -27
- package/server/routes/apps.js +195 -1
- package/server/views/app.ejs +3041 -717
- package/server/views/autolaunch.ejs +917 -0
- package/server/views/bootstrap.ejs +7 -1
- package/server/views/d.ejs +408 -65
- package/server/views/editor.ejs +85 -19
- package/server/views/index.ejs +661 -111
- package/server/views/init/index.ejs +1 -1
- package/server/views/install.ejs +1 -1
- package/server/views/logs.ejs +164 -86
- package/server/views/net.ejs +7 -1
- package/server/views/partials/d_terminal_column.ejs +2 -2
- package/server/views/partials/d_terminal_options.ejs +0 -8
- package/server/views/partials/fs_status.ejs +47 -0
- package/server/views/partials/home_action_modal.ejs +86 -0
- package/server/views/partials/home_run_menu.ejs +87 -0
- package/server/views/partials/main_sidebar.ejs +2 -0
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/plugin_detail.ejs +19 -4
- package/server/views/plugins.ejs +201 -3
- package/server/views/pre.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/shell.ejs +40 -18
- package/server/views/skills.ejs +506 -0
- package/server/views/terminal.ejs +45 -19
- package/spec/INSTRUCTION_SYNC.md +20 -10
- package/system/plugin/antigravity-cli/antigravity.png +0 -0
- package/system/plugin/antigravity-cli/common.js +155 -0
- package/system/plugin/antigravity-cli/install.js +272 -0
- package/system/plugin/antigravity-cli/pinokio.js +13 -0
- package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
- package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
- package/system/plugin/claude/claude.png +0 -0
- package/system/plugin/claude/pinokio.js +47 -0
- package/system/plugin/claude-auto/claude.png +0 -0
- package/system/plugin/claude-auto/pinokio.js +58 -0
- package/system/plugin/claude-desktop/icon.jpeg +0 -0
- package/system/plugin/claude-desktop/pinokio.js +23 -0
- package/system/plugin/codex/openai.webp +0 -0
- package/system/plugin/codex/pinokio.js +42 -0
- package/system/plugin/codex-auto/openai.webp +0 -0
- package/system/plugin/codex-auto/pinokio.js +49 -0
- package/system/plugin/codex-desktop/icon.png +0 -0
- package/system/plugin/codex-desktop/pinokio.js +23 -0
- package/system/plugin/crush/crush.png +0 -0
- package/system/plugin/crush/pinokio.js +15 -0
- package/system/plugin/cursor/cursor.jpeg +0 -0
- package/system/plugin/cursor/pinokio.js +23 -0
- package/system/plugin/qwen/pinokio.js +34 -0
- package/system/plugin/qwen/qwen.png +0 -0
- package/system/plugin/vscode/pinokio.js +20 -0
- package/system/plugin/vscode/vscode.png +0 -0
- package/system/plugin/windsurf/pinokio.js +23 -0
- package/system/plugin/windsurf/windsurf.png +0 -0
- package/test/antigravity-cli-plugin.test.js +185 -0
- package/test/app-api.test.js +239 -0
- package/test/app-log-report.test.js +67 -0
- package/test/environment-cache-preflight.test.js +98 -0
- package/test/git-bin.test.js +59 -0
- package/test/git-defaults.test.js +97 -0
- package/test/github-api.test.js +158 -0
- package/test/github-connection.test.js +117 -0
- package/test/huggingface-bin.test.js +25 -0
- package/test/managed-skills.test.js +351 -0
- package/test/plugin-action-functions.test.js +337 -0
- package/test/plugin-dev-iframe.test.js +17 -0
- package/test/plugin-sources.test.js +203 -0
- package/test/privacy-filter-worker-heuristics.test.js +69 -0
- package/test/process-wait.test.js +169 -0
- package/test/script-api.test.js +97 -0
- package/test/shell-api.test.js +134 -0
- package/test/shell-run-template.test.js +209 -0
- package/test/storage-api.test.js +137 -0
- package/test/uri-api.test.js +100 -0
package/server/index.js
CHANGED
|
@@ -30,6 +30,8 @@ const registerFileRoutes = require('./routes/files')
|
|
|
30
30
|
const registerAppRoutes = require('./routes/apps')
|
|
31
31
|
const Git = require("../kernel/git")
|
|
32
32
|
const TerminalApi = require('../kernel/api/terminal')
|
|
33
|
+
const PluginSources = require("../kernel/plugin_sources")
|
|
34
|
+
const ManagedSkills = require("../kernel/managed_skills")
|
|
33
35
|
|
|
34
36
|
const git = require('isomorphic-git')
|
|
35
37
|
const http = require('isomorphic-git/http/node')
|
|
@@ -82,8 +84,10 @@ const { createContentValidationService } = require("./lib/content_validation")
|
|
|
82
84
|
const { buildSecureRouterDebugSnapshot, createSecureRouterDebugStore } = require("./lib/secure_router_debug")
|
|
83
85
|
const AppRegistryService = require("./lib/app_registry")
|
|
84
86
|
const AppLogService = require("./lib/app_logs")
|
|
87
|
+
const AppLogReportService = require("./lib/app_log_report")
|
|
85
88
|
const AppSearchService = require("./lib/app_search")
|
|
86
89
|
const AppPreferencesService = require("./lib/app_preferences")
|
|
90
|
+
const ResourceUsageService = require("../kernel/resource_usage")
|
|
87
91
|
|
|
88
92
|
function normalize(str) {
|
|
89
93
|
if (!str) return '';
|
|
@@ -214,7 +218,9 @@ class Server {
|
|
|
214
218
|
this.appRegistry = new AppRegistryService({ kernel: this.kernel })
|
|
215
219
|
this.appPreferences = new AppPreferencesService({ kernel: this.kernel })
|
|
216
220
|
this.kernel.appPreferences = this.appPreferences
|
|
221
|
+
this.resourceUsage = new ResourceUsageService({ kernel: this.kernel })
|
|
217
222
|
this.appLogs = new AppLogService({ registry: this.appRegistry })
|
|
223
|
+
this.appLogReports = new AppLogReportService({ registry: this.appRegistry, kernel: this.kernel })
|
|
218
224
|
this.appSearch = new AppSearchService({
|
|
219
225
|
kernel: this.kernel,
|
|
220
226
|
registry: this.appRegistry,
|
|
@@ -491,14 +497,12 @@ class Server {
|
|
|
491
497
|
assignProjectSlug(obj)
|
|
492
498
|
running_dynamic.push(obj)
|
|
493
499
|
}
|
|
494
|
-
} else if (
|
|
495
|
-
let
|
|
496
|
-
let _filepath = uri_path.split("/").filter(x=>x).slice(1)
|
|
497
|
-
let filepath = this.kernel.path(..._filepath)
|
|
500
|
+
} else if (PluginSources.isRunPath(href)) {
|
|
501
|
+
let filepath = PluginSources.resolveRunPath(this.kernel, href)
|
|
498
502
|
let id = `${filepath}?cwd=${cwd}`
|
|
499
503
|
obj.script_id = id
|
|
500
504
|
//if (this.kernel.api.running[filepath]) {
|
|
501
|
-
if (obj.src
|
|
505
|
+
if (PluginSources.pluginSelectionMatches(obj.src, selected_query && selected_query.plugin)) {
|
|
502
506
|
obj.running = true
|
|
503
507
|
obj.display = "indent"
|
|
504
508
|
obj.default = true
|
|
@@ -920,15 +924,8 @@ class Server {
|
|
|
920
924
|
|
|
921
925
|
const config = await this.kernel.git.config(dir)
|
|
922
926
|
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
if (await this.exists(hostsFile)) {
|
|
926
|
-
hosts = await fs.promises.readFile(hostsFile, "utf8")
|
|
927
|
-
if (hosts.startsWith("{}")) {
|
|
928
|
-
hosts = ""
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
const connected = hosts.length > 0
|
|
927
|
+
const githubConnection = await this.get_github_connection()
|
|
928
|
+
const connected = Boolean(githubConnection.connected)
|
|
932
929
|
|
|
933
930
|
let remote = null
|
|
934
931
|
if (config && config["remote \"origin\""]) {
|
|
@@ -990,6 +987,10 @@ class Server {
|
|
|
990
987
|
}
|
|
991
988
|
}
|
|
992
989
|
async get_github_hosts() {
|
|
990
|
+
const connection = await this.get_github_connection()
|
|
991
|
+
return connection.display
|
|
992
|
+
}
|
|
993
|
+
async get_legacy_github_hosts() {
|
|
993
994
|
let hosts = ""
|
|
994
995
|
let hosts_file = this.kernel.path("config/gh/hosts.yml")
|
|
995
996
|
let e = await this.exists(hosts_file)
|
|
@@ -1001,6 +1002,135 @@ class Server {
|
|
|
1001
1002
|
}
|
|
1002
1003
|
return hosts
|
|
1003
1004
|
}
|
|
1005
|
+
github_command_env(interactive = false) {
|
|
1006
|
+
const env = this.kernel && this.kernel.envs
|
|
1007
|
+
? { ...this.kernel.envs }
|
|
1008
|
+
: (this.kernel && this.kernel.bin && typeof this.kernel.bin.envs === 'function'
|
|
1009
|
+
? this.kernel.bin.envs(process.env)
|
|
1010
|
+
: { ...process.env })
|
|
1011
|
+
|
|
1012
|
+
if (interactive) {
|
|
1013
|
+
delete env.GCM_INTERACTIVE
|
|
1014
|
+
delete env.GIT_TERMINAL_PROMPT
|
|
1015
|
+
delete env.GIT_ASKPASS
|
|
1016
|
+
delete env.SSH_ASKPASS
|
|
1017
|
+
} else {
|
|
1018
|
+
Object.assign(env, NON_INTERACTIVE_GIT_ENV)
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return env
|
|
1022
|
+
}
|
|
1023
|
+
async github_gcm(args, options = {}) {
|
|
1024
|
+
return new Promise((resolve, reject) => {
|
|
1025
|
+
execFile(
|
|
1026
|
+
'git',
|
|
1027
|
+
['credential-manager', 'github', ...args],
|
|
1028
|
+
{
|
|
1029
|
+
cwd: this.kernel.homedir,
|
|
1030
|
+
env: this.github_command_env(Boolean(options.interactive)),
|
|
1031
|
+
timeout: Number.isFinite(options.timeout) ? options.timeout : 15000,
|
|
1032
|
+
maxBuffer: 1024 * 1024,
|
|
1033
|
+
},
|
|
1034
|
+
(error, stdout, stderr) => {
|
|
1035
|
+
if (error) {
|
|
1036
|
+
error.stderr = stderr
|
|
1037
|
+
reject(error)
|
|
1038
|
+
return
|
|
1039
|
+
}
|
|
1040
|
+
resolve(stdout || "")
|
|
1041
|
+
}
|
|
1042
|
+
)
|
|
1043
|
+
})
|
|
1044
|
+
}
|
|
1045
|
+
async get_github_connection() {
|
|
1046
|
+
if (this._githubConnectionPromise) {
|
|
1047
|
+
return this._githubConnectionPromise
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
this._githubConnectionPromise = (async () => {
|
|
1051
|
+
const legacyHosts = await this.get_legacy_github_hosts()
|
|
1052
|
+
let accounts = []
|
|
1053
|
+
let gcmError = null
|
|
1054
|
+
|
|
1055
|
+
try {
|
|
1056
|
+
const stdout = await this.github_gcm(['list'], { timeout: 10000 })
|
|
1057
|
+
accounts = stdout
|
|
1058
|
+
.split(/\r?\n/)
|
|
1059
|
+
.map((line) => line.trim())
|
|
1060
|
+
.filter(Boolean)
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
gcmError = error && (error.stderr || error.message) ? String(error.stderr || error.message).trim() : String(error)
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const display = accounts.length > 0
|
|
1066
|
+
? accounts.map((account) => `github.com: ${account}`).join("\n")
|
|
1067
|
+
: ""
|
|
1068
|
+
|
|
1069
|
+
return {
|
|
1070
|
+
accounts,
|
|
1071
|
+
legacyHosts,
|
|
1072
|
+
display,
|
|
1073
|
+
connected: accounts.length > 0,
|
|
1074
|
+
provider: accounts.length > 0 ? "gcm" : null,
|
|
1075
|
+
gcmError,
|
|
1076
|
+
}
|
|
1077
|
+
})()
|
|
1078
|
+
|
|
1079
|
+
try {
|
|
1080
|
+
return await this._githubConnectionPromise
|
|
1081
|
+
} finally {
|
|
1082
|
+
this._githubConnectionPromise = null
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
github_login_params() {
|
|
1086
|
+
const doneMarker = "PINOKIO_GITHUB_LOGIN_DONE"
|
|
1087
|
+
const delimiter = this.kernel.platform === "win32" ? " && " : " ; "
|
|
1088
|
+
const verifyCommand = this.kernel.platform === "win32"
|
|
1089
|
+
? "cmd /C \"set GIT_TERMINAL_PROMPT=0&& set GCM_INTERACTIVE=never&& (echo protocol=https& echo host=github.com& echo.) | git credential fill >NUL\""
|
|
1090
|
+
: "printf 'protocol=https\\nhost=github.com\\n\\n' | GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never git credential fill >/dev/null"
|
|
1091
|
+
const doneCommand = this.kernel.platform === "win32"
|
|
1092
|
+
? "echo P^INOKIO_GITHUB_LOGIN_DONE"
|
|
1093
|
+
: "(GCM_DONE=INOKIO_GITHUB_LOGIN_DONE; printf 'P%s\\n' \"$GCM_DONE\")"
|
|
1094
|
+
const loginCommand = [
|
|
1095
|
+
"git credential-manager github login --web --force",
|
|
1096
|
+
verifyCommand,
|
|
1097
|
+
doneCommand
|
|
1098
|
+
].join(" && ")
|
|
1099
|
+
|
|
1100
|
+
return {
|
|
1101
|
+
doneMarker,
|
|
1102
|
+
message: [
|
|
1103
|
+
"git config --global --replace-all credential.helper manager",
|
|
1104
|
+
"git config --global --replace-all credential.gitHubAuthModes oauth",
|
|
1105
|
+
"git config --global --replace-all credential.namespace pinokio",
|
|
1106
|
+
"git config --global --replace-all credential.https://github.com.helper manager",
|
|
1107
|
+
"git config --global --replace-all credential.https://github.com.provider github",
|
|
1108
|
+
loginCommand
|
|
1109
|
+
].join(delimiter)
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
github_logout_command(connection) {
|
|
1113
|
+
const accounts = connection && Array.isArray(connection.accounts) ? connection.accounts : []
|
|
1114
|
+
const safeAccounts = accounts.filter((account) => /^[A-Za-z0-9._-]+$/.test(account))
|
|
1115
|
+
if (safeAccounts.length === 0) {
|
|
1116
|
+
return null
|
|
1117
|
+
}
|
|
1118
|
+
const delimiter = this.kernel.platform === "win32" ? " && " : " ; "
|
|
1119
|
+
return safeAccounts.map((account) => `git credential-manager github logout ${account}`).join(delimiter)
|
|
1120
|
+
}
|
|
1121
|
+
async github_logout_params(connection) {
|
|
1122
|
+
const hadLegacyAuth = Boolean(connection && connection.legacyHosts && connection.legacyHosts.length > 0)
|
|
1123
|
+
if (hadLegacyAuth) {
|
|
1124
|
+
await this.clear_legacy_github_auth()
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
hadLegacyAuth,
|
|
1128
|
+
message: this.github_logout_command(connection)
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
async clear_legacy_github_auth() {
|
|
1132
|
+
await fs.promises.rm(this.kernel.path("config/gh/hosts.yml"), { force: true }).catch(() => {})
|
|
1133
|
+
}
|
|
1004
1134
|
async current_urls(current_path) {
|
|
1005
1135
|
return {}
|
|
1006
1136
|
// let router_running = await this.check_router_up()
|
|
@@ -1037,6 +1167,296 @@ class Server {
|
|
|
1037
1167
|
list: this.getPeers(),
|
|
1038
1168
|
}
|
|
1039
1169
|
}
|
|
1170
|
+
normalizeAutolaunchAppId(value) {
|
|
1171
|
+
if (typeof value !== "string") {
|
|
1172
|
+
return ""
|
|
1173
|
+
}
|
|
1174
|
+
const id = value.trim()
|
|
1175
|
+
if (!id || id === "." || id === ".." || id.includes("\0") || /[\\/]/.test(id)) {
|
|
1176
|
+
return ""
|
|
1177
|
+
}
|
|
1178
|
+
return id
|
|
1179
|
+
}
|
|
1180
|
+
normalizeAutolaunchScriptPath(value) {
|
|
1181
|
+
if (typeof value !== "string") {
|
|
1182
|
+
return ""
|
|
1183
|
+
}
|
|
1184
|
+
let script = value.trim().replace(/\\/g, "/")
|
|
1185
|
+
if (!script || script.includes("\0")) {
|
|
1186
|
+
return ""
|
|
1187
|
+
}
|
|
1188
|
+
script = script.split("#")[0].split("?")[0].trim()
|
|
1189
|
+
if (!script || /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(script) || script.startsWith("//")) {
|
|
1190
|
+
return ""
|
|
1191
|
+
}
|
|
1192
|
+
if (script.startsWith("/")) {
|
|
1193
|
+
return ""
|
|
1194
|
+
}
|
|
1195
|
+
const normalized = path.posix.normalize(script).replace(/^\.\/+/, "")
|
|
1196
|
+
if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../") || path.posix.isAbsolute(normalized)) {
|
|
1197
|
+
return ""
|
|
1198
|
+
}
|
|
1199
|
+
return normalized
|
|
1200
|
+
}
|
|
1201
|
+
isAutolaunchScriptFilename(filename) {
|
|
1202
|
+
const ext = path.extname(filename || "").toLowerCase()
|
|
1203
|
+
if (![".js", ".json", ".mjs", ".cjs"].includes(ext)) {
|
|
1204
|
+
return false
|
|
1205
|
+
}
|
|
1206
|
+
const base = path.basename(filename || "").toLowerCase()
|
|
1207
|
+
return !["package.json", "pinokio.js", "pinokio.json", "pinokio_meta.json"].includes(base)
|
|
1208
|
+
}
|
|
1209
|
+
stripAutolaunchLabel(value) {
|
|
1210
|
+
if (typeof value !== "string") {
|
|
1211
|
+
return ""
|
|
1212
|
+
}
|
|
1213
|
+
return value
|
|
1214
|
+
.replace(/<[^>]*>/g, " ")
|
|
1215
|
+
.replace(/\s+/g, " ")
|
|
1216
|
+
.trim()
|
|
1217
|
+
}
|
|
1218
|
+
async getAutolaunchAppById(appId) {
|
|
1219
|
+
const id = this.normalizeAutolaunchAppId(appId)
|
|
1220
|
+
if (!id) {
|
|
1221
|
+
return null
|
|
1222
|
+
}
|
|
1223
|
+
const apps = await this.kernel.api.listApps()
|
|
1224
|
+
return apps.find((app) => app && app.id === id) || null
|
|
1225
|
+
}
|
|
1226
|
+
async getAutolaunchEnvInfo(app) {
|
|
1227
|
+
const appRoot = path.resolve(this.kernel.api.userdir, app.id)
|
|
1228
|
+
const gotRoot = await Environment.get_root({ path: appRoot }, this.kernel)
|
|
1229
|
+
const envRoot = gotRoot && gotRoot.root ? gotRoot.root : appRoot
|
|
1230
|
+
const envPath = path.resolve(envRoot, "ENVIRONMENT")
|
|
1231
|
+
const env = await Util.parse_env(envPath)
|
|
1232
|
+
const value = typeof env.PINOKIO_SCRIPT_AUTOLAUNCH === "string"
|
|
1233
|
+
? env.PINOKIO_SCRIPT_AUTOLAUNCH.trim()
|
|
1234
|
+
: ""
|
|
1235
|
+
const exists = await this.exists(envPath)
|
|
1236
|
+
return {
|
|
1237
|
+
appRoot,
|
|
1238
|
+
envRoot,
|
|
1239
|
+
envPath,
|
|
1240
|
+
envRelpath: gotRoot && gotRoot.relpath ? gotRoot.relpath : "",
|
|
1241
|
+
exists,
|
|
1242
|
+
value,
|
|
1243
|
+
enabled: value.length > 0
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async buildAutolaunchAppState(app) {
|
|
1247
|
+
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
1248
|
+
return {
|
|
1249
|
+
id: app.id,
|
|
1250
|
+
name: app.name,
|
|
1251
|
+
title: app.title || app.name || app.id,
|
|
1252
|
+
description: app.description || "",
|
|
1253
|
+
icon: app.icon || "/pinokio-black.png",
|
|
1254
|
+
workspace_path: app.workspace_path,
|
|
1255
|
+
launcher_path: app.launcher_path,
|
|
1256
|
+
launcher_root: app.launcher_root || "",
|
|
1257
|
+
env_path: envInfo.envPath,
|
|
1258
|
+
autolaunch: envInfo.value,
|
|
1259
|
+
autolaunch_enabled: envInfo.enabled
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
async buildAutolaunchAppsState() {
|
|
1263
|
+
const apps = await this.kernel.api.listApps()
|
|
1264
|
+
const states = []
|
|
1265
|
+
for (const app of apps) {
|
|
1266
|
+
states.push(await this.buildAutolaunchAppState(app))
|
|
1267
|
+
}
|
|
1268
|
+
return states
|
|
1269
|
+
}
|
|
1270
|
+
async resolveAutolaunchScript(appRoot, script) {
|
|
1271
|
+
const normalized = this.normalizeAutolaunchScriptPath(script)
|
|
1272
|
+
if (!normalized || !this.isAutolaunchScriptFilename(normalized)) {
|
|
1273
|
+
return null
|
|
1274
|
+
}
|
|
1275
|
+
const scriptPath = path.resolve(appRoot, normalized)
|
|
1276
|
+
if (!this.is_subpath(appRoot, scriptPath)) {
|
|
1277
|
+
return null
|
|
1278
|
+
}
|
|
1279
|
+
let stat
|
|
1280
|
+
try {
|
|
1281
|
+
stat = await fs.promises.stat(scriptPath)
|
|
1282
|
+
} catch (_) {
|
|
1283
|
+
return null
|
|
1284
|
+
}
|
|
1285
|
+
if (!stat || !stat.isFile()) {
|
|
1286
|
+
return null
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
script: normalized,
|
|
1290
|
+
path: scriptPath
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
flattenAutolaunchMenu(menu, trail = []) {
|
|
1294
|
+
const items = []
|
|
1295
|
+
if (!Array.isArray(menu)) {
|
|
1296
|
+
return items
|
|
1297
|
+
}
|
|
1298
|
+
for (const menuitem of menu) {
|
|
1299
|
+
if (!menuitem || typeof menuitem !== "object") {
|
|
1300
|
+
continue
|
|
1301
|
+
}
|
|
1302
|
+
const label = this.stripAutolaunchLabel(menuitem.text || menuitem.name || menuitem.html || "")
|
|
1303
|
+
const nextTrail = label ? trail.concat(label) : trail
|
|
1304
|
+
if (Array.isArray(menuitem.menu)) {
|
|
1305
|
+
items.push(...this.flattenAutolaunchMenu(menuitem.menu, nextTrail))
|
|
1306
|
+
} else {
|
|
1307
|
+
items.push({ item: menuitem, group: trail.join(" / ") })
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return items
|
|
1311
|
+
}
|
|
1312
|
+
async addAutolaunchCandidate(candidates, seen, candidate, appRoot) {
|
|
1313
|
+
const resolved = await this.resolveAutolaunchScript(appRoot, candidate.script)
|
|
1314
|
+
if (!resolved || seen.has(resolved.script)) {
|
|
1315
|
+
return null
|
|
1316
|
+
}
|
|
1317
|
+
seen.add(resolved.script)
|
|
1318
|
+
const label = this.stripAutolaunchLabel(candidate.label || "") || resolved.script
|
|
1319
|
+
const menuDefault = !!candidate.menu_default
|
|
1320
|
+
const item = {
|
|
1321
|
+
script: resolved.script,
|
|
1322
|
+
label,
|
|
1323
|
+
group: candidate.group || "",
|
|
1324
|
+
icon: candidate.icon || "",
|
|
1325
|
+
source: candidate.source || "local",
|
|
1326
|
+
menu_default: menuDefault,
|
|
1327
|
+
has_params: !!candidate.has_params
|
|
1328
|
+
}
|
|
1329
|
+
candidates.push(item)
|
|
1330
|
+
return item
|
|
1331
|
+
}
|
|
1332
|
+
async collectAutolaunchScriptFiles(root, appRoot) {
|
|
1333
|
+
const results = []
|
|
1334
|
+
const ignoredDirs = new Set([
|
|
1335
|
+
".git",
|
|
1336
|
+
".venv",
|
|
1337
|
+
"__pycache__",
|
|
1338
|
+
"app",
|
|
1339
|
+
"cache",
|
|
1340
|
+
"data",
|
|
1341
|
+
"env",
|
|
1342
|
+
"logs",
|
|
1343
|
+
"models",
|
|
1344
|
+
"node_modules",
|
|
1345
|
+
"output",
|
|
1346
|
+
"outputs",
|
|
1347
|
+
"venv"
|
|
1348
|
+
])
|
|
1349
|
+
const maxResults = 500
|
|
1350
|
+
const walk = async (dir, depth) => {
|
|
1351
|
+
if (depth > 4 || results.length >= maxResults) {
|
|
1352
|
+
return
|
|
1353
|
+
}
|
|
1354
|
+
let entries
|
|
1355
|
+
try {
|
|
1356
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true })
|
|
1357
|
+
} catch (_) {
|
|
1358
|
+
return
|
|
1359
|
+
}
|
|
1360
|
+
for (const entry of entries) {
|
|
1361
|
+
if (!entry || !entry.name || entry.name.includes("\0")) {
|
|
1362
|
+
continue
|
|
1363
|
+
}
|
|
1364
|
+
const fullPath = path.resolve(dir, entry.name)
|
|
1365
|
+
if (entry.isDirectory()) {
|
|
1366
|
+
if (!ignoredDirs.has(entry.name)) {
|
|
1367
|
+
await walk(fullPath, depth + 1)
|
|
1368
|
+
}
|
|
1369
|
+
continue
|
|
1370
|
+
}
|
|
1371
|
+
if (!entry.isFile() || !this.isAutolaunchScriptFilename(entry.name)) {
|
|
1372
|
+
continue
|
|
1373
|
+
}
|
|
1374
|
+
if (!this.is_subpath(appRoot, fullPath)) {
|
|
1375
|
+
continue
|
|
1376
|
+
}
|
|
1377
|
+
const rel = path.relative(appRoot, fullPath).split(path.sep).join("/")
|
|
1378
|
+
results.push(rel)
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
await walk(root, 0)
|
|
1382
|
+
results.sort((a, b) => a.localeCompare(b))
|
|
1383
|
+
return results
|
|
1384
|
+
}
|
|
1385
|
+
async buildAutolaunchCandidates(app) {
|
|
1386
|
+
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
1387
|
+
const appRoot = envInfo.appRoot
|
|
1388
|
+
const launcher = await this.kernel.api.launcher(app.id)
|
|
1389
|
+
const launcherRoot = launcher && launcher.launcher_root
|
|
1390
|
+
? path.resolve(appRoot, launcher.launcher_root)
|
|
1391
|
+
: appRoot
|
|
1392
|
+
const menuCandidates = []
|
|
1393
|
+
const otherCandidates = []
|
|
1394
|
+
const seen = new Set()
|
|
1395
|
+
|
|
1396
|
+
try {
|
|
1397
|
+
let config = await this.kernel.api.meta(app.id)
|
|
1398
|
+
config = await this.processMenu(app.id, safeStructuredClone(config || {}))
|
|
1399
|
+
const flat = this.flattenAutolaunchMenu(config && config.menu ? config.menu : [])
|
|
1400
|
+
for (const entry of flat) {
|
|
1401
|
+
const menuitem = entry.item
|
|
1402
|
+
if (!menuitem || typeof menuitem.href !== "string") {
|
|
1403
|
+
continue
|
|
1404
|
+
}
|
|
1405
|
+
let href = menuitem.href.trim()
|
|
1406
|
+
const apiPrefix = `/api/${app.id}/`
|
|
1407
|
+
if (href.startsWith(apiPrefix)) {
|
|
1408
|
+
href = href.slice(apiPrefix.length)
|
|
1409
|
+
} else if (href.startsWith("/")) {
|
|
1410
|
+
continue
|
|
1411
|
+
}
|
|
1412
|
+
const localScript = this.normalizeAutolaunchScriptPath(href)
|
|
1413
|
+
if (!localScript) {
|
|
1414
|
+
continue
|
|
1415
|
+
}
|
|
1416
|
+
const scriptPath = path.resolve(launcherRoot, localScript)
|
|
1417
|
+
if (!this.is_subpath(appRoot, scriptPath)) {
|
|
1418
|
+
continue
|
|
1419
|
+
}
|
|
1420
|
+
const script = path.relative(appRoot, scriptPath).split(path.sep).join("/")
|
|
1421
|
+
await this.addAutolaunchCandidate(menuCandidates, seen, {
|
|
1422
|
+
script,
|
|
1423
|
+
label: menuitem.text || menuitem.name || script,
|
|
1424
|
+
group: entry.group,
|
|
1425
|
+
icon: typeof menuitem.icon === "string" ? menuitem.icon : "",
|
|
1426
|
+
source: "menu",
|
|
1427
|
+
menu_default: !!menuitem.default,
|
|
1428
|
+
has_params: !!(menuitem.params && typeof menuitem.params === "object")
|
|
1429
|
+
}, appRoot)
|
|
1430
|
+
}
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
console.warn("[autolaunch] failed to resolve menu candidates", app.id, error && error.message ? error.message : error)
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (envInfo.value) {
|
|
1436
|
+
await this.addAutolaunchCandidate(menuCandidates, seen, {
|
|
1437
|
+
script: envInfo.value,
|
|
1438
|
+
label: envInfo.value,
|
|
1439
|
+
source: "current"
|
|
1440
|
+
}, appRoot)
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const localScripts = await this.collectAutolaunchScriptFiles(launcherRoot, appRoot)
|
|
1444
|
+
for (const script of localScripts) {
|
|
1445
|
+
await this.addAutolaunchCandidate(otherCandidates, seen, {
|
|
1446
|
+
script,
|
|
1447
|
+
label: script,
|
|
1448
|
+
source: "local"
|
|
1449
|
+
}, appRoot)
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
return {
|
|
1453
|
+
app: await this.buildAutolaunchAppState(app),
|
|
1454
|
+
launcher_root: path.relative(appRoot, launcherRoot).split(path.sep).join("/"),
|
|
1455
|
+
menu: menuCandidates,
|
|
1456
|
+
other: otherCandidates,
|
|
1457
|
+
current: envInfo.value
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1040
1460
|
async renderInvalidContentPage(req, res, invalid, options = {}) {
|
|
1041
1461
|
const type = invalid && typeof invalid.type === "string" ? invalid.type : "app"
|
|
1042
1462
|
const sidebarSelected = options.sidebarSelected || (type === "plugin" ? "plugins" : type === "task" ? "tasks" : "home")
|
|
@@ -1239,6 +1659,9 @@ class Server {
|
|
|
1239
1659
|
let dev_tab = "/p/" + name + "/dev"
|
|
1240
1660
|
let review_tab = "/p/" + name + "/review"
|
|
1241
1661
|
let files_tab = "/p/" + name + "/files"
|
|
1662
|
+
const dev_initial_tab = type === "browse" && req.query && req.query.pinokio_dev_tab === "files"
|
|
1663
|
+
? "files"
|
|
1664
|
+
: "plugins"
|
|
1242
1665
|
|
|
1243
1666
|
const registryEnabled = await this.isRegistryEnabled().catch(() => false)
|
|
1244
1667
|
let community_url = ""
|
|
@@ -1272,13 +1695,16 @@ class Server {
|
|
|
1272
1695
|
}
|
|
1273
1696
|
|
|
1274
1697
|
let dynamic_url = "/pinokio/dynamic/" + name;
|
|
1275
|
-
|
|
1698
|
+
const dynamicQueryEntries = Object.entries(req.query || {}).filter(([key]) => {
|
|
1699
|
+
return key !== "pinokio_dev_tab"
|
|
1700
|
+
})
|
|
1701
|
+
if (dynamicQueryEntries.length > 0) {
|
|
1276
1702
|
let index = 0
|
|
1277
|
-
for(let key
|
|
1703
|
+
for (let [key, value] of dynamicQueryEntries) {
|
|
1278
1704
|
if (index === 0) {
|
|
1279
|
-
dynamic_url = dynamic_url + `?${key}=${encodeURIComponent(
|
|
1705
|
+
dynamic_url = dynamic_url + `?${key}=${encodeURIComponent(value)}`
|
|
1280
1706
|
} else {
|
|
1281
|
-
dynamic_url = dynamic_url + `&${key}=${encodeURIComponent(
|
|
1707
|
+
dynamic_url = dynamic_url + `&${key}=${encodeURIComponent(value)}`
|
|
1282
1708
|
}
|
|
1283
1709
|
index++;
|
|
1284
1710
|
}
|
|
@@ -1286,6 +1712,22 @@ class Server {
|
|
|
1286
1712
|
const protectionPreference = this.appPreferences && typeof this.appPreferences.getPreference === "function"
|
|
1287
1713
|
? await this.appPreferences.getPreference(name)
|
|
1288
1714
|
: null
|
|
1715
|
+
let autolaunchAppState = null
|
|
1716
|
+
try {
|
|
1717
|
+
const appRoot = this.kernel.path("api", name)
|
|
1718
|
+
autolaunchAppState = await this.buildAutolaunchAppState({
|
|
1719
|
+
id: name,
|
|
1720
|
+
name,
|
|
1721
|
+
title: config && config.title ? config.title : name,
|
|
1722
|
+
description: config && config.description ? config.description : "",
|
|
1723
|
+
icon: config && config.icon ? config.icon : "/pinokio-black.png",
|
|
1724
|
+
workspace_path: appRoot,
|
|
1725
|
+
launcher_path: req.launcher_root ? path.resolve(appRoot, req.launcher_root) : appRoot,
|
|
1726
|
+
launcher_root: req.launcher_root || ""
|
|
1727
|
+
})
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
console.warn("[autolaunch] failed to build app page state", name, error && error.message ? error.message : error)
|
|
1730
|
+
}
|
|
1289
1731
|
|
|
1290
1732
|
const result = {
|
|
1291
1733
|
dev_link,
|
|
@@ -1317,8 +1759,10 @@ class Server {
|
|
|
1317
1759
|
// feed,
|
|
1318
1760
|
tabs: savedTabs,
|
|
1319
1761
|
editor_tab: editor_tab,
|
|
1762
|
+
dev_initial_tab,
|
|
1320
1763
|
config,
|
|
1321
1764
|
protection_enabled: protectionPreference ? protectionPreference.protection_enabled !== false : false,
|
|
1765
|
+
autolaunch_app: autolaunchAppState,
|
|
1322
1766
|
// sidebar_url: "/pinokio/sidebar/" + name,
|
|
1323
1767
|
home: req.originalUrl,
|
|
1324
1768
|
run_tab,
|
|
@@ -2091,15 +2535,8 @@ class Server {
|
|
|
2091
2535
|
|
|
2092
2536
|
const config = await this.kernel.git.config(dir)
|
|
2093
2537
|
|
|
2094
|
-
|
|
2095
|
-
const
|
|
2096
|
-
if (await this.exists(hostsFile)) {
|
|
2097
|
-
hosts = await fs.promises.readFile(hostsFile, "utf8")
|
|
2098
|
-
if (hosts.startsWith("{}")) {
|
|
2099
|
-
hosts = ""
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
const connected = hosts.length > 0
|
|
2538
|
+
const githubConnection = await this.get_github_connection()
|
|
2539
|
+
const connected = Boolean(githubConnection.connected)
|
|
2103
2540
|
|
|
2104
2541
|
let remote = null
|
|
2105
2542
|
if (config && config["remote \"origin\""]) {
|
|
@@ -2393,8 +2830,10 @@ class Server {
|
|
|
2393
2830
|
filepath = full_filepath
|
|
2394
2831
|
}
|
|
2395
2832
|
|
|
2396
|
-
if ((req.action || req.originalUrl
|
|
2397
|
-
const validation = await this.contentValidation.validateRunPath(pathComponents
|
|
2833
|
+
if ((req.action || PluginSources.isRunPath(req.originalUrl)) && this.contentValidation) {
|
|
2834
|
+
const validation = await this.contentValidation.validateRunPath(pathComponents, {
|
|
2835
|
+
system: req.pinokioSystem === true,
|
|
2836
|
+
})
|
|
2398
2837
|
if (validation && !validation.valid) {
|
|
2399
2838
|
await this.renderInvalidContentPage(req, res, validation, {
|
|
2400
2839
|
sidebarSelected: validation.type === "plugin" ? "plugins" : validation.type === "task" ? "tasks" : "home",
|
|
@@ -2753,10 +3192,12 @@ class Server {
|
|
|
2753
3192
|
} else {
|
|
2754
3193
|
resolved = runner(this.kernel, this.kernel.info)
|
|
2755
3194
|
}
|
|
2756
|
-
|
|
3195
|
+
const action = resolved ? resolved[actionKey] : null
|
|
3196
|
+
runnable = typeof action === "function" || (Array.isArray(action) && action.length > 0)
|
|
2757
3197
|
} else {
|
|
2758
|
-
runnable = runner && Array.isArray(runner[actionKey]) && runner[actionKey].length > 0
|
|
2759
3198
|
resolved = runner
|
|
3199
|
+
const action = resolved ? resolved[actionKey] : null
|
|
3200
|
+
runnable = typeof action === "function" || (Array.isArray(action) && action.length > 0)
|
|
2760
3201
|
}
|
|
2761
3202
|
|
|
2762
3203
|
let template = "terminal"
|
|
@@ -2865,11 +3306,19 @@ class Server {
|
|
|
2865
3306
|
const protectionPreference = protectionAppId && this.appPreferences && typeof this.appPreferences.getPreference === "function"
|
|
2866
3307
|
? await this.appPreferences.getPreference(protectionAppId)
|
|
2867
3308
|
: null
|
|
3309
|
+
const activeProcessWait = this.kernel.activeProcessWaits && this.kernel.activeProcessWaits[filepath]
|
|
3310
|
+
? this.kernel.activeProcessWaits[filepath]
|
|
3311
|
+
: null
|
|
2868
3312
|
const result = {
|
|
2869
3313
|
portal: this.portal,
|
|
2870
3314
|
projectName: (pathComponents.length > 0 ? pathComponents[0] : ''),
|
|
2871
3315
|
protection_app_id: protectionAppId,
|
|
2872
3316
|
protection_enabled: protectionPreference ? protectionPreference.protection_enabled !== false : false,
|
|
3317
|
+
active_process_wait: activeProcessWait ? {
|
|
3318
|
+
title: activeProcessWait.title,
|
|
3319
|
+
description: activeProcessWait.description,
|
|
3320
|
+
message: activeProcessWait.message
|
|
3321
|
+
} : null,
|
|
2873
3322
|
kill_message,
|
|
2874
3323
|
callback,
|
|
2875
3324
|
callback_target,
|
|
@@ -2884,6 +3333,7 @@ class Server {
|
|
|
2884
3333
|
//run: true, // run mode by default
|
|
2885
3334
|
run: (req.query && req.query.mode === "source" ? false : true),
|
|
2886
3335
|
stop: (req.query && req.query.stop ? true : false),
|
|
3336
|
+
readonly: req.pinokioSystem === true,
|
|
2887
3337
|
pinokioPath,
|
|
2888
3338
|
action: actionKey,
|
|
2889
3339
|
runnable,
|
|
@@ -3100,6 +3550,7 @@ class Server {
|
|
|
3100
3550
|
if (pathComponents.length === 0) {
|
|
3101
3551
|
const normalizedApiRoot = path.normalize(this.kernel.path("api"))
|
|
3102
3552
|
const normalizedPluginRoot = path.normalize(this.kernel.path("plugin"))
|
|
3553
|
+
const normalizedSystemPluginRoot = path.normalize(PluginSources.systemPluginRoot(this.kernel))
|
|
3103
3554
|
const isPathWithinRoot = (candidatePath, rootPath) => {
|
|
3104
3555
|
if (typeof candidatePath !== "string" || typeof rootPath !== "string") {
|
|
3105
3556
|
return false
|
|
@@ -3125,6 +3576,9 @@ class Server {
|
|
|
3125
3576
|
if (isPathWithinRoot(normalizedCandidate, normalizedPluginRoot)) {
|
|
3126
3577
|
return true
|
|
3127
3578
|
}
|
|
3579
|
+
if (isPathWithinRoot(normalizedCandidate, normalizedSystemPluginRoot)) {
|
|
3580
|
+
return true
|
|
3581
|
+
}
|
|
3128
3582
|
const relativeToApiRoot = path.relative(normalizedApiRoot, normalizedCandidate)
|
|
3129
3583
|
if (!relativeToApiRoot || relativeToApiRoot.startsWith("..") || path.isAbsolute(relativeToApiRoot)) {
|
|
3130
3584
|
return false
|
|
@@ -3138,6 +3592,8 @@ class Server {
|
|
|
3138
3592
|
let item = items[i]
|
|
3139
3593
|
let launcher = await this.kernel.api.launcher(item.name)
|
|
3140
3594
|
let config = launcher.script
|
|
3595
|
+
req.launcher_root = launcher.launcher_root
|
|
3596
|
+
req.pinokioLauncher = launcher
|
|
3141
3597
|
await this.kernel.dns({
|
|
3142
3598
|
name: item.name,
|
|
3143
3599
|
config
|
|
@@ -3145,6 +3601,13 @@ class Server {
|
|
|
3145
3601
|
|
|
3146
3602
|
|
|
3147
3603
|
if (config) {
|
|
3604
|
+
try {
|
|
3605
|
+
config = await this.processMenu(item.name, config)
|
|
3606
|
+
await this.renderMenu(req, this.kernel.path("api"), item.name, config, [])
|
|
3607
|
+
items[i].menu = Array.isArray(config.menu) ? config.menu : []
|
|
3608
|
+
} catch (e) {
|
|
3609
|
+
items[i].menu = []
|
|
3610
|
+
}
|
|
3148
3611
|
|
|
3149
3612
|
if (config.shortcuts) {
|
|
3150
3613
|
if (typeof config.shortcuts === "function") {
|
|
@@ -3786,6 +4249,33 @@ class Server {
|
|
|
3786
4249
|
menuitem.href = "/shell/" + shell_id + "?" + params.toString()
|
|
3787
4250
|
let decoded_shell_id = decodeURIComponent(shell_id)
|
|
3788
4251
|
const shellPrefixId = "shell/" + decoded_shell_id
|
|
4252
|
+
const appendShellIdentityParams = (href, activeShellId) => {
|
|
4253
|
+
if (!href || typeof href !== "string" || !activeShellId || typeof activeShellId !== "string") {
|
|
4254
|
+
return href
|
|
4255
|
+
}
|
|
4256
|
+
const shellQueryIndex = activeShellId.indexOf("?")
|
|
4257
|
+
if (shellQueryIndex < 0) {
|
|
4258
|
+
return href
|
|
4259
|
+
}
|
|
4260
|
+
let identityParams
|
|
4261
|
+
try {
|
|
4262
|
+
identityParams = new URLSearchParams(activeShellId.slice(shellQueryIndex + 1).replace(/&/g, "&"))
|
|
4263
|
+
} catch (error) {
|
|
4264
|
+
return href
|
|
4265
|
+
}
|
|
4266
|
+
const hrefQueryIndex = href.indexOf("?")
|
|
4267
|
+
const pathPart = hrefQueryIndex >= 0 ? href.slice(0, hrefQueryIndex) : href
|
|
4268
|
+
const queryPart = hrefQueryIndex >= 0 ? href.slice(hrefQueryIndex + 1) : ""
|
|
4269
|
+
const nextParams = new URLSearchParams(queryPart)
|
|
4270
|
+
;["session", "terminal_id"].forEach((key) => {
|
|
4271
|
+
const value = identityParams.get(key)
|
|
4272
|
+
if (value && !nextParams.has(key)) {
|
|
4273
|
+
nextParams.set(key, value)
|
|
4274
|
+
}
|
|
4275
|
+
})
|
|
4276
|
+
const qs = nextParams.toString()
|
|
4277
|
+
return qs ? `${pathPart}?${qs}` : pathPart
|
|
4278
|
+
}
|
|
3789
4279
|
let shell = this.kernel.shell.get(shellPrefixId)
|
|
3790
4280
|
if (!shell && this.kernel.shell && Array.isArray(this.kernel.shell.shells)) {
|
|
3791
4281
|
shell = this.kernel.shell.shells.find((entry) => {
|
|
@@ -3797,6 +4287,8 @@ class Server {
|
|
|
3797
4287
|
}
|
|
3798
4288
|
menuitem.shell_id = shellPrefixId
|
|
3799
4289
|
if (shell) {
|
|
4290
|
+
menuitem.shell_id = shell.id || shellPrefixId
|
|
4291
|
+
menuitem.href = appendShellIdentityParams(menuitem.href, shell.id)
|
|
3800
4292
|
menuitem.running = true
|
|
3801
4293
|
}
|
|
3802
4294
|
}
|
|
@@ -3938,8 +4430,7 @@ class Server {
|
|
|
3938
4430
|
if (menuitem.href.startsWith("http")) {
|
|
3939
4431
|
menuitem.src = menuitem.href
|
|
3940
4432
|
} else if (menuitem.href.startsWith("/")) {
|
|
3941
|
-
|
|
3942
|
-
if (menuitem.href.startsWith(run_path)) {
|
|
4433
|
+
if (PluginSources.isRunPath(menuitem.href)) {
|
|
3943
4434
|
menuitem.src = menuitem.href
|
|
3944
4435
|
// u = new URL("http://localhost" + menuitem.href.slice(run_path.length))
|
|
3945
4436
|
// cwd = u.searchParams.get("cwd")
|
|
@@ -3959,7 +4450,14 @@ class Server {
|
|
|
3959
4450
|
}
|
|
3960
4451
|
|
|
3961
4452
|
// check running
|
|
3962
|
-
let
|
|
4453
|
+
let srcPathname = menuitem.src
|
|
4454
|
+
try {
|
|
4455
|
+
srcPathname = new URL("http://localhost" + menuitem.src).pathname
|
|
4456
|
+
} catch (_) {
|
|
4457
|
+
}
|
|
4458
|
+
let fullpath = PluginSources.isRunPath(srcPathname)
|
|
4459
|
+
? PluginSources.resolveRunPath(this.kernel, srcPathname)
|
|
4460
|
+
: this.kernel.path(srcPathname.slice(1))
|
|
3963
4461
|
let relpath = path.relative(this.kernel.homedir, fullpath)
|
|
3964
4462
|
if (relpath.startsWith("api")) {
|
|
3965
4463
|
// api script
|
|
@@ -5279,15 +5777,7 @@ class Server {
|
|
|
5279
5777
|
return normalized
|
|
5280
5778
|
}
|
|
5281
5779
|
isValidBundledPluginConfig(pluginConfig) {
|
|
5282
|
-
|
|
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
|
|
5780
|
+
return PluginSources.isValidPluginConfig(pluginConfig)
|
|
5291
5781
|
}
|
|
5292
5782
|
isPathInsideRootForBundledPlugin(candidatePath, rootPath) {
|
|
5293
5783
|
const relative = path.relative(rootPath, candidatePath)
|
|
@@ -5673,7 +6163,6 @@ class Server {
|
|
|
5673
6163
|
"prototype/PTERM.md",
|
|
5674
6164
|
]
|
|
5675
6165
|
const managedRefreshTargets = [
|
|
5676
|
-
"plugin/code",
|
|
5677
6166
|
"prototype/system",
|
|
5678
6167
|
"network/system",
|
|
5679
6168
|
"prototype/PINOKIO.md",
|
|
@@ -5695,6 +6184,15 @@ class Server {
|
|
|
5695
6184
|
|
|
5696
6185
|
needsManagedRefresh = true
|
|
5697
6186
|
console.log("[TRY] Updating to the new version")
|
|
6187
|
+
let envPath = path.resolve(home, "ENVIRONMENT")
|
|
6188
|
+
let envExists = await this.kernel.exists(envPath)
|
|
6189
|
+
if (!envExists) {
|
|
6190
|
+
let str = await Environment.ENV("system", home, this.kernel)
|
|
6191
|
+
await fs.promises.writeFile(envPath, str)
|
|
6192
|
+
}
|
|
6193
|
+
await Environment.ensurePinokioCacheDirs(this.kernel, {
|
|
6194
|
+
throwOnFailure: true
|
|
6195
|
+
})
|
|
5698
6196
|
this.kernel.store.set("version", this.version.pinokiod)
|
|
5699
6197
|
console.log("[DONE] Updating to the new version")
|
|
5700
6198
|
console.log("not up to date. update py.")
|
|
@@ -5707,9 +6205,6 @@ class Server {
|
|
|
5707
6205
|
let p2 = path.resolve(home, "prototype/system")
|
|
5708
6206
|
await fse.remove(p2)
|
|
5709
6207
|
|
|
5710
|
-
let p3 = path.resolve(home, "plugin/code")
|
|
5711
|
-
await fse.remove(p3)
|
|
5712
|
-
|
|
5713
6208
|
let p4 = path.resolve(home, "network/system")
|
|
5714
6209
|
await fse.remove(p4)
|
|
5715
6210
|
|
|
@@ -5778,10 +6273,6 @@ class Server {
|
|
|
5778
6273
|
await this.kernel.proto.init()
|
|
5779
6274
|
}
|
|
5780
6275
|
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
|
-
}
|
|
5785
6276
|
const networkReady = await this.kernel.exists("network/system")
|
|
5786
6277
|
if (!networkReady && this.kernel.router && typeof this.kernel.router.init === "function") {
|
|
5787
6278
|
await this.kernel.router.init()
|
|
@@ -5962,6 +6453,10 @@ class Server {
|
|
|
5962
6453
|
})
|
|
5963
6454
|
this.app.use(express.static(path.resolve(__dirname, 'public')));
|
|
5964
6455
|
this.app.use("/web", express.static(path.resolve(__dirname, "..", "..", "web")))
|
|
6456
|
+
this.app.use(PluginSources.SYSTEM_ASSET_PREFIX, express.static(PluginSources.systemRoot(this.kernel), {
|
|
6457
|
+
index: false,
|
|
6458
|
+
fallthrough: true,
|
|
6459
|
+
}))
|
|
5965
6460
|
this.app.set('view engine', 'ejs');
|
|
5966
6461
|
this.app.use((req, res, next) => {
|
|
5967
6462
|
const peerForwarded = (req.get('X-Pinokio-Peer') || '').trim().toLowerCase()
|
|
@@ -6039,6 +6534,7 @@ class Server {
|
|
|
6039
6534
|
preferences: this.appPreferences,
|
|
6040
6535
|
appSearch: this.appSearch,
|
|
6041
6536
|
appLogs: this.appLogs,
|
|
6537
|
+
appLogReports: this.appLogReports,
|
|
6042
6538
|
getTheme: () => this.theme
|
|
6043
6539
|
})
|
|
6044
6540
|
|
|
@@ -6103,6 +6599,90 @@ class Server {
|
|
|
6103
6599
|
}
|
|
6104
6600
|
})
|
|
6105
6601
|
*/
|
|
6602
|
+
this.app.get("/autolaunch/candidates", ex(async (req, res) => {
|
|
6603
|
+
const app = await this.getAutolaunchAppById(req.query.app)
|
|
6604
|
+
if (!app) {
|
|
6605
|
+
res.status(404).json({ ok: false, error: "App not found." })
|
|
6606
|
+
return
|
|
6607
|
+
}
|
|
6608
|
+
const state = await this.buildAutolaunchCandidates(app)
|
|
6609
|
+
res.json({ ok: true, ...state })
|
|
6610
|
+
}))
|
|
6611
|
+
this.app.post("/autolaunch", ex(async (req, res) => {
|
|
6612
|
+
const app = await this.getAutolaunchAppById(req.body && req.body.app)
|
|
6613
|
+
if (!app) {
|
|
6614
|
+
res.status(404).json({ ok: false, error: "App not found." })
|
|
6615
|
+
return
|
|
6616
|
+
}
|
|
6617
|
+
const requestedScript = typeof req.body.script === "string" ? req.body.script.trim() : ""
|
|
6618
|
+
if (!requestedScript) {
|
|
6619
|
+
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
6620
|
+
if (envInfo.exists) {
|
|
6621
|
+
await Util.update_env(envInfo.envPath, {
|
|
6622
|
+
PINOKIO_SCRIPT_AUTOLAUNCH: ""
|
|
6623
|
+
})
|
|
6624
|
+
}
|
|
6625
|
+
res.json({
|
|
6626
|
+
ok: true,
|
|
6627
|
+
app: await this.buildAutolaunchAppState(app)
|
|
6628
|
+
})
|
|
6629
|
+
return
|
|
6630
|
+
}
|
|
6631
|
+
|
|
6632
|
+
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
6633
|
+
const resolved = await this.resolveAutolaunchScript(envInfo.appRoot, requestedScript)
|
|
6634
|
+
if (!resolved) {
|
|
6635
|
+
res.status(400).json({
|
|
6636
|
+
ok: false,
|
|
6637
|
+
error: "Select an existing local script inside the app."
|
|
6638
|
+
})
|
|
6639
|
+
return
|
|
6640
|
+
}
|
|
6641
|
+
const initialized = await Environment.init({ name: app.id }, this.kernel)
|
|
6642
|
+
await Util.update_env(initialized.env_path, {
|
|
6643
|
+
PINOKIO_SCRIPT_AUTOLAUNCH: resolved.script
|
|
6644
|
+
})
|
|
6645
|
+
res.json({
|
|
6646
|
+
ok: true,
|
|
6647
|
+
app: await this.buildAutolaunchAppState(app)
|
|
6648
|
+
})
|
|
6649
|
+
}))
|
|
6650
|
+
this.app.post("/autolaunch/disable-all", ex(async (req, res) => {
|
|
6651
|
+
const apps = await this.kernel.api.listApps()
|
|
6652
|
+
let disabled = 0
|
|
6653
|
+
for (const app of apps) {
|
|
6654
|
+
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
6655
|
+
if (envInfo.exists && envInfo.value) {
|
|
6656
|
+
await Util.update_env(envInfo.envPath, {
|
|
6657
|
+
PINOKIO_SCRIPT_AUTOLAUNCH: ""
|
|
6658
|
+
})
|
|
6659
|
+
disabled++
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
res.json({
|
|
6663
|
+
ok: true,
|
|
6664
|
+
disabled,
|
|
6665
|
+
apps: await this.buildAutolaunchAppsState()
|
|
6666
|
+
})
|
|
6667
|
+
}))
|
|
6668
|
+
this.app.get("/autolaunch", ex(async (req, res) => {
|
|
6669
|
+
const peerAccess = await this.composePeerAccessPayload()
|
|
6670
|
+
const list = this.getPeers()
|
|
6671
|
+
const apps = await this.buildAutolaunchAppsState()
|
|
6672
|
+
const appsJson = JSON.stringify(apps).replace(/</g, "\\u003c")
|
|
6673
|
+
res.render("autolaunch", {
|
|
6674
|
+
current_host: this.kernel.peer.host,
|
|
6675
|
+
...peerAccess,
|
|
6676
|
+
apps,
|
|
6677
|
+
appsJson,
|
|
6678
|
+
enabledCount: apps.filter((app) => app.autolaunch_enabled).length,
|
|
6679
|
+
portal: this.portal,
|
|
6680
|
+
logo: this.logo,
|
|
6681
|
+
theme: this.theme,
|
|
6682
|
+
agent: req.agent,
|
|
6683
|
+
list,
|
|
6684
|
+
})
|
|
6685
|
+
}))
|
|
6106
6686
|
this.app.get("/tools", ex(async (req, res) => {
|
|
6107
6687
|
const peerAccess = await this.composePeerAccessPayload()
|
|
6108
6688
|
let list = this.getPeers()
|
|
@@ -6744,24 +7324,7 @@ class Server {
|
|
|
6744
7324
|
}
|
|
6745
7325
|
}
|
|
6746
7326
|
const normalizePluginPath = (value) => {
|
|
6747
|
-
|
|
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
|
|
7327
|
+
return PluginSources.normalizePluginPath(value)
|
|
6765
7328
|
}
|
|
6766
7329
|
const normalizePluginLookupKey = (value) => {
|
|
6767
7330
|
const normalized = normalizePluginPath(value)
|
|
@@ -6777,6 +7340,21 @@ class Server {
|
|
|
6777
7340
|
: `${trimmed.replace(/\/+$/, "")}/pinokio.js`
|
|
6778
7341
|
}
|
|
6779
7342
|
const loadBundledPluginMenu = async () => this.getBundledPluginMenu()
|
|
7343
|
+
const evaluatePluginInstalled = async (pluginItem) => {
|
|
7344
|
+
if (!PluginSources.isInstalledCheck(pluginItem && pluginItem.installed)) {
|
|
7345
|
+
return null
|
|
7346
|
+
}
|
|
7347
|
+
try {
|
|
7348
|
+
const result = await Promise.race([
|
|
7349
|
+
Promise.resolve(pluginItem.installed(this.kernel, this.kernel.info || {})),
|
|
7350
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 1000))
|
|
7351
|
+
])
|
|
7352
|
+
return typeof result === "boolean" ? result : null
|
|
7353
|
+
} catch (error) {
|
|
7354
|
+
console.warn("Failed to evaluate plugin installed state", pluginItem && pluginItem.title, error)
|
|
7355
|
+
return null
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
6780
7358
|
const classifyPluginMenuItem = (pluginItem) => {
|
|
6781
7359
|
const runs = Array.isArray(pluginItem && pluginItem.run) ? pluginItem.run : []
|
|
6782
7360
|
const hasExec = runs.some((step) => step && step.method === "exec")
|
|
@@ -6793,7 +7371,7 @@ class Server {
|
|
|
6793
7371
|
}
|
|
6794
7372
|
return "cli"
|
|
6795
7373
|
}
|
|
6796
|
-
const serializePluginMenuItem = (pluginItem, index) => {
|
|
7374
|
+
const serializePluginMenuItem = async (pluginItem, index) => {
|
|
6797
7375
|
const hrefValue = typeof pluginItem?.href === "string" ? pluginItem.href : ""
|
|
6798
7376
|
let pluginPath = typeof pluginItem?.src === "string" ? pluginItem.src : ""
|
|
6799
7377
|
const extraParams = []
|
|
@@ -6801,7 +7379,9 @@ class Server {
|
|
|
6801
7379
|
try {
|
|
6802
7380
|
const parsed = new URL(hrefValue.startsWith("http") ? hrefValue : `http://localhost${hrefValue}`)
|
|
6803
7381
|
if (!pluginPath) {
|
|
6804
|
-
pluginPath = parsed.pathname
|
|
7382
|
+
pluginPath = PluginSources.isSystemRunPath(parsed.pathname)
|
|
7383
|
+
? parsed.pathname
|
|
7384
|
+
: (parsed.pathname.replace(/^\/run/, "") || "")
|
|
6805
7385
|
}
|
|
6806
7386
|
parsed.searchParams.forEach((value, key) => {
|
|
6807
7387
|
if (key === "cwd") {
|
|
@@ -6816,6 +7396,8 @@ class Server {
|
|
|
6816
7396
|
const normalizedPluginPath = normalizePluginPath(pluginPath)
|
|
6817
7397
|
const category = classifyPluginMenuItem(pluginItem)
|
|
6818
7398
|
const title = pluginItem?.title || pluginItem?.text || pluginItem?.name || "Plugin"
|
|
7399
|
+
const hasInstalledCheck = PluginSources.isInstalledCheck(pluginItem?.installed)
|
|
7400
|
+
const installed = hasInstalledCheck ? await evaluatePluginInstalled(pluginItem) : null
|
|
6819
7401
|
return {
|
|
6820
7402
|
index,
|
|
6821
7403
|
title,
|
|
@@ -6836,9 +7418,11 @@ class Server {
|
|
|
6836
7418
|
cwd: typeof pluginItem.ownerApp.cwd === "string" ? pluginItem.ownerApp.cwd : "",
|
|
6837
7419
|
}
|
|
6838
7420
|
: null,
|
|
6839
|
-
hasInstall:
|
|
6840
|
-
hasUninstall:
|
|
6841
|
-
hasUpdate:
|
|
7421
|
+
hasInstall: PluginSources.isAction(pluginItem?.install),
|
|
7422
|
+
hasUninstall: PluginSources.isAction(pluginItem?.uninstall),
|
|
7423
|
+
hasUpdate: PluginSources.isAction(pluginItem?.update),
|
|
7424
|
+
hasInstalledCheck,
|
|
7425
|
+
installed,
|
|
6842
7426
|
category,
|
|
6843
7427
|
categoryTitle: category === "ide" ? "Desktop Plugin" : "Terminal Plugin",
|
|
6844
7428
|
categorySubtitle: category === "ide" ? "Launch externally" : "Launch in Pinokio",
|
|
@@ -6868,7 +7452,7 @@ class Server {
|
|
|
6868
7452
|
console.warn("Failed to load bundled plugins", bundledError)
|
|
6869
7453
|
}
|
|
6870
7454
|
const mergedPluginMenu = pluginMenu.concat(bundledPluginMenu)
|
|
6871
|
-
return mergedPluginMenu.map((pluginItem, index) => serializePluginMenuItem(pluginItem, index))
|
|
7455
|
+
return Promise.all(mergedPluginMenu.map((pluginItem, index) => serializePluginMenuItem(pluginItem, index)))
|
|
6872
7456
|
}
|
|
6873
7457
|
const buildPluginCategories = (plugins) => {
|
|
6874
7458
|
const buckets = { ide: [], cli: [] }
|
|
@@ -6880,10 +7464,75 @@ class Server {
|
|
|
6880
7464
|
}
|
|
6881
7465
|
})
|
|
6882
7466
|
return [
|
|
6883
|
-
{ key: "ide", title: "Desktop Plugins", subtitle: "Launch externally", items: buckets.ide },
|
|
6884
7467
|
{ key: "cli", title: "Terminal Plugins", subtitle: "Launch in Pinokio", items: buckets.cli },
|
|
7468
|
+
{ key: "ide", title: "Desktop Plugins", subtitle: "Launch externally", items: buckets.ide },
|
|
6885
7469
|
]
|
|
6886
7470
|
}
|
|
7471
|
+
const resolveProjectShellWorkspace = (value) => {
|
|
7472
|
+
const requestedWorkspace = typeof value === "string" ? value.trim() : ""
|
|
7473
|
+
if (!requestedWorkspace) {
|
|
7474
|
+
return ""
|
|
7475
|
+
}
|
|
7476
|
+
try {
|
|
7477
|
+
return path.resolve(this.kernel.api.filePath(requestedWorkspace))
|
|
7478
|
+
} catch (error) {
|
|
7479
|
+
return ""
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
const serializeProjectShellMenu = async (workspacePath) => {
|
|
7483
|
+
if (!workspacePath) {
|
|
7484
|
+
return null
|
|
7485
|
+
}
|
|
7486
|
+
try {
|
|
7487
|
+
const stat = await fs.promises.stat(workspacePath)
|
|
7488
|
+
if (!stat.isDirectory()) {
|
|
7489
|
+
return null
|
|
7490
|
+
}
|
|
7491
|
+
} catch (error) {
|
|
7492
|
+
return null
|
|
7493
|
+
}
|
|
7494
|
+
try {
|
|
7495
|
+
const terminal = await this.devTerminals(workspacePath)
|
|
7496
|
+
const items = []
|
|
7497
|
+
const addShellOption = (item, group = {}) => {
|
|
7498
|
+
if (!item || typeof item !== "object") {
|
|
7499
|
+
return
|
|
7500
|
+
}
|
|
7501
|
+
if (Array.isArray(item.menu) && item.menu.length > 0) {
|
|
7502
|
+
item.menu.forEach((child) => addShellOption(child, item))
|
|
7503
|
+
return
|
|
7504
|
+
}
|
|
7505
|
+
const href = typeof item.href === "string" ? item.href.trim() : ""
|
|
7506
|
+
if (!href || !href.startsWith("/shell/")) {
|
|
7507
|
+
return
|
|
7508
|
+
}
|
|
7509
|
+
const title = typeof item.title === "string" && item.title.trim()
|
|
7510
|
+
? item.title.trim()
|
|
7511
|
+
: (typeof item.text === "string" && item.text.trim() ? item.text.trim() : "Shell")
|
|
7512
|
+
items.push({
|
|
7513
|
+
title,
|
|
7514
|
+
text: typeof item.text === "string" ? item.text : title,
|
|
7515
|
+
subtitle: typeof item.subtitle === "string" ? item.subtitle : "",
|
|
7516
|
+
icon: typeof item.icon === "string" && item.icon ? item.icon : (group.icon || terminal.icon || "fa-solid fa-terminal"),
|
|
7517
|
+
href,
|
|
7518
|
+
target: `@${href}`,
|
|
7519
|
+
shellId: typeof item.shell_id === "string" ? item.shell_id : "",
|
|
7520
|
+
})
|
|
7521
|
+
}
|
|
7522
|
+
;(Array.isArray(terminal.menu) ? terminal.menu : []).forEach((item) => addShellOption(item))
|
|
7523
|
+
if (items.length === 0) {
|
|
7524
|
+
return null
|
|
7525
|
+
}
|
|
7526
|
+
return {
|
|
7527
|
+
title: "Project Shell",
|
|
7528
|
+
subtitle: typeof terminal.subtitle === "string" ? terminal.subtitle : "",
|
|
7529
|
+
items,
|
|
7530
|
+
}
|
|
7531
|
+
} catch (error) {
|
|
7532
|
+
console.warn("Failed to load project shell menu for Ask AI", error)
|
|
7533
|
+
return null
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
6887
7536
|
const findPluginByPath = (plugins, targetPath) => {
|
|
6888
7537
|
const targetKey = normalizePluginLookupKey(targetPath)
|
|
6889
7538
|
if (!targetKey) {
|
|
@@ -6908,7 +7557,7 @@ class Server {
|
|
|
6908
7557
|
index: -1,
|
|
6909
7558
|
title: context.title || "Plugin",
|
|
6910
7559
|
description: typeof config.description === "string" ? config.description : "",
|
|
6911
|
-
href:
|
|
7560
|
+
href: PluginSources.pluginRunHrefForPath(normalizedPluginPath),
|
|
6912
7561
|
link: typeof config.link === "string" ? config.link : "",
|
|
6913
7562
|
image: context.image || null,
|
|
6914
7563
|
icon: null,
|
|
@@ -6921,6 +7570,8 @@ class Server {
|
|
|
6921
7570
|
hasInstall: !!context.hasInstall,
|
|
6922
7571
|
hasUninstall: !!context.hasUninstall,
|
|
6923
7572
|
hasUpdate: !!context.hasUpdate,
|
|
7573
|
+
hasInstalledCheck: !!context.hasInstalledCheck,
|
|
7574
|
+
installed: null,
|
|
6924
7575
|
category,
|
|
6925
7576
|
categoryTitle: category === "ide" ? "Desktop Plugin" : "Terminal Plugin",
|
|
6926
7577
|
categorySubtitle: category === "ide" ? "Launch externally" : "Launch in Pinokio",
|
|
@@ -6948,9 +7599,20 @@ class Server {
|
|
|
6948
7599
|
localLabel: "",
|
|
6949
7600
|
managedPrefix: "",
|
|
6950
7601
|
}
|
|
7602
|
+
if (PluginSources.isSystemPluginPath(normalizedPath)) {
|
|
7603
|
+
const relativeSystemPath = normalizedPath.replace(/^\/pinokio\/run\/+/, "")
|
|
7604
|
+
return {
|
|
7605
|
+
...emptyState,
|
|
7606
|
+
ownership: "system",
|
|
7607
|
+
localLabel: relativeSystemPath,
|
|
7608
|
+
}
|
|
7609
|
+
}
|
|
6951
7610
|
if (!normalizedPath.startsWith("/plugin/")) {
|
|
6952
7611
|
return emptyState
|
|
6953
7612
|
}
|
|
7613
|
+
if (PluginSources.isLegacyPluginCodePath(normalizedPath)) {
|
|
7614
|
+
return emptyState
|
|
7615
|
+
}
|
|
6954
7616
|
const pluginFilePath = path.resolve(this.kernel.path(normalizedPath.slice(1)))
|
|
6955
7617
|
if (!isPathInsideRoot(pluginFilePath, pluginRoot)) {
|
|
6956
7618
|
return emptyState
|
|
@@ -7176,6 +7838,11 @@ class Server {
|
|
|
7176
7838
|
label: "Local plugin",
|
|
7177
7839
|
tone: "accent"
|
|
7178
7840
|
})
|
|
7841
|
+
} else if (ownership === "system") {
|
|
7842
|
+
badges.push({
|
|
7843
|
+
label: "Built-in plugin",
|
|
7844
|
+
tone: "neutral"
|
|
7845
|
+
})
|
|
7179
7846
|
} else if (ownership === "managed") {
|
|
7180
7847
|
badges.push({
|
|
7181
7848
|
label: "Managed by Pinokio",
|
|
@@ -7191,6 +7858,17 @@ class Server {
|
|
|
7191
7858
|
label: plugin && plugin.category === "ide" ? "Desktop plugin" : "Terminal plugin",
|
|
7192
7859
|
tone: "neutral"
|
|
7193
7860
|
})
|
|
7861
|
+
if (plugin && plugin.installed === true) {
|
|
7862
|
+
badges.push({
|
|
7863
|
+
label: "Installed",
|
|
7864
|
+
tone: "accent"
|
|
7865
|
+
})
|
|
7866
|
+
} else if (plugin && plugin.installed === false) {
|
|
7867
|
+
badges.push({
|
|
7868
|
+
label: "Not installed",
|
|
7869
|
+
tone: "warning"
|
|
7870
|
+
})
|
|
7871
|
+
}
|
|
7194
7872
|
if (remoteLabel) {
|
|
7195
7873
|
badges.push({
|
|
7196
7874
|
label: "Remote linked",
|
|
@@ -7228,8 +7906,14 @@ class Server {
|
|
|
7228
7906
|
sourceValue = shareState.localLabel
|
|
7229
7907
|
statusValue = changes.length > 0 ? pluralizeTaskFiles(changes.length) : "Updated with Pinokio"
|
|
7230
7908
|
githubPanelTitle = "Managed by Pinokio"
|
|
7231
|
-
githubPanelCopy = "This plugin lives inside
|
|
7909
|
+
githubPanelCopy = "This plugin lives inside Pinokio-managed source. Open the folder if you need to inspect it, but don't treat it as your own publishable repo."
|
|
7232
7910
|
localChangesCopy = "These edits live inside Pinokio-managed source and may be overwritten by future Pinokio updates."
|
|
7911
|
+
} else if (ownership === "system") {
|
|
7912
|
+
sourceLabel = "System plugin"
|
|
7913
|
+
sourceValue = shareState.localLabel || sourceValue
|
|
7914
|
+
statusValue = "Read-only"
|
|
7915
|
+
githubPanelTitle = "Built in to Pinokio"
|
|
7916
|
+
githubPanelCopy = "This plugin ships with Pinokio and is not editable from the local plugin workspace."
|
|
7233
7917
|
}
|
|
7234
7918
|
const changePreview = changes.slice(0, 6).map((change) => ({
|
|
7235
7919
|
file: change && change.file ? change.file : "",
|
|
@@ -7272,6 +7956,7 @@ class Server {
|
|
|
7272
7956
|
}))
|
|
7273
7957
|
this.app.get("/plugin", ex(async (req, res) => {
|
|
7274
7958
|
const requestedPath = typeof req.query.path === "string" ? req.query.path.trim() : ""
|
|
7959
|
+
const requestedCwd = typeof req.query.cwd === "string" ? req.query.cwd.trim() : ""
|
|
7275
7960
|
if (!requestedPath) {
|
|
7276
7961
|
res.redirect("/plugins")
|
|
7277
7962
|
return
|
|
@@ -7301,6 +7986,7 @@ class Server {
|
|
|
7301
7986
|
...sidebarContext,
|
|
7302
7987
|
plugin,
|
|
7303
7988
|
pluginUi,
|
|
7989
|
+
pluginCwd: requestedCwd,
|
|
7304
7990
|
shareState,
|
|
7305
7991
|
apps,
|
|
7306
7992
|
})
|
|
@@ -7337,14 +8023,154 @@ class Server {
|
|
|
7337
8023
|
}))
|
|
7338
8024
|
this.app.get("/api/plugin/menu", ex(async (req, res) => {
|
|
7339
8025
|
try {
|
|
8026
|
+
const workspacePath = resolveProjectShellWorkspace(req.query.workspace)
|
|
7340
8027
|
const pluginMenu = await loadSerializedPlugins()
|
|
8028
|
+
const projectShell = await serializeProjectShellMenu(workspacePath)
|
|
7341
8029
|
res.set("Cache-Control", "no-store")
|
|
7342
|
-
res.json({ menu: pluginMenu })
|
|
8030
|
+
res.json({ menu: pluginMenu, projectShell })
|
|
7343
8031
|
} catch (error) {
|
|
7344
8032
|
console.warn('Failed to load plugin menu for create launcher modal', error)
|
|
7345
8033
|
res.json({ menu: [] })
|
|
7346
8034
|
}
|
|
7347
8035
|
}))
|
|
8036
|
+
this.app.get("/api/plugin/install-state", ex(async (req, res) => {
|
|
8037
|
+
const requestedPath = typeof req.query.path === "string" ? req.query.path.trim() : ""
|
|
8038
|
+
if (!requestedPath) {
|
|
8039
|
+
res.status(400).json({
|
|
8040
|
+
ok: false,
|
|
8041
|
+
error: "Plugin path is required."
|
|
8042
|
+
})
|
|
8043
|
+
return
|
|
8044
|
+
}
|
|
8045
|
+
const validation = await contentValidation.validatePluginByPath(requestedPath)
|
|
8046
|
+
if (!validation.valid) {
|
|
8047
|
+
res.status(404).json({
|
|
8048
|
+
ok: false,
|
|
8049
|
+
error: validation.message || "Plugin not found."
|
|
8050
|
+
})
|
|
8051
|
+
return
|
|
8052
|
+
}
|
|
8053
|
+
const context = validation.context && typeof validation.context === "object" ? validation.context : {}
|
|
8054
|
+
const config = context.config && typeof context.config === "object" ? context.config : {}
|
|
8055
|
+
const normalizedPluginPath = normalizePluginPath(context.pluginPath || requestedPath)
|
|
8056
|
+
const hasInstall = PluginSources.isAction(config.install)
|
|
8057
|
+
const hasInstalledCheck = PluginSources.isInstalledCheck(config.installed)
|
|
8058
|
+
const installed = hasInstalledCheck ? await evaluatePluginInstalled(config) : null
|
|
8059
|
+
res.set("Cache-Control", "no-store")
|
|
8060
|
+
res.json({
|
|
8061
|
+
ok: true,
|
|
8062
|
+
title: context.title || "Plugin",
|
|
8063
|
+
pluginPath: normalizedPluginPath,
|
|
8064
|
+
detailUrl: `/plugin?path=${encodeURIComponent(normalizedPluginPath)}`,
|
|
8065
|
+
hasInstall,
|
|
8066
|
+
hasInstalledCheck,
|
|
8067
|
+
installed,
|
|
8068
|
+
})
|
|
8069
|
+
}))
|
|
8070
|
+
const redirectSkills = (res, params = {}) => {
|
|
8071
|
+
const query = new URLSearchParams()
|
|
8072
|
+
if (params.notice) query.set("notice", params.notice)
|
|
8073
|
+
if (params.error) query.set("error", params.error)
|
|
8074
|
+
res.redirect(`/skills${query.toString() ? `?${query.toString()}` : ""}`)
|
|
8075
|
+
}
|
|
8076
|
+
const requireSkillsHome = (res) => {
|
|
8077
|
+
if (this.kernel && this.kernel.homedir) {
|
|
8078
|
+
return true
|
|
8079
|
+
}
|
|
8080
|
+
res.redirect("/home?mode=settings")
|
|
8081
|
+
return false
|
|
8082
|
+
}
|
|
8083
|
+
this.app.get("/skills", ex(async (req, res) => {
|
|
8084
|
+
if (!requireSkillsHome(res)) {
|
|
8085
|
+
return
|
|
8086
|
+
}
|
|
8087
|
+
let skills = []
|
|
8088
|
+
let indexError = ""
|
|
8089
|
+
try {
|
|
8090
|
+
skills = await ManagedSkills.listManagedSkills(this.kernel)
|
|
8091
|
+
} catch (error) {
|
|
8092
|
+
indexError = error && error.message ? error.message : "Failed to read managed skills index."
|
|
8093
|
+
}
|
|
8094
|
+
res.render("skills", {
|
|
8095
|
+
theme: this.theme,
|
|
8096
|
+
agent: req.agent,
|
|
8097
|
+
skills,
|
|
8098
|
+
skillsRootPath: ManagedSkills.skillsRoot(this.kernel),
|
|
8099
|
+
indexPath: ManagedSkills.indexPath(this.kernel),
|
|
8100
|
+
publishRoots: ManagedSkills.publishRoots(),
|
|
8101
|
+
notice: typeof req.query.notice === "string" ? req.query.notice : "",
|
|
8102
|
+
error: typeof req.query.error === "string" ? req.query.error : "",
|
|
8103
|
+
indexError
|
|
8104
|
+
})
|
|
8105
|
+
}))
|
|
8106
|
+
this.app.post("/skills/install", ex(async (req, res) => {
|
|
8107
|
+
if (!requireSkillsHome(res)) {
|
|
8108
|
+
return
|
|
8109
|
+
}
|
|
8110
|
+
try {
|
|
8111
|
+
const skill = await ManagedSkills.installSkillFromGit(this.kernel, {
|
|
8112
|
+
ref: typeof req.body.ref === "string" ? req.body.ref : ""
|
|
8113
|
+
})
|
|
8114
|
+
redirectSkills(res, {
|
|
8115
|
+
notice: skill && skill.valid
|
|
8116
|
+
? `Downloaded ${skill.label || skill.id}.`
|
|
8117
|
+
: `Downloaded ${skill ? skill.id : "skill"}, but it is invalid.`
|
|
8118
|
+
})
|
|
8119
|
+
} catch (error) {
|
|
8120
|
+
redirectSkills(res, {
|
|
8121
|
+
error: error && error.message ? error.message : "Failed to download skill."
|
|
8122
|
+
})
|
|
8123
|
+
}
|
|
8124
|
+
}))
|
|
8125
|
+
this.app.post("/skills/:id/toggle", ex(async (req, res) => {
|
|
8126
|
+
if (!requireSkillsHome(res)) {
|
|
8127
|
+
return
|
|
8128
|
+
}
|
|
8129
|
+
const id = typeof req.params.id === "string" ? req.params.id : ""
|
|
8130
|
+
const enabled = req.body.enabled === "1" || req.body.enabled === "true"
|
|
8131
|
+
try {
|
|
8132
|
+
const skill = await ManagedSkills.setSkillEnabled(this.kernel, id, enabled)
|
|
8133
|
+
redirectSkills(res, {
|
|
8134
|
+
notice: `${skill && skill.label ? skill.label : id} ${enabled ? "turned on" : "turned off"}.`
|
|
8135
|
+
})
|
|
8136
|
+
} catch (error) {
|
|
8137
|
+
redirectSkills(res, {
|
|
8138
|
+
error: error && error.message ? error.message : "Failed to update skill."
|
|
8139
|
+
})
|
|
8140
|
+
}
|
|
8141
|
+
}))
|
|
8142
|
+
this.app.post("/skills/:id/publish-name", ex(async (req, res) => {
|
|
8143
|
+
if (!requireSkillsHome(res)) {
|
|
8144
|
+
return
|
|
8145
|
+
}
|
|
8146
|
+
const id = typeof req.params.id === "string" ? req.params.id : ""
|
|
8147
|
+
try {
|
|
8148
|
+
const skill = await ManagedSkills.setSkillPublishName(this.kernel, id, req.body.publishName)
|
|
8149
|
+
redirectSkills(res, {
|
|
8150
|
+
notice: `${skill && skill.label ? skill.label : id} syncs as ${skill.publishName}.`
|
|
8151
|
+
})
|
|
8152
|
+
} catch (error) {
|
|
8153
|
+
redirectSkills(res, {
|
|
8154
|
+
error: error && error.message ? error.message : "Failed to update sync folder name."
|
|
8155
|
+
})
|
|
8156
|
+
}
|
|
8157
|
+
}))
|
|
8158
|
+
this.app.post("/skills/:id/remove", ex(async (req, res) => {
|
|
8159
|
+
if (!requireSkillsHome(res)) {
|
|
8160
|
+
return
|
|
8161
|
+
}
|
|
8162
|
+
const id = typeof req.params.id === "string" ? req.params.id : ""
|
|
8163
|
+
try {
|
|
8164
|
+
await ManagedSkills.removeSkill(this.kernel, id)
|
|
8165
|
+
redirectSkills(res, {
|
|
8166
|
+
notice: `${id} removed.`
|
|
8167
|
+
})
|
|
8168
|
+
} catch (error) {
|
|
8169
|
+
redirectSkills(res, {
|
|
8170
|
+
error: error && error.message ? error.message : "Failed to remove skill."
|
|
8171
|
+
})
|
|
8172
|
+
}
|
|
8173
|
+
}))
|
|
7348
8174
|
this.app.get("/api/tasks", ex(async (req, res) => {
|
|
7349
8175
|
const query = typeof req.query.q === "string" ? req.query.q.trim().toLowerCase() : ""
|
|
7350
8176
|
const targetFilter = typeof req.query.target === "string"
|
|
@@ -7488,8 +8314,13 @@ class Server {
|
|
|
7488
8314
|
const peerAccess = await this.composePeerAccessPayload()
|
|
7489
8315
|
const list = this.getPeers()
|
|
7490
8316
|
const workspace = typeof req.query.workspace === 'string' ? req.query.workspace.trim() : ''
|
|
8317
|
+
const embedded = req.query.embed === '1' || req.query.embed === 'true'
|
|
8318
|
+
const requestedView = typeof req.query.view === 'string' ? req.query.view.trim().toLowerCase() : ''
|
|
8319
|
+
const initialView = workspace && requestedView !== 'raw' ? 'latest' : 'raw'
|
|
7491
8320
|
let context
|
|
7492
8321
|
const downloadUrl = workspace ? `/pinokio/logs.zip?workspace=${encodeURIComponent(workspace)}` : '/pinokio/logs.zip'
|
|
8322
|
+
const reportUrl = workspace ? `/apps/logs/${encodeURIComponent(workspace)}/report` : ''
|
|
8323
|
+
const draftUrl = workspace ? `/apps/logs/${encodeURIComponent(workspace)}/drafts` : ''
|
|
7493
8324
|
try {
|
|
7494
8325
|
context = await this.resolveLogsRoot({ workspace })
|
|
7495
8326
|
} catch (error) {
|
|
@@ -7506,6 +8337,10 @@ class Server {
|
|
|
7506
8337
|
logsTitle: workspace || null,
|
|
7507
8338
|
logsError: error && error.message ? error.message : 'Workspace not found',
|
|
7508
8339
|
logsDownloadUrl: downloadUrl,
|
|
8340
|
+
logsReportUrl: reportUrl,
|
|
8341
|
+
logsDraftUrl: draftUrl,
|
|
8342
|
+
logsEmbedded: embedded,
|
|
8343
|
+
logsInitialView: initialView,
|
|
7509
8344
|
})
|
|
7510
8345
|
return
|
|
7511
8346
|
}
|
|
@@ -7522,6 +8357,10 @@ class Server {
|
|
|
7522
8357
|
logsTitle: context.title,
|
|
7523
8358
|
logsError: null,
|
|
7524
8359
|
logsDownloadUrl: downloadUrl,
|
|
8360
|
+
logsReportUrl: reportUrl,
|
|
8361
|
+
logsDraftUrl: draftUrl,
|
|
8362
|
+
logsEmbedded: embedded,
|
|
8363
|
+
logsInitialView: initialView,
|
|
7525
8364
|
})
|
|
7526
8365
|
}))
|
|
7527
8366
|
this.app.get("/api/logs/tree", ex(async (req, res) => {
|
|
@@ -8094,24 +8933,7 @@ class Server {
|
|
|
8094
8933
|
}
|
|
8095
8934
|
return targetPath
|
|
8096
8935
|
}
|
|
8097
|
-
|
|
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
|
-
}
|
|
8936
|
+
const resolveUniversalLauncherPluginHref = PluginSources.resolveLauncherPluginHref
|
|
8115
8937
|
const persistLauncherPromptContext = async (targetPath, options = {}) => {
|
|
8116
8938
|
const prompt = typeof options.prompt === "string" ? options.prompt.trim() : ""
|
|
8117
8939
|
if (!prompt) {
|
|
@@ -8390,7 +9212,7 @@ class Server {
|
|
|
8390
9212
|
type: "create_app",
|
|
8391
9213
|
name: folderName,
|
|
8392
9214
|
cwd: path.resolve(this.kernel.path("api"), folderName),
|
|
8393
|
-
url: response.success
|
|
9215
|
+
url: PluginSources.normalizeLauncherSuccessPlugin(response.success, selectedTool)
|
|
8394
9216
|
}
|
|
8395
9217
|
}
|
|
8396
9218
|
const taskPackages = createTaskPackageService({
|
|
@@ -9377,10 +10199,6 @@ class Server {
|
|
|
9377
10199
|
res.redirect("/home?mode=settings")
|
|
9378
10200
|
return
|
|
9379
10201
|
}
|
|
9380
|
-
const terminalDefaultSkillIds = Array.from(new Set([
|
|
9381
|
-
path.resolve(os.homedir(), ".agents", "skills", "pinokio", "SKILL.md"),
|
|
9382
|
-
path.resolve(os.homedir(), ".agents", "skills", "gepeto", "SKILL.md")
|
|
9383
|
-
].map((skillPath) => crypto.createHash("sha1").update(skillPath).digest("hex").slice(0, 16))))
|
|
9384
10202
|
const peerAccess = await this.composePeerAccessPayload()
|
|
9385
10203
|
res.render("terminals", {
|
|
9386
10204
|
...peerAccess,
|
|
@@ -9392,7 +10210,7 @@ class Server {
|
|
|
9392
10210
|
items: [],
|
|
9393
10211
|
providers: getTerminalStarterProviders().map((provider) => ({ key: provider.key, label: provider.label })),
|
|
9394
10212
|
skills: [],
|
|
9395
|
-
defaultSkillIds:
|
|
10213
|
+
defaultSkillIds: [],
|
|
9396
10214
|
ishome: false
|
|
9397
10215
|
})
|
|
9398
10216
|
return
|
|
@@ -12114,15 +12932,16 @@ class Server {
|
|
|
12114
12932
|
}))
|
|
12115
12933
|
this.app.get("/settings/docs/:skill/download", ex(async (req, res) => {
|
|
12116
12934
|
const skill = typeof req.params.skill === "string" ? req.params.skill.trim().toLowerCase() : ""
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12935
|
+
if (!this.kernel || !this.kernel.homedir) {
|
|
12936
|
+
res.status(404).send("Pinokio home not configured")
|
|
12937
|
+
return
|
|
12120
12938
|
}
|
|
12121
|
-
const
|
|
12122
|
-
if (!
|
|
12939
|
+
const managedSkill = await ManagedSkills.getManagedSkill(this.kernel, skill, { sync: false })
|
|
12940
|
+
if (!managedSkill || (skill !== "pinokio" && skill !== "gepeto")) {
|
|
12123
12941
|
res.status(404).send("Unknown skill")
|
|
12124
12942
|
return
|
|
12125
12943
|
}
|
|
12944
|
+
const filepath = managedSkill.path
|
|
12126
12945
|
try {
|
|
12127
12946
|
await fs.promises.access(filepath, fs.constants.R_OK)
|
|
12128
12947
|
} catch (error) {
|
|
@@ -12202,10 +13021,11 @@ class Server {
|
|
|
12202
13021
|
let md = await fs.promises.readFile(path.resolve(__dirname, "..", "kernel/connect/providers/github/README.md"), "utf8")
|
|
12203
13022
|
let readme = marked.parse(md)
|
|
12204
13023
|
|
|
12205
|
-
|
|
13024
|
+
const githubConnection = await this.get_github_connection()
|
|
13025
|
+
let hosts = githubConnection.display
|
|
12206
13026
|
|
|
12207
13027
|
let items
|
|
12208
|
-
if (
|
|
13028
|
+
if (githubConnection.connected) {
|
|
12209
13029
|
// logged in => display logout
|
|
12210
13030
|
items = [{
|
|
12211
13031
|
icon: "fa-solid fa-circle-xmark",
|
|
@@ -12280,7 +13100,7 @@ class Server {
|
|
|
12280
13100
|
this.app.get("/github/status", ex(async (req, res) => {
|
|
12281
13101
|
let id = "gh_status"
|
|
12282
13102
|
let params = new URLSearchParams()
|
|
12283
|
-
let message = "
|
|
13103
|
+
let message = "git credential-manager github list"
|
|
12284
13104
|
params.set("message", encodeURIComponent(message))
|
|
12285
13105
|
params.set("path", this.kernel.homedir)
|
|
12286
13106
|
// params.set("kill", "/Logged in/i")
|
|
@@ -12294,7 +13114,15 @@ class Server {
|
|
|
12294
13114
|
this.app.get("/github/logout", ex(async (req, res) => {
|
|
12295
13115
|
let id = "gh_logout"
|
|
12296
13116
|
let params = new URLSearchParams()
|
|
12297
|
-
|
|
13117
|
+
const githubConnection = await this.get_github_connection()
|
|
13118
|
+
let { hadLegacyAuth, message } = await this.github_logout_params(githubConnection)
|
|
13119
|
+
if (!message && hadLegacyAuth) {
|
|
13120
|
+
res.redirect("/github")
|
|
13121
|
+
return
|
|
13122
|
+
}
|
|
13123
|
+
if (!message) {
|
|
13124
|
+
message = "git credential-manager github list"
|
|
13125
|
+
}
|
|
12298
13126
|
params.set("message", encodeURIComponent(message))
|
|
12299
13127
|
params.set("path", this.kernel.homedir)
|
|
12300
13128
|
// params.set("kill", "/Logged in/i")
|
|
@@ -12308,21 +13136,11 @@ class Server {
|
|
|
12308
13136
|
this.app.get("/github/login", ex(async (req, res) => {
|
|
12309
13137
|
let id = "gh_login"
|
|
12310
13138
|
let params = new URLSearchParams()
|
|
12311
|
-
|
|
12312
|
-
if (this.kernel.platform === "win32") {
|
|
12313
|
-
delimiter = " && "; // must use &&. & doesn't necessariliy wait until the curruent command finishes
|
|
12314
|
-
} else {
|
|
12315
|
-
delimiter = " ; ";
|
|
12316
|
-
}
|
|
12317
|
-
let message = [
|
|
12318
|
-
"gh auth setup-git --hostname github.com --force",
|
|
12319
|
-
"gh auth login --web --clipboard --git-protocol https"
|
|
12320
|
-
].join(delimiter)
|
|
13139
|
+
const { doneMarker, message } = this.github_login_params()
|
|
12321
13140
|
params.set("message", encodeURIComponent(message))
|
|
12322
13141
|
params.set("input", true)
|
|
12323
13142
|
params.set("path", this.kernel.homedir)
|
|
12324
|
-
params.set("kill",
|
|
12325
|
-
// params.set("kill_message", "Your Github account is now connected.")
|
|
13143
|
+
params.set("kill", `/${doneMarker}/`)
|
|
12326
13144
|
params.set("callback", encodeURIComponent("/github"))
|
|
12327
13145
|
params.set("id", id)
|
|
12328
13146
|
params.set("target", "_top")
|
|
@@ -12756,23 +13574,6 @@ class Server {
|
|
|
12756
13574
|
})
|
|
12757
13575
|
}
|
|
12758
13576
|
}))
|
|
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
|
-
}))
|
|
12776
13577
|
this.app.post("/network/reset", ex(async (req, res) => {
|
|
12777
13578
|
let caddy_path = this.kernel.path("cache/XDG_DATA_HOME/caddy")
|
|
12778
13579
|
await rimraf(caddy_path)
|
|
@@ -13549,14 +14350,15 @@ class Server {
|
|
|
13549
14350
|
this.app.get("/pre/api/:name", ex(async (req, res) => {
|
|
13550
14351
|
let launcher = await this.kernel.api.launcher(req.params.name)
|
|
13551
14352
|
let config = launcher.script
|
|
13552
|
-
if (config && config.pre) {
|
|
13553
|
-
config.pre.
|
|
13554
|
-
|
|
14353
|
+
if (config && Array.isArray(config.pre)) {
|
|
14354
|
+
const items = config.pre.filter((item) => item && typeof item === "object")
|
|
14355
|
+
items.forEach((item) => {
|
|
14356
|
+
if (typeof item.icon === "string" && item.icon) {
|
|
13555
14357
|
item.icon = `/api/${req.params.name}/${item.icon}?raw=true`
|
|
13556
14358
|
} else {
|
|
13557
14359
|
item.icon = "/pinokio-black.png"
|
|
13558
14360
|
}
|
|
13559
|
-
if (!item.href.startsWith("http")) {
|
|
14361
|
+
if (typeof item.href === "string" && item.href && !item.href.startsWith("http")) {
|
|
13560
14362
|
item.href = path.resolve(this.kernel.homedir, "api", req.params.name, item.href)
|
|
13561
14363
|
}
|
|
13562
14364
|
})
|
|
@@ -13567,7 +14369,7 @@ class Server {
|
|
|
13567
14369
|
theme: this.theme,
|
|
13568
14370
|
agent: req.agent,
|
|
13569
14371
|
name: req.params.name,
|
|
13570
|
-
items
|
|
14372
|
+
items,
|
|
13571
14373
|
env
|
|
13572
14374
|
})
|
|
13573
14375
|
} else {
|
|
@@ -13684,6 +14486,18 @@ class Server {
|
|
|
13684
14486
|
res.json({ du: 0 })
|
|
13685
14487
|
}
|
|
13686
14488
|
}))
|
|
14489
|
+
this.app.post("/resource-usage/preferences", ex(async (req, res) => {
|
|
14490
|
+
const preferences = await this.resourceUsage.updatePreferences(req.body || {})
|
|
14491
|
+
res.json({ preferences })
|
|
14492
|
+
}))
|
|
14493
|
+
this.app.get("/resource-usage/workspace/:name", ex(async (req, res) => {
|
|
14494
|
+
const usage = await this.resourceUsage.getWorkspaceUsage(req.params.name)
|
|
14495
|
+
if (!usage.ok) {
|
|
14496
|
+
res.status(400).json(usage)
|
|
14497
|
+
return
|
|
14498
|
+
}
|
|
14499
|
+
res.json(usage)
|
|
14500
|
+
}))
|
|
13687
14501
|
this.app.get("/edit/*", ex(async (req, res) => {
|
|
13688
14502
|
let pathComponents = req.params[0].split("/")
|
|
13689
14503
|
let filepath = path.resolve(this.kernel.homedir, req.params[0])
|
|
@@ -14053,7 +14867,7 @@ class Server {
|
|
|
14053
14867
|
mode = "launch_type.desktop"
|
|
14054
14868
|
} else if (launchType === "terminal") {
|
|
14055
14869
|
mode = "launch_type.terminal"
|
|
14056
|
-
} else {
|
|
14870
|
+
} else if (Array.isArray(item.run)) {
|
|
14057
14871
|
for(let step of item.run) {
|
|
14058
14872
|
if (step.method === "exec") {
|
|
14059
14873
|
mode = "exec"
|
|
@@ -14068,6 +14882,8 @@ class Server {
|
|
|
14068
14882
|
break
|
|
14069
14883
|
}
|
|
14070
14884
|
}
|
|
14885
|
+
} else if (typeof item.run === "function") {
|
|
14886
|
+
mode = "shell"
|
|
14071
14887
|
}
|
|
14072
14888
|
if (mode === "launch_type.desktop" || mode === "exec" || mode === "launch") {
|
|
14073
14889
|
item.type = "Open"
|
|
@@ -14195,8 +15011,11 @@ class Server {
|
|
|
14195
15011
|
}))
|
|
14196
15012
|
this.app.get("/action/:action/*", ex(async (req, res) => {
|
|
14197
15013
|
const action = typeof req.params.action === 'string' ? req.params.action : ''
|
|
14198
|
-
const
|
|
14199
|
-
|
|
15014
|
+
const rawComponents = req.params[0] ? req.params[0].split("/").filter(Boolean) : []
|
|
15015
|
+
const actionTarget = PluginSources.normalizeActionPathComponents(rawComponents)
|
|
15016
|
+
const pathComponents = actionTarget.pathComponents
|
|
15017
|
+
req.base = actionTarget.system ? PluginSources.systemRoot(this.kernel) : this.kernel.homedir
|
|
15018
|
+
req.pinokioSystem = actionTarget.system
|
|
14200
15019
|
req.action = action
|
|
14201
15020
|
try {
|
|
14202
15021
|
await this.render(req, res, pathComponents)
|
|
@@ -14204,6 +15023,17 @@ class Server {
|
|
|
14204
15023
|
res.status(404).send(e.message)
|
|
14205
15024
|
}
|
|
14206
15025
|
}))
|
|
15026
|
+
this.app.get(`${PluginSources.SYSTEM_RUN_PREFIX}/*`, ex(async (req, res) => {
|
|
15027
|
+
const runPath = typeof req.params[0] === "string" ? req.params[0] : ""
|
|
15028
|
+
let pathComponents = runPath.split("/")
|
|
15029
|
+
req.base = PluginSources.systemRoot(this.kernel)
|
|
15030
|
+
req.pinokioSystem = true
|
|
15031
|
+
try {
|
|
15032
|
+
await this.render(req, res, pathComponents)
|
|
15033
|
+
} catch (e) {
|
|
15034
|
+
res.status(404).send(e.message)
|
|
15035
|
+
}
|
|
15036
|
+
}))
|
|
14207
15037
|
this.app.get("/run/*", ex(async (req, res) => {
|
|
14208
15038
|
const runPath = typeof req.params[0] === "string" ? req.params[0] : ""
|
|
14209
15039
|
let pathComponents = runPath.split("/")
|
|
@@ -14937,7 +15767,7 @@ class Server {
|
|
|
14937
15767
|
await this.chrome(req, res, "browse")
|
|
14938
15768
|
}))
|
|
14939
15769
|
this.app.get("/p/:name/files", ex(async (req, res) => {
|
|
14940
|
-
|
|
15770
|
+
res.redirect(`/p/${encodeURIComponent(req.params.name)}/dev?pinokio_dev_tab=files`)
|
|
14941
15771
|
}))
|
|
14942
15772
|
this.app.get("/p/:name/browse", ex(async (req, res) => {
|
|
14943
15773
|
await this.chrome(req, res, "browse")
|
|
@@ -14970,6 +15800,9 @@ class Server {
|
|
|
14970
15800
|
let folderPath = this.kernel.path("cache")
|
|
14971
15801
|
await fse.remove(folderPath)
|
|
14972
15802
|
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
15803
|
+
await Environment.ensurePinokioCacheDirs(this.kernel, {
|
|
15804
|
+
throwOnFailure: true
|
|
15805
|
+
})
|
|
14973
15806
|
res.json({ success: true })
|
|
14974
15807
|
} else if (req.body.type === 'env') {
|
|
14975
15808
|
let envpath = this.kernel.path("ENVIRONMENT")
|
|
@@ -15629,12 +16462,7 @@ class Server {
|
|
|
15629
16462
|
}))
|
|
15630
16463
|
this.app.get("/bin_ready", ex(async (req, res) => {
|
|
15631
16464
|
if (this.kernel.bin && !this.kernel.bin.requirements_pending) {
|
|
15632
|
-
|
|
15633
|
-
if (code_exists) {
|
|
15634
|
-
res.json({ success: true })
|
|
15635
|
-
} else {
|
|
15636
|
-
res.json({ success: false })
|
|
15637
|
-
}
|
|
16465
|
+
res.json({ success: true })
|
|
15638
16466
|
} else {
|
|
15639
16467
|
res.json({ success: false })
|
|
15640
16468
|
}
|