@wenathlan/saddle 2.0.153 → 2.0.155

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 (307) hide show
  1. package/Dockerfile +139 -55
  2. package/README.md +58 -59
  3. package/browser.ts +482 -0
  4. package/cli.ts +274 -0
  5. package/distribution.ts +731 -0
  6. package/docker.config +29 -8
  7. package/docs/artifactavailability.md +2 -2
  8. package/docs/browser.md +18 -1
  9. package/docs/{containerplatforms.2.1.0.md → containerplatforms210.md} +1 -1
  10. package/docs/{documentmap.1.8.12.md → documentmap1812.md} +1 -1
  11. package/docs/extension.md +13 -2
  12. package/docs/libraryapi.md +1 -1
  13. package/docs/{ninety.modalities.md → ninetymodalities.md} +87 -4
  14. package/docs/{planning.ninety.md → planningninety.md} +6 -6
  15. package/docs/{releasenotes.1.8.13.md → releasenotes1813.md} +2 -2
  16. package/docs/{releasenotes.1.8.14.md → releasenotes1814.md} +1 -1
  17. package/docs/{repo.synthesis.1.8.16.md → reposynthesis1816.md} +1 -1
  18. package/docs/todo.md +2 -2
  19. package/docs/{todo.1.8.15.md → todo1815.md} +1 -1
  20. package/docs/{todo.1.8.16.md → todo1816.md} +5 -5
  21. package/execution.ts +677 -0
  22. package/foundation.ts +314 -0
  23. package/index.ts +35 -27
  24. package/isolation.ts +84 -0
  25. package/modes.ts +170 -0
  26. package/mttg.config +1 -1
  27. package/orchestrator.ts +49 -15
  28. package/package.json +90 -75
  29. package/qemu.config +2 -2
  30. package/sandbox.ts +2094 -0
  31. package/scheduler.ts +1 -1
  32. package/server.ts +52 -0
  33. package/tsconfig.json +11 -12
  34. package/virtual.ts +1105 -0
  35. package/virtualgpu.ts +1 -1
  36. package/virtualization.ts +1 -1
  37. package/workloadlaw.ts +293 -0
  38. package/dist/App.d.ts +0 -5
  39. package/dist/App.js +0 -52
  40. package/dist/Sol/Sol.d.ts +0 -3
  41. package/dist/Sol/Sol.js +0 -46
  42. package/dist/Sol/agentbrowser/agentbrowser.d.ts +0 -9
  43. package/dist/Sol/agentbrowser/agentbrowser.js +0 -121
  44. package/dist/Sol/architecture/architecture.d.ts +0 -9
  45. package/dist/Sol/architecture/architecture.js +0 -93
  46. package/dist/Sol/compute/compute.d.ts +0 -9
  47. package/dist/Sol/compute/compute.js +0 -22
  48. package/dist/Sol/console/console.d.ts +0 -15
  49. package/dist/Sol/console/console.js +0 -292
  50. package/dist/Sol/dashboard/dashboard.d.ts +0 -10
  51. package/dist/Sol/dashboard/dashboard.js +0 -181
  52. package/dist/Sol/docs/docs.d.ts +0 -9
  53. package/dist/Sol/docs/docs.js +0 -28
  54. package/dist/Sol/home/home.d.ts +0 -35
  55. package/dist/Sol/home/home.js +0 -239
  56. package/dist/Sol/integrations/integrations.d.ts +0 -9
  57. package/dist/Sol/integrations/integrations.js +0 -83
  58. package/dist/Sol/intro/intro.d.ts +0 -7
  59. package/dist/Sol/intro/intro.js +0 -102
  60. package/dist/Sol/login/login.d.ts +0 -16
  61. package/dist/Sol/login/login.js +0 -49
  62. package/dist/Sol/notfound/notfound.d.ts +0 -9
  63. package/dist/Sol/notfound/notfound.js +0 -15
  64. package/dist/Sol/onboarding/onboarding.d.ts +0 -25
  65. package/dist/Sol/onboarding/onboarding.js +0 -60
  66. package/dist/Sol/playground/playground.d.ts +0 -9
  67. package/dist/Sol/playground/playground.js +0 -127
  68. package/dist/Sol/register/register.d.ts +0 -16
  69. package/dist/Sol/register/register.js +0 -193
  70. package/dist/Sol/shell/Shell.d.ts +0 -124
  71. package/dist/Sol/shell/Shell.js +0 -335
  72. package/dist/Sol/wingets/ComputeBootladder.d.ts +0 -6
  73. package/dist/Sol/wingets/ComputeBootladder.js +0 -71
  74. package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +0 -7
  75. package/dist/Sol/wingets/ComputeModalitycatalog.js +0 -71
  76. package/dist/Sol/wingets/ConsoleApibadge.d.ts +0 -7
  77. package/dist/Sol/wingets/ConsoleApibadge.js +0 -5
  78. package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +0 -6
  79. package/dist/Sol/wingets/ConsoleEdgelaw.js +0 -36
  80. package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +0 -19
  81. package/dist/Sol/wingets/ConsoleEventstimeline.js +0 -12
  82. package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +0 -8
  83. package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +0 -8
  84. package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +0 -21
  85. package/dist/Sol/wingets/ConsoleSpecpanel.js +0 -75
  86. package/dist/Sol/wingets/ConsoleTerminal.d.ts +0 -22
  87. package/dist/Sol/wingets/ConsoleTerminal.js +0 -77
  88. package/dist/Sol/wingets/DashboardAccountpanel.d.ts +0 -18
  89. package/dist/Sol/wingets/DashboardAccountpanel.js +0 -65
  90. package/dist/Sol/wingets/DashboardAdminpanel.d.ts +0 -6
  91. package/dist/Sol/wingets/DashboardAdminpanel.js +0 -90
  92. package/dist/Sol/wingets/DashboardAuditlog.d.ts +0 -8
  93. package/dist/Sol/wingets/DashboardAuditlog.js +0 -50
  94. package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +0 -10
  95. package/dist/Sol/wingets/DashboardCreatesandboxform.js +0 -71
  96. package/dist/Sol/wingets/DashboardEventstimeline.d.ts +0 -8
  97. package/dist/Sol/wingets/DashboardEventstimeline.js +0 -83
  98. package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +0 -8
  99. package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +0 -57
  100. package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +0 -8
  101. package/dist/Sol/wingets/DashboardMeshnodestable.js +0 -103
  102. package/dist/Sol/wingets/DashboardSandboxlist.d.ts +0 -10
  103. package/dist/Sol/wingets/DashboardSandboxlist.js +0 -163
  104. package/dist/Sol/wingets/DashboardUserstable.d.ts +0 -8
  105. package/dist/Sol/wingets/DashboardUserstable.js +0 -78
  106. package/dist/Sol/wingets/HomeEntrygate.d.ts +0 -23
  107. package/dist/Sol/wingets/HomeEntrygate.js +0 -73
  108. package/dist/Sol/wingets/HomeMetricstrip.d.ts +0 -9
  109. package/dist/Sol/wingets/HomeMetricstrip.js +0 -4
  110. package/dist/Sol/wingets/HomeRuntimediagram.d.ts +0 -1
  111. package/dist/Sol/wingets/HomeRuntimediagram.js +0 -22
  112. package/dist/Sol/wingets/LoginForm.d.ts +0 -19
  113. package/dist/Sol/wingets/LoginForm.js +0 -141
  114. package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +0 -13
  115. package/dist/Sol/wingets/RegisterPasswordconfirm.js +0 -16
  116. package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +0 -19
  117. package/dist/Sol/wingets/RegisterPasswordmeter.js +0 -65
  118. package/dist/Sol/wingets/RegisterUsernamerules.d.ts +0 -11
  119. package/dist/Sol/wingets/RegisterUsernamerules.js +0 -16
  120. package/dist/Sol/wingets/index.d.ts +0 -39
  121. package/dist/Sol/wingets/index.js +0 -39
  122. package/dist/alternatives.d.ts +0 -508
  123. package/dist/alternatives.js +0 -950
  124. package/dist/api.d.ts +0 -154
  125. package/dist/api.js +0 -174
  126. package/dist/auth.d.ts +0 -268
  127. package/dist/auth.js +0 -476
  128. package/dist/automation.d.ts +0 -425
  129. package/dist/automation.js +0 -566
  130. package/dist/browser.d.ts +0 -561
  131. package/dist/browser.js +0 -1058
  132. package/dist/capacitor.config.d.ts +0 -45
  133. package/dist/capacitor.config.js +0 -48
  134. package/dist/catalog.d.ts +0 -43
  135. package/dist/catalog.js +0 -44
  136. package/dist/cli.d.ts +0 -16
  137. package/dist/cli.js +0 -168
  138. package/dist/communication.d.ts +0 -181
  139. package/dist/communication.js +0 -376
  140. package/dist/compute.d.ts +0 -1160
  141. package/dist/compute.js +0 -2272
  142. package/dist/dashboardlib.d.ts +0 -58
  143. package/dist/dashboardlib.js +0 -161
  144. package/dist/db.d.ts +0 -569
  145. package/dist/db.js +0 -1080
  146. package/dist/distribution.d.ts +0 -528
  147. package/dist/distribution.js +0 -810
  148. package/dist/drizzle.config.d.ts +0 -20
  149. package/dist/drizzle.config.js +0 -18
  150. package/dist/edgelandes.d.ts +0 -70
  151. package/dist/edgelandes.host.d.ts +0 -26
  152. package/dist/edgelandes.host.js +0 -1089
  153. package/dist/edgelandes.inproc.d.ts +0 -27
  154. package/dist/edgelandes.inproc.js +0 -952
  155. package/dist/edgelandes.js +0 -1133
  156. package/dist/edgelandes.ninety.d.ts +0 -31
  157. package/dist/edgelandes.ninety.js +0 -174
  158. package/dist/edgelandes.virtual.d.ts +0 -25
  159. package/dist/edgelandes.virtual.js +0 -629
  160. package/dist/edgeplane.d.ts +0 -114
  161. package/dist/edgeplane.js +0 -305
  162. package/dist/examplelocaljob.d.ts +0 -1
  163. package/dist/examplelocaljob.js +0 -14
  164. package/dist/execution.d.ts +0 -529
  165. package/dist/execution.js +0 -760
  166. package/dist/family.d.ts +0 -16
  167. package/dist/family.js +0 -42
  168. package/dist/familyurl.d.ts +0 -21
  169. package/dist/familyurl.js +0 -24
  170. package/dist/format.d.ts +0 -18
  171. package/dist/format.js +0 -59
  172. package/dist/foundation.d.ts +0 -172
  173. package/dist/foundation.js +0 -312
  174. package/dist/index.d.ts +0 -735
  175. package/dist/index.js +0 -1020
  176. package/dist/integration.d.ts +0 -235
  177. package/dist/integration.js +0 -228
  178. package/dist/intelligence.d.ts +0 -93
  179. package/dist/intelligence.js +0 -144
  180. package/dist/isolation.d.ts +0 -128
  181. package/dist/isolation.js +0 -95
  182. package/dist/labels.d.ts +0 -665
  183. package/dist/labels.js +0 -751
  184. package/dist/localauth.d.ts +0 -127
  185. package/dist/localauth.js +0 -413
  186. package/dist/media.d.ts +0 -343
  187. package/dist/media.js +0 -985
  188. package/dist/mesh.d.ts +0 -177
  189. package/dist/mesh.js +0 -468
  190. package/dist/modalityregistry.d.ts +0 -109
  191. package/dist/modalityregistry.js +0 -1386
  192. package/dist/modes.d.ts +0 -358
  193. package/dist/modes.js +0 -133
  194. package/dist/onboardingstate.d.ts +0 -79
  195. package/dist/onboardingstate.js +0 -282
  196. package/dist/orchestrator.d.ts +0 -1338
  197. package/dist/orchestrator.js +0 -4310
  198. package/dist/packager/targetcli.js +0 -4
  199. package/dist/paths.d.ts +0 -2
  200. package/dist/paths.js +0 -14
  201. package/dist/performance.d.ts +0 -415
  202. package/dist/performance.js +0 -826
  203. package/dist/quantum.d.ts +0 -809
  204. package/dist/quantum.js +0 -2072
  205. package/dist/render.d.ts +0 -380
  206. package/dist/render.js +0 -1072
  207. package/dist/runall.d.ts +0 -2
  208. package/dist/runall.js +0 -39
  209. package/dist/runtimeprobe.d.ts +0 -4
  210. package/dist/runtimeprobe.js +0 -8
  211. package/dist/sandbox.d.ts +0 -355
  212. package/dist/sandbox.js +0 -1721
  213. package/dist/sandboxenvprofile.d.ts +0 -29
  214. package/dist/sandboxenvprofile.js +0 -89
  215. package/dist/sandboxprofile.d.ts +0 -137
  216. package/dist/sandboxprofile.js +0 -241
  217. package/dist/sandboxqueue.d.ts +0 -108
  218. package/dist/sandboxqueue.js +0 -201
  219. package/dist/sandboxquota.d.ts +0 -121
  220. package/dist/sandboxquota.js +0 -236
  221. package/dist/scheduler.d.ts +0 -595
  222. package/dist/scheduler.js +0 -1258
  223. package/dist/scriptlanes.d.ts +0 -182
  224. package/dist/scriptlanes.js +0 -660
  225. package/dist/scripts.buildengine.d.ts +0 -1
  226. package/dist/scripts.buildengine.js +0 -39
  227. package/dist/scripts.securitygate.d.ts +0 -1
  228. package/dist/scripts.securitygate.js +0 -68
  229. package/dist/scripts.validateflatnative.d.ts +0 -1
  230. package/dist/scripts.validateflatnative.js +0 -72
  231. package/dist/security.d.ts +0 -500
  232. package/dist/security.js +0 -1169
  233. package/dist/seedcatalog.d.ts +0 -13
  234. package/dist/seedcatalog.js +0 -46
  235. package/dist/server.d.ts +0 -20
  236. package/dist/server.js +0 -53
  237. package/dist/speccatalog.d.ts +0 -31
  238. package/dist/speccatalog.js +0 -163
  239. package/dist/tiers.d.ts +0 -1001
  240. package/dist/tiers.js +0 -2169
  241. package/dist/utils.d.ts +0 -2
  242. package/dist/utils.js +0 -5
  243. package/dist/vaulthandoff.d.ts +0 -141
  244. package/dist/vaulthandoff.js +0 -444
  245. package/dist/virtual.d.ts +0 -640
  246. package/dist/virtual.js +0 -1284
  247. package/dist/virtualcpu.d.ts +0 -315
  248. package/dist/virtualcpu.js +0 -1180
  249. package/dist/virtualgpu.d.ts +0 -344
  250. package/dist/virtualgpu.js +0 -1000
  251. package/dist/virtualization.d.ts +0 -606
  252. package/dist/virtualization.js +0 -1186
  253. package/dist/virtualmemory.d.ts +0 -817
  254. package/dist/virtualmemory.js +0 -1795
  255. package/dist/vite.config.d.ts +0 -3
  256. package/dist/vite.config.js +0 -43
  257. package/dist/vitest.config.d.ts +0 -2
  258. package/dist/vitest.config.js +0 -25
  259. package/dist/webserver.d.ts +0 -166
  260. package/dist/webserver.js +0 -2226
  261. package/dist/workloadlaw.d.ts +0 -133
  262. package/dist/workloadlaw.js +0 -193
  263. package/manifest.json +0 -20
  264. package/media.ts +0 -1337
  265. /package/docs/{architecture.1.8.19.md → architecture1819.md} +0 -0
  266. /package/docs/{architecture.1.8.19.mmd → architecture1819.mmd} +0 -0
  267. /package/docs/{artifactresearch.1.8.9.md → artifactresearch189.md} +0 -0
  268. /package/docs/{batches.1816.to.1820.md → batches1816to1820.md} +0 -0
  269. /package/docs/{brancharchive.2026.08.18.md → brancharchive20260818.md} +0 -0
  270. /package/docs/{containerplatforms.1.8.17.md → containerplatforms1817.md} +0 -0
  271. /package/docs/{e2ugh.engine.md → e2ughengine.md} +0 -0
  272. /package/docs/{flatbuildresearch.1.8.11.md → flatbuildresearch1811.md} +0 -0
  273. /package/docs/{licenseaudit.1.8.12.md → licenseaudit1812.md} +0 -0
  274. /package/docs/{mobileconversionresearch.1.8.10.md → mobileconversionresearch1810.md} +0 -0
  275. /package/docs/{nativeidentityresearch.1.8.11.md → nativeidentityresearch1811.md} +0 -0
  276. /package/docs/{native.wrappers.md → nativewrappers.md} +0 -0
  277. /package/docs/{planning.1.8.18.md → planning1818.md} +0 -0
  278. /package/docs/{planning.1.8.19.md → planning1819.md} +0 -0
  279. /package/docs/{planning.engine.md → planningengine.md} +0 -0
  280. /package/docs/{releasenotes.1.8.12.md → releasenotes1812.md} +0 -0
  281. /package/docs/{releasenotes.1.8.15.md → releasenotes1815.md} +0 -0
  282. /package/docs/{releasenotes.1.8.16.md → releasenotes1816.md} +0 -0
  283. /package/docs/{releasenotes.1.8.17.md → releasenotes1817.md} +0 -0
  284. /package/docs/{releasenotes.1.8.18.md → releasenotes1818.md} +0 -0
  285. /package/docs/{releasenotes.1.8.19.md → releasenotes1819.md} +0 -0
  286. /package/docs/{reorganization.1.8.8.md → reorganization188.md} +0 -0
  287. /package/docs/{reorganization.1.8.9.md → reorganization189.md} +0 -0
  288. /package/docs/{repo.analysis.1.8.21.json → repoanalysis1821.json} +0 -0
  289. /package/docs/{repo.candidates.1.8.21.jsonl → repocandidates1821.jsonl} +0 -0
  290. /package/docs/{repo.research.1.8.16.md → reporesearch1816.md} +0 -0
  291. /package/docs/{repo.research.1.8.21.md → reporesearch1821.md} +0 -0
  292. /package/docs/{repo.selected.1.8.21.json → reposelected1821.json} +0 -0
  293. /package/docs/{repo.synthesis.1.8.21.md → reposynthesis1821.md} +0 -0
  294. /package/docs/{research.1.8.18.isolation.md → research1818isolation.md} +0 -0
  295. /package/docs/{research.1.8.19.virtual.browser.md → research1819virtualbrowser.md} +0 -0
  296. /package/docs/{scanningresearch.1.8.11.md → scanningresearch1811.md} +0 -0
  297. /package/docs/{securityaudit.1.8.7.md → securityaudit187.md} +0 -0
  298. /package/docs/{securityreferences.1.8.11.md → securityreferences1811.md} +0 -0
  299. /package/docs/{securityresearch.1.8.11.md → securityresearch1811.md} +0 -0
  300. /package/docs/{signingresearch.1.8.12.md → signingresearch1812.md} +0 -0
  301. /package/docs/{tauri.glib.risk.1.8.11.md → tauriglibrisk1811.md} +0 -0
  302. /package/docs/{todo.1.8.18.md → todo1818.md} +0 -0
  303. /package/docs/{todo.1.8.19.md → todo1819.md} +0 -0
  304. /package/docs/{toolchainresearch.1.8.9.md → toolchainresearch189.md} +0 -0
  305. /package/docs/{web.duplicatecleanup.md → webduplicatecleanup.md} +0 -0
  306. /package/docs/{web.ideas.md → webideas.md} +0 -0
  307. /package/docs/{workflowimprovements.2026.08.19.md → workflowimprovements20260819.md} +0 -0
package/dist/sandbox.js DELETED
@@ -1,1721 +0,0 @@
1
- /**
2
- * sandbox.ts — browser-pure port of the saddle virtual hardware engine.
3
- *
4
- * this module is a lightweight rewrite of the engine generators
5
- * (virtualcpu.ts, virtualmemory.ts, virtualgpu.ts and render.ts) that runs
6
- * unchanged in the browser and in node: it imports nothing, touches no
7
- * node:* builtin and no dom api at module scope, and produces the exact
8
- * same procfs payloads, the 89-character nvidia-smi summary table, the
9
- * mesa software-stack summaries (rusticl opencl 3.1, lavapipe vulkan 1.4,
10
- * llvmpipe opengl 4.6) and the boot dmesg sequence the native engine emits.
11
- *
12
- * contexts (22): cpudata, gpudata, migprofiles, vendortables, topology,
13
- * cpuinfo, lscpu, pagealign, meminfo, freeh, smiformat, smitable, smilist,
14
- * clinfo, vulkaninfo, glxinfo, mesaenv, bootsequence, sandboxstate,
15
- * virtualfs, dispatcher, commands.
16
- *
17
- * rules: lowercase identifiers, english jsdoc in third person, no emoji,
18
- * zero dependencies, and deterministic fallbacks when crypto randomness
19
- * is unavailable.
20
- */
21
- /* ------------------------------------------------------------------ */
22
- /* context: shared helpers */
23
- /* ------------------------------------------------------------------ */
24
- /** inclusive pseudo-random integer between min and max; deterministic
25
- * fallback when crypto randomness is unavailable. */
26
- function rnd(min, max) {
27
- try {
28
- const span = max - min + 1;
29
- if (typeof globalThis.crypto?.getRandomValues === 'function') {
30
- const buf = new Uint32Array(1);
31
- globalThis.crypto.getRandomValues(buf);
32
- return min + (buf[0] % span);
33
- }
34
- }
35
- catch {
36
- /* falls through to math.random */
37
- }
38
- return min + Math.floor(Math.random() * (max - min + 1));
39
- }
40
- /** right-pad helper that never exceeds the target width. */
41
- function pad(text, width) {
42
- return text.length >= width ? text.slice(0, width) : text + ' '.repeat(width - text.length);
43
- }
44
- /** left-pad helper for right-aligned columns. */
45
- function padstart(text, width) {
46
- return text.length >= width
47
- ? text.slice(text.length - width)
48
- : ' '.repeat(width - text.length) + text;
49
- }
50
- /**
51
- * formats one byte count with binary units (512 B, 4 KiB, 16 MiB, 1.5 GiB);
52
- * whole numbers drop the decimal so the default quota reads "16 MiB".
53
- *
54
- * @param {number} bytes the byte count.
55
- * @returns {string} the human readable size.
56
- */
57
- function humanbytes(bytes) {
58
- const value = Math.max(0, Math.round(bytes));
59
- const unit = (size, suffix) => {
60
- const scaled = value / size;
61
- const rounded = Math.round(scaled * 10) / 10;
62
- return `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)} ${suffix}`;
63
- };
64
- if (value >= 1024 * 1024 * 1024) {
65
- return unit(1024 * 1024 * 1024, 'GiB');
66
- }
67
- if (value >= 1024 * 1024) {
68
- return unit(1024 * 1024, 'MiB');
69
- }
70
- if (value >= 1024) {
71
- return unit(1024, 'KiB');
72
- }
73
- return `${value} B`;
74
- }
75
- /** lowercases a model string for case-insensitive lookups. */
76
- function normalizemodel(model) {
77
- return String(model ?? '').trim().toLowerCase();
78
- }
79
- /** generates a random uuid-shaped hex token with a prefix. */
80
- function token(prefix) {
81
- const raw = typeof globalThis.crypto?.randomUUID === 'function'
82
- ? globalThis.crypto.randomUUID().replace(/-/g, '')
83
- : Array.from({ length: 32 }, () => '0123456789abcdef'[rnd(0, 15)]).join('');
84
- return `${prefix}${raw.slice(0, 8)}-${raw.slice(8, 12)}-${raw.slice(12, 16)}-${raw.slice(16, 20)}-${raw.slice(20, 32)}`;
85
- }
86
- /* ------------------------------------------------------------------ */
87
- /* context: cpudata — the processor bank (specs verified 2026-08-22) */
88
- /* ------------------------------------------------------------------ */
89
- /**
90
- * cpudata carries the six engine-bank processors (amd epyc 9965, ryzen 9
91
- * 9950x3d, threadripper pro 9995wx, threadripper 7980x, core ultra 9
92
- * 285k, epyc 9955) plus the two web showcase additions intel xeon 6980p
93
- * granite rapids and apple m3 ultra. every field mirrors the vendor pages
94
- * and the techpowerup database as pinned by best_virtual_processors.
95
- */
96
- export const cpudata = [
97
- {
98
- model: 'AMD EPYC 9965',
99
- displayname: 'AMD EPYC 9965 192-Core Processor',
100
- vendor: 'amd',
101
- arch: 'x86_64',
102
- cores: 192,
103
- threads: 384,
104
- baseclockmhz: 2250,
105
- boostclockmhz: 3700,
106
- allcoreclockmhz: 3350,
107
- l3mb: 384,
108
- l2mb: 192,
109
- socket: 'SP5 (LGA6096)',
110
- tdpwatts: 500,
111
- pcie: 'PCIe 5.0 x128',
112
- maxmemorygb: 6144,
113
- memorytype: 'DDR5-6400 ECC RDIMM',
114
- memorychannels: 12,
115
- microarch: 'Zen 5c (Turin)',
116
- cpufamily: 25,
117
- cpumodel: 17,
118
- stepping: 2,
119
- physicalline: '52 bits physical',
120
- virtualline: '57 bits virtual',
121
- launch: '2024-10-10',
122
- },
123
- {
124
- model: 'AMD Ryzen 9 9950X3D',
125
- displayname: 'AMD Ryzen 9 9950X3D 16-Core Processor',
126
- vendor: 'amd',
127
- arch: 'x86_64',
128
- cores: 16,
129
- threads: 32,
130
- baseclockmhz: 4300,
131
- boostclockmhz: 5700,
132
- allcoreclockmhz: 5400,
133
- l3mb: 128,
134
- l2mb: 16,
135
- socket: 'AM5',
136
- tdpwatts: 170,
137
- pcie: 'PCIe 5.0 x24',
138
- maxmemorygb: 192,
139
- memorytype: 'DDR5-5600',
140
- memorychannels: 2,
141
- microarch: 'Zen 5 X3D (3D V-Cache on one CCD)',
142
- cpufamily: 25,
143
- cpumodel: 17,
144
- stepping: 2,
145
- physicalline: '48 bits physical',
146
- virtualline: '57 bits virtual',
147
- launch: '2025-03-12',
148
- },
149
- {
150
- model: 'AMD Threadripper PRO 9995WX',
151
- displayname: 'AMD Ryzen Threadripper PRO 9995WX 96-Core Processor',
152
- vendor: 'amd',
153
- arch: 'x86_64',
154
- cores: 96,
155
- threads: 192,
156
- baseclockmhz: 2500,
157
- boostclockmhz: 5400,
158
- allcoreclockmhz: 4100,
159
- l3mb: 384,
160
- l2mb: 96,
161
- socket: 'sTR5 (LGA4844)',
162
- tdpwatts: 350,
163
- pcie: 'PCIe 5.0 x128',
164
- maxmemorygb: 2048,
165
- memorytype: 'DDR5-6400 ECC RDIMM',
166
- memorychannels: 8,
167
- microarch: 'Zen 5 (Shimada Peak, WRX90)',
168
- cpufamily: 25,
169
- cpumodel: 17,
170
- stepping: 2,
171
- physicalline: '52 bits physical',
172
- virtualline: '57 bits virtual',
173
- launch: '2025-07',
174
- },
175
- {
176
- model: 'AMD Threadripper 7980X',
177
- displayname: 'AMD Ryzen Threadripper 7980X 64-Core Processor',
178
- vendor: 'amd',
179
- arch: 'x86_64',
180
- cores: 64,
181
- threads: 128,
182
- baseclockmhz: 3200,
183
- boostclockmhz: 5100,
184
- allcoreclockmhz: 4400,
185
- l3mb: 256,
186
- l2mb: 64,
187
- socket: 'sTR5',
188
- tdpwatts: 350,
189
- pcie: 'PCIe 5.0 x48',
190
- maxmemorygb: 1024,
191
- memorytype: 'DDR5-6400',
192
- memorychannels: 4,
193
- microarch: 'Zen 5 (TRX50)',
194
- cpufamily: 25,
195
- cpumodel: 17,
196
- stepping: 2,
197
- physicalline: '52 bits physical',
198
- virtualline: '57 bits virtual',
199
- launch: '2023-10',
200
- },
201
- {
202
- model: 'Intel Core Ultra 9 285K',
203
- displayname: 'Intel(R) Core(TM) Ultra 9 285K',
204
- vendor: 'intel',
205
- arch: 'x86_64',
206
- cores: 24,
207
- threads: 24,
208
- baseclockmhz: 3700,
209
- boostclockmhz: 5700,
210
- allcoreclockmhz: 5400,
211
- l3mb: 36,
212
- l2mb: 36,
213
- socket: 'LGA1851',
214
- tdpwatts: 125,
215
- pcie: 'PCIe 5.0 x20 + PCIe 4.0 x4',
216
- maxmemorygb: 192,
217
- memorytype: 'DDR5-6400',
218
- memorychannels: 2,
219
- microarch: 'Arrow Lake (8P + 16E, no hyper-threading)',
220
- cpufamily: 6,
221
- cpumodel: 191,
222
- stepping: 2,
223
- physicalline: '46 bits physical',
224
- virtualline: '57 bits virtual',
225
- launch: '2024-10-24',
226
- },
227
- {
228
- model: 'AMD EPYC 9955',
229
- displayname: 'AMD EPYC 9955 128-Core Processor',
230
- vendor: 'amd',
231
- arch: 'x86_64',
232
- cores: 128,
233
- threads: 256,
234
- baseclockmhz: 2600,
235
- boostclockmhz: 3750,
236
- allcoreclockmhz: 3450,
237
- l3mb: 384,
238
- l2mb: 128,
239
- socket: 'SP5',
240
- tdpwatts: 400,
241
- pcie: 'PCIe 5.0 x128',
242
- maxmemorygb: 6144,
243
- memorytype: 'DDR5-6400 ECC RDIMM',
244
- memorychannels: 12,
245
- microarch: 'Zen 5c (Turin dense)',
246
- cpufamily: 25,
247
- cpumodel: 17,
248
- stepping: 2,
249
- physicalline: '52 bits physical',
250
- virtualline: '57 bits virtual',
251
- launch: '2024-10-10',
252
- },
253
- {
254
- model: 'Intel Xeon 6980P',
255
- displayname: 'Intel(R) Xeon(R) Platinum 6980P',
256
- vendor: 'intel',
257
- arch: 'x86_64',
258
- cores: 128,
259
- threads: 256,
260
- baseclockmhz: 2200,
261
- boostclockmhz: 3900,
262
- allcoreclockmhz: 3300,
263
- l3mb: 504,
264
- l2mb: 128,
265
- socket: 'LGA4710',
266
- tdpwatts: 350,
267
- pcie: 'PCIe 5.0 x128 + CXL 2.0 x16',
268
- maxmemorygb: 6144,
269
- memorytype: 'DDR5-6400 / MRDIMM-8800',
270
- memorychannels: 12,
271
- microarch: 'Granite Rapids (Intel 3)',
272
- cpufamily: 6,
273
- cpumodel: 173,
274
- stepping: 2,
275
- physicalline: '46 bits physical',
276
- virtualline: '57 bits virtual',
277
- launch: '2025-02',
278
- },
279
- {
280
- model: 'Apple M3 Ultra',
281
- displayname: 'Apple M3 Ultra (24P + 8E, 32-core CPU)',
282
- vendor: 'apple',
283
- arch: 'arm64',
284
- cores: 32,
285
- threads: 32,
286
- baseclockmhz: 1000,
287
- boostclockmhz: 4050,
288
- allcoreclockmhz: 3400,
289
- l3mb: 96,
290
- l2mb: 96,
291
- socket: 'soldered (UltraFusion interconnect)',
292
- tdpwatts: 140,
293
- pcie: 'PCIe 4.0 x8 + Thunderbolt 5 x2',
294
- maxmemorygb: 512,
295
- memorytype: 'LPDDR5 unified 819 GB/s',
296
- memorychannels: 16,
297
- microarch: 'Everest (3nm, two M3 Max dies fused)',
298
- cpufamily: 0,
299
- cpumodel: 0,
300
- stepping: 0,
301
- physicalline: '44 bits physical',
302
- virtualline: '48 bits virtual',
303
- launch: '2025-03',
304
- },
305
- ];
306
- /** resolves a processor spec by model name, case-insensitive; returns
307
- * undefined for unknown models so callers can shape friendly errors. */
308
- export function getcpu(model) {
309
- const needle = normalizemodel(model);
310
- return cpudata.find((spec) => normalizemodel(spec.model) === needle);
311
- }
312
- /**
313
- * resolves a catalog entry by its slug id (epyc-9965) as served by the
314
- * /api/v1/specs/cpus endpoint; returns undefined for unknown ids.
315
- * @param {string} id the catalog id.
316
- * @returns {object | undefined} the matching cpu entry.
317
- */
318
- export function getcpubyid(id) {
319
- const key = String(id).toLowerCase().replace(/\s+/g, '-');
320
- const strip = (name) => name
321
- .toLowerCase()
322
- .replace(/\s+/g, '-')
323
- .replace(/^(amd|intel|apple|nvidia)-/, '');
324
- return cpudata.find((entry) => {
325
- const slug = entry.model.toLowerCase().replace(/\s+/g, '-');
326
- return slug === key || slug.endsWith(key) || strip(entry.model) === strip(key);
327
- });
328
- }
329
- /* ------------------------------------------------------------------ */
330
- /* context: vendortables — flag sets and identity strings */
331
- /* ------------------------------------------------------------------ */
332
- /** zen 5 flag set exactly as generatevirtualcpuinfo renders it (avx-512
333
- * family, sha_ni, gfni and friends). */
334
- const zen5flags = [
335
- 'fpu', 'vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce', 'cx8', 'apic',
336
- 'sep', 'mtrr', 'pge', 'mca', 'cmov', 'pat', 'pse36', 'clflush', 'mmx',
337
- 'fxsr', 'sse', 'sse2', 'ht', 'syscall', 'nx', 'mmxext', 'fxsr_opt',
338
- 'pdpe1gb', 'rdtscp', 'lm', 'constant_tsc', 'rep_good', 'amd_lbr_v2',
339
- 'nopl', 'nonstop_tsc', 'cpuid', 'extd_apicid', 'aperfmperf', 'rapl',
340
- 'pclmulqdq', 'monitor', 'ssse3', 'fma', 'cx16', 'sse4_1', 'sse4_2',
341
- 'x2apic', 'movbe', 'popcnt', 'aes', 'xsave', 'avx', 'f16c', 'rdrand',
342
- 'lahf_lm', 'cmp_legacy', 'svm', 'extapic', 'cr8_legacy', 'abm', 'sse4a',
343
- 'misalignsse', '3dnowprefetch', 'osvw', 'ibs', 'skinit', 'wdt', 'tce',
344
- 'topoext', 'perfctr_core', 'perfctr_nb', 'bpext', 'perfctr_llc',
345
- 'mwaitx', 'cpb', 'cat_l3', 'cdp_l3', 'hw_pstate', 'ssbd', 'mba',
346
- 'perfmon_v2', 'ibrs', 'ibpb', 'stibp', 'vmmcall', 'fsgsbase', 'bmi1',
347
- 'avx2', 'smep', 'bmi2', 'erms', 'invpcid', 'cqm', 'rdt_a', 'avx512f',
348
- 'avx512dq', 'rdseed', 'adx', 'smap', 'clflushopt', 'clwb', 'avx512vl',
349
- 'avx512bw', 'avx512cd', 'avx512_bf16', 'avx512vbmi', 'avx512vbmi2',
350
- 'avx512ifma', 'avx512vp2intersect', 'sha_ni', 'gfni', 'vaes',
351
- 'vpclmulqdq', 'avx512vpopcntdq', 'avx512vnni', 'avx512bitalg', 'rdpid',
352
- 'movdiri', 'movdir64b', 'fsrm', 'cldemote', 'serialize', 'flush_l1d',
353
- 'arch_capabilities',
354
- ];
355
- /** arrow lake / granite rapids flag set: avx2 with avx_vnni and no avx-512
356
- * execution units; the ht flag is appended per model because arrow lake
357
- * ships without hyper-threading while xeon 6 keeps it. */
358
- const intelflags = [
359
- 'fpu', 'vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce', 'cx8', 'apic',
360
- 'sep', 'mtrr', 'pge', 'mca', 'cmov', 'pat', 'pse36', 'clflush', 'mmx',
361
- 'fxsr', 'sse', 'sse2', 'syscall', 'nx', 'pdpe1gb', 'rdtscp', 'lm',
362
- 'constant_tsc', 'arch_perfmon', 'rep_good', 'nopl', 'xtopology',
363
- 'nonstop_tsc', 'cpuid', 'aperfmperf', 'tsc_known_freq', 'pclmulqdq',
364
- 'monitor', 'ssse3', 'fma', 'cx16', 'sse4_1', 'sse4_2', 'x2apic', 'movbe',
365
- 'popcnt', 'aes', 'xsave', 'avx', 'f16c', 'rdrand', 'lahf_lm', 'abm',
366
- '3dnowprefetch', 'cpuid_fault', 'epb', 'invpcid_single', 'ssbd', 'ibrs',
367
- 'ibpb', 'stibp', 'ibrs_enhanced', 'tpr_shadow', 'vnmi', 'flexpriority',
368
- 'ept', 'vpid', 'ept_ad', 'fsgsbase', 'tsc_adjust', 'bmi1', 'avx2',
369
- 'smep', 'bmi2', 'erms', 'invpcid', 'rdseed', 'adx', 'smap',
370
- 'clflushopt', 'clwb', 'sha_ni', 'xsaveopt', 'xsavec', 'xgetbv1',
371
- 'xsaves', 'split_lock_detect', 'user_shstk', 'avx_vnni', 'waitpkg',
372
- 'gfni', 'vaes', 'vpclmulqdq', 'rdpid', 'movdiri', 'movdir64b', 'fsrm',
373
- 'md_clear', 'serialize', 'tsxldtrk', 'la57', 'cldemote',
374
- 'arch_capabilities',
375
- ];
376
- /** arm64 feature line rendered for apple silicon cpuinfo blocks. */
377
- const armfeatures = [
378
- 'fp', 'asimd', 'aes', 'pmull', 'sha1', 'sha2', 'sha3', 'sha512', 'crc32',
379
- 'atomics', 'fphp', 'asimdhp', 'cpuid', 'asimdrdm', 'jscvt', 'fcma',
380
- 'lrcpc', 'dcpop', 'sm3', 'sm4', 'asimddp', 'uscat', 'ilrcpc', 'flagm',
381
- 'ssbs', 'sb', 'paca', 'pacg', 'dcpodp', 'flagm2', 'frint', 'ecs', 'wfxt',
382
- 'bti',
383
- ];
384
- /** vendor identity table consumed by cpuinfo and lscpu. */
385
- const vendortables = {
386
- amd: {
387
- vendorid: 'AuthenticAMD',
388
- flags: zen5flags,
389
- bugs: 'sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass srso',
390
- tlbsize: '3584 2M/4M pages',
391
- cpuidlevel: 16,
392
- powermanagement: 'ts ttp tm hwpstate cpb eff_freq_ro [13] [14]',
393
- },
394
- intel: {
395
- vendorid: 'GenuineIntel',
396
- flags: intelflags,
397
- bugs: 'spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb',
398
- tlbsize: null,
399
- cpuidlevel: 32,
400
- powermanagement: 'ts ttp tm hwpstate cpb eff_freq_ro [8] [9] [10] [11] [12] [13] [14]',
401
- },
402
- apple: {
403
- vendorid: 'Apple',
404
- flags: armfeatures,
405
- bugs: '',
406
- tlbsize: null,
407
- cpuidlevel: 0,
408
- powermanagement: '',
409
- },
410
- };
411
- /* ------------------------------------------------------------------ */
412
- /* context: topology */
413
- /* ------------------------------------------------------------------ */
414
- /**
415
- * solves the package topology for a vcpu count against a spec; mirrors
416
- * solvetopology from virtualcpu.ts (smt parts round core counts up,
417
- * heterogeneous parts report one thread per core and the count is clamped
418
- * to the model thread ceiling).
419
- */
420
- export function solvetopology(spec, vcpus) {
421
- const threadspercore = Math.max(1, Math.round(spec.threads / spec.cores));
422
- const count = Math.max(1, Math.min(Math.round(vcpus) || 1, spec.threads));
423
- return {
424
- vcpus: count,
425
- threadspercore,
426
- coresonline: Math.ceil(count / threadspercore),
427
- siblings: count,
428
- sockets: 1,
429
- fullypopulated: count === spec.threads,
430
- };
431
- }
432
- /* ------------------------------------------------------------------ */
433
- /* context: cpuinfo — /proc/cpuinfo generation */
434
- /* ------------------------------------------------------------------ */
435
- /** per-key tab alignment the kernel uses on x86 cpuinfo lines. */
436
- const keytabs = {
437
- processor: '\t', vendor_id: '\t', 'cpu family': '\t', model: '\t\t',
438
- 'model name': '\t', stepping: '\t', microcode: '\t', 'cpu MHz': '\t\t',
439
- 'cache size': '\t', 'physical id': '\t', siblings: '\t', 'core id': '\t\t',
440
- 'cpu cores': '\t', apicid: '\t\t', 'initial apicid': '\t', fpu: '\t\t',
441
- fpu_exception: '\t', 'cpuid level': '\t', wp: '\t\t', flags: '\t\t',
442
- bugs: '\t\t', bogomips: '\t', 'TLB size': '\t', 'clflush size': '\t',
443
- cache_alignment: '', 'address sizes': '\t', 'power management': '',
444
- };
445
- /** renders one cpuinfo line exactly as procfs does: key, tabs, colon, value. */
446
- function line(key, value) {
447
- const tabs = keytabs[key] ?? '\t';
448
- return `${key}${tabs}: ${value}`;
449
- }
450
- /**
451
- * generates a complete /proc/cpuinfo text for a bank model with an
452
- * arbitrary vcpu count; x86_64 models render the full zen 5 / intel flag
453
- * set per logical processor and the apple model renders the arm64 cpuinfo
454
- * block shape instead.
455
- *
456
- * @param {string} model model name present in cpudata.
457
- * @param {number} vcpus number of logical processors to render.
458
- * @returns {string} the full procfs payload.
459
- */
460
- export function cpuinfo(model, vcpus) {
461
- const spec = getcpu(model);
462
- if (spec === undefined) {
463
- throw new Error(`unknown processor model "${model}"; known models: ${cpudata.map((entry) => entry.model).join(', ')}`);
464
- }
465
- const topology = solvetopology(spec, vcpus);
466
- const table = vendortables[spec.vendor];
467
- const blocks = [];
468
- for (let processor = 0; processor < topology.vcpus; processor += 1) {
469
- if (spec.vendor === 'apple') {
470
- blocks.push([
471
- `processor\t: ${processor}`,
472
- 'BogoMIPS\t: 48.00',
473
- `Features\t: ${table.flags.join(' ')}`,
474
- 'CPU implementer\t: 0x41',
475
- 'CPU architecture: 8',
476
- 'CPU variant\t: 0x2',
477
- 'CPU part\t: 0x612',
478
- `CPU revision\t: ${processor % 2 === 0 ? 0 : 3}`,
479
- '',
480
- `MIDR_EL1\t: 0x0000000041026${processor % 2 === 0 ? '00' : '03'}`,
481
- 'Revidr_EL1\t: 0x00000000',
482
- `CPU affinity\t: ${processor}`,
483
- ].join('\n'));
484
- continue;
485
- }
486
- const currentmhz = rnd(Math.round(spec.baseclockmhz), Math.round(spec.boostclockmhz) + 1);
487
- const bogomips = (spec.baseclockmhz * 2 + rnd(-40, 41) / 100).toFixed(2);
488
- const coreid = Math.floor(processor / topology.threadspercore);
489
- const flags = spec.vendor === 'intel' && topology.threadspercore === 2
490
- ? `${table.flags.join(' ')} ht`
491
- : table.flags.join(' ');
492
- const rows = [
493
- line('processor', processor),
494
- line('vendor_id', table.vendorid),
495
- line('cpu family', spec.cpufamily),
496
- line('model', spec.cpumodel),
497
- line('model name', spec.displayname),
498
- line('stepping', spec.stepping),
499
- line('microcode', '0xffffffff'),
500
- line('cpu MHz', currentmhz.toFixed(3)),
501
- line('cache size', `${spec.l3mb} MB`),
502
- line('physical id', 0),
503
- line('siblings', topology.siblings),
504
- line('core id', coreid),
505
- line('cpu cores', topology.coresonline),
506
- line('apicid', processor),
507
- line('initial apicid', processor),
508
- line('fpu', 'yes'),
509
- line('fpu_exception', 'yes'),
510
- line('cpuid level', table.cpuidlevel),
511
- line('wp', 'yes'),
512
- line('flags', flags),
513
- line('bugs', table.bugs),
514
- line('bogomips', bogomips),
515
- ];
516
- if (table.tlbsize !== null) {
517
- rows.push(line('TLB size', table.tlbsize));
518
- }
519
- rows.push(line('clflush size', 64), line('cache_alignment', 64), line('address sizes', `${spec.physicalline}, ${spec.virtualline}`), line('power management', table.powermanagement));
520
- blocks.push(rows.join('\n'));
521
- }
522
- return blocks.join('\n\n');
523
- }
524
- /**
525
- * generates the util-linux lscpu summary for a bank model with numa node
526
- * ranges and vulnerability lines, mirroring generatevirtuallscpu.
527
- *
528
- * @param {string} model model name present in cpudata.
529
- * @param {number} vcpus logical processor count.
530
- * @param {number} [numanodes=1] number of numa nodes to spread over.
531
- * @returns {string} the lscpu text block.
532
- */
533
- export function lscpu(model, vcpus, numanodes = 1) {
534
- const spec = getcpu(model);
535
- if (spec === undefined) {
536
- throw new Error(`unknown processor model "${model}"`);
537
- }
538
- const topology = solvetopology(spec, vcpus);
539
- const corespersocket = Math.ceil(topology.coresonline / numanodes) * numanodes;
540
- const rows = [
541
- ['Architecture:', spec.arch === 'arm64' ? 'aarch64' : 'x86_64'],
542
- ['CPU op-mode(s):', '32-bit, 64-bit'],
543
- ['Byte Order:', 'Little Endian'],
544
- ['Address sizes:', `${spec.physicalline}, ${spec.virtualline}`],
545
- ['CPU(s):', String(topology.vcpus)],
546
- ['On-line CPU(s) list:', topology.vcpus === 1 ? '0' : `0-${topology.vcpus - 1}`],
547
- ];
548
- if (spec.vendor === 'apple') {
549
- rows.push(['Vendor ID:', 'Apple'], ['Model name:', spec.displayname], ['Thread(s) per core:', '1'], ['Core(s) per socket:', String(topology.coresonline)], ['Socket(s):', '1'], ['Cluster(s)-wide:', '24 performance + 8 efficiency cores'], ['Frequency boost:', 'enabled'], ['CPU max MHz:', `${spec.boostclockmhz}.0000`], ['CPU min MHz:', '600.0000']);
550
- }
551
- else {
552
- rows.push(['Vendor ID:', vendortables[spec.vendor].vendorid], ['Model name:', spec.displayname], ['CPU family:', String(spec.cpufamily)], ['Model:', String(spec.cpumodel)], ['Thread(s) per core:', String(topology.threadspercore)], ['Core(s) per socket:', String(corespersocket)], ['Socket(s):', '1'], ['Stepping:', String(spec.stepping)], ['Frequency boost:', 'enabled'], ['CPU max MHz:', `${spec.boostclockmhz}.0000`], ['CPU min MHz:', `${(spec.baseclockmhz * 0.62).toFixed(4)}`], ['BogoMIPS:', `${(spec.baseclockmhz * 2).toFixed(2)}`], [
553
- 'L1d cache:',
554
- `${Math.round((spec.l2mb / spec.cores) * 1024)} KiB (${spec.cores} instances)`,
555
- ], ['L2 cache:', `${spec.l2mb} MiB (${spec.cores} instances)`], ['L3 cache:', `${spec.l3mb} MiB (${Math.max(1, Math.round(spec.cores / 8))} instances)`]);
556
- }
557
- rows.push(['NUMA node(s):', String(numanodes)]);
558
- const pernode = Math.ceil(topology.vcpus / numanodes);
559
- for (let node = 0; node < numanodes; node += 1) {
560
- const start = node * pernode;
561
- const end = Math.min(start + pernode, topology.vcpus) - 1;
562
- rows.push([`NUMA node${node} CPU(s):`, start === end ? String(start) : `${start}-${end}`]);
563
- }
564
- if (spec.vendor !== 'apple') {
565
- rows.push(['Vulnerability Itlb multihit:', 'Not affected'], ['Vulnerability L1tf:', 'Not affected'], ['Vulnerability Mds:', 'Not affected'], ['Vulnerability Meltdown:', 'Not affected'], [
566
- 'Vulnerability Spectre v1:',
567
- 'Mitigation; usercopy/swapgs barriers and __user pointer sanitization',
568
- ], [
569
- 'Vulnerability Spectre v2:',
570
- spec.vendor === 'amd' ? 'Mitigation; Retpolines, IBPB' : 'Mitigation; Enhanced IBRS',
571
- ]);
572
- }
573
- return rows.map(([key, value]) => `${key.padEnd(28)}${value}`).join('\n');
574
- }
575
- /* ------------------------------------------------------------------ */
576
- /* context: gpudata and migprofiles */
577
- /* ------------------------------------------------------------------ */
578
- /**
579
- * gpudata mirrors the verified gpu_bank: seven real identities with
580
- * vrammib (physical capacity) and smireportedmib (the value after the
581
- * firmware carveout) cross-checked on 2026-08-22.
582
- */
583
- export const gpudata = [
584
- {
585
- id: 'rtx5090', name: 'NVIDIA GeForce RTX 5090', vendor: 'nvidia',
586
- pcivendor: '10DE', pcidevice: '2B85', vrammib: 32768,
587
- smireportedmib: 32607, memtype: 'GDDR7', busbits: 512,
588
- bandwidthgbs: 1792, tdpwatts: 575, arch: 'GB202 Blackwell',
589
- smarch: 'sm_120', smcount: 170, mig: false, driver: '575.57.08',
590
- cuda: '12.9',
591
- },
592
- {
593
- id: 'rtxpro6000', name: 'NVIDIA RTX PRO 6000 Blackwell', vendor: 'nvidia',
594
- pcivendor: '10DE', pcidevice: '26B5', vrammib: 98304,
595
- smireportedmib: 98304, memtype: 'GDDR7 ECC', busbits: 512,
596
- bandwidthgbs: 1792, tdpwatts: 600, arch: 'GB202 Blackwell workstation',
597
- smarch: 'sm_120', smcount: 188, mig: true, driver: '575.57.08',
598
- cuda: '12.9',
599
- },
600
- {
601
- id: 'b200', name: 'NVIDIA B200', vendor: 'nvidia',
602
- pcivendor: '10DE', pcidevice: '2665', vrammib: 196608,
603
- smireportedmib: 184320, memtype: 'HBM3e', busbits: 8192,
604
- bandwidthgbs: 8000, tdpwatts: 1000, arch: 'GB100 Blackwell dual-die',
605
- smarch: 'sm_100', smcount: 208, mig: true, driver: '575.57.08',
606
- cuda: '12.9',
607
- },
608
- {
609
- id: 'h100', name: 'NVIDIA H100 80GB HBM3', vendor: 'nvidia',
610
- pcivendor: '10DE', pcidevice: '2330', vrammib: 81920,
611
- smireportedmib: 81559, memtype: 'HBM3', busbits: 6144,
612
- bandwidthgbs: 3350, tdpwatts: 700, arch: 'GH100 Hopper SXM5',
613
- smarch: 'sm_90', smcount: 132, mig: true, driver: '575.57.08',
614
- cuda: '12.9',
615
- },
616
- {
617
- id: 'a100', name: 'NVIDIA A100-SXM4-40GB', vendor: 'nvidia',
618
- pcivendor: '10DE', pcidevice: '20B0', vrammib: 40960,
619
- smireportedmib: 40536, memtype: 'HBM2e', busbits: 5120,
620
- bandwidthgbs: 1555, tdpwatts: 400, arch: 'GA100 Ampere SXM4',
621
- smarch: 'sm_80', smcount: 108, mig: true, driver: '575.57.08',
622
- cuda: '12.9',
623
- },
624
- {
625
- id: 'rx9070xt', name: 'AMD Radeon RX 9070 XT', vendor: 'amd',
626
- pcivendor: '1002', pcidevice: '748E', vrammib: 16384,
627
- smireportedmib: 16384, memtype: 'GDDR6', busbits: 256,
628
- bandwidthgbs: 640, tdpwatts: 304, arch: 'Navi 48 RDNA 4',
629
- smarch: 'gfx1201', smcount: 64, mig: false,
630
- driver: 'mesa 26.2.1 / rusticl', cuda: null,
631
- },
632
- {
633
- id: 'mi350x', name: 'AMD Instinct MI350X', vendor: 'amd',
634
- pcivendor: '1002', pcidevice: '75A0', vrammib: 294912,
635
- smireportedmib: 294912, memtype: 'HBM3e', busbits: 8192,
636
- bandwidthgbs: 8000, tdpwatts: 1000, arch: 'CDNA 4 Aqua Vanjaram',
637
- smarch: 'gfx950', smcount: 304, mig: false, driver: 'rocm 7.x',
638
- cuda: null,
639
- },
640
- ];
641
- /** resolves a gpu spec by id or name, case-insensitive. */
642
- export function getgpu(gpu) {
643
- const needle = normalizemodel(gpu);
644
- return gpudata.find((spec) => normalizemodel(spec.id) === needle || normalizemodel(spec.name) === needle);
645
- }
646
- /**
647
- * migprofiles mirrors the mig catalog of the 96 gb-class virtual device:
648
- * 1g.24gb exposes 24 gb slices (4 instances), 2g.48gb 48 gb slices (2
649
- * instances) and 4g.96gb dedicates the full device to one instance.
650
- */
651
- export const migprofiles = [
652
- { id: '1g.24gb', slicegb: 24, maxinstances: 4 },
653
- { id: '2g.48gb', slicegb: 48, maxinstances: 2 },
654
- { id: '4g.96gb', slicegb: 96, maxinstances: 1 },
655
- ];
656
- /** resolves a mig profile by id; null for 'off' and unknown names. */
657
- export function getmig(id) {
658
- if (id === 'off' || id === undefined || id === null || id === '') {
659
- return null;
660
- }
661
- return migprofiles.find((profile) => profile.id === id) ?? null;
662
- }
663
- /* ------------------------------------------------------------------ */
664
- /* context: meminfo and free — /proc/meminfo generation */
665
- /* ------------------------------------------------------------------ */
666
- /** rounds a kb value down to the nearest 4 kb page boundary. */
667
- export function pagealign(kb) {
668
- return Math.floor(kb / 4) * 4;
669
- }
670
- /**
671
- * generates a complete /proc/meminfo text for the requested total ram in
672
- * gb, mirroring generatevirtualmeminfo: memtotal, memfree, memavailable,
673
- * buffers, cached, the anon/file split, swap, slab, commit accounting
674
- * under vm.overcommit_memory=2 semantics and the direct map breakdown.
675
- *
676
- * @param {number} ramgb total guest ram in gb (positive number).
677
- * @param {{swapgb?: number, vcpus?: number, freefraction?: number,
678
- * availablefraction?: number}} [options] optional overrides.
679
- * @returns {string} the full meminfo payload.
680
- */
681
- export function meminfo(ramgb, options = {}) {
682
- if (!Number.isFinite(ramgb) || ramgb <= 0) {
683
- throw new Error(`ramgb must be a positive number, received ${ramgb}`);
684
- }
685
- const vcpus = options.vcpus ?? 8;
686
- const totalkb = pagealign(ramgb * 1024 * 1024 - 131072);
687
- const freefraction = options.freefraction ?? rnd(38, 68) / 1000;
688
- const availablefraction = options.availablefraction ?? rnd(860, 930) / 1000;
689
- const memfree = pagealign(totalkb * freefraction);
690
- const memavailable = pagealign(totalkb * availablefraction);
691
- const buffers = pagealign(totalkb * (rnd(50, 80) / 10000));
692
- const cached = pagealign(totalkb * (rnd(300, 420) / 1000));
693
- const activeanon = pagealign(totalkb * (rnd(240, 320) / 1000));
694
- const inactiveanon = pagealign(totalkb * (rnd(60, 120) / 1000));
695
- const active = pagealign(activeanon + cached * 0.55);
696
- const inactive = pagealign(inactiveanon + cached * 0.4);
697
- const swapgb = options.swapgb ?? Math.min(Math.max(Math.round(ramgb / 8), 2), 128);
698
- const swaptotal = swapgb * 1024 * 1024;
699
- const swapfree = pagealign(swaptotal * (rnd(850, 980) / 1000));
700
- const commitlimit = pagealign(swaptotal + (totalkb * 50) / 100);
701
- const committedas = pagealign(totalkb * (rnd(95, 160) / 100));
702
- const slab = pagealign(totalkb * (rnd(190, 240) / 10000));
703
- const sreclaimable = pagealign(slab * 0.64);
704
- const anonhuge = pagealign(activeanon * (rnd(20, 45) / 100));
705
- const directmap1g = ramgb >= 64 ? pagealign(totalkb * 0.8) : 0;
706
- const directmap2m = directmap1g > 0 ? pagealign(totalkb * 0.15) : pagealign(totalkb * 0.9);
707
- const rows = [
708
- ['MemTotal', totalkb], ['MemFree', memfree],
709
- ['MemAvailable', memavailable], ['Buffers', buffers], ['Cached', cached],
710
- ['SwapCached', 0], ['Active', active], ['Inactive', inactive],
711
- ['Active(anon)', activeanon], ['Inactive(anon)', inactiveanon],
712
- ['Active(file)', pagealign(active - activeanon)],
713
- ['Inactive(file)', pagealign(inactive - inactiveanon)],
714
- ['Unevictable', pagealign(rnd(16384, 65536))],
715
- ['Mlocked', pagealign(rnd(4096, 16384))],
716
- ['SwapTotal', swaptotal], ['SwapFree', swapfree],
717
- ['Dirty', pagealign(rnd(20480, 262144))], ['Writeback', 0],
718
- ['AnonPages', pagealign(activeanon * 1.08)],
719
- ['Mapped', pagealign(totalkb * (rnd(20, 42) / 1000))],
720
- ['Shmem', pagealign(totalkb * (rnd(5, 12) / 1000))],
721
- ['KReclaimable', sreclaimable], ['Slab', slab],
722
- ['SReclaimable', sreclaimable],
723
- ['SUnreclaim', pagealign(slab - sreclaimable)],
724
- ['KernelStack', pagealign(vcpus * 320)],
725
- ['PageTables', pagealign(totalkb * (rnd(30, 60) / 10000))],
726
- ['SecPageTables', pagealign(totalkb * (rnd(8, 16) / 10000))],
727
- ['NFS_Unstable', 0], ['Bounce', 0], ['WritebackTmp', 0],
728
- ['CommitLimit', commitlimit], ['Committed_AS', committedas],
729
- ['VmallocTotal', 34359738367],
730
- ['VmallocUsed', pagealign(rnd(49152, 131072))], ['VmallocChunk', 0],
731
- ['Percpu', pagealign(vcpus * rnd(20480, 40960))],
732
- ['HardwareCorrupted', 0], ['AnonHugePages', anonhuge],
733
- ['ShmemHugePages', 0], ['ShmemPmdMapped', 0], ['FileHugePages', 0],
734
- ['FilePmdMapped', 0], ['Unaccepted', 0], ['HugePages_Total', 0],
735
- ['HugePages_Free', 0], ['HugePages_Rsvd', 0], ['HugePages_Surp', 0],
736
- ['Hugepagesize', 2048], ['Hugetlb', 0],
737
- ['DirectMap4k', pagealign(rnd(131072, 262144))],
738
- ['DirectMap2M', directmap2m], ['DirectMap1G', directmap1g],
739
- ];
740
- return rows
741
- .map(([key, value]) => `${key}:${String(value).padStart(24 - key.length - 1)} kB`)
742
- .join('\n');
743
- }
744
- /** converts a kb value to the largest human unit for free -h. */
745
- function human(kb) {
746
- if (kb >= 1024 * 1024) {
747
- const gb = kb / (1024 * 1024);
748
- return `${gb >= 100 ? Math.round(gb) : gb.toFixed(1)}Gi`;
749
- }
750
- if (kb >= 1024) {
751
- return `${(kb / 1024).toFixed(1)}Mi`;
752
- }
753
- return `${Math.round(kb)}Ki`;
754
- }
755
- /**
756
- * renders the free -h table derived from a memoized meminfo snapshot so
757
- * cat /proc/meminfo and free -h always agree inside one sandbox.
758
- *
759
- * @param {string} meminfotext a payload previously returned by meminfo().
760
- * @returns {string} the free -h table.
761
- */
762
- export function freeh(meminfotext) {
763
- const map = {};
764
- for (const row of meminfotext.split('\n')) {
765
- const match = /^([A-Za-z_()]+):\s*(\d+)\skB$/.exec(row);
766
- if (match !== null) {
767
- map[match[1]] = Number(match[2]);
768
- }
769
- }
770
- const total = map.MemTotal ?? 0;
771
- const freekb = map.MemFree ?? 0;
772
- const buffers = map.Buffers ?? 0;
773
- const cached = (map.Cached ?? 0) + (map.SReclaimable ?? 0);
774
- const available = map.MemAvailable ?? 0;
775
- const used = Math.max(0, total - freekb - buffers - cached);
776
- const swap = map.SwapTotal ?? 0;
777
- const swapfreekb = map.SwapFree ?? 0;
778
- const swapused = swap - swapfreekb;
779
- const w = (text) => padstart(String(text), 13);
780
- return [
781
- `${w('total')}${w('used')}${w('free')}${w('shared')}${w('buff/cache')}${w('available')}`,
782
- ['Mem:', w(human(total)), w(human(used)), w(human(freekb)),
783
- w(human(map.Shmem ?? 0)), w(human(buffers + cached)), w(human(available))].join(''),
784
- ['Swap:', w(human(swap)), w(human(swapused)), w(human(swapfreekb))].join(''),
785
- ].join('\n');
786
- }
787
- /* ------------------------------------------------------------------ */
788
- /* context: smiformat — the exact 89-char summary table */
789
- /* ------------------------------------------------------------------ */
790
- const smiwidth = 89;
791
- const smicolumns = [40, 24, 21];
792
- const procwidth = 75;
793
- const smidriver = '575.57.08';
794
- const cudaversion = '12.9';
795
- const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
796
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
797
- /** formats the nvidia-smi timestamp "weekday month day hh:mm:ss year". */
798
- function formatstamp(date) {
799
- const two = (value) => String(value).padStart(2, '0');
800
- return `${weekdays[date.getDay()]} ${months[date.getMonth()]} ${two(date.getDate())} ${two(date.getHours())}:${two(date.getMinutes())}:${two(date.getSeconds())} ${date.getFullYear()}`;
801
- }
802
- /** full-width border line: "+" plus 87 fill characters plus "+". */
803
- function smiborder(fill) {
804
- return `+${fill.repeat(smiwidth - 2)}+`;
805
- }
806
- /** full-width content line padded to the 89-char table width. */
807
- function smifull(content) {
808
- return `|${pad(content, smiwidth - 2)}|`;
809
- }
810
- /** three-column separator: pipes at fixed offsets 1, 42 and 67. */
811
- function smisep(fill) {
812
- return `|${fill.repeat(smicolumns[0])}+${fill.repeat(smicolumns[1])}+${fill.repeat(smicolumns[2])}|`;
813
- }
814
- /** three-column data row within the 89-char width. */
815
- function smirow(left, mid, right) {
816
- return `|${pad(left, smicolumns[0])}|${pad(mid, smicolumns[1])}|${pad(right, smicolumns[2])}|`;
817
- }
818
- /** process table content line within the 75-char width. */
819
- function procline(content) {
820
- return `|${pad(content, procwidth - 2)}|`;
821
- }
822
- /**
823
- * renders the complete virtual nvidia-smi adapter report; the summary
824
- * table is exactly 89 characters wide and the header pins driver
825
- * 575.57.08 and cuda 12.9 exactly as the native smiadapterbuilder does.
826
- * when a mig profile is active the memory column reflects the slice size
827
- * and a mig line joins the table.
828
- *
829
- * @param {string} gpu gpu id or name present in gpudata.
830
- * @param {string} [migprofile='off'] one of 1g.24gb, 2g.48gb, 4g.96gb or off.
831
- * @returns {string} the full report text.
832
- */
833
- export function nvidiaSmiTable(gpu, migprofile = 'off') {
834
- const spec = getgpu(gpu);
835
- if (spec === undefined) {
836
- throw new Error(`unknown gpu "${gpu}"; known gpus: ${gpudata.map((entry) => entry.id).join(', ')}`);
837
- }
838
- const mig = getmig(migprofile);
839
- const vrammib = mig !== null ? mig.slicegb * 1024 : spec.smireportedmib;
840
- const memusedmib = rnd(0, 24);
841
- const util = rnd(0, 3);
842
- const temperature = rnd(38, 47);
843
- const fanspeed = rnd(24, 38);
844
- const powerw = Math.max(10, Math.round(spec.tdpwatts * 0.04));
845
- const head = ` NVIDIA-SMI ${smidriver} ${' '.repeat(13)}Driver Version: ${smidriver}${' '.repeat(6)}CUDA Version: ${cudaversion}`;
846
- const lines = [
847
- formatstamp(new Date()),
848
- smiborder('-'),
849
- smifull(head),
850
- smisep('-'),
851
- smirow(`${pad(' GPU Name', 27)}${pad('Persistence-M', 13)}`, pad(' Bus-Id Disp.A', 24), ` ${padstart('Volatile Uncorr. ECC', 20)}`),
852
- smirow(pad(' Fan Temp Perf Pwr:Usage/Cap', 40), pad(' Memory-Usage', 24), ` ${padstart('GPU-Util Compute M.', 20)}`),
853
- smisep('='),
854
- smirow(`${pad(` 0 ${spec.name}`, 36)}${pad('Off', 4)}`, pad(' 00000000:01:00.0 Off', 24), ` ${padstart('N/A', 20)}`),
855
- smirow(pad(` ${fanspeed}% ${temperature}C P0 ${powerw}W / ${spec.tdpwatts}W`, 40), padstart(`${memusedmib}MiB / ${vrammib}MiB`, 24), `${padstart(`${util}%`, 10)}${pad(' Default', 11)}`),
856
- ];
857
- if (mig !== null) {
858
- lines.push(smifull(` MIG: enabled, profile ${mig.id}, ${mig.maxinstances} instance(s) of ${mig.slicegb} GB`));
859
- }
860
- lines.push(smirow('', '', ''), smiborder('-'), '', 'Processes:', procline(`${pad(' GPU GI CI', 20)}${pad('PID', 7)}${pad('Type', 8)}${pad('Process name', 27)}${pad('GPU Memory', 11)}`), procline('='.repeat(procwidth - 2)));
861
- if (mig !== null) {
862
- for (let instance = 0; instance < mig.maxinstances; instance += 1) {
863
- lines.push(procline(`${pad(` 0 ${instance} 0`, 20)}${pad(String(1200 + instance), 7)}${pad('MIG', 8)}${pad('mig-worker', 27)}${pad(`${rnd(0, 512)}MiB`, 11)}`));
864
- }
865
- }
866
- else {
867
- lines.push(procline(' No running processes found'));
868
- }
869
- lines.push(`+${'-'.repeat(procwidth - 2)}+`);
870
- return lines.join('\n');
871
- }
872
- /**
873
- * renders the nvidia-smi -l device listing; with an active mig profile
874
- * the mig device lines follow the parent gpu line.
875
- *
876
- * @param {string} gpu gpu id or name present in gpudata.
877
- * @param {string} [migprofile='off'] mig profile id or off.
878
- * @returns {string} the -l listing.
879
- */
880
- export function nvidiaSmiList(gpu, migprofile = 'off') {
881
- const spec = getgpu(gpu);
882
- if (spec === undefined) {
883
- throw new Error(`unknown gpu "${gpu}"`);
884
- }
885
- const mig = getmig(migprofile);
886
- const lines = [`GPU 0: ${spec.name} (UUID: ${token('GPU-')})`];
887
- if (mig !== null) {
888
- for (let instance = 0; instance < mig.maxinstances; instance += 1) {
889
- lines.push(` MIG ${mig.id} Device ${instance}: (UUID: ${token('MIG-')})`);
890
- }
891
- }
892
- return lines.join('\n');
893
- }
894
- /* ------------------------------------------------------------------ */
895
- /* context: mesa stack summaries */
896
- /* ------------------------------------------------------------------ */
897
- /** the software rendering stack pinned from primary sources. */
898
- const mesastack = {
899
- mesa: '26.2.1', llvm: '22.1.8', gl: '4.6', gles: '3.2', vulkan: '1.4',
900
- opencl: '3.1',
901
- };
902
- /**
903
- * renders the clinfo summary for the rusticl platform on llvmpipe: opencl
904
- * 3.1 (same-day spec support in mesa 26.2), fp16 default since 25.2 and
905
- * fp64 through rusticl_features.
906
- *
907
- * @param {number} [vcpus=8] vcpu count reported as compute units.
908
- * @returns {string} the clinfo summary block.
909
- */
910
- export function clinfoSummary(vcpus = 8) {
911
- const units = Math.max(1, Math.min(vcpus, 32));
912
- return [
913
- 'Number of platforms 1',
914
- ' Platform Name rusticl',
915
- ' Platform Vendor Mesa/X.org',
916
- ' Platform Version OpenCL 3.1',
917
- ' Platform Profile FULL_PROFILE',
918
- ' Platform Extensions cl_khr_fp64 cl_khr_fp16 cl_khr_icd cl_khr_command_buffer',
919
- 'Number of devices 1',
920
- ' Device Name llvmpipe (CPU)',
921
- ' Device Vendor Mesa/X.org',
922
- ' Device Vendor ID 0x10005 (Mesa)',
923
- ' Device Version OpenCL 3.1',
924
- ' Device Type GPU',
925
- ` Max compute units ${units}`,
926
- ' Max work item dimensions 3',
927
- ' Max work group size 1024',
928
- ' Preferred vector width char/int/float 16/16/16',
929
- ' Max clock frequency 3700MHz',
930
- ' Address bits 64',
931
- ' Half precision support cl_khr_fp16 (default since mesa 25.2)',
932
- ' Double precision support cl_khr_fp64 (rusticl_features=fp64)',
933
- ' Device available Yes',
934
- ' Compiler available Yes',
935
- ' SPIR versions 1.2',
936
- ].join('\n');
937
- }
938
- /**
939
- * renders the vulkaninfo --summary block for lavapipe: vulkan 1.4 surface
940
- * since mesa 25.1, khronos-conformant on the vulkan 1.3 cts, selected
941
- * through vk_driver_files (vk_icd_filenames is deprecated).
942
- *
943
- * @returns {string} the vulkan summary block.
944
- */
945
- export function vulkanSummary() {
946
- return [
947
- '==========',
948
- 'VULKANINFO',
949
- '==========',
950
- '',
951
- 'Instance Extensions: count=10',
952
- 'VK_KHR_surface : extension revision 25',
953
- 'VK_KHR_get_physical_device_properties2 : extension revision 2',
954
- 'VK_EXT_headless_surface : extension revision 1',
955
- '',
956
- 'API Version: 1.4.313',
957
- '',
958
- 'GPU0:',
959
- ' apiVersion = 1.4.313',
960
- ' driverVersion = 26.2.1',
961
- ' vendorID = 0x10005',
962
- ' deviceID = 0x0000',
963
- ' deviceType = CPU',
964
- ' deviceName = llvmpipe (CPU (x86_64))',
965
- ' driverName = mesa_llvmpipe',
966
- ` driverInfo = Mesa ${mesastack.mesa} (LLVM ${mesastack.llvm})`,
967
- ' conformantVersion = 1.3.0.0',
968
- '',
969
- 'Device Extensions: count=204',
970
- 'VK_KHR_8bit_storage VK_KHR_16bit_storage VK_KHR_shader_float16_int8',
971
- 'VK_KHR_spirv_1_4 VK_KHR_storage_buffer_storage_class',
972
- ].join('\n');
973
- }
974
- /**
975
- * renders the glxinfo header block for llvmpipe: opengl 4.6 core profile
976
- * 100% (161/161 extensions tracked by mesamatrix) and the llvmpipe
977
- * renderer string with the llvm version.
978
- *
979
- * @returns {string} the glxinfo block.
980
- */
981
- export function glxinfoSummary() {
982
- return [
983
- 'name of display: :0',
984
- 'display: :0 screen: 0',
985
- 'direct rendering: Yes',
986
- 'server glx vendor string: SGI',
987
- 'client glx vendor string: Mesa Project and SGI',
988
- 'OpenGL vendor string: Mesa',
989
- `OpenGL renderer string: llvmpipe (LLVM ${mesastack.llvm}, 256 bits)`,
990
- `OpenGL core profile version string: ${mesastack.gl} (Core Profile) Mesa ${mesastack.mesa}`,
991
- 'OpenGL core profile shading language version string: 4.60',
992
- '',
993
- 'OpenGL core profile context flags: (none)',
994
- 'OpenGL core profile profile mask: core profile',
995
- '',
996
- 'OpenGL core profile extensions: 161',
997
- `OpenGL version string: ${mesastack.gl} (Compatibility Profile) Mesa ${mesastack.mesa}`,
998
- 'OpenGL shading language version string: 4.60',
999
- 'OpenGL context flags: (none)',
1000
- '',
1001
- 'Extended renderer info (GLX_MESA_query_renderer):',
1002
- ' Vendor: Mesa/X.org (0x1002)',
1003
- ` Device: llvmpipe (LLVM ${mesastack.llvm}, 256 bits) (0xffffffff)`,
1004
- ` Version: ${mesastack.mesa}`,
1005
- ' Accelerated: no',
1006
- ' Video memory: 32768MB',
1007
- ' Unified memory: yes',
1008
- ' Texture from pixmap: yes',
1009
- ].join('\n');
1010
- }
1011
- /**
1012
- * assembles the validated mesa environment exactly as mesaenvbuilder +
1013
- * renderenv('opengl') do: llvmpipe forced, gl 4.6, glsl 460, vector width
1014
- * 512 to exercise the avx-512 paths, lavapipe through vk_driver_files and
1015
- * rusticl opencl 3.1 with the fp64 feature.
1016
- *
1017
- * @param {number} [vcpus=8] vcpu count used for lp_num_threads (0 uses
1018
- * every core up to the lp_max_threads=32 ceiling).
1019
- * @returns {Record<string, string>} the environment record.
1020
- */
1021
- export function mesaenv(vcpus = 8) {
1022
- const threads = vcpus <= 0 ? 0 : Math.min(vcpus, 32);
1023
- return {
1024
- LIBGL_ALWAYS_SOFTWARE: 'true',
1025
- GALLIUM_DRIVER: 'llvmpipe',
1026
- MESA_GL_VERSION_OVERRIDE: mesastack.gl,
1027
- MESA_GLSL_VERSION_OVERRIDE: '460',
1028
- MESA_GLES_VERSION_OVERRIDE: mesastack.gles,
1029
- MESA_VK_VERSION_OVERRIDE: mesastack.vulkan,
1030
- LP_NUM_THREADS: String(threads),
1031
- LP_NATIVE_VECTOR_WIDTH: '512',
1032
- MESA_NO_ERROR: '1',
1033
- VK_DRIVER_FILES: '/usr/share/vulkan/icd.d/lvp_icd.x86_64.json',
1034
- MESA_VK_WSI_HEADLESS_SWAPCHAIN: '1',
1035
- RUSTICL_ENABLE: 'llvmpipe',
1036
- RUSTICL_DEVICE_TYPE: 'gpu',
1037
- RUSTICL_CL_VERSION: mesastack.opencl,
1038
- RUSTICL_FEATURES: 'fp64',
1039
- };
1040
- }
1041
- /* ------------------------------------------------------------------ */
1042
- /* context: bootsequence — dmesg-style boot lines */
1043
- /* ------------------------------------------------------------------ */
1044
- /**
1045
- * renders the firecracker-style boot log for a sandbox: kernel banner,
1046
- * 125 ms microvm bring-up, memory sizing, vcpu online lines, the mesa
1047
- * software stack bring-up and the virtual gpu identity binding. every
1048
- * line carries the kernel [ 0.000000] timestamp style.
1049
- *
1050
- * @param {string} model cpu model name present in cpudata.
1051
- * @param {number} vcpus logical processor count.
1052
- * @param {number} [ramgb=32] guest ram in gb.
1053
- * @param {string} [gpu='rtx5090'] gpu id present in gpudata.
1054
- * @param {number} [quota=16777216] persistent workspace quota in bytes.
1055
- * @returns {string[]} ordered dmesg lines.
1056
- */
1057
- export function bootSequence(model, vcpus, ramgb = 32, gpu = 'rtx5090', quota = 16 * 1024 * 1024) {
1058
- const spec = getcpu(model) ?? cpudata[0];
1059
- const gpuspec = getgpu(gpu) ?? gpudata[0];
1060
- const topology = solvetopology(spec, vcpus);
1061
- const arch = spec.arch === 'arm64' ? 'aarch64' : 'x86_64';
1062
- const stamp = (seconds) => `[${seconds.toFixed(6).padStart(12)}]`;
1063
- const onlinetime = 0.039 + 0.00042 * topology.vcpus;
1064
- return [
1065
- `${stamp(0)} Linux version 6.12.0-saddle (root@saddle) (gcc 15.1.0, ld 2.44) #1 SMP PREEMPT_DYNAMIC ${arch}`,
1066
- `${stamp(0.000049)} Command line: root=/dev/vda rw console=ttyS0 reboot=k panic=1 pci=off i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd`,
1067
- `${stamp(0.004201)} BIOS-provided physical RAM map (virtio-mem + memfd hugetlb backend)`,
1068
- `${stamp(0.008913)} Memory: ${pagealign(ramgb * 1024 * 1024 - 131072)}K available (${ramgb} GB guest plan, 4 kB pages)`,
1069
- `${stamp(0.012451)} firecracker: microvm vm_state=Running boot_time=125ms vmm=fc v1.16.1`,
1070
- `${stamp(0.02133)} virtio_blk virtio1: [vda] 8589934592 512-byte logical blocks (8.6 GB)`,
1071
- `${stamp(0.038774)} smpboot: ${topology.coresonline} cores, ${topology.vcpus} threads online (${spec.model})`,
1072
- `${stamp(0.039001)} cpu cpu0: ${spec.microarch}, boost ${spec.boostclockmhz} MHz, l3 ${spec.l3mb} MB`,
1073
- `${stamp(onlinetime)} smp: brought up ${topology.vcpus} cpus, cpu mask 0-${topology.vcpus - 1}`,
1074
- `${stamp(onlinetime + 0.0148)} NUMA: ${Math.max(1, Math.round(topology.vcpus / 64))} node(s), interleave enabled`,
1075
- `${stamp(onlinetime + 0.0283)} hugepage: 2048 kB pages reserved, thp=always`,
1076
- `${stamp(onlinetime + 0.0422)} docker: engine 29.7.2 ready, containerd 2.3.1, memswap=-1 shm=2g`,
1077
- `${stamp(onlinetime + 0.0551)} qemu-system-${arch}: bridge 11.1.0 attached (qmp unix:/run/saddle/qemu-0.sock)`,
1078
- `${stamp(onlinetime + 0.0784)} mesa ${mesastack.mesa}: llvmpipe rasterizer, lp_num_threads=${Math.min(topology.vcpus, 32)}, lp_max_threads=32`,
1079
- `${stamp(onlinetime + 0.0789)} llvmpipe: avx-512 vector width 512 selected by gallivm (llvm ${mesastack.llvm})`,
1080
- `${stamp(onlinetime + 0.0812)} lavapipe: vulkan ${mesastack.vulkan}, icd lvp_icd.${arch}.json, wsi headless`,
1081
- `${stamp(onlinetime + 0.0837)} rusticl: opencl ${mesastack.opencl} on llvmpipe, features fp64 (fp16 default since 25.2)`,
1082
- `${stamp(onlinetime + 0.091)} virtualgpu: identity ${gpuspec.name} [${gpuspec.pcivendor}:${gpuspec.pcidevice}] ${gpuspec.memtype} ${gpuspec.vrammib / 1024} GB`,
1083
- `${stamp(onlinetime + 0.0922)} nvml shim: FAKE_MODEL="${gpuspec.name}" FAKE_VRAM=${gpuspec.smireportedmib} (fake-nvidia-smi adapter ready)`,
1084
- `${stamp(onlinetime + 0.1045)} saddle: virtual hardware engine v2.0.127, sandbox created -> running`,
1085
- `${stamp(onlinetime + 0.1047)} saddle: persistent workspace: ${humanbytes(quota)} quota, data stays with the sandbox id`,
1086
- `${stamp(onlinetime + 0.1046)} Freeing unused kernel image memory`,
1087
- ];
1088
- }
1089
- /* ------------------------------------------------------------------ */
1090
- /* context: sandboxstate and dispatcher */
1091
- /* ------------------------------------------------------------------ */
1092
- /**
1093
- * creates the state object shared by the browser terminal and the api
1094
- * exec endpoint: resolved specs, a stable hostname, the boot timestamp and
1095
- * a memoized meminfo snapshot so memory reports stay consistent within
1096
- * one sandbox session.
1097
- *
1098
- * @param {{model: string, vcpus: number, ramgb: number, gpu: string,
1099
- * mig?: string, id?: string}} spec the sandbox specification.
1100
- * @returns {object} the sandbox state consumed by dispatch.
1101
- */
1102
- export function createSandboxState(spec) {
1103
- // the reviewed catalog is the only source of processor identity; ram
1104
- // accepts any user chosen plan up to the 18 tb virtual ceiling.
1105
- const cpu = getcpu(spec.model) ?? cpudata[0];
1106
- const gpu = getgpu(spec.gpu) ?? gpudata[0];
1107
- const topology = solvetopology(cpu, spec.vcpus ?? 8);
1108
- const ramgb = Math.max(1, Math.min(Math.round(spec.ramgb ?? 32), 18432));
1109
- const id = spec.id ?? token('sb-');
1110
- return {
1111
- model: cpu.model,
1112
- cpuspec: cpu,
1113
- gpuspec: gpu,
1114
- vcpus: topology.vcpus,
1115
- ramgb,
1116
- gpu: gpu.id,
1117
- mig: getmig(spec.mig) === null ? 'off' : spec.mig,
1118
- id,
1119
- hostname: `saddle-${id.replace(/^sb-/, '').slice(0, 8)}`,
1120
- boottime: Date.now(),
1121
- kernel: cpu.arch === 'arm64' ? '6.12.0-saddle-aarch64' : '6.12.0-saddle',
1122
- memsnapshot: meminfo(ramgb, { vcpus: topology.vcpus }),
1123
- history: [],
1124
- files: new Map(),
1125
- };
1126
- }
1127
- /** the supported command list, reused by help and the terminal. */
1128
- export const commands = [
1129
- 'help', 'lscpu', 'cat /proc/cpuinfo', 'cat /proc/meminfo', 'free -h',
1130
- 'nvidia-smi', 'nvidia-smi -L', 'clinfo', 'vulkaninfo --summary',
1131
- 'glxinfo', 'uname -a', 'ls /etc/virtual', 'env', 'docker --version',
1132
- 'qemu-system-x86_64 --version', 'uptime', 'whoami', 'neofetch', 'clear',
1133
- 'echo', 'echo <text> > <file>', 'echo <text> >> <file>', 'touch <file>',
1134
- 'cat <file>', 'ls', 'rm <file>', 'df -h', 'stat <file>', 'pwd',
1135
- 'history', 'streaming', 'man <command>',
1136
- ];
1137
- /** renders the help text with every supported command. */
1138
- function helpText() {
1139
- const rows = [
1140
- ['help', 'this command list'],
1141
- ['lscpu', 'virtual processor topology summary'],
1142
- ['cat /proc/cpuinfo', 'full procfs cpuinfo payload'],
1143
- ['cat /proc/meminfo', 'full procfs meminfo payload'],
1144
- ['free -h', 'human readable memory table'],
1145
- ['nvidia-smi', 'virtual gpu adapter report (89-char table)'],
1146
- ['nvidia-smi -L', 'virtual gpu device listing'],
1147
- ['clinfo', 'rusticl opencl 3.1 summary on llvmpipe'],
1148
- ['vulkaninfo --summary', 'lavapipe vulkan 1.4 summary'],
1149
- ['glxinfo', 'llvmpipe opengl 4.6 summary'],
1150
- ['uname -a', 'kernel and architecture line'],
1151
- ['ls /etc/virtual', 'virtual hardware definition files'],
1152
- ['env', 'mesa llvmpipe environment variables'],
1153
- ['docker --version', 'docker engine version'],
1154
- ['qemu-system-x86_64 --version', 'qemu bridge version'],
1155
- ['uptime', 'sandbox uptime and load average'],
1156
- ['whoami', 'current user'],
1157
- ['neofetch', 'saddle ascii logo and sandbox specs'],
1158
- ['clear', 'clear the terminal'],
1159
- ['echo <text>', 'print text'],
1160
- ['echo <text> > <file>', 'write text to a workspace file'],
1161
- ['echo <text> >> <file>', 'append text to a workspace file'],
1162
- ['touch <file>', 'create an empty workspace file'],
1163
- ['cat <file>', 'print a persistent workspace file'],
1164
- ['ls', 'list the workspace root with sizes and dates'],
1165
- ['rm <file>', 'delete one workspace file'],
1166
- ['df -h', 'workspace quota usage as a filesystem table'],
1167
- ['stat <file>', 'size and timestamps of one workspace file'],
1168
- ['pwd', 'print the working directory (always /)'],
1169
- ['history', 'streaming', 'the last 50 commands of this session'],
1170
- ['man <command>', 'short manual for one command'],
1171
- ];
1172
- return rows.map(([name, about]) => ` ${pad(name, 32)}${about}`).join('\n');
1173
- }
1174
- /** renders the neofetch block: the saddle ascii logo beside sandbox specs. */
1175
- function neofetch(state) {
1176
- const cpu = state.cpuspec;
1177
- const gpu = state.gpuspec;
1178
- const logo = [
1179
- ' ``-://////:-`` ',
1180
- ' ./+++++++++++++/-. ',
1181
- ' -+++++++++++++++++++-` ',
1182
- ' ./+++ saddle sandbox ++/. ',
1183
- ' -+++++++++++++++++++++-` ',
1184
- ' `-:/+++++++++++++/-. ',
1185
- ' .-://////:-. ',
1186
- ' ',
1187
- ];
1188
- const uptime = Math.max(1, Math.round((Date.now() - state.boottime) / 1000));
1189
- const mins = Math.floor(uptime / 60);
1190
- const hours = Math.floor(mins / 60);
1191
- const uptimeText = hours > 0 ? `${hours} hour(s), ${mins % 60} min` : `${mins} min`;
1192
- const total = pagealign(state.ramgb * 1024 * 1024 - 131072);
1193
- const mig = getmig(state.mig);
1194
- const info = [
1195
- `root@${state.hostname}`,
1196
- '-----------------',
1197
- 'OS: saddle linux (virtual hardware engine v2.0.127)',
1198
- 'Host: firecracker microvm (125 ms boot)',
1199
- `Kernel: ${state.kernel}`,
1200
- `Uptime: ${uptimeText}`,
1201
- 'Packages: 412 (dpkg), 8 (snap)',
1202
- 'Shell: bash 5.2.37',
1203
- `CPU: ${cpu.displayname} (${state.vcpus}) @ ${cpu.boostclockmhz}.00MHz`,
1204
- `GPU: ${gpu.name} [${gpu.pcivendor}:${gpu.pcidevice}]`,
1205
- `Memory: ${human(Math.round(total * 0.31))} / ${human(total)}`,
1206
- `vRAM: ${gpu.memtype} ${gpu.vrammib / 1024} GB${mig !== null ? ` (MIG ${mig.id})` : ''}`,
1207
- 'Renderer: llvmpipe (LLVM 22.1.8) / lavapipe 1.4 / rusticl 3.1',
1208
- ];
1209
- const rows = Math.max(logo.length, info.length);
1210
- const out = [];
1211
- for (let index = 0; index < rows; index += 1) {
1212
- out.push(`${(logo[index] ?? '').padEnd(30)}${info[index] ?? ''}`);
1213
- }
1214
- return out.join('\n');
1215
- }
1216
- /** renders the uptime line with a load average derived from the vcpus. */
1217
- function uptimeLine(state) {
1218
- const totalSeconds = Math.max(0, Math.floor((Date.now() - state.boottime) / 1000));
1219
- const now = new Date();
1220
- const mins = Math.max(1, Math.round(totalSeconds / 60));
1221
- const load = (decay) => (Math.random() * state.vcpus * 0.18 * Math.exp(-decay / 30)).toFixed(2);
1222
- return ` ${now.toTimeString().slice(0, 8)} up ${mins} min, 1 user, load average: ${load(1)}, ${load(5)}, ${load(15)}`;
1223
- }
1224
- /** renders the env listing: the mesa stack plus the sandbox variables. */
1225
- function envText(state) {
1226
- const mig = getmig(state.mig);
1227
- const entries = {
1228
- ...mesaenv(state.vcpus),
1229
- FAKE_MODEL: state.gpuspec.name,
1230
- FAKE_VRAM: String(mig !== null ? mig.slicegb * 1024 : state.gpuspec.smireportedmib),
1231
- SADDLE_VERSION: '2.0.127',
1232
- SADDLE_SANDBOX: state.id,
1233
- SADDLE_STATE: 'running',
1234
- SADDLE_CPU: state.model,
1235
- SADDLE_GPU: state.gpu,
1236
- SADDLE_MIG: state.mig,
1237
- HOSTNAME: state.hostname,
1238
- TERM: 'xterm-256color',
1239
- SHELL: '/bin/bash',
1240
- USER: 'root',
1241
- HOME: '/root',
1242
- PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
1243
- LANG: 'C.UTF-8',
1244
- };
1245
- return Object.keys(entries)
1246
- .sort()
1247
- .map((key) => `${key}=${entries[key]}`)
1248
- .join('\n');
1249
- }
1250
- /* ------------------------------------------------------------------ */
1251
- /* context: virtualfs — the persistent workspace filesystem */
1252
- /* ------------------------------------------------------------------ */
1253
- /** the default workspace quota mirrored from the api contract. */
1254
- const defaultquota = 16 * 1024 * 1024;
1255
- /** the per-session history cap shown by the history command. */
1256
- const historycap = 50;
1257
- /**
1258
- * normalizes one workspace path: the working directory is always the
1259
- * sandbox root, so relative names gain the leading slash, duplicated
1260
- * slashes collapse, trailing slashes drop and any traversal attempt
1261
- * ("..") or oversized path is rejected.
1262
- *
1263
- * @param {string} input the raw path typed by the user.
1264
- * @returns {{ok: true, path: string} | {ok: false, error: string}} the verdict.
1265
- */
1266
- function normalizepath(input) {
1267
- const raw = String(input ?? '').trim();
1268
- if (raw.length === 0) {
1269
- return { ok: false, error: 'empty path' };
1270
- }
1271
- if (raw.includes('..')) {
1272
- return { ok: false, error: 'path traversal is not allowed' };
1273
- }
1274
- let path = raw.replace(/\/+/, '/');
1275
- if (!path.startsWith('/')) {
1276
- path = `/${path}`;
1277
- }
1278
- if (path.length > 1 && path.endsWith('/')) {
1279
- path = path.slice(0, -1);
1280
- }
1281
- if (path.length > 128) {
1282
- return { ok: false, error: 'path is too long (max 128 characters)' };
1283
- }
1284
- return { ok: true, path };
1285
- }
1286
- /**
1287
- * builds the fallback filesystem for contexts without callbacks (the
1288
- * browser terminal): a per-state map that lives exactly as long as the
1289
- * sandbox session and mirrors the {write, read, list, del} contract.
1290
- *
1291
- * @param {object} state a state built by createsandboxstate.
1292
- * @returns {{write: Function, read: Function, list: Function, del: Function}}
1293
- * the in-memory filesystem adapter.
1294
- */
1295
- function memoryfs(state) {
1296
- return {
1297
- write(path, content) {
1298
- const text = content === null || content === undefined ? '' : String(content);
1299
- const size = new TextEncoder().encode(text).length;
1300
- const updatedat = new Date().toISOString();
1301
- state.files.set(path, { content: text, size, updatedat });
1302
- return { path, size, updatedat };
1303
- },
1304
- read(path) {
1305
- const entry = state.files.get(path);
1306
- return entry === undefined ? null : { path, ...entry };
1307
- },
1308
- list() {
1309
- return [...state.files.entries()]
1310
- .map(([path, entry]) => ({ path, size: entry.size, updatedat: entry.updatedat }))
1311
- .sort((a, b) => (a.path < b.path ? -1 : 1));
1312
- },
1313
- del(path) {
1314
- return state.files.delete(path);
1315
- },
1316
- };
1317
- }
1318
- /**
1319
- * renders one ls row in the long listing style with size and date.
1320
- *
1321
- * @param {{path: string, size: number, updatedat: string}} file the file row.
1322
- * @returns {string} the formatted line.
1323
- */
1324
- function lsrow(file) {
1325
- const when = String(file.updatedat ?? '').slice(0, 16).replace('T', ' ');
1326
- return `-rw-r--r-- 1 root root ${padstart(String(file.size), 9)} ${when} ${file.path}`;
1327
- }
1328
- /** the short manual pages served by the man command. */
1329
- const manualpages = {
1330
- streaming: 'streaming: shows the streaming memory plan - any workload size runs inside a small hot window (mmap layers, evict after last consumer)',
1331
- quantum: 'quantum: quantum layer summary - simulator, bb84, grover, dna vault, optical planner',
1332
- tiers: 'tiers: shows the layered virtual memory - l1 ram to l4 buckets, the free pool and the npm-as-disk farm',
1333
- touch: 'touch <file> - create an empty file in the persistent workspace (or refresh its timestamp)',
1334
- echo: 'echo <text> - print text; echo <text> > <file> writes and echo <text> >> <file> appends (stored verbatim, no implicit newline)',
1335
- cat: 'cat <file> - print one persistent workspace file (also /proc/cpuinfo and /proc/meminfo)',
1336
- ls: 'ls [path] - list the persistent workspace root with sizes and dates; ls /etc/virtual keeps the hardware table',
1337
- rm: 'rm <file> - delete one persistent workspace file',
1338
- df: 'df [-h] - workspace quota usage as a filesystem table; the quota is fixed per sandbox id',
1339
- stat: 'stat <file> - size and timestamps of one persistent workspace file',
1340
- pwd: 'pwd - print the working directory; the sandbox is always rooted at /',
1341
- history: 'history - the last 50 commands typed in this session',
1342
- man: 'man <command> - this short manual',
1343
- };
1344
- /**
1345
- * dispatches one shell command against a sandbox state; the return shape
1346
- * matches the api exec contract {output, exitCode}. the optional context
1347
- * carries the persistent filesystem callbacks {write, read, list, del}
1348
- * bound to the sandbox id by the api host; without a context the same
1349
- * commands run on the per-session in-memory workspace so the browser
1350
- * terminal keeps working unchanged.
1351
- *
1352
- * @param {string} command the raw command line typed by the user.
1353
- * @param {object} state a state built by createsandboxstate.
1354
- * @param {{fs?: {write: Function, read: Function, list: Function,
1355
- * del: Function}, quota?: number}} [context] the optional filesystem
1356
- * context provided by the api host.
1357
- * @returns {{output: string, exitCode: number, clear?: boolean}} the result.
1358
- */
1359
- export function dispatch(command, state, context) {
1360
- const raw = String(command ?? '').trim();
1361
- if (raw.length === 0) {
1362
- return { output: '', exitCode: 0 };
1363
- }
1364
- try {
1365
- if (!Array.isArray(state.history)) {
1366
- state.history = [];
1367
- }
1368
- state.history.push(raw);
1369
- if (state.history.length > historycap) {
1370
- state.history.splice(0, state.history.length - historycap);
1371
- }
1372
- }
1373
- catch {
1374
- /* history is best effort; a frozen state must not break exec */
1375
- }
1376
- const fs = context?.fs ?? memoryfs(state);
1377
- const argv = raw.split(/\s+/);
1378
- const head = argv[0];
1379
- const rest = argv.slice(1).join(' ');
1380
- try {
1381
- /* echo is the one command with output redirection: the text is
1382
- * stored verbatim ("echo a > f" then "echo b >> f" yields "ab").
1383
- * The split is a linear scan off the last '>' — the lazy regex it
1384
- * replaces walked every boundary of a space run and the redos gate
1385
- * refused the polynomial detour: the operator is the last '>' doubled
1386
- * or single, the filename the single whitespace-free token after it
1387
- * and the command everything before the operator. */
1388
- const gt = raw.lastIndexOf('>');
1389
- let redirect = null;
1390
- if (gt >= 0) {
1391
- const file = raw.slice(gt + 1).trim();
1392
- if (file.length > 0 && !/\s/.test(file)) {
1393
- const doubled = gt > 0 && raw[gt - 1] === '>';
1394
- redirect = {
1395
- command: raw.slice(0, doubled ? gt - 1 : gt).trim(),
1396
- operator: doubled ? '>>' : '>',
1397
- file,
1398
- };
1399
- }
1400
- }
1401
- if (redirect !== null) {
1402
- const commandpart = redirect.command;
1403
- const redirhead = commandpart.split(/\s+/)[0];
1404
- if (redirhead !== 'echo') {
1405
- return {
1406
- output: `bash: output redirection is only supported for echo (got ${redirhead})`,
1407
- exitCode: 1,
1408
- };
1409
- }
1410
- const text = commandpart.slice(redirhead.length).trim();
1411
- const norm = normalizepath(redirect.file);
1412
- if (!norm.ok) {
1413
- return { output: `bash: echo: ${redirect.file}: ${norm.error}`, exitCode: 1 };
1414
- }
1415
- try {
1416
- if (redirect.operator === '>>') {
1417
- const existing = fs.read(norm.path);
1418
- fs.write(norm.path, `${existing === null ? '' : existing.content}${text}`);
1419
- }
1420
- else {
1421
- fs.write(norm.path, text);
1422
- }
1423
- return { output: '', exitCode: 0 };
1424
- }
1425
- catch (writeerror) {
1426
- return {
1427
- output: `bash: echo: ${writeerror instanceof Error ? writeerror.message : String(writeerror)}`,
1428
- exitCode: 1,
1429
- };
1430
- }
1431
- }
1432
- switch (head) {
1433
- case 'help':
1434
- return { output: `saddle sandbox commands:\n${helpText()}`, exitCode: 0 };
1435
- case 'lscpu':
1436
- return { output: lscpu(state.model, state.vcpus, 1), exitCode: 0 };
1437
- case 'cat': {
1438
- if (argv[1] === '/proc/cpuinfo') {
1439
- return { output: cpuinfo(state.model, state.vcpus), exitCode: 0 };
1440
- }
1441
- if (argv[1] === '/proc/meminfo') {
1442
- return { output: state.memsnapshot, exitCode: 0 };
1443
- }
1444
- if (argv[1] === undefined) {
1445
- return { output: 'cat: missing file operand', exitCode: 1 };
1446
- }
1447
- const catnorm = normalizepath(argv[1]);
1448
- if (!catnorm.ok) {
1449
- return { output: `cat: ${argv[1]}: ${catnorm.error}`, exitCode: 1 };
1450
- }
1451
- const catfile = fs.read(catnorm.path);
1452
- if (catfile === null) {
1453
- return {
1454
- output: `cat: ${argv[1]}: No such file or directory`,
1455
- exitCode: 1,
1456
- };
1457
- }
1458
- return { output: catfile.content, exitCode: 0 };
1459
- }
1460
- case 'free':
1461
- return { output: freeh(state.memsnapshot), exitCode: 0 };
1462
- case 'nvidia-smi':
1463
- if (argv[1] === '-L') {
1464
- return { output: nvidiaSmiList(state.gpu, state.mig), exitCode: 0 };
1465
- }
1466
- return { output: nvidiaSmiTable(state.gpu, state.mig), exitCode: 0 };
1467
- case 'clinfo':
1468
- return { output: clinfoSummary(state.vcpus), exitCode: 0 };
1469
- case 'vulkaninfo':
1470
- return { output: vulkanSummary(), exitCode: 0 };
1471
- case 'glxinfo':
1472
- return { output: glxinfoSummary(), exitCode: 0 };
1473
- case 'uname':
1474
- return {
1475
- output: `Linux ${state.hostname} ${state.kernel} #1 SMP PREEMPT_DYNAMIC ${state.cpuspec.arch === 'arm64' ? 'aarch64' : 'x86_64'} GNU/Linux`,
1476
- exitCode: 0,
1477
- };
1478
- case 'ls': {
1479
- const target = argv[1] ?? '/';
1480
- if (target === '/etc/virtual') {
1481
- return {
1482
- output: [
1483
- 'boards.json cores.json gpus.json',
1484
- 'processors.json virtualhardware.json vm.config.json',
1485
- 'cpuinfo meminfo lscpu nvidia-smi',
1486
- ].join('\n'),
1487
- exitCode: 0,
1488
- };
1489
- }
1490
- const lsnorm = normalizepath(target);
1491
- if (!lsnorm.ok) {
1492
- return { output: `ls: ${target}: ${lsnorm.error}`, exitCode: 2 };
1493
- }
1494
- const rows = fs.list().filter((entry) => lsnorm.path === '/' || entry.path === lsnorm.path);
1495
- if (rows.length === 0) {
1496
- if (lsnorm.path !== '/') {
1497
- return {
1498
- output: `ls: cannot access '${target}': No such file or directory`,
1499
- exitCode: 2,
1500
- };
1501
- }
1502
- return { output: '', exitCode: 0 };
1503
- }
1504
- const blocks = Math.max(1, Math.ceil(rows.reduce((sum, row) => sum + row.size, 0) / 1024));
1505
- return {
1506
- output: [`total ${blocks}`, ...rows.map(lsrow)].join('\n'),
1507
- exitCode: 0,
1508
- };
1509
- }
1510
- case 'env':
1511
- return { output: envText(state), exitCode: 0 };
1512
- case 'docker':
1513
- return { output: 'Docker version 29.7.2, build 5ea4c1f', exitCode: 0 };
1514
- case 'qemu-system-x86_64':
1515
- return {
1516
- output: [
1517
- 'QEMU emulator version 11.1.0 (saddle bridge)',
1518
- 'Copyright (c) 2003-2026 Fabrice Bellard and the QEMU Project developers',
1519
- ].join('\n'),
1520
- exitCode: 0,
1521
- };
1522
- case 'uptime':
1523
- return { output: uptimeLine(state), exitCode: 0 };
1524
- case 'whoami':
1525
- return { output: 'root', exitCode: 0 };
1526
- case 'neofetch':
1527
- return { output: neofetch(state), exitCode: 0 };
1528
- case 'echo':
1529
- return { output: rest, exitCode: 0 };
1530
- case 'touch': {
1531
- if (argv[1] === undefined) {
1532
- return { output: 'touch: missing file operand', exitCode: 1 };
1533
- }
1534
- const touchnorm = normalizepath(argv[1]);
1535
- if (!touchnorm.ok) {
1536
- return { output: `touch: ${argv[1]}: ${touchnorm.error}`, exitCode: 1 };
1537
- }
1538
- try {
1539
- const existing = fs.read(touchnorm.path);
1540
- fs.write(touchnorm.path, existing === null ? '' : existing.content);
1541
- return { output: '', exitCode: 0 };
1542
- }
1543
- catch (toucherror) {
1544
- return {
1545
- output: `touch: ${toucherror instanceof Error ? toucherror.message : String(toucherror)}`,
1546
- exitCode: 1,
1547
- };
1548
- }
1549
- }
1550
- case 'rm': {
1551
- if (argv[1] === undefined) {
1552
- return { output: 'rm: missing operand', exitCode: 1 };
1553
- }
1554
- const rmnorm = normalizepath(argv[1]);
1555
- if (!rmnorm.ok) {
1556
- return { output: `rm: ${argv[1]}: ${rmnorm.error}`, exitCode: 1 };
1557
- }
1558
- try {
1559
- const removed = fs.del(rmnorm.path);
1560
- if (!removed) {
1561
- return {
1562
- output: `rm: cannot remove '${argv[1]}': No such file or directory`,
1563
- exitCode: 1,
1564
- };
1565
- }
1566
- return { output: '', exitCode: 0 };
1567
- }
1568
- catch (rmerror) {
1569
- return {
1570
- output: `rm: ${rmerror instanceof Error ? rmerror.message : String(rmerror)}`,
1571
- exitCode: 1,
1572
- };
1573
- }
1574
- }
1575
- case 'quantum':
1576
- return { output: quantumdemo(), exitCode: 0 };
1577
- case 'tiers':
1578
- return { output: tiersdemo(), exitCode: 0 };
1579
- case 'streaming':
1580
- return { output: streamingdemo(), exitCode: 0 };
1581
- case 'df': {
1582
- const rows = fs.list();
1583
- const used = rows.reduce((sum, row) => sum + row.size, 0);
1584
- const quota = Number(context?.quota ?? defaultquota);
1585
- const avail = Math.max(0, quota - used);
1586
- const percent = quota > 0 ? Math.min(100, Math.floor((used / quota) * 100)) : 0;
1587
- const col = (text, width) => padstart(String(text), width);
1588
- return {
1589
- output: [
1590
- `Filesystem ${col('Size', 10)}${col('Used', 10)}${col('Avail', 10)}${col('Use%', 6)} Mounted on`,
1591
- `${pad(state.id, 11)}${col(humanbytes(quota), 10)}${col(humanbytes(used), 10)}${col(humanbytes(avail), 10)}${col(`${percent}%`, 6)} /`,
1592
- ].join('\n'),
1593
- exitCode: 0,
1594
- };
1595
- }
1596
- case 'stat': {
1597
- if (argv[1] === undefined) {
1598
- return { output: 'stat: missing operand', exitCode: 1 };
1599
- }
1600
- const statnorm = normalizepath(argv[1]);
1601
- if (!statnorm.ok) {
1602
- return { output: `stat: ${argv[1]}: ${statnorm.error}`, exitCode: 1 };
1603
- }
1604
- const file = fs.read(statnorm.path);
1605
- if (file === null) {
1606
- return {
1607
- output: `stat: cannot stat '${argv[1]}': No such file or directory`,
1608
- exitCode: 1,
1609
- };
1610
- }
1611
- return {
1612
- output: [
1613
- ` File: ${file.path ?? statnorm.path}`,
1614
- ` Size: ${padstart(String(file.size), 10)}\tBlocks: ${padstart(String(Math.ceil(file.size / 512)), 6)} IO Block: 4096 regular file`,
1615
- `Device: saddle/virtual\tInode: ${padstart(String(state.files instanceof Map ? state.files.size + 1 : 1), 12)} Links: 1`,
1616
- `Modify: ${file.updatedat}`,
1617
- ].join('\n'),
1618
- exitCode: 0,
1619
- };
1620
- }
1621
- case 'pwd':
1622
- return { output: '/', exitCode: 0 };
1623
- case 'history': {
1624
- const lines = (Array.isArray(state.history) ? state.history : [])
1625
- .map((entry, index) => `${padstart(String(index + 1), 5)} ${entry}`);
1626
- return { output: lines.join('\n'), exitCode: 0 };
1627
- }
1628
- case 'man': {
1629
- const topic = argv[1];
1630
- if (topic === undefined) {
1631
- return { output: 'What manual page do you want?', exitCode: 1 };
1632
- }
1633
- const page = manualpages[topic];
1634
- if (page === undefined) {
1635
- return { output: `man: no manual for ${topic} (try: ${Object.keys(manualpages).join(', ')})`, exitCode: 1 };
1636
- }
1637
- return { output: `${topic.toUpperCase()}(1)\n\n ${page}`, exitCode: 0 };
1638
- }
1639
- case 'clear':
1640
- return { output: '', exitCode: 0, clear: true };
1641
- default:
1642
- return {
1643
- output: `bash: ${head}: command not found (try "help")`,
1644
- exitCode: 127,
1645
- };
1646
- }
1647
- }
1648
- catch (error) {
1649
- return {
1650
- output: `bash: ${head}: engine error: ${error instanceof Error ? error.message : String(error)}`,
1651
- exitCode: 1,
1652
- };
1653
- }
1654
- }
1655
- /**
1656
- * renders the streaming plan for a workload larger than any host: the
1657
- * demo the terminal shows for "how does a 1.5 tb model run here" - layers
1658
- * stream through a fixed window, the workload size is unbounded.
1659
- * @param {number} totalbytes workload size in bytes.
1660
- * @param {number} windowbytes hot-set budget in bytes.
1661
- * @returns {string} the plan summary lines.
1662
- */
1663
- /**
1664
- * renders the layered virtual memory architecture: everything is vram and
1665
- * the only physical layer is the free pool (github, npm cdn and friends -
1666
- * the saddle storage doctrine applied to this engine).
1667
- * @returns {string} the tier summary lines.
1668
- */
1669
- /**
1670
- * renders the quantum layer summary: a classical statevector simulator
1671
- * (1-20 qubits), the bb84/e91 key exchange, grover search, dna and 5d
1672
- * optical storage planners - every "quantum" capability is software.
1673
- * @returns {string} the quantum summary lines.
1674
- */
1675
- export function quantumdemo() {
1676
- return [
1677
- 'saddle quantum layer (100 percent classical simulation)',
1678
- ' simulator statevector, float64 interleaved re/im, 1-20 qubits',
1679
- ' gates h x y z s t rx ry rz cnot cz toffoli swap',
1680
- ' algorithms bellstate, ghz, grover2 (provable), grover3, deutsch, teleport',
1681
- ' protocols bb84 (1984) with intercept-resend detection, e91 (1991) + chsh',
1682
- ' randomness quantum-inspired (csprng) with honest anu qrng fallback note',
1683
- ' storage dna vault (goldman 2013, 215 pb/g, 4 strands, roundtrip proven)',
1684
- ' 5d quartz planner (360 tb/disc, 13.8 billion years at room)',
1685
- ' qram bucket-brigade model (giovannetti prl 2008)',
1686
- ' engine: import { quantumsim, runbb84, dnavaultencode } from ./quantum',
1687
- ].join('\n');
1688
- }
1689
- export function tiersdemo() {
1690
- const ladder = ['ram ~100ns', 'zram ~500ns', 'tmpfs ~1us', 'mmap ~5us', 'sqlite ~10us', 'r2 ~50us'];
1691
- return [
1692
- 'saddle tiered virtual memory (everything is vram)',
1693
- ' l1 ram working set, ephemeral ~100 ns',
1694
- ' l2 vram compute-bound identity layer spoofed',
1695
- ' l3 storage repos as ram (sqlite kv, lru) ~10 us',
1696
- ' l4 buckets hf 10tb + kaggle 20tb + terabox ~50 us',
1697
- ' physical github artifacts + ghcr + npm cdn (the only disks)',
1698
- ` ladder ${ladder.join(' | ')}`,
1699
- ' autoscale <64mb memfs / <1gb mmap / larger sqlite+r2',
1700
- ' pool >33 tb free across accounts (see tiers.ts FREEPOOL)',
1701
- ' npm-as-disk 200 mb chunks as .bin.js packages on the cdn farm',
1702
- ' vdr 64-bit bigint addressing up to 9.22 eb',
1703
- ' engine: import { creatiersengine } from the package (./tiers)',
1704
- ].join('\n');
1705
- }
1706
- export function streamingdemo(totalbytes = 1649267441664, windowbytes = 4 * 1024 * 1024 * 1024) {
1707
- const layerbytes = 512 * 1024 * 1024;
1708
- const count = Math.ceil(totalbytes / layerbytes);
1709
- const batches = Math.ceil(count / Math.max(1, Math.floor(windowbytes / layerbytes)));
1710
- const fmt = (b) => (b >= 1024 ** 4 ? (b / 1024 ** 4).toFixed(2) + ' TB' : (b / 1024 ** 3).toFixed(1) + ' GB');
1711
- return [
1712
- 'saddle streaming memory plan',
1713
- ` workload: ${fmt(totalbytes)} decomposed into ${count} layers of ${layerbytes / 1024 ** 2} MiB`,
1714
- ` window: ${fmt(windowbytes)} hot-set budget (peak resident)`,
1715
- ` batches: ${batches} load/run/evict passes over the weights`,
1716
- ' technique: mmap + page cache streams each layer on demand; eviction',
1717
- ' after the last consumer keeps the resident set inside the',
1718
- ' window - the workload size is unbounded, only the window',
1719
- ' is fixed. this is how engines run models larger than ram.',
1720
- ].join('\n');
1721
- }