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
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TAIL_LINES = 800
|
|
6
|
+
const MAX_SECTION_CHARS = 120000
|
|
7
|
+
const SENSITIVE_ENV_KEY = /(?:TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|API[_-]?KEY|APIKEY|CREDENTIAL|COOKIE|SESSION|AUTH|PRIVATE[_-]?KEY)/i
|
|
8
|
+
|
|
9
|
+
const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
10
|
+
|
|
11
|
+
class AppLogReportService {
|
|
12
|
+
constructor({ registry, kernel = null }) {
|
|
13
|
+
if (!registry) {
|
|
14
|
+
throw new Error('AppLogReportService requires registry')
|
|
15
|
+
}
|
|
16
|
+
this.registry = registry
|
|
17
|
+
this.kernel = kernel
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async buildReport({ appId, status, tail = DEFAULT_TAIL_LINES, redact = true }) {
|
|
21
|
+
const appRoot = status && status.path ? status.path : null
|
|
22
|
+
if (!appRoot) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
const tailLines = this.registry.parseTailCount(tail, DEFAULT_TAIL_LINES)
|
|
26
|
+
const rawSections = await this.collectSections(appRoot, tailLines)
|
|
27
|
+
const sections = []
|
|
28
|
+
const totals = {}
|
|
29
|
+
|
|
30
|
+
for (const section of rawSections) {
|
|
31
|
+
if (redact) {
|
|
32
|
+
const redacted = this.redactText(section.text, { appRoot })
|
|
33
|
+
this.mergeCounts(totals, redacted.counts)
|
|
34
|
+
sections.push({
|
|
35
|
+
...section,
|
|
36
|
+
text: redacted.text,
|
|
37
|
+
redactions: redacted.counts
|
|
38
|
+
})
|
|
39
|
+
} else {
|
|
40
|
+
sections.push({
|
|
41
|
+
...section,
|
|
42
|
+
redactions: {}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const metadata = {
|
|
48
|
+
app_id: appId,
|
|
49
|
+
title: status.title || appId,
|
|
50
|
+
repo_url: this.sanitizeRemoteUrl(status.repo_url || this.readGitRemote(appRoot)),
|
|
51
|
+
generated_at: new Date().toISOString(),
|
|
52
|
+
pinokiod: this.readPinokioVersion(),
|
|
53
|
+
platform: os.platform(),
|
|
54
|
+
arch: os.arch(),
|
|
55
|
+
node: process.version,
|
|
56
|
+
tail_count: tailLines,
|
|
57
|
+
system_spec: this.buildSystemSpec(),
|
|
58
|
+
redaction_mode: redact ? 'server_deterministic' : 'none'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
...metadata,
|
|
63
|
+
section_count: sections.length,
|
|
64
|
+
sections,
|
|
65
|
+
redactions: totals,
|
|
66
|
+
markdown: this.renderMarkdown(metadata, sections, totals)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async collectSections(appRoot, tailLines) {
|
|
71
|
+
const sections = []
|
|
72
|
+
const apiLogsRoot = path.resolve(appRoot, 'logs', 'api')
|
|
73
|
+
|
|
74
|
+
if (await this.registry.pathIsDirectory(apiLogsRoot)) {
|
|
75
|
+
const latestFiles = await this.findNamedFiles(apiLogsRoot, 'latest')
|
|
76
|
+
for (const file of latestFiles) {
|
|
77
|
+
const relativeDir = path.relative(apiLogsRoot, path.dirname(file))
|
|
78
|
+
const script = this.toPosix(relativeDir)
|
|
79
|
+
sections.push(await this.buildSection({
|
|
80
|
+
appRoot,
|
|
81
|
+
root: apiLogsRoot,
|
|
82
|
+
file,
|
|
83
|
+
source: 'api',
|
|
84
|
+
script,
|
|
85
|
+
tailLines
|
|
86
|
+
}))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return sections
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.sort((a, b) => this.compareSections(a, b))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async findNamedFiles(root, filename) {
|
|
96
|
+
const out = []
|
|
97
|
+
const walk = async (dir) => {
|
|
98
|
+
let entries = []
|
|
99
|
+
try {
|
|
100
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true })
|
|
101
|
+
} catch (_) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const entryPath = path.resolve(dir, entry.name)
|
|
106
|
+
if (!this.registry.isPathWithin(root, entryPath)) {
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
await walk(entryPath)
|
|
111
|
+
} else if (entry.name === filename) {
|
|
112
|
+
out.push(entryPath)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
await walk(root)
|
|
117
|
+
return out
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async buildSection({ appRoot, root, file, source, script, tailLines }) {
|
|
121
|
+
if (!this.registry.isPathWithin(root, file)) {
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
let text = ''
|
|
125
|
+
let stats = null
|
|
126
|
+
try {
|
|
127
|
+
text = await fs.promises.readFile(file, 'utf8')
|
|
128
|
+
stats = await fs.promises.stat(file)
|
|
129
|
+
} catch (_) {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
const allLines = text.split(/\r?\n/)
|
|
133
|
+
if (allLines.length > 0 && allLines[allLines.length - 1] === '') {
|
|
134
|
+
allLines.pop()
|
|
135
|
+
}
|
|
136
|
+
let tail = allLines.slice(-tailLines).join('\n')
|
|
137
|
+
let truncatedByChars = false
|
|
138
|
+
if (tail.length > MAX_SECTION_CHARS) {
|
|
139
|
+
tail = tail.slice(-MAX_SECTION_CHARS)
|
|
140
|
+
truncatedByChars = true
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
source,
|
|
144
|
+
script,
|
|
145
|
+
file: this.toPosix(path.relative(appRoot, file)),
|
|
146
|
+
line_count: allLines.length,
|
|
147
|
+
tail_count: tailLines,
|
|
148
|
+
size: stats ? stats.size : Buffer.byteLength(text),
|
|
149
|
+
modified: stats ? stats.mtime : null,
|
|
150
|
+
truncated: allLines.length > tailLines || truncatedByChars,
|
|
151
|
+
text: tail
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
redactText(input, context = {}) {
|
|
156
|
+
let text = typeof input === 'string' ? input : ''
|
|
157
|
+
const counts = {}
|
|
158
|
+
const replace = (name, pattern, replacement) => {
|
|
159
|
+
text = text.replace(pattern, (...args) => {
|
|
160
|
+
counts[name] = (counts[name] || 0) + 1
|
|
161
|
+
if (typeof replacement === 'function') {
|
|
162
|
+
return replacement(...args)
|
|
163
|
+
}
|
|
164
|
+
return String(replacement).replace(/\$(\d+)/g, (_, index) => {
|
|
165
|
+
const value = args[Number.parseInt(index, 10)]
|
|
166
|
+
return value === undefined ? '' : String(value)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
replace('private_keys', /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, '[REDACTED_PRIVATE_KEY]')
|
|
172
|
+
text = text.replace(/^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_.-]*)(\s*=\s*).*$/gm, (match, prefix, key, separator) => {
|
|
173
|
+
if (!SENSITIVE_ENV_KEY.test(key)) {
|
|
174
|
+
return match
|
|
175
|
+
}
|
|
176
|
+
counts.env_secrets = (counts.env_secrets || 0) + 1
|
|
177
|
+
return `${prefix}${key}${separator}[REDACTED_SECRET]`
|
|
178
|
+
})
|
|
179
|
+
replace('auth_headers', /\b(Authorization|Proxy-Authorization)\s*:\s*([^\r\n]+)/gi, '$1: [REDACTED_AUTH]')
|
|
180
|
+
replace('url_credentials', /([a-z][a-z0-9+.-]*:\/\/)([^/\s:@]+):([^@\s/]+)@/gi, '$1[REDACTED_CREDENTIALS]@')
|
|
181
|
+
replace('sensitive_query_params', /([?&](?:access_token|refresh_token|token|secret|password|passwd|api[_-]?key|apikey|auth|session|cookie|key)=)([^&\s"'<>]+)/gi, '$1[REDACTED_SECRET]')
|
|
182
|
+
replace('secret_flags', /((?:--?|\/)(?:token|secret|password|passwd|passphrase|api[-_]?key|apikey|credential|cookie|session|auth)(?:=|\s+))("[^"]*"|'[^']*'|[^\s]+)/gi, '$1[REDACTED_SECRET]')
|
|
183
|
+
replace('openai_keys', /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20,}\b/g, '[REDACTED_OPENAI_KEY]')
|
|
184
|
+
replace('huggingface_tokens', /\bhf_[A-Za-z0-9]{20,}\b/g, '[REDACTED_HF_TOKEN]')
|
|
185
|
+
replace('github_tokens', /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g, '[REDACTED_GITHUB_TOKEN]')
|
|
186
|
+
replace('github_pat_tokens', /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, '[REDACTED_GITHUB_TOKEN]')
|
|
187
|
+
replace('aws_access_keys', /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, '[REDACTED_AWS_ACCESS_KEY]')
|
|
188
|
+
replace('stripe_secret_keys', /\bsk_live_[A-Za-z0-9]{20,}\b/g, '[REDACTED_STRIPE_KEY]')
|
|
189
|
+
replace('jwt_tokens', /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, '[REDACTED_JWT]')
|
|
190
|
+
replace('emails', /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '[REDACTED_EMAIL]')
|
|
191
|
+
|
|
192
|
+
const pathPatterns = this.buildPathPatterns(context)
|
|
193
|
+
for (const pattern of pathPatterns) {
|
|
194
|
+
replace('local_paths', pattern, (match, prefix) => `${prefix || ''}[REDACTED_LOCAL_PATH]`)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
text = text.replace(/(^|\s)([A-Za-z0-9_./+=-]{48,})(?=\s|$)/g, (match, prefix, value) => {
|
|
198
|
+
if (!/[A-Za-z]/.test(value) || !/[0-9]/.test(value)) {
|
|
199
|
+
return match
|
|
200
|
+
}
|
|
201
|
+
counts.likely_secret_values = (counts.likely_secret_values || 0) + 1
|
|
202
|
+
return `${prefix}[REDACTED_SECRET]`
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
return { text, counts }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
buildPathPatterns(context = {}) {
|
|
209
|
+
const patterns = [
|
|
210
|
+
/(^|[\s"'(=])\/Users\/[^/\s"')]+/g,
|
|
211
|
+
/(^|[\s"'(=])\/home\/[^/\s"')]+/g,
|
|
212
|
+
/(^|[\s"'(=])[A-Za-z]:\\Users\\[^\\\s"')]+/g
|
|
213
|
+
]
|
|
214
|
+
const appRoot = context.appRoot ? String(context.appRoot) : ''
|
|
215
|
+
if (appRoot) {
|
|
216
|
+
patterns.push(new RegExp(`(^|[\\s"'(=])${escapeRegExp(appRoot)}`, 'g'))
|
|
217
|
+
}
|
|
218
|
+
return patterns
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
mergeCounts(target, source) {
|
|
222
|
+
for (const [key, value] of Object.entries(source || {})) {
|
|
223
|
+
target[key] = (target[key] || 0) + value
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
renderMarkdown(metadata, sections, redactions) {
|
|
228
|
+
const lines = [
|
|
229
|
+
'# Issue Report',
|
|
230
|
+
'',
|
|
231
|
+
`App: ${metadata.title} (${metadata.app_id})`,
|
|
232
|
+
metadata.repo_url ? `Repo: ${metadata.repo_url}` : null,
|
|
233
|
+
`Generated: ${metadata.generated_at}`,
|
|
234
|
+
`Pinokio: ${metadata.pinokiod || 'unknown'}`,
|
|
235
|
+
`Platform: ${metadata.platform} ${metadata.arch}`,
|
|
236
|
+
`Node: ${metadata.node}`,
|
|
237
|
+
'',
|
|
238
|
+
'## Summary',
|
|
239
|
+
'',
|
|
240
|
+
'',
|
|
241
|
+
'## System',
|
|
242
|
+
'',
|
|
243
|
+
'```json',
|
|
244
|
+
JSON.stringify(metadata.system_spec || {}, null, 2),
|
|
245
|
+
'```'
|
|
246
|
+
].filter((line) => line !== null)
|
|
247
|
+
|
|
248
|
+
if (metadata.redaction_mode !== 'none') {
|
|
249
|
+
lines.push(
|
|
250
|
+
'',
|
|
251
|
+
'## Sanitization',
|
|
252
|
+
'',
|
|
253
|
+
this.renderRedactionSummary(redactions, metadata.redaction_mode)
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
lines.push('', '## Logs')
|
|
258
|
+
|
|
259
|
+
if (sections.length === 0) {
|
|
260
|
+
lines.push('', 'No app log files were found.')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const section of sections) {
|
|
264
|
+
lines.push(
|
|
265
|
+
'',
|
|
266
|
+
`### ${section.file}`,
|
|
267
|
+
'',
|
|
268
|
+
`Source: ${section.source}${section.script ? ` / ${section.script}` : ''}`,
|
|
269
|
+
`Lines: ${section.line_count} total, last ${Math.min(section.line_count, section.tail_count)} included${section.truncated ? ' (truncated)' : ''}`,
|
|
270
|
+
'',
|
|
271
|
+
'```text',
|
|
272
|
+
section.text || '',
|
|
273
|
+
'```'
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return lines.join('\n')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
renderRedactionSummary(redactions, redactionMode = 'server_deterministic') {
|
|
281
|
+
if (redactionMode === 'none') {
|
|
282
|
+
return ''
|
|
283
|
+
}
|
|
284
|
+
const entries = Object.entries(redactions || {}).filter(([, count]) => count > 0)
|
|
285
|
+
if (entries.length === 0) {
|
|
286
|
+
return 'Basic redaction did not find structured secrets.'
|
|
287
|
+
}
|
|
288
|
+
return entries
|
|
289
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
290
|
+
.map(([name, count]) => `- ${name.replace(/_/g, ' ')}: ${count}`)
|
|
291
|
+
.join('\n')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
readGitRemote(appRoot) {
|
|
295
|
+
if (!appRoot) {
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const configPath = path.resolve(appRoot, '.git', 'config')
|
|
300
|
+
if (!this.registry.isPathWithin(appRoot, configPath)) {
|
|
301
|
+
return null
|
|
302
|
+
}
|
|
303
|
+
const raw = fs.readFileSync(configPath, 'utf8')
|
|
304
|
+
const originMatch = raw.match(/\[remote "origin"\][\s\S]*?\n\s*url\s*=\s*([^\r\n]+)/)
|
|
305
|
+
if (originMatch && originMatch[1]) {
|
|
306
|
+
return this.sanitizeRemoteUrl(originMatch[1])
|
|
307
|
+
}
|
|
308
|
+
const firstRemote = raw.match(/\[remote "[^"]+"\][\s\S]*?\n\s*url\s*=\s*([^\r\n]+)/)
|
|
309
|
+
return firstRemote && firstRemote[1] ? this.sanitizeRemoteUrl(firstRemote[1]) : null
|
|
310
|
+
} catch (_) {
|
|
311
|
+
return null
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
sanitizeRemoteUrl(value) {
|
|
316
|
+
const text = typeof value === 'string' ? value.trim() : ''
|
|
317
|
+
if (!text) {
|
|
318
|
+
return null
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const parsed = new URL(text)
|
|
322
|
+
if (parsed.username || parsed.password) {
|
|
323
|
+
parsed.username = ''
|
|
324
|
+
parsed.password = ''
|
|
325
|
+
return parsed.toString()
|
|
326
|
+
}
|
|
327
|
+
} catch (_) {
|
|
328
|
+
return text
|
|
329
|
+
}
|
|
330
|
+
return text
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
compareSections(a, b) {
|
|
334
|
+
const sourceOrder = { api: 0, shell: 1 }
|
|
335
|
+
const bySource = (sourceOrder[a.source] ?? 99) - (sourceOrder[b.source] ?? 99)
|
|
336
|
+
if (bySource !== 0) return bySource
|
|
337
|
+
return this.scriptRank(a.script) - this.scriptRank(b.script)
|
|
338
|
+
|| String(a.script || '').localeCompare(String(b.script || ''))
|
|
339
|
+
|| String(a.file || '').localeCompare(String(b.file || ''))
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
scriptRank(script) {
|
|
343
|
+
const value = String(script || '').toLowerCase()
|
|
344
|
+
const ordered = ['install', 'update', 'start', 'run', 'launch', 'serve', 'shell']
|
|
345
|
+
const idx = ordered.findIndex((name) => value === name || value.startsWith(`${name}.`) || value.includes(`/${name}.`))
|
|
346
|
+
return idx >= 0 ? idx : ordered.length
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
toPosix(value) {
|
|
350
|
+
return String(value || '').split(path.sep).filter(Boolean).join('/')
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
buildSystemSpec() {
|
|
354
|
+
const kernel = this.kernel || {}
|
|
355
|
+
const info = kernel.sysinfo && typeof kernel.sysinfo === 'object' ? kernel.sysinfo : {}
|
|
356
|
+
return this.compactObject({
|
|
357
|
+
pinokio: {
|
|
358
|
+
version: this.readPinokioVersion(),
|
|
359
|
+
node: process.version,
|
|
360
|
+
platform: kernel.platform || process.platform,
|
|
361
|
+
arch: kernel.arch || process.arch,
|
|
362
|
+
torch_backend: kernel.torch_backend || info.torch_backend || null
|
|
363
|
+
},
|
|
364
|
+
hardware: this.compactObject({
|
|
365
|
+
gpu: kernel.gpu || info.gpu || null,
|
|
366
|
+
gpu_model: kernel.gpu_model || info.gpu_model || null,
|
|
367
|
+
ram_gb: typeof kernel.ram === 'number' ? kernel.ram : info.ram,
|
|
368
|
+
vram_gb: typeof kernel.vram === 'number' ? kernel.vram : info.vram
|
|
369
|
+
}),
|
|
370
|
+
os: this.sanitizeOsInfo(info.osInfo),
|
|
371
|
+
system: this.sanitizeSystem(info.system),
|
|
372
|
+
cpu: this.sanitizeCpu(info.cpu),
|
|
373
|
+
memory: this.sanitizeMemory(info.mem),
|
|
374
|
+
gpus: this.sanitizeGpus(info.gpus),
|
|
375
|
+
graphics: this.sanitizeGraphics(info.graphics)
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
sanitizeOsInfo(value) {
|
|
380
|
+
return this.pickObject(value, [
|
|
381
|
+
'platform',
|
|
382
|
+
'distro',
|
|
383
|
+
'release',
|
|
384
|
+
'codename',
|
|
385
|
+
'kernel',
|
|
386
|
+
'arch',
|
|
387
|
+
'build',
|
|
388
|
+
'servicepack',
|
|
389
|
+
'uefi'
|
|
390
|
+
])
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
sanitizeSystem(value) {
|
|
394
|
+
return this.pickObject(value, [
|
|
395
|
+
'manufacturer',
|
|
396
|
+
'model',
|
|
397
|
+
'version',
|
|
398
|
+
'virtual'
|
|
399
|
+
])
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
sanitizeCpu(value) {
|
|
403
|
+
return this.pickObject(value, [
|
|
404
|
+
'manufacturer',
|
|
405
|
+
'brand',
|
|
406
|
+
'vendor',
|
|
407
|
+
'family',
|
|
408
|
+
'model',
|
|
409
|
+
'stepping',
|
|
410
|
+
'revision',
|
|
411
|
+
'speed',
|
|
412
|
+
'speedMin',
|
|
413
|
+
'speedMax',
|
|
414
|
+
'cores',
|
|
415
|
+
'physicalCores',
|
|
416
|
+
'processors',
|
|
417
|
+
'performanceCores',
|
|
418
|
+
'efficiencyCores',
|
|
419
|
+
'virtualization',
|
|
420
|
+
'cache'
|
|
421
|
+
])
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
sanitizeMemory(value) {
|
|
425
|
+
return this.pickObject(value, [
|
|
426
|
+
'total',
|
|
427
|
+
'free',
|
|
428
|
+
'used',
|
|
429
|
+
'active',
|
|
430
|
+
'available',
|
|
431
|
+
'buffers',
|
|
432
|
+
'cached',
|
|
433
|
+
'slab',
|
|
434
|
+
'buffcache',
|
|
435
|
+
'swaptotal',
|
|
436
|
+
'swapused',
|
|
437
|
+
'swapfree'
|
|
438
|
+
])
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
sanitizeGpus(value) {
|
|
442
|
+
if (!Array.isArray(value)) {
|
|
443
|
+
return []
|
|
444
|
+
}
|
|
445
|
+
return value.map((gpu) => this.pickObject(gpu, [
|
|
446
|
+
'vendor',
|
|
447
|
+
'model',
|
|
448
|
+
'bus',
|
|
449
|
+
'vram',
|
|
450
|
+
'vramDynamic',
|
|
451
|
+
'cores',
|
|
452
|
+
'metalVersion',
|
|
453
|
+
'cudaVersion',
|
|
454
|
+
'driverVersion'
|
|
455
|
+
])).filter((gpu) => Object.keys(gpu).length > 0)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
sanitizeGraphics(value) {
|
|
459
|
+
if (!value || typeof value !== 'object') {
|
|
460
|
+
return null
|
|
461
|
+
}
|
|
462
|
+
return this.compactObject({
|
|
463
|
+
controllers: Array.isArray(value.controllers)
|
|
464
|
+
? value.controllers.map((controller) => this.pickObject(controller, [
|
|
465
|
+
'vendor',
|
|
466
|
+
'model',
|
|
467
|
+
'bus',
|
|
468
|
+
'vram',
|
|
469
|
+
'vramDynamic',
|
|
470
|
+
'cores',
|
|
471
|
+
'metalVersion',
|
|
472
|
+
'cudaVersion',
|
|
473
|
+
'driverVersion'
|
|
474
|
+
])).filter((controller) => Object.keys(controller).length > 0)
|
|
475
|
+
: [],
|
|
476
|
+
displays: Array.isArray(value.displays)
|
|
477
|
+
? value.displays.map((display) => this.pickObject(display, [
|
|
478
|
+
'model',
|
|
479
|
+
'main',
|
|
480
|
+
'builtin',
|
|
481
|
+
'connection',
|
|
482
|
+
'currentResX',
|
|
483
|
+
'currentResY',
|
|
484
|
+
'resolutionX',
|
|
485
|
+
'resolutionY',
|
|
486
|
+
'pixelDepth',
|
|
487
|
+
'currentRefreshRate'
|
|
488
|
+
])).filter((display) => Object.keys(display).length > 0)
|
|
489
|
+
: []
|
|
490
|
+
})
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
pickObject(value, keys) {
|
|
494
|
+
if (!value || typeof value !== 'object') {
|
|
495
|
+
return null
|
|
496
|
+
}
|
|
497
|
+
const out = {}
|
|
498
|
+
for (const key of keys) {
|
|
499
|
+
if (Object.prototype.hasOwnProperty.call(value, key) && value[key] !== undefined && value[key] !== null && value[key] !== '') {
|
|
500
|
+
out[key] = value[key]
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return this.compactObject(out)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
compactObject(value) {
|
|
507
|
+
if (!value || typeof value !== 'object') {
|
|
508
|
+
return value
|
|
509
|
+
}
|
|
510
|
+
if (Array.isArray(value)) {
|
|
511
|
+
return value
|
|
512
|
+
.map((entry) => this.compactObject(entry))
|
|
513
|
+
.filter((entry) => {
|
|
514
|
+
if (entry === null || entry === undefined || entry === '') return false
|
|
515
|
+
if (Array.isArray(entry)) return entry.length > 0
|
|
516
|
+
if (typeof entry === 'object') return Object.keys(entry).length > 0
|
|
517
|
+
return true
|
|
518
|
+
})
|
|
519
|
+
}
|
|
520
|
+
const out = {}
|
|
521
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
522
|
+
const compacted = this.compactObject(entry)
|
|
523
|
+
if (compacted === null || compacted === undefined || compacted === '') continue
|
|
524
|
+
if (Array.isArray(compacted) && compacted.length === 0) continue
|
|
525
|
+
if (typeof compacted === 'object' && !Array.isArray(compacted) && Object.keys(compacted).length === 0) continue
|
|
526
|
+
out[key] = compacted
|
|
527
|
+
}
|
|
528
|
+
return out
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
readPinokioVersion() {
|
|
532
|
+
try {
|
|
533
|
+
const pkg = require('../../package.json')
|
|
534
|
+
return pkg && pkg.version ? String(pkg.version) : null
|
|
535
|
+
} catch (_) {
|
|
536
|
+
return null
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
AppLogReportService.DEFAULT_TAIL_LINES = DEFAULT_TAIL_LINES
|
|
542
|
+
|
|
543
|
+
module.exports = AppLogReportService
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require("fs")
|
|
2
2
|
const path = require("path")
|
|
3
3
|
const clearModule = require("clear-module")
|
|
4
|
+
const PluginSources = require("../../kernel/plugin_sources")
|
|
4
5
|
|
|
5
6
|
const {
|
|
6
7
|
TASK_CONFIG_FILENAME,
|
|
@@ -117,23 +118,7 @@ function createContentValidationService({ kernel }) {
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
const normalizePluginPath = (value) => {
|
|
120
|
-
|
|
121
|
-
if (!normalized) {
|
|
122
|
-
return ""
|
|
123
|
-
}
|
|
124
|
-
normalized = normalized.replace(/\\/g, "/")
|
|
125
|
-
normalized = normalized.replace(/^\/run(?=\/)/, "")
|
|
126
|
-
if (!normalized.startsWith("/")) {
|
|
127
|
-
normalized = `/${normalized}`
|
|
128
|
-
}
|
|
129
|
-
normalized = normalized.replace(/\/{2,}/g, "/").replace(/\/+$/, "")
|
|
130
|
-
if (!normalized) {
|
|
131
|
-
return ""
|
|
132
|
-
}
|
|
133
|
-
if (!normalized.endsWith("/pinokio.js")) {
|
|
134
|
-
normalized = `${normalized}/pinokio.js`
|
|
135
|
-
}
|
|
136
|
-
return normalized
|
|
121
|
+
return PluginSources.normalizePluginPath(value)
|
|
137
122
|
}
|
|
138
123
|
|
|
139
124
|
const normalizeBundledPluginSpec = (value) => {
|
|
@@ -162,16 +147,17 @@ function createContentValidationService({ kernel }) {
|
|
|
162
147
|
absolutePath,
|
|
163
148
|
dir: pluginDir,
|
|
164
149
|
config,
|
|
165
|
-
hasInstall:
|
|
166
|
-
hasUpdate:
|
|
167
|
-
hasUninstall:
|
|
150
|
+
hasInstall: PluginSources.isAction(config && config.install),
|
|
151
|
+
hasUpdate: PluginSources.isAction(config && config.update),
|
|
152
|
+
hasUninstall: PluginSources.isAction(config && config.uninstall),
|
|
153
|
+
hasInstalledCheck: PluginSources.isInstalledCheck(config && config.installed),
|
|
168
154
|
image: null,
|
|
169
155
|
}
|
|
170
156
|
|
|
171
157
|
if (config && typeof config.icon === "string" && config.icon.trim()) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
context.image =
|
|
158
|
+
const pluginImage = PluginSources.pluginAssetHrefForIcon(normalizedPath, config.icon)
|
|
159
|
+
if (pluginImage) {
|
|
160
|
+
context.image = pluginImage
|
|
175
161
|
} else if (normalizedPath.startsWith("/api/")) {
|
|
176
162
|
const segments = normalizedPath.replace(/^\/+/, "").split("/")
|
|
177
163
|
const appName = segments[1] || ""
|
|
@@ -192,7 +178,10 @@ function createContentValidationService({ kernel }) {
|
|
|
192
178
|
const fallbackTitle = normalizedPath
|
|
193
179
|
? path.basename(path.posix.dirname(normalizedPath))
|
|
194
180
|
: "Plugin"
|
|
195
|
-
|
|
181
|
+
const isSystemPlugin = PluginSources.isSystemPluginPath(normalizedPath)
|
|
182
|
+
const isHomePlugin = normalizedPath.startsWith("/plugin/")
|
|
183
|
+
const isApiPlugin = normalizedPath.startsWith("/api/")
|
|
184
|
+
if (!normalizedPath || (!isSystemPlugin && !isHomePlugin && !isApiPlugin)) {
|
|
196
185
|
return buildInvalid({
|
|
197
186
|
type: "plugin",
|
|
198
187
|
subjectTitle: fallbackTitle,
|
|
@@ -205,8 +194,21 @@ function createContentValidationService({ kernel }) {
|
|
|
205
194
|
detailUrl: "/plugins",
|
|
206
195
|
})
|
|
207
196
|
}
|
|
197
|
+
if (PluginSources.isLegacyPluginCodePath(normalizedPath)) {
|
|
198
|
+
return buildInvalid({
|
|
199
|
+
type: "plugin",
|
|
200
|
+
subjectTitle: fallbackTitle,
|
|
201
|
+
errors: [
|
|
202
|
+
buildError(
|
|
203
|
+
"This managed plugin path is no longer used.",
|
|
204
|
+
"Open the built-in Pinokio plugin instead."
|
|
205
|
+
),
|
|
206
|
+
],
|
|
207
|
+
detailUrl: "/plugins",
|
|
208
|
+
})
|
|
209
|
+
}
|
|
208
210
|
|
|
209
|
-
const absolutePath =
|
|
211
|
+
const absolutePath = PluginSources.pluginPathToAbsolute(kernel, normalizedPath)
|
|
210
212
|
const folderPath = path.dirname(absolutePath)
|
|
211
213
|
const loaded = await loadJsFile(absolutePath)
|
|
212
214
|
if (!loaded.exists) {
|
|
@@ -251,25 +253,42 @@ function createContentValidationService({ kernel }) {
|
|
|
251
253
|
{ file: absolutePath }
|
|
252
254
|
))
|
|
253
255
|
} else {
|
|
254
|
-
if (!
|
|
256
|
+
if (!PluginSources.isAction(config.run)) {
|
|
255
257
|
errors.push(buildError(
|
|
256
|
-
"Plugins must define a top-level run array.",
|
|
257
|
-
"Add `run: [...]` to pinokio.js.",
|
|
258
|
+
"Plugins must define a top-level run array or function.",
|
|
259
|
+
"Add `run: [...]` or `run: async (ctx) => [...]` to pinokio.js.",
|
|
258
260
|
{ file: absolutePath }
|
|
259
261
|
))
|
|
260
262
|
}
|
|
261
|
-
const topLevelFunctionKeys = Object.keys(config).filter((key) =>
|
|
263
|
+
const topLevelFunctionKeys = Object.keys(config).filter((key) => {
|
|
264
|
+
return typeof config[key] === "function" && !PluginSources.FUNCTION_KEYS.has(key)
|
|
265
|
+
})
|
|
262
266
|
if (topLevelFunctionKeys.length > 0) {
|
|
263
267
|
errors.push(buildError(
|
|
264
268
|
`Top-level function fields are not supported: ${topLevelFunctionKeys.join(", ")}.`,
|
|
265
|
-
"
|
|
269
|
+
"Only action fields such as run, install, uninstall, update, and the installed status check may be functions.",
|
|
270
|
+
{ file: absolutePath }
|
|
271
|
+
))
|
|
272
|
+
}
|
|
273
|
+
for (const key of PluginSources.ACTION_KEYS) {
|
|
274
|
+
if (key in config && !PluginSources.isAction(config[key])) {
|
|
275
|
+
errors.push(buildError(
|
|
276
|
+
`Plugin action ${key} must be an array or function.`,
|
|
277
|
+
`Set ${key} to an array or async function returning an array.`,
|
|
278
|
+
{ file: absolutePath }
|
|
279
|
+
))
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if ("installed" in config && !PluginSources.isInstalledCheck(config.installed)) {
|
|
283
|
+
errors.push(buildError(
|
|
284
|
+
"Plugin installed must be a function.",
|
|
285
|
+
"Set installed to a function returning true when the plugin-managed setup exists.",
|
|
266
286
|
{ file: absolutePath }
|
|
267
287
|
))
|
|
268
288
|
}
|
|
269
289
|
if (normalizedPath.startsWith("/plugin/")) {
|
|
270
|
-
const isManagedDefaultPlugin = normalizedPath.startsWith("/plugin/code/")
|
|
271
290
|
const declaredPath = typeof config.path === "string" ? config.path.trim() : ""
|
|
272
|
-
if (
|
|
291
|
+
if (declaredPath !== "plugin") {
|
|
273
292
|
errors.push(buildError(
|
|
274
293
|
'Standalone plugins must set `path: "plugin"`.',
|
|
275
294
|
'Add `path: "plugin"` to pinokio.js.',
|
|
@@ -567,7 +586,7 @@ function createContentValidationService({ kernel }) {
|
|
|
567
586
|
})
|
|
568
587
|
}
|
|
569
588
|
|
|
570
|
-
const validateRunPath = async (pathComponents) => {
|
|
589
|
+
const validateRunPath = async (pathComponents, options = {}) => {
|
|
571
590
|
if (!Array.isArray(pathComponents) || pathComponents.length === 0) {
|
|
572
591
|
return buildValid()
|
|
573
592
|
}
|
|
@@ -575,6 +594,9 @@ function createContentValidationService({ kernel }) {
|
|
|
575
594
|
if (normalized.length === 0) {
|
|
576
595
|
return buildValid()
|
|
577
596
|
}
|
|
597
|
+
if (options.system && normalized[0] === "plugin") {
|
|
598
|
+
return validatePluginByPath(`${PluginSources.SYSTEM_PLUGIN_RUN_PREFIX}/${normalized.slice(1).join("/")}`)
|
|
599
|
+
}
|
|
578
600
|
if (normalized[0] === "plugin") {
|
|
579
601
|
return validatePluginByPath(`/${normalized.join("/")}`)
|
|
580
602
|
}
|