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.
Files changed (122) hide show
  1. package/kernel/api/github/index.js +444 -0
  2. package/kernel/api/index.js +199 -11
  3. package/kernel/api/process/index.js +124 -44
  4. package/kernel/api/shell_run_template.js +273 -0
  5. package/kernel/api/uri/index.js +51 -0
  6. package/kernel/bin/git.js +9 -10
  7. package/kernel/bin/huggingface.js +1 -1
  8. package/kernel/bin/zip.js +9 -1
  9. package/kernel/connect/providers/github/README.md +5 -4
  10. package/kernel/environment.js +195 -92
  11. package/kernel/git.js +126 -19
  12. package/kernel/gitconfig_template +7 -0
  13. package/kernel/gpu/amd.js +72 -0
  14. package/kernel/gpu/apple.js +8 -0
  15. package/kernel/gpu/common.js +12 -0
  16. package/kernel/gpu/intel.js +47 -0
  17. package/kernel/gpu/nvidia.js +8 -0
  18. package/kernel/index.js +11 -1
  19. package/kernel/managed_skills.js +871 -0
  20. package/kernel/plugin.js +6 -58
  21. package/kernel/plugin_sources.js +316 -0
  22. package/kernel/resource_usage/gpu.js +349 -0
  23. package/kernel/resource_usage/index.js +322 -0
  24. package/kernel/resource_usage/macos_footprint.js +197 -0
  25. package/kernel/resource_usage/preferences.js +92 -0
  26. package/kernel/resource_usage/process_tree.js +303 -0
  27. package/kernel/scripts/git/create +4 -4
  28. package/kernel/scripts/git/fork +7 -8
  29. package/kernel/shell.js +23 -2
  30. package/kernel/shells.js +41 -0
  31. package/kernel/sysinfo.js +62 -9
  32. package/kernel/util.js +60 -0
  33. package/package.json +1 -1
  34. package/server/index.js +984 -156
  35. package/server/lib/app_log_report.js +543 -0
  36. package/server/lib/content_validation.js +55 -33
  37. package/server/lib/launcher_instruction_bootstrap.js +4 -96
  38. package/server/lib/terminal_session_helpers.js +0 -3
  39. package/server/public/common.js +77 -31
  40. package/server/public/create-launcher.js +4 -32
  41. package/server/public/logs.js +1428 -0
  42. package/server/public/nav.js +7 -0
  43. package/server/public/plugin-detail.js +93 -10
  44. package/server/public/privacy_filter_worker.js +391 -0
  45. package/server/public/style.css +1104 -154
  46. package/server/public/task-launcher.js +8 -29
  47. package/server/public/universal-launcher.css +8 -6
  48. package/server/public/universal-launcher.js +3 -27
  49. package/server/routes/apps.js +195 -1
  50. package/server/views/app.ejs +3041 -717
  51. package/server/views/autolaunch.ejs +917 -0
  52. package/server/views/bootstrap.ejs +7 -1
  53. package/server/views/d.ejs +408 -65
  54. package/server/views/editor.ejs +85 -19
  55. package/server/views/index.ejs +661 -111
  56. package/server/views/init/index.ejs +1 -1
  57. package/server/views/install.ejs +1 -1
  58. package/server/views/logs.ejs +164 -86
  59. package/server/views/net.ejs +7 -1
  60. package/server/views/partials/d_terminal_column.ejs +2 -2
  61. package/server/views/partials/d_terminal_options.ejs +0 -8
  62. package/server/views/partials/fs_status.ejs +47 -0
  63. package/server/views/partials/home_action_modal.ejs +86 -0
  64. package/server/views/partials/home_run_menu.ejs +87 -0
  65. package/server/views/partials/main_sidebar.ejs +2 -0
  66. package/server/views/partials/menu.ejs +1 -1
  67. package/server/views/plugin_detail.ejs +19 -4
  68. package/server/views/plugins.ejs +201 -3
  69. package/server/views/pre.ejs +1 -1
  70. package/server/views/pro.ejs +1 -1
  71. package/server/views/shell.ejs +40 -18
  72. package/server/views/skills.ejs +506 -0
  73. package/server/views/terminal.ejs +45 -19
  74. package/spec/INSTRUCTION_SYNC.md +20 -10
  75. package/system/plugin/antigravity-cli/antigravity.png +0 -0
  76. package/system/plugin/antigravity-cli/common.js +155 -0
  77. package/system/plugin/antigravity-cli/install.js +272 -0
  78. package/system/plugin/antigravity-cli/pinokio.js +13 -0
  79. package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
  80. package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
  81. package/system/plugin/claude/claude.png +0 -0
  82. package/system/plugin/claude/pinokio.js +47 -0
  83. package/system/plugin/claude-auto/claude.png +0 -0
  84. package/system/plugin/claude-auto/pinokio.js +58 -0
  85. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  86. package/system/plugin/claude-desktop/pinokio.js +23 -0
  87. package/system/plugin/codex/openai.webp +0 -0
  88. package/system/plugin/codex/pinokio.js +42 -0
  89. package/system/plugin/codex-auto/openai.webp +0 -0
  90. package/system/plugin/codex-auto/pinokio.js +49 -0
  91. package/system/plugin/codex-desktop/icon.png +0 -0
  92. package/system/plugin/codex-desktop/pinokio.js +23 -0
  93. package/system/plugin/crush/crush.png +0 -0
  94. package/system/plugin/crush/pinokio.js +15 -0
  95. package/system/plugin/cursor/cursor.jpeg +0 -0
  96. package/system/plugin/cursor/pinokio.js +23 -0
  97. package/system/plugin/qwen/pinokio.js +34 -0
  98. package/system/plugin/qwen/qwen.png +0 -0
  99. package/system/plugin/vscode/pinokio.js +20 -0
  100. package/system/plugin/vscode/vscode.png +0 -0
  101. package/system/plugin/windsurf/pinokio.js +23 -0
  102. package/system/plugin/windsurf/windsurf.png +0 -0
  103. package/test/antigravity-cli-plugin.test.js +185 -0
  104. package/test/app-api.test.js +239 -0
  105. package/test/app-log-report.test.js +67 -0
  106. package/test/environment-cache-preflight.test.js +98 -0
  107. package/test/git-bin.test.js +59 -0
  108. package/test/git-defaults.test.js +150 -0
  109. package/test/github-api.test.js +158 -0
  110. package/test/github-connection.test.js +117 -0
  111. package/test/huggingface-bin.test.js +25 -0
  112. package/test/managed-skills.test.js +351 -0
  113. package/test/plugin-action-functions.test.js +337 -0
  114. package/test/plugin-dev-iframe.test.js +17 -0
  115. package/test/plugin-sources.test.js +203 -0
  116. package/test/privacy-filter-worker-heuristics.test.js +69 -0
  117. package/test/process-wait.test.js +169 -0
  118. package/test/script-api.test.js +97 -0
  119. package/test/shell-api.test.js +134 -0
  120. package/test/shell-run-template.test.js +209 -0
  121. package/test/storage-api.test.js +137 -0
  122. package/test/uri-api.test.js +100 -0
@@ -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 platform = os.platform()
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 hasProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(item.host);
463
- const url = new URL(hasProtocol ? item.host : `https://${item.host}`);
464
- item.host = url.host
465
- item.val = await kernel.kv.get(item.host, item.index)
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 agent skills in sync for the Pinokio home root
773
- await syncGepetoSkillFromAgents()
774
- await syncPinokioSkillFromTemplate()
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
- for (const [key, value] of Object.entries(tplSection)) {
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,8 @@
1
+ // Apple GPU routes to the built-in PyTorch MPS backend on macOS.
2
+ const supports_torch_backend = (controller, platform) => {
3
+ return !!controller && platform === "darwin"
4
+ }
5
+
6
+ module.exports = {
7
+ supports_torch_backend
8
+ }
@@ -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
+ }