pinokiod 7.3.1 → 7.3.4
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 +126 -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 +150 -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/kernel/environment.js
CHANGED
|
@@ -1,9 +1,172 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const portfinder = require('portfinder-cp')
|
|
3
|
-
const os = require('os')
|
|
4
3
|
const fs = require('fs')
|
|
5
4
|
const Util = require('./util')
|
|
6
|
-
const
|
|
5
|
+
const ManagedSkills = require('./managed_skills')
|
|
6
|
+
const TEMP_ENV_KEYS = ["TMP", "TEMP", "TMPDIR", "PIP_TMPDIR"]
|
|
7
|
+
const CACHE_ENV_KEYS = ["UV_CACHE_DIR", "PIP_CACHE_DIR"]
|
|
8
|
+
const CACHE_PREFLIGHT_KEYS = TEMP_ENV_KEYS.concat(CACHE_ENV_KEYS)
|
|
9
|
+
|
|
10
|
+
const formatCachePreflightError = (error) => {
|
|
11
|
+
if (!error) {
|
|
12
|
+
return ""
|
|
13
|
+
}
|
|
14
|
+
const parts = []
|
|
15
|
+
if (error.code) {
|
|
16
|
+
parts.push(`code=${error.code}`)
|
|
17
|
+
}
|
|
18
|
+
if (typeof error.errno !== "undefined") {
|
|
19
|
+
parts.push(`errno=${error.errno}`)
|
|
20
|
+
}
|
|
21
|
+
if (error.syscall) {
|
|
22
|
+
parts.push(`syscall=${error.syscall}`)
|
|
23
|
+
}
|
|
24
|
+
if (error.path) {
|
|
25
|
+
parts.push(`path=${error.path}`)
|
|
26
|
+
}
|
|
27
|
+
if (error.dest) {
|
|
28
|
+
parts.push(`dest=${error.dest}`)
|
|
29
|
+
}
|
|
30
|
+
if (error.message) {
|
|
31
|
+
parts.push(`message=${error.message}`)
|
|
32
|
+
}
|
|
33
|
+
return parts.join(" ")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const logCachePreflight = (message) => {
|
|
37
|
+
console.log(`[Pinokio cache preflight] ${message}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const probeCacheDir = async (dirPath) => {
|
|
41
|
+
const probeDir = path.resolve(
|
|
42
|
+
dirPath,
|
|
43
|
+
`.pinokio-cache-probe-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
44
|
+
)
|
|
45
|
+
const probeFile = path.resolve(probeDir, "probe.tmp")
|
|
46
|
+
const renamedFile = path.resolve(probeDir, "probe-renamed.tmp")
|
|
47
|
+
const steps = [
|
|
48
|
+
["create probe directory", () => fs.promises.mkdir(probeDir, { recursive: false })],
|
|
49
|
+
["write probe file", () => fs.promises.writeFile(probeFile, "pinokio")],
|
|
50
|
+
["append probe file", () => fs.promises.appendFile(probeFile, "-cache-probe")],
|
|
51
|
+
["rename probe file", () => fs.promises.rename(probeFile, renamedFile)],
|
|
52
|
+
["delete probe file", () => fs.promises.unlink(renamedFile)],
|
|
53
|
+
["remove probe directory", () => fs.promises.rmdir(probeDir)]
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
for (const [step, run] of steps) {
|
|
57
|
+
try {
|
|
58
|
+
await run()
|
|
59
|
+
} catch (error) {
|
|
60
|
+
await fs.promises.rm(probeDir, { recursive: true, force: true }).catch(() => {})
|
|
61
|
+
return { ok: false, step, error }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { ok: true }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const managedCacheEnvDefaults = () => {
|
|
68
|
+
const defaults = {}
|
|
69
|
+
for (const key of CACHE_PREFLIGHT_KEYS) {
|
|
70
|
+
defaults[key] = `./cache/${key}`
|
|
71
|
+
}
|
|
72
|
+
return defaults
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ensureCachePreflightDir = async (key, targetPath) => {
|
|
76
|
+
logCachePreflight(`${key}: target=${targetPath}`)
|
|
77
|
+
try {
|
|
78
|
+
await fs.promises.mkdir(targetPath, { recursive: true })
|
|
79
|
+
logCachePreflight(`${key}: mkdir ok`)
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logCachePreflight(`${key}: mkdir failed ${formatCachePreflightError(error)}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const firstProbe = await probeCacheDir(targetPath)
|
|
85
|
+
if (firstProbe.ok) {
|
|
86
|
+
logCachePreflight(`${key}: probe ok`)
|
|
87
|
+
return { key, path: targetPath, repaired: false, ok: true }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
logCachePreflight(`${key}: probe failed step="${firstProbe.step}" ${formatCachePreflightError(firstProbe.error)}`)
|
|
91
|
+
logCachePreflight(`${key}: repair delete start path=${targetPath}`)
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await fs.promises.rm(targetPath, { recursive: true, force: true })
|
|
95
|
+
logCachePreflight(`${key}: repair delete ok`)
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logCachePreflight(`${key}: repair delete failed ${formatCachePreflightError(error)}`)
|
|
98
|
+
return { key, path: targetPath, repaired: false, ok: false, step: "repair delete", error }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await fs.promises.mkdir(targetPath, { recursive: true })
|
|
103
|
+
logCachePreflight(`${key}: repair mkdir ok`)
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logCachePreflight(`${key}: repair mkdir failed ${formatCachePreflightError(error)}`)
|
|
106
|
+
return { key, path: targetPath, repaired: true, ok: false, step: "repair mkdir", error }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const secondProbe = await probeCacheDir(targetPath)
|
|
110
|
+
if (secondProbe.ok) {
|
|
111
|
+
logCachePreflight(`${key}: repair probe ok`)
|
|
112
|
+
return { key, path: targetPath, repaired: true, ok: true }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
logCachePreflight(`${key}: repair probe failed step="${secondProbe.step}" ${formatCachePreflightError(secondProbe.error)}`)
|
|
116
|
+
return { key, path: targetPath, repaired: true, ok: false, step: secondProbe.step, error: secondProbe.error }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const ensurePinokioCacheDirs = async (kernel, options = {}) => {
|
|
120
|
+
if (!kernel || !kernel.homedir) {
|
|
121
|
+
return {}
|
|
122
|
+
}
|
|
123
|
+
const throwOnFailure = !!options.throwOnFailure
|
|
124
|
+
const root = path.resolve(kernel.homedir)
|
|
125
|
+
const cacheRoot = path.resolve(root, "cache")
|
|
126
|
+
const envPath = path.resolve(root, "ENVIRONMENT")
|
|
127
|
+
const defaults = managedCacheEnvDefaults()
|
|
128
|
+
logCachePreflight(`start root=${root}`)
|
|
129
|
+
await Util.update_env(envPath, defaults)
|
|
130
|
+
logCachePreflight(`ENVIRONMENT updated keys=${CACHE_PREFLIGHT_KEYS.join(",")}`)
|
|
131
|
+
try {
|
|
132
|
+
await fs.promises.mkdir(cacheRoot, { recursive: true })
|
|
133
|
+
logCachePreflight(`cache root mkdir ok path=${cacheRoot}`)
|
|
134
|
+
} catch (error) {
|
|
135
|
+
logCachePreflight(`cache root mkdir failed path=${cacheRoot} ${formatCachePreflightError(error)}`)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (process.platform === "darwin") await fs.promises.writeFile(path.resolve(root, ".metadata_never_index"), "", { flag: "a" }).catch(() => {})
|
|
139
|
+
|
|
140
|
+
const errors = []
|
|
141
|
+
const results = []
|
|
142
|
+
|
|
143
|
+
for (const key of CACHE_PREFLIGHT_KEYS) {
|
|
144
|
+
const targetPath = path.resolve(cacheRoot, key)
|
|
145
|
+
const result = await ensureCachePreflightDir(key, targetPath)
|
|
146
|
+
results.push(result)
|
|
147
|
+
if (!result.ok) {
|
|
148
|
+
errors.push(result)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (errors.length > 0) {
|
|
153
|
+
kernel.cacheDirErrors = errors
|
|
154
|
+
const message = errors
|
|
155
|
+
.map((error) => `${error.key}: ${error.path} (${error.step || "unknown"} ${formatCachePreflightError(error.error)})`)
|
|
156
|
+
.join(", ")
|
|
157
|
+
logCachePreflight(`failed ${message}`)
|
|
158
|
+
if (throwOnFailure) {
|
|
159
|
+
throw new Error(`Pinokio could not create writable cache directories: ${message}`)
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
kernel.cacheDirErrors = []
|
|
163
|
+
logCachePreflight(`complete ok checked=${results.length} repaired=${results.filter((result) => result.repaired).length}`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
kernel.cacheDirPreflight = results
|
|
167
|
+
const env = await get(root, kernel)
|
|
168
|
+
return { env, errors, results }
|
|
169
|
+
}
|
|
7
170
|
const ENVS = async () => {
|
|
8
171
|
// const primary_port = 80
|
|
9
172
|
// const secondary_port = 42000
|
|
@@ -438,18 +601,21 @@ const requirements = async (script, cwd, kernel) => {
|
|
|
438
601
|
let requires_instantiation = false
|
|
439
602
|
if (script) {
|
|
440
603
|
let pre
|
|
441
|
-
if (script.pre) {
|
|
604
|
+
if (Array.isArray(script.pre)) {
|
|
442
605
|
pre = script.pre
|
|
443
|
-
} else if (script.env) {
|
|
606
|
+
} else if (Array.isArray(script.env)) {
|
|
444
607
|
pre = script.env
|
|
445
608
|
}
|
|
446
609
|
if (pre) {
|
|
447
610
|
let env = await get2(cwd, kernel)
|
|
448
611
|
for(let item of pre) {
|
|
612
|
+
if (!item || typeof item !== "object") {
|
|
613
|
+
continue
|
|
614
|
+
}
|
|
449
615
|
let env_key
|
|
450
|
-
if (item.env) {
|
|
616
|
+
if (typeof item.env === "string" && item.env) {
|
|
451
617
|
env_key = item.env
|
|
452
|
-
} else if (item.key) {
|
|
618
|
+
} else if (typeof item.key === "string" && item.key) {
|
|
453
619
|
env_key = item.key
|
|
454
620
|
item.env = item.key
|
|
455
621
|
}
|
|
@@ -458,11 +624,20 @@ const requirements = async (script, cwd, kernel) => {
|
|
|
458
624
|
if (!item.index) {
|
|
459
625
|
item.index = 0
|
|
460
626
|
}
|
|
461
|
-
if (item.host) {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
627
|
+
if (typeof item.host === "string" && item.host.trim()) {
|
|
628
|
+
const host = item.host.trim()
|
|
629
|
+
let parsedHost = ""
|
|
630
|
+
try {
|
|
631
|
+
const hasProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(host);
|
|
632
|
+
const url = new URL(hasProtocol ? host : `https://${host}`);
|
|
633
|
+
parsedHost = url.host
|
|
634
|
+
} catch (e) {
|
|
635
|
+
item.host = ""
|
|
636
|
+
}
|
|
637
|
+
if (parsedHost) {
|
|
638
|
+
item.host = parsedHost
|
|
639
|
+
item.val = await kernel.kv.get(item.host, item.index)
|
|
640
|
+
}
|
|
466
641
|
} else {
|
|
467
642
|
item.host = ""
|
|
468
643
|
}
|
|
@@ -527,21 +702,6 @@ const init = async (options, kernel) => {
|
|
|
527
702
|
}
|
|
528
703
|
const homeRoot = path.resolve(kernel.homedir)
|
|
529
704
|
const isHomeRoot = path.resolve(root) === homeRoot
|
|
530
|
-
const writeSkillIfChanged = async (skillPath, content) => {
|
|
531
|
-
let shouldWrite = true
|
|
532
|
-
try {
|
|
533
|
-
const existingSkillContent = await fs.promises.readFile(skillPath, "utf8")
|
|
534
|
-
shouldWrite = existingSkillContent !== content
|
|
535
|
-
} catch (error) {
|
|
536
|
-
if (!(error && error.code === "ENOENT")) {
|
|
537
|
-
throw error
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (shouldWrite) {
|
|
542
|
-
await fs.promises.writeFile(skillPath, content, "utf8")
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
705
|
const writeFileIfChanged = async (targetPath, content) => {
|
|
546
706
|
let shouldWrite = true
|
|
547
707
|
try {
|
|
@@ -581,68 +741,6 @@ const init = async (options, kernel) => {
|
|
|
581
741
|
}
|
|
582
742
|
await fs.promises.copyFile(targetPath, backupPath)
|
|
583
743
|
}
|
|
584
|
-
const syncSkillToAgentRoots = async (skillName, content) => {
|
|
585
|
-
const home = os.homedir()
|
|
586
|
-
const targetDirs = [
|
|
587
|
-
path.resolve(home, ".agents", "skills", skillName),
|
|
588
|
-
path.resolve(home, ".claude", "skills", skillName),
|
|
589
|
-
path.resolve(home, ".hermes", "skills", skillName)
|
|
590
|
-
]
|
|
591
|
-
for (let i = 0; i < targetDirs.length; i++) {
|
|
592
|
-
const skillDir = targetDirs[i]
|
|
593
|
-
const skillPath = path.resolve(skillDir, "SKILL.md")
|
|
594
|
-
await fs.promises.mkdir(skillDir, { recursive: true })
|
|
595
|
-
await writeSkillIfChanged(skillPath, content)
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
const syncGepetoSkillFromAgents = async () => {
|
|
599
|
-
if (!isHomeRoot) {
|
|
600
|
-
return
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
const agentsPath = path.resolve(homeRoot, "AGENTS.md")
|
|
604
|
-
const agentsExists = await kernel.exists(agentsPath)
|
|
605
|
-
if (!agentsExists) {
|
|
606
|
-
return
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
let agentsContent = ""
|
|
610
|
-
try {
|
|
611
|
-
agentsContent = await fs.promises.readFile(agentsPath, "utf8")
|
|
612
|
-
} catch (error) {
|
|
613
|
-
if (error && error.code === "ENOENT") {
|
|
614
|
-
return
|
|
615
|
-
}
|
|
616
|
-
throw error
|
|
617
|
-
}
|
|
618
|
-
const skillFrontmatter = [
|
|
619
|
-
"---",
|
|
620
|
-
"name: gepeto",
|
|
621
|
-
"description: Guide for building 1-click launchers and building apps with launchers built-in using Pinokio",
|
|
622
|
-
"---"
|
|
623
|
-
].join("\n")
|
|
624
|
-
const desiredSkillContent = `${skillFrontmatter}\n\n${agentsContent}`
|
|
625
|
-
await syncSkillToAgentRoots("gepeto", desiredSkillContent)
|
|
626
|
-
}
|
|
627
|
-
const syncPinokioSkillFromTemplate = async () => {
|
|
628
|
-
if (!isHomeRoot) {
|
|
629
|
-
return
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
const templatePath = path.resolve(__dirname, "../prototype/system/SKILL_PINOKIO.md")
|
|
633
|
-
let templateContent
|
|
634
|
-
try {
|
|
635
|
-
templateContent = await fs.promises.readFile(templatePath, "utf8")
|
|
636
|
-
} catch (error) {
|
|
637
|
-
if (error && error.code === "ENOENT") {
|
|
638
|
-
return
|
|
639
|
-
}
|
|
640
|
-
throw error
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
await syncSkillToAgentRoots("pinokio", templateContent)
|
|
644
|
-
}
|
|
645
|
-
|
|
646
744
|
let current = path.resolve(root, "ENVIRONMENT")
|
|
647
745
|
let exists = await kernel.exists(current)
|
|
648
746
|
if (exists) {
|
|
@@ -769,9 +867,14 @@ const init = async (options, kernel) => {
|
|
|
769
867
|
}
|
|
770
868
|
}
|
|
771
869
|
|
|
772
|
-
// Keep
|
|
773
|
-
|
|
774
|
-
|
|
870
|
+
// Keep Pinokio-managed skills in sync for the home root.
|
|
871
|
+
if (isHomeRoot) {
|
|
872
|
+
try {
|
|
873
|
+
await ManagedSkills.syncManagedSkills(kernel)
|
|
874
|
+
} catch (error) {
|
|
875
|
+
console.warn("[managed skills] Failed to sync managed skills", error)
|
|
876
|
+
}
|
|
877
|
+
}
|
|
775
878
|
|
|
776
879
|
const gitDir = path.resolve(root, ".git")
|
|
777
880
|
const gitDirExists = await kernel.exists(gitDir)
|
|
@@ -828,4 +931,4 @@ const init = async (options, kernel) => {
|
|
|
828
931
|
env_path: current
|
|
829
932
|
}
|
|
830
933
|
}
|
|
831
|
-
module.exports = { ENV, get, get2, init_folders, requirements, init, get_root }
|
|
934
|
+
module.exports = { ENV, get, get2, init_folders, ensurePinokioCacheDirs, requirements, init, get_root }
|
package/kernel/git.js
CHANGED
|
@@ -2,10 +2,17 @@ const git = require('isomorphic-git')
|
|
|
2
2
|
const fs = require('fs')
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const crypto = require('crypto')
|
|
5
|
+
const { execFile } = require('child_process')
|
|
5
6
|
const { glob, sync, hasMagic } = require('glob-gitignore')
|
|
6
7
|
const http = require('isomorphic-git/http/node')
|
|
7
8
|
const ini = require('ini')
|
|
8
9
|
const Util = require('./util')
|
|
10
|
+
const NON_INTERACTIVE_GIT_ENV = {
|
|
11
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
12
|
+
GIT_ASKPASS: "",
|
|
13
|
+
SSH_ASKPASS: "",
|
|
14
|
+
GCM_INTERACTIVE: "never"
|
|
15
|
+
}
|
|
9
16
|
class Git {
|
|
10
17
|
constructor(kernel) {
|
|
11
18
|
this.kernel = kernel
|
|
@@ -52,6 +59,68 @@ class Git {
|
|
|
52
59
|
}
|
|
53
60
|
return str
|
|
54
61
|
}
|
|
62
|
+
parseCredentialOutput(stdout) {
|
|
63
|
+
const credential = {}
|
|
64
|
+
for (const line of String(stdout || "").split(/\r?\n/)) {
|
|
65
|
+
const index = line.indexOf("=")
|
|
66
|
+
if (index <= 0) continue
|
|
67
|
+
credential[line.slice(0, index)] = line.slice(index + 1)
|
|
68
|
+
}
|
|
69
|
+
return credential
|
|
70
|
+
}
|
|
71
|
+
credentialEnv() {
|
|
72
|
+
const env = this.kernel && this.kernel.envs ? { ...this.kernel.envs } : { ...process.env }
|
|
73
|
+
if (this.kernel && this.kernel.homedir && !env.GIT_CONFIG_GLOBAL) {
|
|
74
|
+
env.GIT_CONFIG_GLOBAL = path.resolve(this.kernel.homedir, "gitconfig")
|
|
75
|
+
}
|
|
76
|
+
return { ...env, ...NON_INTERACTIVE_GIT_ENV }
|
|
77
|
+
}
|
|
78
|
+
async getCredentialForUrl(rawUrl) {
|
|
79
|
+
let target
|
|
80
|
+
try {
|
|
81
|
+
target = new URL(rawUrl)
|
|
82
|
+
} catch (_) {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
if (!/^https?:$/.test(target.protocol) || target.hostname.toLowerCase() !== "github.com") {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await this.ensureDefaults()
|
|
90
|
+
} catch (_) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const child = execFile(
|
|
95
|
+
"git",
|
|
96
|
+
["credential", "fill"],
|
|
97
|
+
{
|
|
98
|
+
cwd: (this.kernel && this.kernel.homedir) || process.cwd(),
|
|
99
|
+
env: this.credentialEnv(),
|
|
100
|
+
timeout: 30000,
|
|
101
|
+
maxBuffer: 1024 * 1024,
|
|
102
|
+
windowsHide: true
|
|
103
|
+
},
|
|
104
|
+
(error, stdout) => {
|
|
105
|
+
if (error) {
|
|
106
|
+
resolve(null)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
const credential = this.parseCredentialOutput(stdout)
|
|
110
|
+
resolve(credential.password ? credential : null)
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
child.stdin.end(`protocol=${target.protocol.slice(0, -1)}\nhost=${target.hostname}\n\n`)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
async getIsomorphicGitAuth(rawUrl) {
|
|
117
|
+
const credential = await this.getCredentialForUrl(rawUrl)
|
|
118
|
+
if (!credential || !credential.password) return undefined
|
|
119
|
+
return {
|
|
120
|
+
username: credential.username || "x-access-token",
|
|
121
|
+
password: credential.password
|
|
122
|
+
}
|
|
123
|
+
}
|
|
55
124
|
normalizeGitPerson(person) {
|
|
56
125
|
if (!person || typeof person !== "object") return null
|
|
57
126
|
const name = typeof person.name === "string" ? person.name : null
|
|
@@ -1087,6 +1156,46 @@ class Git {
|
|
|
1087
1156
|
dirty = true
|
|
1088
1157
|
}
|
|
1089
1158
|
|
|
1159
|
+
const mergeMissing = (target, source) => {
|
|
1160
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1161
|
+
const valueIsObject = typeof value === "object" && value !== null && !Array.isArray(value)
|
|
1162
|
+
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
1163
|
+
target[key] = valueIsObject ? { ...value } : value
|
|
1164
|
+
dirty = true
|
|
1165
|
+
continue
|
|
1166
|
+
}
|
|
1167
|
+
if (valueIsObject && typeof target[key] === "object" && target[key] !== null && !Array.isArray(target[key])) {
|
|
1168
|
+
mergeMissing(target[key], value)
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
const clone = (value) => {
|
|
1173
|
+
if (typeof value !== "object" || value === null) return value
|
|
1174
|
+
return JSON.parse(JSON.stringify(value))
|
|
1175
|
+
}
|
|
1176
|
+
const sameConfigValue = (a, b) => JSON.stringify(a) === JSON.stringify(b)
|
|
1177
|
+
const isCredentialSection = (section) => section === "credential" || section.startsWith('credential "')
|
|
1178
|
+
const resetCredentialDefaults = () => {
|
|
1179
|
+
const templateCredentialSections = Object.fromEntries(
|
|
1180
|
+
Object.entries(templateConfig).filter(([section]) => isCredentialSection(section))
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
for (const section of Object.keys(config)) {
|
|
1184
|
+
if (!isCredentialSection(section)) continue
|
|
1185
|
+
if (!Object.prototype.hasOwnProperty.call(templateCredentialSections, section)) {
|
|
1186
|
+
delete config[section]
|
|
1187
|
+
dirty = true
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
for (const [section, tplSection] of Object.entries(templateCredentialSections)) {
|
|
1192
|
+
if (!sameConfigValue(config[section], tplSection)) {
|
|
1193
|
+
config[section] = clone(tplSection)
|
|
1194
|
+
dirty = true
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1090
1199
|
for (const [section, tplSection] of Object.entries(templateConfig)) {
|
|
1091
1200
|
if (typeof tplSection !== "object" || tplSection === null) continue
|
|
1092
1201
|
if (!config[section]) {
|
|
@@ -1094,12 +1203,7 @@ class Git {
|
|
|
1094
1203
|
dirty = true
|
|
1095
1204
|
continue
|
|
1096
1205
|
}
|
|
1097
|
-
|
|
1098
|
-
if (!Object.prototype.hasOwnProperty.call(config[section], key)) {
|
|
1099
|
-
config[section][key] = value
|
|
1100
|
-
dirty = true
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1206
|
+
mergeMissing(config[section], tplSection)
|
|
1103
1207
|
}
|
|
1104
1208
|
|
|
1105
1209
|
for (const { section, key, value } of required) {
|
|
@@ -1118,6 +1222,21 @@ class Git {
|
|
|
1118
1222
|
dirty = true
|
|
1119
1223
|
}
|
|
1120
1224
|
|
|
1225
|
+
resetCredentialDefaults()
|
|
1226
|
+
|
|
1227
|
+
const githubCredentialSection = config['credential "https://github'] && config['credential "https://github']['com"']
|
|
1228
|
+
if (githubCredentialSection && typeof githubCredentialSection === "object") {
|
|
1229
|
+
if (!githubCredentialSection.provider) {
|
|
1230
|
+
githubCredentialSection.provider = "github"
|
|
1231
|
+
dirty = true
|
|
1232
|
+
}
|
|
1233
|
+
const helper = typeof githubCredentialSection.helper === "string" ? githubCredentialSection.helper : ""
|
|
1234
|
+
if (!helper) {
|
|
1235
|
+
githubCredentialSection.helper = "manager"
|
|
1236
|
+
dirty = true
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1121
1240
|
if (dirty) {
|
|
1122
1241
|
await fs.promises.writeFile(gitconfigPath, ini.stringify(config))
|
|
1123
1242
|
}
|
|
@@ -1378,19 +1497,6 @@ class Git {
|
|
|
1378
1497
|
const absoluteTarget = path.resolve(targetPath)
|
|
1379
1498
|
const home = path.resolve(this.kernel.homedir)
|
|
1380
1499
|
const managedTargets = [
|
|
1381
|
-
{
|
|
1382
|
-
kind: "plugin",
|
|
1383
|
-
root: path.resolve(home, "plugin/code"),
|
|
1384
|
-
matches: (root, target) => target === root || target.startsWith(`${root}${path.sep}`),
|
|
1385
|
-
bootstrap: async () => {
|
|
1386
|
-
await fs.promises.rm(path.resolve(home, "plugin/code"), { recursive: true, force: true })
|
|
1387
|
-
this.dirs.delete(path.resolve(home, "plugin/code"))
|
|
1388
|
-
if (this.kernel.plugin && typeof this.kernel.plugin.init === "function") {
|
|
1389
|
-
await this.kernel.plugin.init()
|
|
1390
|
-
}
|
|
1391
|
-
},
|
|
1392
|
-
exists: async () => this.kernel.exists("plugin/code")
|
|
1393
|
-
},
|
|
1394
1500
|
{
|
|
1395
1501
|
kind: "prototype",
|
|
1396
1502
|
root: path.resolve(home, "prototype/system"),
|
|
@@ -1531,6 +1637,7 @@ class Git {
|
|
|
1531
1637
|
dir,
|
|
1532
1638
|
remote,
|
|
1533
1639
|
tags: false,
|
|
1640
|
+
onAuth: (url) => this.getIsomorphicGitAuth(url),
|
|
1534
1641
|
})
|
|
1535
1642
|
if (fetchResult && fetchResult.defaultBranch) {
|
|
1536
1643
|
addCandidate(`refs/remotes/${remote}/${fetchResult.defaultBranch}`)
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
smudge = git-lfs smudge -- %f
|
|
10
10
|
process = git-lfs filter-process
|
|
11
11
|
required = true
|
|
12
|
+
[credential]
|
|
13
|
+
helper = manager
|
|
14
|
+
gitHubAuthModes = oauth
|
|
15
|
+
namespace = pinokio
|
|
16
|
+
[credential "https://github.com"]
|
|
17
|
+
helper = manager
|
|
18
|
+
provider = github
|
|
12
19
|
[user]
|
|
13
20
|
name = pinokio
|
|
14
21
|
email = pinokio@localhost
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { normalize_model } = require("./common")
|
|
2
|
+
|
|
3
|
+
// Detect uninformative AMD APU names that need CPU-brand fallback matching.
|
|
4
|
+
const is_generic_gpu_model = (model) => {
|
|
5
|
+
let normalized = normalize_model(model)
|
|
6
|
+
return /^(amd )?radeon graphics$/.test(normalized)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Resolve CPU brand lazily so discrete GPU matches do not trigger CPU probing.
|
|
10
|
+
const resolve_cpu_brand = async (cpu_brand) => {
|
|
11
|
+
if (typeof cpu_brand === "function") {
|
|
12
|
+
return await cpu_brand()
|
|
13
|
+
} else {
|
|
14
|
+
return cpu_brand
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Match AMD GPU model names that Pinokio should route to PyTorch ROCm wheels.
|
|
19
|
+
const matches_rocm_torch_model = (model) => {
|
|
20
|
+
let normalized = normalize_model(model)
|
|
21
|
+
|
|
22
|
+
// AMD PyTorch ROCm install policy. These checks are based on the
|
|
23
|
+
// hardware families Pinokio should route to ROCm-flavored PyTorch wheels,
|
|
24
|
+
// not on whether ROCm is already installed on the machine.
|
|
25
|
+
|
|
26
|
+
// Radeon RX 7600+:
|
|
27
|
+
// - RX 7600 / 7600 XT / 7600M / 7600M XT
|
|
28
|
+
// - RX 7700 / 7800 / 7900 families
|
|
29
|
+
// - RX 9000 families such as RX 9060 and RX 9070
|
|
30
|
+
// - Future RX 8xxx/9xxx names are treated as ROCm-intended by policy.
|
|
31
|
+
let rx = normalized.match(/\brx\s*([7-9]\d{3})(?!\d)/)
|
|
32
|
+
if (rx && Number(rx[1]) >= 7600) {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Radeon PRO W7700+:
|
|
37
|
+
// - PRO W7700 / W7800 / W7900
|
|
38
|
+
// - Future PRO W 8xxx/9xxx names are treated as ROCm-intended by policy.
|
|
39
|
+
let pro = normalized.match(/\bpro\s*w\s*([7-9]\d{3})(?!\d)/)
|
|
40
|
+
if (pro && Number(pro[1]) >= 7700) {
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
// Radeon PRO V710
|
|
46
|
+
/\bpro\s*v\s*710\b/.test(normalized) ||
|
|
47
|
+
// Radeon AI PRO R9600/R9700 variants, including suffixes like R9700S.
|
|
48
|
+
/\bai\s*pro\s*r\s*9[67]\d{2}[a-z]?\b/.test(normalized) ||
|
|
49
|
+
// Supported APUs and codenames seen in PyTorch ROCm wheel support.
|
|
50
|
+
/\b(780m|820m|880m|890m|8050s|8060s|strix|phoenix|fire range)\b/.test(normalized)
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Decide AMD ROCm install intent, using CPU brand only for generic APU GPU names.
|
|
55
|
+
const supports_torch_backend = async (model, cpu_brand) => {
|
|
56
|
+
if (matches_rocm_torch_model(model)) {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
if (!is_generic_gpu_model(model)) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
// APU-only systems can report the GPU as just "AMD Radeon Graphics".
|
|
63
|
+
// In that generic case, use the CPU brand as a fallback because it often
|
|
64
|
+
// includes the Radeon model or codename, such as 8060S or Strix Halo.
|
|
65
|
+
return matches_rocm_torch_model(await resolve_cpu_brand(cpu_brand))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
is_generic_gpu_model,
|
|
70
|
+
matches_rocm_torch_model,
|
|
71
|
+
supports_torch_backend
|
|
72
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Normalize GPU/CPU names so matching works across vendor punctuation variants.
|
|
2
|
+
const normalize_model = (model) => {
|
|
3
|
+
return (model || "")
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.replace(/\((tm|r)\)/g, "")
|
|
6
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
7
|
+
.trim()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
normalize_model
|
|
12
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { normalize_model } = require("./common")
|
|
2
|
+
|
|
3
|
+
// Resolve CPU brand lazily so clear GPU model matches do not trigger CPU probing.
|
|
4
|
+
const resolve_cpu_brand = async (cpu_brand) => {
|
|
5
|
+
if (typeof cpu_brand === "function") {
|
|
6
|
+
return await cpu_brand()
|
|
7
|
+
} else {
|
|
8
|
+
return cpu_brand
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Match Intel GPU model names that Pinokio should route to PyTorch XPU wheels.
|
|
13
|
+
const matches_xpu_torch_model = (model) => {
|
|
14
|
+
let normalized = normalize_model(model)
|
|
15
|
+
return (
|
|
16
|
+
// Intel Arc A-Series / B-Series, Arc Pro, and Core Ultra iGPUs when the
|
|
17
|
+
// GPU model itself includes Arc.
|
|
18
|
+
/\barc\b/.test(normalized) ||
|
|
19
|
+
// Intel Data Center GPU Max Series. Do not match generic Data Center GPU
|
|
20
|
+
// names because Data Center GPU Flex is not in current PyTorch XPU docs.
|
|
21
|
+
/\b(data center gpu max|gpu max|ponte vecchio)\b/.test(normalized)
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Detect uninformative Intel iGPU names that need CPU-brand fallback matching.
|
|
26
|
+
const is_generic_gpu_model = (model) => {
|
|
27
|
+
let normalized = normalize_model(model)
|
|
28
|
+
return /^(intel )?graphics$/.test(normalized)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Decide Intel XPU install intent, using CPU brand only for generic iGPU names.
|
|
32
|
+
const supports_torch_backend = async (model, cpu_brand) => {
|
|
33
|
+
if (matches_xpu_torch_model(model)) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
if (!is_generic_gpu_model(model)) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
let brand = normalize_model(await resolve_cpu_brand(cpu_brand))
|
|
40
|
+
return /\bcore\s*ultra\b/.test(brand)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
is_generic_gpu_model,
|
|
45
|
+
matches_xpu_torch_model,
|
|
46
|
+
supports_torch_backend
|
|
47
|
+
}
|