aigpu 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +85 -0
- package/bin/aigpu.js +15 -0
- package/client.d.ts +15 -0
- package/dist/agent-tools.d.ts +61 -0
- package/dist/agent-tools.d.ts.map +1 -0
- package/dist/agent-tools.js +107 -0
- package/dist/agent-tools.js.map +1 -0
- package/dist/agent.d.ts +56 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +243 -0
- package/dist/agent.js.map +1 -0
- package/dist/api-types.d.ts +56 -0
- package/dist/api-types.d.ts.map +1 -0
- package/dist/api-types.js +2 -0
- package/dist/api-types.js.map +1 -0
- package/dist/bind-cache.d.ts +19 -0
- package/dist/bind-cache.d.ts.map +1 -0
- package/dist/bind-cache.js +43 -0
- package/dist/bind-cache.js.map +1 -0
- package/dist/bundle.d.ts +30 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +127 -0
- package/dist/bundle.js.map +1 -0
- package/dist/claim-validation-encode.d.ts +14 -0
- package/dist/claim-validation-encode.d.ts.map +1 -0
- package/dist/claim-validation-encode.js +28 -0
- package/dist/claim-validation-encode.js.map +1 -0
- package/dist/claim-validation.d.ts +73 -0
- package/dist/claim-validation.d.ts.map +1 -0
- package/dist/claim-validation.js +135 -0
- package/dist/claim-validation.js.map +1 -0
- package/dist/cli/bin/aigpu.js +101 -0
- package/dist/cli/lib/check/run.js +153 -0
- package/dist/cli/lib/docs/commands/cat.js +12 -0
- package/dist/cli/lib/docs/commands/find.js +180 -0
- package/dist/cli/lib/docs/commands/grep.js +49 -0
- package/dist/cli/lib/docs/commands/ls.js +41 -0
- package/dist/cli/lib/docs/commands/path.js +12 -0
- package/dist/cli/lib/docs/commands/resolve.js +41 -0
- package/dist/cli/lib/docs/commands/shared.js +13 -0
- package/dist/cli/lib/docs/commands/symbols.js +25 -0
- package/dist/cli/lib/docs/errors.js +8 -0
- package/dist/cli/lib/docs/generate/check-drift.js +61 -0
- package/dist/cli/lib/docs/generate/cli.js +12 -0
- package/dist/cli/lib/docs/generate/generate.js +113 -0
- package/dist/cli/lib/docs/generate/manifest.js +218 -0
- package/dist/cli/lib/docs/generate/skill.js +150 -0
- package/dist/cli/lib/docs/help.js +21 -0
- package/dist/cli/lib/docs/index.js +62 -0
- package/dist/cli/lib/docs/model.js +7 -0
- package/dist/cli/lib/docs/run.js +24 -0
- package/dist/cli/lib/docs/service.js +119 -0
- package/dist/cli/lib/doctor/run.js +236 -0
- package/dist/cli/lib/examples/errors.js +13 -0
- package/dist/cli/lib/examples/hashing.js +9 -0
- package/dist/cli/lib/examples/help.js +13 -0
- package/dist/cli/lib/examples/local-source.js +142 -0
- package/dist/cli/lib/examples/paths.js +12 -0
- package/dist/cli/lib/examples/pull.js +58 -0
- package/dist/cli/lib/examples/run.js +71 -0
- package/dist/cli/lib/examples/search.js +6 -0
- package/dist/cli/lib/examples/service.js +118 -0
- package/dist/cli/lib/generated/docs-manifest.generated.js +4758 -0
- package/dist/cli/lib/install-dawn/run.js +24 -0
- package/dist/cli/lib/install-software-renderer/run.js +17 -0
- package/dist/cli/lib/mcp/http.js +93 -0
- package/dist/cli/lib/mcp/server.js +258 -0
- package/dist/cli/lib/mcp/stdio.js +34 -0
- package/dist/cli/lib/snapshot/png.js +62 -0
- package/dist/cli/lib/snapshot/render.js +22 -0
- package/dist/cli/lib/snapshot/run.js +79 -0
- package/dist/cli/lib/snapshot/shaders.js +11 -0
- package/dist/cli/package.json +4 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/clock.d.ts +26 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +50 -0
- package/dist/clock.js.map +1 -0
- package/dist/compute.d.ts +41 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +132 -0
- package/dist/compute.js.map +1 -0
- package/dist/core/render-bundle.d.ts +30 -0
- package/dist/core/render-bundle.d.ts.map +1 -0
- package/dist/core/render-bundle.js +39 -0
- package/dist/core/render-bundle.js.map +1 -0
- package/dist/core/storage-buffer.d.ts +81 -0
- package/dist/core/storage-buffer.d.ts.map +1 -0
- package/dist/core/storage-buffer.js +91 -0
- package/dist/core/storage-buffer.js.map +1 -0
- package/dist/core/structured-uniform.d.ts +33 -0
- package/dist/core/structured-uniform.d.ts.map +1 -0
- package/dist/core/structured-uniform.js +110 -0
- package/dist/core/structured-uniform.js.map +1 -0
- package/dist/core/uniform-pool-internals.d.ts +12 -0
- package/dist/core/uniform-pool-internals.d.ts.map +1 -0
- package/dist/core/uniform-pool-internals.js +24 -0
- package/dist/core/uniform-pool-internals.js.map +1 -0
- package/dist/core/uniform-pool-types.d.ts +22 -0
- package/dist/core/uniform-pool-types.d.ts.map +1 -0
- package/dist/core/uniform-pool-types.js +2 -0
- package/dist/core/uniform-pool-types.js.map +1 -0
- package/dist/core/uniform-pool.d.ts +22 -0
- package/dist/core/uniform-pool.d.ts.map +1 -0
- package/dist/core/uniform-pool.js +128 -0
- package/dist/core/uniform-pool.js.map +1 -0
- package/dist/core/uniform.d.ts +55 -0
- package/dist/core/uniform.d.ts.map +1 -0
- package/dist/core/uniform.js +64 -0
- package/dist/core/uniform.js.map +1 -0
- package/dist/core/wgsl-alignment.d.ts +24 -0
- package/dist/core/wgsl-alignment.d.ts.map +1 -0
- package/dist/core/wgsl-alignment.js +81 -0
- package/dist/core/wgsl-alignment.js.map +1 -0
- package/dist/core.d.ts +13 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +7 -0
- package/dist/core.js.map +1 -0
- package/dist/dom.d.ts +37 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +81 -0
- package/dist/dom.js.map +1 -0
- package/dist/draw-protocols.d.ts +36 -0
- package/dist/draw-protocols.d.ts.map +1 -0
- package/dist/draw-protocols.js +10 -0
- package/dist/draw-protocols.js.map +1 -0
- package/dist/draw.d.ts +248 -0
- package/dist/draw.d.ts.map +1 -0
- package/dist/draw.js +890 -0
- package/dist/draw.js.map +1 -0
- package/dist/effect.d.ts +53 -0
- package/dist/effect.d.ts.map +1 -0
- package/dist/effect.js +75 -0
- package/dist/effect.js.map +1 -0
- package/dist/entry-metadata.d.ts +16 -0
- package/dist/entry-metadata.d.ts.map +1 -0
- package/dist/entry-metadata.js +27 -0
- package/dist/entry-metadata.js.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +616 -0
- package/dist/errors.js.map +1 -0
- package/dist/frame-protocols.d.ts +107 -0
- package/dist/frame-protocols.d.ts.map +1 -0
- package/dist/frame-protocols.js +20 -0
- package/dist/frame-protocols.js.map +1 -0
- package/dist/frame-state.d.ts +32 -0
- package/dist/frame-state.d.ts.map +1 -0
- package/dist/frame-state.js +62 -0
- package/dist/frame-state.js.map +1 -0
- package/dist/frame.d.ts +127 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +645 -0
- package/dist/frame.js.map +1 -0
- package/dist/group-claim.d.ts +9 -0
- package/dist/group-claim.d.ts.map +1 -0
- package/dist/group-claim.js +7 -0
- package/dist/group-claim.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/indirect.d.ts +39 -0
- package/dist/indirect.d.ts.map +1 -0
- package/dist/indirect.js +61 -0
- package/dist/indirect.js.map +1 -0
- package/dist/init-from-device.d.ts +11 -0
- package/dist/init-from-device.d.ts.map +1 -0
- package/dist/init-from-device.js +56 -0
- package/dist/init-from-device.js.map +1 -0
- package/dist/init.d.ts +12 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +7 -0
- package/dist/init.js.map +1 -0
- package/dist/kernel.d.ts +104 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +151 -0
- package/dist/kernel.js.map +1 -0
- package/dist/lifecycle.d.ts +10 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +21 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/live-kernel.d.ts +48 -0
- package/dist/live-kernel.d.ts.map +1 -0
- package/dist/live-kernel.js +67 -0
- package/dist/live-kernel.js.map +1 -0
- package/dist/mock.d.ts +42 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +29 -0
- package/dist/mock.js.map +1 -0
- package/dist/node.d.ts +48 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +36 -0
- package/dist/node.js.map +1 -0
- package/dist/ping-pong.d.ts +19 -0
- package/dist/ping-pong.d.ts.map +1 -0
- package/dist/ping-pong.js +67 -0
- package/dist/ping-pong.js.map +1 -0
- package/dist/pipeline-store.d.ts +89 -0
- package/dist/pipeline-store.d.ts.map +1 -0
- package/dist/pipeline-store.js +366 -0
- package/dist/pipeline-store.js.map +1 -0
- package/dist/query-ring.d.ts +91 -0
- package/dist/query-ring.d.ts.map +1 -0
- package/dist/query-ring.js +178 -0
- package/dist/query-ring.js.map +1 -0
- package/dist/render-service.d.ts +23 -0
- package/dist/render-service.d.ts.map +1 -0
- package/dist/render-service.js +37 -0
- package/dist/render-service.js.map +1 -0
- package/dist/sampler.d.ts +19 -0
- package/dist/sampler.d.ts.map +1 -0
- package/dist/sampler.js +46 -0
- package/dist/sampler.js.map +1 -0
- package/dist/scene/camera.d.ts +91 -0
- package/dist/scene/camera.d.ts.map +1 -0
- package/dist/scene/camera.js +221 -0
- package/dist/scene/camera.js.map +1 -0
- package/dist/scene/geometries.d.ts +26 -0
- package/dist/scene/geometries.d.ts.map +1 -0
- package/dist/scene/geometries.js +26 -0
- package/dist/scene/geometries.js.map +1 -0
- package/dist/scene/geometry-descriptor.d.ts +120 -0
- package/dist/scene/geometry-descriptor.d.ts.map +1 -0
- package/dist/scene/geometry-descriptor.js +378 -0
- package/dist/scene/geometry-descriptor.js.map +1 -0
- package/dist/scene/geometry-recipe.d.ts +41 -0
- package/dist/scene/geometry-recipe.d.ts.map +1 -0
- package/dist/scene/geometry-recipe.js +40 -0
- package/dist/scene/geometry-recipe.js.map +1 -0
- package/dist/scene/geometry-src/camera-math.d.ts +6 -0
- package/dist/scene/geometry-src/camera-math.d.ts.map +1 -0
- package/dist/scene/geometry-src/camera-math.js +47 -0
- package/dist/scene/geometry-src/camera-math.js.map +1 -0
- package/dist/scene/geometry-src/camera.d.ts +10 -0
- package/dist/scene/geometry-src/camera.d.ts.map +1 -0
- package/dist/scene/geometry-src/camera.js +2 -0
- package/dist/scene/geometry-src/camera.js.map +1 -0
- package/dist/scene/geometry-src/capsule-data.d.ts +10 -0
- package/dist/scene/geometry-src/capsule-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/capsule-data.js +61 -0
- package/dist/scene/geometry-src/capsule-data.js.map +1 -0
- package/dist/scene/geometry-src/cone-data.d.ts +13 -0
- package/dist/scene/geometry-src/cone-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/cone-data.js +52 -0
- package/dist/scene/geometry-src/cone-data.js.map +1 -0
- package/dist/scene/geometry-src/cylinder-data.d.ts +14 -0
- package/dist/scene/geometry-src/cylinder-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/cylinder-data.js +58 -0
- package/dist/scene/geometry-src/cylinder-data.js.map +1 -0
- package/dist/scene/geometry-src/deg-to-rad.d.ts +2 -0
- package/dist/scene/geometry-src/deg-to-rad.d.ts.map +1 -0
- package/dist/scene/geometry-src/deg-to-rad.js +4 -0
- package/dist/scene/geometry-src/deg-to-rad.js.map +1 -0
- package/dist/scene/geometry-src/disk-data.d.ts +12 -0
- package/dist/scene/geometry-src/disk-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/disk-data.js +21 -0
- package/dist/scene/geometry-src/disk-data.js.map +1 -0
- package/dist/scene/geometry-src/errors.d.ts +3 -0
- package/dist/scene/geometry-src/errors.d.ts.map +1 -0
- package/dist/scene/geometry-src/errors.js +5 -0
- package/dist/scene/geometry-src/errors.js.map +1 -0
- package/dist/scene/geometry-src/icosphere-data.d.ts +8 -0
- package/dist/scene/geometry-src/icosphere-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/icosphere-data.js +58 -0
- package/dist/scene/geometry-src/icosphere-data.js.map +1 -0
- package/dist/scene/geometry-src/index.d.ts +36 -0
- package/dist/scene/geometry-src/index.d.ts.map +1 -0
- package/dist/scene/geometry-src/index.js +21 -0
- package/dist/scene/geometry-src/index.js.map +1 -0
- package/dist/scene/geometry-src/mesh-box.d.ts +8 -0
- package/dist/scene/geometry-src/mesh-box.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-box.js +57 -0
- package/dist/scene/geometry-src/mesh-box.js.map +1 -0
- package/dist/scene/geometry-src/mesh-cache.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-cache.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-cache.js +14 -0
- package/dist/scene/geometry-src/mesh-cache.js.map +1 -0
- package/dist/scene/geometry-src/mesh-capsule.d.ts +14 -0
- package/dist/scene/geometry-src/mesh-capsule.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-capsule.js +52 -0
- package/dist/scene/geometry-src/mesh-capsule.js.map +1 -0
- package/dist/scene/geometry-src/mesh-cone.d.ts +16 -0
- package/dist/scene/geometry-src/mesh-cone.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-cone.js +54 -0
- package/dist/scene/geometry-src/mesh-cone.js.map +1 -0
- package/dist/scene/geometry-src/mesh-cylinder.d.ts +18 -0
- package/dist/scene/geometry-src/mesh-cylinder.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-cylinder.js +67 -0
- package/dist/scene/geometry-src/mesh-cylinder.js.map +1 -0
- package/dist/scene/geometry-src/mesh-disk.d.ts +11 -0
- package/dist/scene/geometry-src/mesh-disk.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-disk.js +45 -0
- package/dist/scene/geometry-src/mesh-disk.js.map +1 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.d.ts +7 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.js +39 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.js.map +1 -0
- package/dist/scene/geometry-src/mesh-icosahedron.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-icosahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-icosahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-icosahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-icosphere.d.ts +11 -0
- package/dist/scene/geometry-src/mesh-icosphere.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-icosphere.js +47 -0
- package/dist/scene/geometry-src/mesh-icosphere.js.map +1 -0
- package/dist/scene/geometry-src/mesh-octahedron.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-octahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-octahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-octahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-plane.d.ts +13 -0
- package/dist/scene/geometry-src/mesh-plane.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-plane.js +51 -0
- package/dist/scene/geometry-src/mesh-plane.js.map +1 -0
- package/dist/scene/geometry-src/mesh-ring.d.ts +12 -0
- package/dist/scene/geometry-src/mesh-ring.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-ring.js +47 -0
- package/dist/scene/geometry-src/mesh-ring.js.map +1 -0
- package/dist/scene/geometry-src/mesh-sphere.d.ts +10 -0
- package/dist/scene/geometry-src/mesh-sphere.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-sphere.js +51 -0
- package/dist/scene/geometry-src/mesh-sphere.js.map +1 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.d.ts +5 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-torus.d.ts +17 -0
- package/dist/scene/geometry-src/mesh-torus.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-torus.js +56 -0
- package/dist/scene/geometry-src/mesh-torus.js.map +1 -0
- package/dist/scene/geometry-src/mesh-types.d.ts +38 -0
- package/dist/scene/geometry-src/mesh-types.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-types.js +2 -0
- package/dist/scene/geometry-src/mesh-types.js.map +1 -0
- package/dist/scene/geometry-src/mesh.d.ts +61 -0
- package/dist/scene/geometry-src/mesh.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh.js +49 -0
- package/dist/scene/geometry-src/mesh.js.map +1 -0
- package/dist/scene/geometry-src/orthographic-camera.d.ts +14 -0
- package/dist/scene/geometry-src/orthographic-camera.d.ts.map +1 -0
- package/dist/scene/geometry-src/orthographic-camera.js +9 -0
- package/dist/scene/geometry-src/orthographic-camera.js.map +1 -0
- package/dist/scene/geometry-src/perspective-camera.d.ts +12 -0
- package/dist/scene/geometry-src/perspective-camera.d.ts.map +1 -0
- package/dist/scene/geometry-src/perspective-camera.js +9 -0
- package/dist/scene/geometry-src/perspective-camera.js.map +1 -0
- package/dist/scene/geometry-src/plane-data.d.ts +12 -0
- package/dist/scene/geometry-src/plane-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/plane-data.js +25 -0
- package/dist/scene/geometry-src/plane-data.js.map +1 -0
- package/dist/scene/geometry-src/polyhedron-data.d.ts +7 -0
- package/dist/scene/geometry-src/polyhedron-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/polyhedron-data.js +42 -0
- package/dist/scene/geometry-src/polyhedron-data.js.map +1 -0
- package/dist/scene/geometry-src/polyhedron-mesh.d.ts +10 -0
- package/dist/scene/geometry-src/polyhedron-mesh.d.ts.map +1 -0
- package/dist/scene/geometry-src/polyhedron-mesh.js +37 -0
- package/dist/scene/geometry-src/polyhedron-mesh.js.map +1 -0
- package/dist/scene/geometry-src/polyhedron-seeds.d.ts +6 -0
- package/dist/scene/geometry-src/polyhedron-seeds.d.ts.map +1 -0
- package/dist/scene/geometry-src/polyhedron-seeds.js +51 -0
- package/dist/scene/geometry-src/polyhedron-seeds.js.map +1 -0
- package/dist/scene/geometry-src/primitive-data-utils.d.ts +9 -0
- package/dist/scene/geometry-src/primitive-data-utils.d.ts.map +1 -0
- package/dist/scene/geometry-src/primitive-data-utils.js +36 -0
- package/dist/scene/geometry-src/primitive-data-utils.js.map +1 -0
- package/dist/scene/geometry-src/ring-data.d.ts +13 -0
- package/dist/scene/geometry-src/ring-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/ring-data.js +24 -0
- package/dist/scene/geometry-src/ring-data.js.map +1 -0
- package/dist/scene/geometry-src/sphere-data.d.ts +11 -0
- package/dist/scene/geometry-src/sphere-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/sphere-data.js +31 -0
- package/dist/scene/geometry-src/sphere-data.js.map +1 -0
- package/dist/scene/geometry-src/srgb.d.ts +5 -0
- package/dist/scene/geometry-src/srgb.d.ts.map +1 -0
- package/dist/scene/geometry-src/srgb.js +23 -0
- package/dist/scene/geometry-src/srgb.js.map +1 -0
- package/dist/scene/geometry-src/torus-data.d.ts +11 -0
- package/dist/scene/geometry-src/torus-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/torus-data.js +38 -0
- package/dist/scene/geometry-src/torus-data.js.map +1 -0
- package/dist/scene/geometry-src/vertex-layout.d.ts +2 -0
- package/dist/scene/geometry-src/vertex-layout.d.ts.map +1 -0
- package/dist/scene/geometry-src/vertex-layout.js +2 -0
- package/dist/scene/geometry-src/vertex-layout.js.map +1 -0
- package/dist/scene/geometry.d.ts +70 -0
- package/dist/scene/geometry.d.ts.map +1 -0
- package/dist/scene/geometry.js +71 -0
- package/dist/scene/geometry.js.map +1 -0
- package/dist/scene/orbit.d.ts +9 -0
- package/dist/scene/orbit.d.ts.map +1 -0
- package/dist/scene/orbit.js +15 -0
- package/dist/scene/orbit.js.map +1 -0
- package/dist/scene/tree/errors.d.ts +4 -0
- package/dist/scene/tree/errors.d.ts.map +1 -0
- package/dist/scene/tree/errors.js +19 -0
- package/dist/scene/tree/errors.js.map +1 -0
- package/dist/scene/tree/light.d.ts +42 -0
- package/dist/scene/tree/light.d.ts.map +1 -0
- package/dist/scene/tree/light.js +102 -0
- package/dist/scene/tree/light.js.map +1 -0
- package/dist/scene/tree/material.d.ts +79 -0
- package/dist/scene/tree/material.d.ts.map +1 -0
- package/dist/scene/tree/material.js +117 -0
- package/dist/scene/tree/material.js.map +1 -0
- package/dist/scene/tree/math.d.ts +26 -0
- package/dist/scene/tree/math.d.ts.map +1 -0
- package/dist/scene/tree/math.js +217 -0
- package/dist/scene/tree/math.js.map +1 -0
- package/dist/scene/tree/mesh-node.d.ts +17 -0
- package/dist/scene/tree/mesh-node.d.ts.map +1 -0
- package/dist/scene/tree/mesh-node.js +24 -0
- package/dist/scene/tree/mesh-node.js.map +1 -0
- package/dist/scene/tree/node.d.ts +70 -0
- package/dist/scene/tree/node.d.ts.map +1 -0
- package/dist/scene/tree/node.js +220 -0
- package/dist/scene/tree/node.js.map +1 -0
- package/dist/scene/tree/orbit-controls.d.ts +69 -0
- package/dist/scene/tree/orbit-controls.d.ts.map +1 -0
- package/dist/scene/tree/orbit-controls.js +226 -0
- package/dist/scene/tree/orbit-controls.js.map +1 -0
- package/dist/scene.d.ts +21 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +11 -0
- package/dist/scene.js.map +1 -0
- package/dist/set-core.d.ts +50 -0
- package/dist/set-core.d.ts.map +1 -0
- package/dist/set-core.js +268 -0
- package/dist/set-core.js.map +1 -0
- package/dist/set-layouts.d.ts +20 -0
- package/dist/set-layouts.d.ts.map +1 -0
- package/dist/set-layouts.js +103 -0
- package/dist/set-layouts.js.map +1 -0
- package/dist/set-packing.d.ts +4 -0
- package/dist/set-packing.d.ts.map +1 -0
- package/dist/set-packing.js +82 -0
- package/dist/set-packing.js.map +1 -0
- package/dist/set-resources.d.ts +20 -0
- package/dist/set-resources.d.ts.map +1 -0
- package/dist/set-resources.js +140 -0
- package/dist/set-resources.js.map +1 -0
- package/dist/shader-source.d.ts +4 -0
- package/dist/shader-source.d.ts.map +1 -0
- package/dist/shader-source.js +21 -0
- package/dist/shader-source.js.map +1 -0
- package/dist/storage.d.ts +38 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +76 -0
- package/dist/storage.js.map +1 -0
- package/dist/surface.d.ts +89 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +243 -0
- package/dist/surface.js.map +1 -0
- package/dist/target-offscreen.d.ts +41 -0
- package/dist/target-offscreen.d.ts.map +1 -0
- package/dist/target-offscreen.js +115 -0
- package/dist/target-offscreen.js.map +1 -0
- package/dist/target-utils.d.ts +37 -0
- package/dist/target-utils.d.ts.map +1 -0
- package/dist/target-utils.js +103 -0
- package/dist/target-utils.js.map +1 -0
- package/dist/target.d.ts +65 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +2 -0
- package/dist/target.js.map +1 -0
- package/dist/timer.d.ts +78 -0
- package/dist/timer.d.ts.map +1 -0
- package/dist/timer.js +261 -0
- package/dist/timer.js.map +1 -0
- package/dist/uniforms-layout.d.ts +8 -0
- package/dist/uniforms-layout.d.ts.map +1 -0
- package/dist/uniforms-layout.js +84 -0
- package/dist/uniforms-layout.js.map +1 -0
- package/dist/uniforms.d.ts +43 -0
- package/dist/uniforms.d.ts.map +1 -0
- package/dist/uniforms.js +176 -0
- package/dist/uniforms.js.map +1 -0
- package/dist/visibility.d.ts +118 -0
- package/dist/visibility.d.ts.map +1 -0
- package/dist/visibility.js +290 -0
- package/dist/visibility.js.map +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { findDocs } from "./commands/find.js";
|
|
2
|
+
import { grepDocs } from "./commands/grep.js";
|
|
3
|
+
import { listDocs } from "./commands/ls.js";
|
|
4
|
+
import { resolveDocsTarget } from "./commands/resolve.js";
|
|
5
|
+
import { listDocSymbols } from "./commands/symbols.js";
|
|
6
|
+
import { DocsError } from "./errors.js";
|
|
7
|
+
import { buildIndex, normalizePath } from "./index.js";
|
|
8
|
+
|
|
9
|
+
export function createDocsService(index = buildIndex()) {
|
|
10
|
+
return {
|
|
11
|
+
execute(input) {
|
|
12
|
+
if (input.operation === "read" || input.operation === "resolve") {
|
|
13
|
+
const resolved = resolveOne(index, input.target);
|
|
14
|
+
if (input.operation === "resolve") {
|
|
15
|
+
return {
|
|
16
|
+
operation: "resolve",
|
|
17
|
+
target: input.target,
|
|
18
|
+
...recordMetadata(resolved),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
operation: "read",
|
|
23
|
+
document: {
|
|
24
|
+
...recordMetadata(resolved),
|
|
25
|
+
summary: resolved.summary,
|
|
26
|
+
content: resolved.content.trimEnd(),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (input.operation === "list") {
|
|
31
|
+
const path = normalizePath(input.path ?? "/");
|
|
32
|
+
const entries = listDocs(index, path);
|
|
33
|
+
if (!entries) {
|
|
34
|
+
throw new DocsError("AIGPU-DOCS-NOT-FOUND", `Path not found: ${path}`, {
|
|
35
|
+
target: path,
|
|
36
|
+
lookup: "path",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
operation: "list",
|
|
41
|
+
path,
|
|
42
|
+
entries,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (input.operation === "grep") {
|
|
46
|
+
const matches = grepDocs(index, {
|
|
47
|
+
pattern: input.pattern,
|
|
48
|
+
ignoreCase: input.ignoreCase,
|
|
49
|
+
packageName: input.package,
|
|
50
|
+
});
|
|
51
|
+
if (matches.length === 0) {
|
|
52
|
+
throw new DocsError("AIGPU-DOCS-NOT-FOUND", `No matches for: ${input.pattern}`, {
|
|
53
|
+
pattern: input.pattern,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const limit = Math.min(Math.max(input.limit ?? 50, 1), 200);
|
|
57
|
+
return {
|
|
58
|
+
operation: "grep",
|
|
59
|
+
pattern: input.pattern,
|
|
60
|
+
truncated: matches.length > limit,
|
|
61
|
+
matches: matches.slice(0, limit),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (input.operation === "symbols") {
|
|
65
|
+
const query = input.query?.toLowerCase();
|
|
66
|
+
const symbols = listDocSymbols(index)
|
|
67
|
+
.filter((symbol) => !input.package || matchesPackage(symbol.package, input.package))
|
|
68
|
+
.filter((symbol) => !query || Object.values(symbol).some((value) => value.toLowerCase().includes(query)));
|
|
69
|
+
const limit = Math.min(Math.max(input.limit ?? 50, 1), 200);
|
|
70
|
+
return {
|
|
71
|
+
operation: "symbols",
|
|
72
|
+
truncated: symbols.length > limit,
|
|
73
|
+
symbols: symbols.slice(0, limit),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (input.operation !== "search") throw new Error(`Unsupported docs operation: ${input.operation}`);
|
|
77
|
+
const found = findDocs(index, input.query);
|
|
78
|
+
if (found.results.length === 0) {
|
|
79
|
+
throw new DocsError("AIGPU-DOCS-NOT-FOUND", `No docs found for: ${input.query}`, {
|
|
80
|
+
query: input.query,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
operation: "search",
|
|
85
|
+
truncated: found.truncated,
|
|
86
|
+
results: found.results,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function matchesPackage(recordPackage, filter) {
|
|
93
|
+
return recordPackage === filter || recordPackage.startsWith(`${filter}/`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveOne(index, target) {
|
|
97
|
+
const { resolved, lookup } = resolveDocsTarget(index, target);
|
|
98
|
+
if (!resolved) {
|
|
99
|
+
const kind = lookup === "path" ? "Path" : "Symbol";
|
|
100
|
+
throw new DocsError("AIGPU-DOCS-NOT-FOUND", `${kind} not found: ${target}`, { target, lookup });
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(resolved)) {
|
|
103
|
+
throw new DocsError("AIGPU-DOCS-AMBIGUOUS", `Ambiguous symbol: ${target}`, {
|
|
104
|
+
target,
|
|
105
|
+
candidates: resolved.map(recordMetadata),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return resolved;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function recordMetadata(record) {
|
|
112
|
+
return {
|
|
113
|
+
symbol: record.symbol,
|
|
114
|
+
package: record.package,
|
|
115
|
+
virtualPath: record.virtualPath,
|
|
116
|
+
repoPath: record.repoPath,
|
|
117
|
+
kind: record.kind,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const statuses = new Set(["ok", "warn", "fail", "skip"]);
|
|
9
|
+
|
|
10
|
+
export const probeRegistry = [
|
|
11
|
+
{ id: "binary-resolution", platforms: ["all"], run: probeBinaryResolution },
|
|
12
|
+
{ id: "binary-compatibility", platforms: ["all"], run: probeBinaryCompatibility },
|
|
13
|
+
{ id: "software-renderer-cache", platforms: ["all"], run: probeSoftwareRendererCache },
|
|
14
|
+
{ id: "linux-glibc", platforms: ["linux"], run: probeGlibc },
|
|
15
|
+
{ id: "linux-vulkan-loader", platforms: ["linux"], run: probeVulkanLoader },
|
|
16
|
+
{ id: "linux-vulkan-icd", platforms: ["linux"], run: probeVulkanIcd },
|
|
17
|
+
{ id: "linux-mesa", platforms: ["linux"], run: probeMesa },
|
|
18
|
+
{ id: "linux-display", platforms: ["linux"], run: probeDisplay },
|
|
19
|
+
{ id: "macos-version", platforms: ["darwin"], run: probeMacVersion },
|
|
20
|
+
{ id: "macos-architecture", platforms: ["darwin"], run: probeMacArchitecture },
|
|
21
|
+
{ id: "windows-version", platforms: ["win32"], run: probeWindowsVersion },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export async function runProbeRegistry(registry, context) {
|
|
25
|
+
const findings = [];
|
|
26
|
+
for (const probe of registry) {
|
|
27
|
+
if (!probe.platforms.includes("all") && !probe.platforms.includes(context.platform)) continue;
|
|
28
|
+
const result = await probe.run(context);
|
|
29
|
+
if (!statuses.has(result.status)) throw new Error(`Invalid status from probe ${probe.id}: ${result.status}`);
|
|
30
|
+
findings.push({ probe: probe.id, ...result });
|
|
31
|
+
}
|
|
32
|
+
return findings;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function runDoctor(args, overrides = {}) {
|
|
36
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
37
|
+
return { code: 0, stdout: "Usage: aigpu doctor [--no-render] [--pretty]\n\nDiagnose whether this machine can render headless with aigpu/node. JSON is written by default.\n" };
|
|
38
|
+
}
|
|
39
|
+
const unknown = args.find((arg) => arg !== "--no-render" && arg !== "--pretty");
|
|
40
|
+
if (unknown) return { code: 1, stderr: `Unknown doctor option: ${unknown}\n` };
|
|
41
|
+
|
|
42
|
+
const context = createContext(overrides);
|
|
43
|
+
const findings = await runProbeRegistry(probeRegistry, context);
|
|
44
|
+
let adapter = null;
|
|
45
|
+
let verdict = "unverified";
|
|
46
|
+
|
|
47
|
+
if (args.includes("--no-render")) {
|
|
48
|
+
findings.push({ probe: "render", status: "skip", evidence: "Real render skipped by --no-render; health is unverified." });
|
|
49
|
+
} else {
|
|
50
|
+
let rendered;
|
|
51
|
+
try {
|
|
52
|
+
rendered = await context.render();
|
|
53
|
+
adapter = rendered.adapter;
|
|
54
|
+
verdict = "healthy";
|
|
55
|
+
findings.push({ probe: "render", status: "ok", evidence: `Rendered and read back a 16x16 offscreen target with ${adapter.name} (${adapter.type}).` });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
verdict = "unhealthy";
|
|
58
|
+
findings.push({ probe: "render", status: "fail", evidence: errorEvidence(error), prescription: lavapipePrescription(context.osRelease) });
|
|
59
|
+
} finally {
|
|
60
|
+
rendered?.dispose?.();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const report = { verdict, adapter, findings };
|
|
65
|
+
return { code: verdict === "healthy" ? 0 : 1, stdout: args.includes("--pretty") ? prettyReport(report) : `${JSON.stringify(report)}\n` };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createContext(overrides) {
|
|
69
|
+
const env = overrides.env ?? process.env;
|
|
70
|
+
const platform = overrides.platform ?? process.platform;
|
|
71
|
+
const command = overrides.command ?? defaultCommand;
|
|
72
|
+
return {
|
|
73
|
+
platform,
|
|
74
|
+
arch: overrides.arch ?? process.arch,
|
|
75
|
+
env,
|
|
76
|
+
osRelease: overrides.osRelease ?? (platform === "linux" ? parseOsRelease(safeRead("/etc/os-release")) : {}),
|
|
77
|
+
exists: overrides.exists ?? existsSync,
|
|
78
|
+
listIcds: overrides.listIcds ?? (() => defaultIcdFiles(env)),
|
|
79
|
+
command,
|
|
80
|
+
glibc: overrides.glibc ?? runtimeGlibc(),
|
|
81
|
+
release: overrides.release ?? command(platform === "win32" ? "cmd" : "uname", platform === "win32" ? ["/c", "ver"] : ["-r"]),
|
|
82
|
+
render: overrides.render ?? realRender,
|
|
83
|
+
softwareRenderer: overrides.softwareRenderer ?? softwareRendererState,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function probeBinaryResolution(context) {
|
|
88
|
+
if (context.env.AIGPU_DAWN_BINARY) {
|
|
89
|
+
const path = resolve(context.env.AIGPU_DAWN_BINARY);
|
|
90
|
+
return context.exists(path)
|
|
91
|
+
? { status: "ok", evidence: `AIGPU_DAWN_BINARY resolves to ${path}.` }
|
|
92
|
+
: { status: "fail", evidence: `AIGPU_DAWN_BINARY points to missing file ${path}.`, prescription: "Set AIGPU_DAWN_BINARY to a verified Dawn .node file, or unset it to use the normal resolution chain." };
|
|
93
|
+
}
|
|
94
|
+
const cache = context.env.AIGPU_CACHE_DIR ?? context.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
|
|
95
|
+
try {
|
|
96
|
+
const stock = require.resolve("webgpu");
|
|
97
|
+
return { status: "ok", evidence: `Normal chain will try aigpu cache under ${cache}, then stock webgpu at ${stock}, then the downloadable prebuild.` };
|
|
98
|
+
} catch {
|
|
99
|
+
return { status: "warn", evidence: `No stock webgpu package resolved; normal chain will try aigpu cache under ${cache}, then download the portable Dawn prebuild if supported.` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function probeSoftwareRendererCache(context) {
|
|
104
|
+
const state = await (context.softwareRenderer?.() ?? { path: null, error: null });
|
|
105
|
+
if (state.path) return { status: "ok", evidence: `Portable software renderer 25.0.7-aigpu.1 is cached and sha256-verified at ${state.path}.` };
|
|
106
|
+
return { status: "skip", evidence: state.error ? `Portable software renderer cache is unusable: ${state.error}` : "Portable software renderer is not cached (it is installed only with explicit consent)." };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function probeBinaryCompatibility(context) {
|
|
110
|
+
const path = context.env.AIGPU_DAWN_BINARY;
|
|
111
|
+
if (!path) return { status: "ok", evidence: `Normal Dawn resolution selects a binary for process ${context.platform}/${context.arch}.` };
|
|
112
|
+
const lower = path.toLowerCase();
|
|
113
|
+
const namedArch = ["arm64", "x64", "ia32"].find((arch) => lower.includes(arch));
|
|
114
|
+
const namedPlatform = lower.includes("linux") ? "linux" : lower.includes("darwin") || lower.includes("macos") ? "darwin" : lower.includes("win32") || lower.includes("windows") ? "win32" : null;
|
|
115
|
+
if ((namedArch && namedArch !== context.arch) || (namedPlatform && namedPlatform !== context.platform)) {
|
|
116
|
+
return { status: "fail", evidence: `${path} appears to target ${namedPlatform ?? "unknown platform"}/${namedArch ?? "unknown architecture"}, but this process is ${context.platform}/${context.arch}.`, prescription: "Use a Dawn .node binary matching process.platform and process.arch." };
|
|
117
|
+
}
|
|
118
|
+
return { status: "ok", evidence: `${path} has no filename mismatch with process ${context.platform}/${context.arch}; native loading is verified by the render probe.` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function probeGlibc(context) {
|
|
122
|
+
return context.glibc ? { status: "ok", evidence: `glibc ${context.glibc}.` } : { status: "warn", evidence: "glibc version could not be detected (the host may use another libc)." };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function probeVulkanLoader(context) {
|
|
126
|
+
const paths = ["/usr/lib/libvulkan.so.1", "/usr/lib64/libvulkan.so.1", "/usr/lib/x86_64-linux-gnu/libvulkan.so.1", "/usr/lib/aarch64-linux-gnu/libvulkan.so.1"];
|
|
127
|
+
const found = paths.find(context.exists);
|
|
128
|
+
const ldconfig = context.command("ldconfig", ["-p"]);
|
|
129
|
+
if (found || /libvulkan\.so\.1/u.test(ldconfig ?? "")) return { status: "ok", evidence: found ? `Vulkan loader found at ${found}.` : "Vulkan loader libvulkan.so.1 is registered with ldconfig." };
|
|
130
|
+
return { status: "fail", evidence: "Vulkan loader libvulkan.so.1 was not found.", prescription: prescriptionsFor(context.osRelease).install };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function probeVulkanIcd(context) {
|
|
134
|
+
const files = context.listIcds();
|
|
135
|
+
if (files.length) return { status: "ok", evidence: `Vulkan ICD manifests: ${files.join(", ")}.` };
|
|
136
|
+
const softwareRenderer = await (context.softwareRenderer?.() ?? { path: null });
|
|
137
|
+
if (softwareRenderer.path) return { status: "skip", evidence: "No system Vulkan ICD manifest was found; the cached portable software renderer is available as the automatic last resort." };
|
|
138
|
+
return { status: "fail", evidence: "No Vulkan ICD manifest was found in /usr/share/vulkan/icd.d, VK_ICD_FILENAMES, or VK_DRIVER_FILES.", prescription: lavapipePrescription(context.osRelease) };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function probeMesa(context) {
|
|
142
|
+
const outputs = [context.command("glxinfo", ["-B"]), context.command("vulkaninfo", ["--summary"])].filter(Boolean).join("\n");
|
|
143
|
+
const match = outputs.match(/Mesa[^\d]*(\d+)\.(\d+)(?:\.\d+)?/iu);
|
|
144
|
+
if (!match) return { status: "skip", evidence: "Mesa version was not detected; the render probe verifies the active driver." };
|
|
145
|
+
const version = `${match[1]}.${match[2]}`;
|
|
146
|
+
return Number(match[1]) < 23
|
|
147
|
+
? { status: "warn", evidence: `Mesa ${version} detected; Mesa 23 or newer is recommended for lavapipe.`, prescription: prescriptionsFor(context.osRelease).install }
|
|
148
|
+
: { status: "ok", evidence: `Mesa ${version} detected (>=23).` };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function probeDisplay(context) {
|
|
152
|
+
const display = context.env.DISPLAY;
|
|
153
|
+
const wayland = context.env.WAYLAND_DISPLAY;
|
|
154
|
+
return !display && !wayland
|
|
155
|
+
? { status: "ok", evidence: "DISPLAY and WAYLAND_DISPLAY are unset; this is valid for headless Vulkan." }
|
|
156
|
+
: { status: "ok", evidence: `Display state: DISPLAY=${display ?? "unset"}, WAYLAND_DISPLAY=${wayland ?? "unset"}; Dawn may select its OpenGL path.` };
|
|
157
|
+
}
|
|
158
|
+
function probeMacVersion(context) { return { status: "ok", evidence: `macOS ${context.command("sw_vers", ["-productVersion"]) ?? context.release ?? "unknown"}.` }; }
|
|
159
|
+
function probeMacArchitecture(context) {
|
|
160
|
+
const translated = context.command("sysctl", ["-in", "sysctl.proc_translated"]) === "1";
|
|
161
|
+
return { status: "ok", evidence: `${context.arch}${translated ? " under Rosetta 2" : " native process"}; binary compatibility is verified by loading and rendering.` };
|
|
162
|
+
}
|
|
163
|
+
function probeWindowsVersion(context) { return { status: "ok", evidence: `Windows ${context.release ?? "unknown"}; D3D12 availability is verified by the render probe.` }; }
|
|
164
|
+
|
|
165
|
+
export function prescriptionsFor(osRelease) {
|
|
166
|
+
const ids = `${osRelease.ID ?? ""} ${osRelease.ID_LIKE ?? ""}`.toLowerCase();
|
|
167
|
+
const install = /debian|ubuntu/u.test(ids)
|
|
168
|
+
? "apt-get update && apt-get install -y libvulkan1 libdrm2 zlib1g libzstd1 libudev1 mesa-vulkan-drivers"
|
|
169
|
+
: /fedora|rhel|centos|amzn/u.test(ids)
|
|
170
|
+
? "dnf install -y vulkan-loader libdrm zlib libzstd systemd-libs mesa-vulkan-drivers"
|
|
171
|
+
: "Install the Vulkan loader, libdrm, zlib, zstd, libudev, and Mesa Vulkan drivers (lavapipe) with this distribution's package manager.";
|
|
172
|
+
const discoverIcd = "$(find /usr/share/vulkan/icd.d -name 'lvp_icd*.json' | head -1)";
|
|
173
|
+
return { install, env: `export VK_ICD_FILENAMES=${discoverIcd}\nexport VK_DRIVER_FILES=$VK_ICD_FILENAMES` };
|
|
174
|
+
}
|
|
175
|
+
function lavapipePrescription(osRelease) {
|
|
176
|
+
const prescription = prescriptionsFor(osRelease);
|
|
177
|
+
return `run: npx aigpu install-software-renderer\nAlternative (system packages): ${prescription.install}\n${prescription.env}`;
|
|
178
|
+
}
|
|
179
|
+
async function softwareRendererState() {
|
|
180
|
+
try {
|
|
181
|
+
const { getCachedSoftwareRenderer } = await import("@aigpu/adapter-node/install-software-renderer");
|
|
182
|
+
return { path: getCachedSoftwareRenderer(), error: null };
|
|
183
|
+
} catch (error) { return { path: null, error: errorEvidence(error) }; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function realRender() {
|
|
187
|
+
const { init, effect, frame, target } = await import("aigpu/node");
|
|
188
|
+
const gpu = await init();
|
|
189
|
+
try {
|
|
190
|
+
const colorTarget = target(gpu, { size: [16, 16], format: "rgba8unorm", label: "aigpu-doctor" });
|
|
191
|
+
const probe = effect(gpu, "@fragment fn main() -> @location(0) vec4f { return vec4f(0.25, 0.5, 0.75, 1.0); }");
|
|
192
|
+
frame(gpu, (current) => current.pass({ target: colorTarget }, (encoder) => encoder.draw(probe)));
|
|
193
|
+
const pixels = await colorTarget.read();
|
|
194
|
+
if (!pixels || pixels.byteLength < 16 * 16 * 4) throw new Error("render readback returned too few bytes");
|
|
195
|
+
const info = gpu.device?.adapterInfo ?? {};
|
|
196
|
+
const name = info.description || info.device || info.vendor || "unknown adapter";
|
|
197
|
+
const type = info.adapterType || info.type || (/llvmpipe|lavapipe|swiftshader|software|cpu/iu.test(name) ? "cpu" : "unknown");
|
|
198
|
+
return { adapter: { name: String(name), type: String(type) }, dispose: () => gpu.dispose() };
|
|
199
|
+
} catch (error) {
|
|
200
|
+
gpu.dispose();
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function parseOsRelease(contents) {
|
|
206
|
+
const result = {};
|
|
207
|
+
for (const line of contents.split(/\r?\n/u)) {
|
|
208
|
+
const match = line.match(/^([A-Z][A-Z0-9_]*)=(.*)$/u);
|
|
209
|
+
if (match) result[match[1]] = match[2].replace(/^(["'])(.*)\1$/u, "$2");
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
function defaultIcdFiles(env) {
|
|
214
|
+
const explicit = [env.VK_ICD_FILENAMES, env.VK_DRIVER_FILES].filter(Boolean).flatMap((value) => value.split(":"));
|
|
215
|
+
let scanned = [];
|
|
216
|
+
try { scanned = readdirSync("/usr/share/vulkan/icd.d").filter((name) => name.endsWith(".json")).map((name) => join("/usr/share/vulkan/icd.d", name)); } catch {}
|
|
217
|
+
return [...new Set([...explicit, ...scanned])].filter(Boolean);
|
|
218
|
+
}
|
|
219
|
+
function defaultCommand(command, args) {
|
|
220
|
+
const result = spawnSync(command, args, { encoding: "utf8", timeout: 3000 });
|
|
221
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
222
|
+
}
|
|
223
|
+
function runtimeGlibc() { try { return process.report?.getReport()?.header?.glibcVersionRuntime ?? null; } catch { return null; } }
|
|
224
|
+
function safeRead(path) { try { return readFileSync(path, "utf8"); } catch { return ""; } }
|
|
225
|
+
function errorEvidence(error) {
|
|
226
|
+
const details = error && typeof error === "object" ? error : {};
|
|
227
|
+
return `${details.code ? `${details.code}: ` : ""}${String(details.message ?? error)}`;
|
|
228
|
+
}
|
|
229
|
+
function prettyReport(report) {
|
|
230
|
+
const lines = [`Verdict: ${report.verdict}`, `Adapter: ${report.adapter ? `${report.adapter.name} (${report.adapter.type})` : "none"}`, "", "Findings:"];
|
|
231
|
+
for (const finding of report.findings) {
|
|
232
|
+
lines.push(` [${finding.status.toUpperCase()}] ${finding.probe}: ${finding.evidence}`);
|
|
233
|
+
if (finding.prescription) lines.push(...finding.prescription.split("\n").map((line) => ` Fix: ${line}`));
|
|
234
|
+
}
|
|
235
|
+
return `${lines.join("\n")}\n`;
|
|
236
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class ExamplesError extends Error {
|
|
2
|
+
constructor(code, message, exitCode = 5, details) { super(message); this.name = 'ExamplesError'; this.code = code; this.exitCode = exitCode; this.details = details; }
|
|
3
|
+
}
|
|
4
|
+
export const usage = (message) => new ExamplesError('AIGPU-EXAMPLES-USAGE', message, 2);
|
|
5
|
+
export const notFound = (message) => new ExamplesError('AIGPU-EXAMPLES-NOT-FOUND', message, 3);
|
|
6
|
+
export const network = (message) => new ExamplesError('AIGPU-EXAMPLES-NETWORK', message, 4);
|
|
7
|
+
export const integrity = (message) => new ExamplesError('AIGPU-EXAMPLES-INTEGRITY', message, 5);
|
|
8
|
+
export const destinationExists = (message) => new ExamplesError('AIGPU-EXAMPLES-DESTINATION-EXISTS', message, 6);
|
|
9
|
+
export const filesystem = (message) => new ExamplesError('AIGPU-EXAMPLES-FILESYSTEM', message, 7);
|
|
10
|
+
export function errorResult(error) {
|
|
11
|
+
const e = error instanceof ExamplesError ? error : filesystem(error instanceof Error ? error.message : String(error));
|
|
12
|
+
return { code: e.exitCode, stderr: `${JSON.stringify({ error: { code: e.code, message: e.message, ...(e.details === undefined ? {} : { details: e.details }) } })}\n` };
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
3
|
+
const field = (value) => `${Buffer.byteLength(value)}:${value}\n`;
|
|
4
|
+
export function aggregateSha256(manifest) {
|
|
5
|
+
let value = `aigpu-example-aggregate/v1\0${field(manifest.id)}${field(manifest.title)}${field(manifest.description)}`;
|
|
6
|
+
value += `${manifest.tags.length}\n${manifest.tags.map(field).join('')}${manifest.capabilities.length}\n${manifest.capabilities.map(field).join('')}${manifest.files.length}\n`;
|
|
7
|
+
for (const file of manifest.files) value += `${field(file.path)}${field(file.contentType)}${file.size}\n${file.sha256}\n`;
|
|
8
|
+
return sha256(value);
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const examplesHelp = `aigpu examples — inspect local example source (never executes code)
|
|
2
|
+
|
|
3
|
+
The catalog is read from ./examples or AIGPU_EXAMPLES_DIR. No network request is made.
|
|
4
|
+
Canonical local invocation: npx aigpu examples ...
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
aigpu examples search <query> [--any] [--limit <n>] [--revision <sha256>] [--root <dir>] [--pretty]
|
|
8
|
+
aigpu examples show <id> [--revision <sha256>] [--root <dir>] [--pretty]
|
|
9
|
+
aigpu examples cat <id> <path> [--revision <sha256>] [--root <dir>] [--json]
|
|
10
|
+
aigpu examples pull <id> --out <directory> [--revision <sha256>] [--root <dir>] [--force] [--pretty]
|
|
11
|
+
|
|
12
|
+
The pull operation uses an atomic, symlink-safe local copy. MCP also exposes this local catalog over stdio.
|
|
13
|
+
`;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { extname, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { filesystem, integrity, notFound } from "./errors.js";
|
|
5
|
+
import { assertSafeRelativePath } from "./paths.js";
|
|
6
|
+
|
|
7
|
+
const SKIP = new Set([".git", "node_modules", "dist", "build", ".next", ".cache"]);
|
|
8
|
+
const CONTENT_TYPES = new Map([
|
|
9
|
+
[".css", "text/css"],
|
|
10
|
+
[".html", "text/html"],
|
|
11
|
+
[".js", "text/javascript"],
|
|
12
|
+
[".json", "application/json"],
|
|
13
|
+
[".md", "text/markdown"],
|
|
14
|
+
[".mjs", "text/javascript"],
|
|
15
|
+
[".jsx", "text/jsx"],
|
|
16
|
+
[".svelte", "text/svelte"],
|
|
17
|
+
[".ts", "text/typescript"],
|
|
18
|
+
[".tsx", "text/tsx"],
|
|
19
|
+
[".vue", "text/vue"],
|
|
20
|
+
[".wgsl", "text/plain"],
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Filesystem-backed example source. It is intentionally local-only: no HTTP client, registry,
|
|
25
|
+
* telemetry, or hosted catalog is involved. The directory can be changed with AIGPU_EXAMPLES_DIR.
|
|
26
|
+
*/
|
|
27
|
+
export class LocalExamplesSource {
|
|
28
|
+
constructor({ root = process.env.AIGPU_EXAMPLES_DIR ?? resolve(process.cwd(), "examples") } = {}) {
|
|
29
|
+
this.root = resolve(root);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getIndex({ revision } = {}) {
|
|
33
|
+
const examples = await this.#discoverExamples();
|
|
34
|
+
const computedRevision = sha256(JSON.stringify(examples.map(({ files, ...entry }) => ({ ...entry, files: files.map(file => [file.path, file.sha256, file.size]) }))));
|
|
35
|
+
if (revision && revision !== computedRevision) throw integrity(`Local examples revision is ${computedRevision}, not ${revision}`);
|
|
36
|
+
return {
|
|
37
|
+
index: { revision: computedRevision, generatedAt: new Date().toISOString(), examples },
|
|
38
|
+
offline: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getManifest(index, id) {
|
|
43
|
+
const entry = index.examples.find((candidate) => candidate.id === id);
|
|
44
|
+
if (!entry) throw notFound(`Local example not found: ${id}`);
|
|
45
|
+
const files = entry.files.map((file) => ({ ...file, url: `file://${resolve(this.root, id, file.path)}` }));
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
revision: index.revision,
|
|
49
|
+
title: entry.title,
|
|
50
|
+
description: entry.description,
|
|
51
|
+
files,
|
|
52
|
+
aggregateSha256: sha256(JSON.stringify(files.map(file => [file.path, file.size, file.sha256]))),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getFile(manifest, file) {
|
|
57
|
+
assertSafeRelativePath(file.path);
|
|
58
|
+
const exampleRoot = await this.#safeExampleRoot(manifest.id);
|
|
59
|
+
const path = resolve(exampleRoot, file.path);
|
|
60
|
+
const fromRoot = relative(exampleRoot, path);
|
|
61
|
+
if (!fromRoot || fromRoot === ".." || fromRoot.startsWith(`..${sep}`)) throw filesystem(`Example file escapes its root: ${file.path}`);
|
|
62
|
+
let bytes;
|
|
63
|
+
try {
|
|
64
|
+
const stat = await lstat(path);
|
|
65
|
+
if (!stat.isFile()) throw new Error("not a regular file");
|
|
66
|
+
bytes = await readFile(path);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error?.code === "ENOENT") throw notFound(`Local example file not found: ${file.path}`);
|
|
69
|
+
throw filesystem(`Cannot read local example file ${file.path}: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
if (bytes.byteLength !== file.size || sha256(bytes) !== file.sha256) throw integrity(`Local example integrity mismatch: ${file.path}`);
|
|
72
|
+
return bytes;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async #discoverExamples() {
|
|
76
|
+
let entries;
|
|
77
|
+
try {
|
|
78
|
+
entries = await readdir(this.root, { withFileTypes: true });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error?.code === "ENOENT") throw notFound(`Examples directory does not exist: ${this.root}`);
|
|
81
|
+
throw filesystem(`Cannot read examples directory: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
const examples = [];
|
|
84
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
85
|
+
if (!entry.isDirectory() || SKIP.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
86
|
+
const files = await collectFiles(resolve(this.root, entry.name));
|
|
87
|
+
if (files.length === 0) continue;
|
|
88
|
+
const packageInfo = await readPackageJson(resolve(this.root, entry.name));
|
|
89
|
+
const tags = [...new Set([entry.name, ...entry.name.split(/[^a-z0-9]+/iu).filter(Boolean), "webgpu", "aigpu"])]
|
|
90
|
+
.map((tag) => tag.toLowerCase());
|
|
91
|
+
examples.push({
|
|
92
|
+
id: entry.name,
|
|
93
|
+
title: packageInfo?.name ?? titleFromId(entry.name),
|
|
94
|
+
description: packageInfo?.description ?? `Local AIGpu example: ${titleFromId(entry.name)}.`,
|
|
95
|
+
tags,
|
|
96
|
+
capabilities: ["webgpu", "local"],
|
|
97
|
+
fileCount: files.length,
|
|
98
|
+
files,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return examples;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async #safeExampleRoot(id) {
|
|
105
|
+
assertSafeRelativePath(id);
|
|
106
|
+
const path = resolve(this.root, id);
|
|
107
|
+
const fromRoot = relative(this.root, path);
|
|
108
|
+
if (!fromRoot || fromRoot === ".." || fromRoot.startsWith(`..${sep}`)) throw filesystem(`Example id escapes the examples root: ${id}`);
|
|
109
|
+
const stat = await lstat(path).catch((error) => { throw error?.code === "ENOENT" ? notFound(`Local example not found: ${id}`) : error; });
|
|
110
|
+
if (!stat.isDirectory()) throw notFound(`Local example not found: ${id}`);
|
|
111
|
+
return path;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function collectFiles(root, prefix = "") {
|
|
116
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
117
|
+
const files = [];
|
|
118
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
119
|
+
if (SKIP.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
120
|
+
const path = resolve(root, entry.name);
|
|
121
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
122
|
+
if (entry.isDirectory()) files.push(...await collectFiles(path, relativePath));
|
|
123
|
+
else if (entry.isFile()) {
|
|
124
|
+
const bytes = await readFile(path);
|
|
125
|
+
files.push({ path: relativePath, size: bytes.byteLength, sha256: sha256(bytes), contentType: CONTENT_TYPES.get(extname(entry.name).toLowerCase()) ?? "application/octet-stream" });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return files;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function readPackageJson(root) {
|
|
132
|
+
try { return JSON.parse(await readFile(resolve(root, "package.json"), "utf8")); }
|
|
133
|
+
catch { return undefined; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function titleFromId(id) {
|
|
137
|
+
return id.replace(/[-_]+/gu, " ").replace(/\b\w/gu, (letter) => letter.toUpperCase());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function sha256(value) {
|
|
141
|
+
return createHash("sha256").update(value).digest("hex");
|
|
142
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { posix } from 'node:path';
|
|
2
|
+
import { integrity } from './errors.js';
|
|
3
|
+
export function assertSafeRelativePath(value) {
|
|
4
|
+
let decoded;
|
|
5
|
+
try { decoded = decodeURIComponent(value); } catch { throw integrity(`Invalid encoded path: ${value}`); }
|
|
6
|
+
if (!value || decoded !== value || value !== posix.normalize(value) || value.startsWith('/') || value.startsWith('\\') || value.includes('\\') || value.includes('%') || /^[A-Za-z]:/.test(value) || /[\0-\x1f\x7f]/.test(value) || value.split('/').some((p) => !p || p === '.' || p === '..')) throw integrity(`Unsafe source path: ${value}`);
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
export function assertUniquePaths(files) {
|
|
10
|
+
const exact = new Set(), folded = new Set();
|
|
11
|
+
for (const file of files) { const p=assertSafeRelativePath(file.path); const f=p.normalize('NFC').toLocaleLowerCase('en-US'); if(exact.has(p)||folded.has(f)) throw integrity(`Colliding source path: ${p}`); exact.add(p); folded.add(f); }
|
|
12
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { constants,existsSync,renameSync,rmSync } from 'node:fs';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { mkdir,lstat,open,readFile,readdir,rename,rm,rmdir,writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname,basename,parse,relative,resolve,join,sep } from 'node:path';
|
|
5
|
+
import { ExamplesError,destinationExists,filesystem,integrity } from './errors.js';
|
|
6
|
+
|
|
7
|
+
const MAX_PULL_BYTES = 32 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
const TOKEN=/^[a-f0-9]{32}$/;
|
|
10
|
+
const SELF_START=`${process.pid}:${Date.now()-Math.round(process.uptime()*1000)}`;
|
|
11
|
+
// Darwin O_NOFOLLOW_ANY rejects symlinks anywhere in a path; combining it with O_NOFOLLOW returns EINVAL.
|
|
12
|
+
const DARWIN_NOFOLLOW_ANY=process.platform==='darwin'?0x20000000:0;
|
|
13
|
+
async function exists(path){try{return await lstat(path)}catch(e){if(e.code==='ENOENT')return;throw e}}
|
|
14
|
+
function portablePath(path,platform){const full=resolve(path);if(platform!=='darwin'||process.platform!=='darwin')return full;for(const prefix of ['/tmp','/var','/etc'])if(full===prefix||full.startsWith(`${prefix}/`))return `/private${full}`;return full;}
|
|
15
|
+
async function portableParent(destination,platform){const output=resolve(destination),absolute=portablePath(output,platform),parent=dirname(absolute),root=parse(parent).root;let directory;try{let cursor=root;for(const part of relative(root,parent).split(sep).filter(Boolean)){cursor=join(cursor,part);let stat=await exists(cursor);if(!stat){try{await mkdir(cursor,{mode:0o700})}catch(e){if(e.code!=='EEXIST')throw e}stat=await lstat(cursor)}if(stat.isSymbolicLink()||!stat.isDirectory())throw filesystem(`Unsafe destination directory: ${cursor}`);const checked=await open(cursor,constants.O_RDONLY|constants.O_DIRECTORY|DARWIN_NOFOLLOW_ANY);await checked.close()}directory=await open(parent,constants.O_RDONLY|constants.O_DIRECTORY|DARWIN_NOFOLLOW_ANY);return {absolute,parent,output,portable:true,parentIdentity:identity(await directory.stat()),close:()=>directory.close()}}catch(e){await directory?.close().catch(()=>{});if(e?.code?.startsWith?.('AIGPU-'))throw e;throw filesystem(`Unsafe destination parent: ${e.message}`)}}
|
|
16
|
+
async function safeParent(destination,platform=process.platform){if(platform==='darwin')return portableParent(destination,platform);if(platform!=='linux')throw filesystem(`Safe destination storage is unsupported on ${platform}`);const output=resolve(destination),root=parse(output).root,parts=relative(root,dirname(output)).split(sep).filter(Boolean);let directory;try{directory=await open(root,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW);for(const part of parts){const child=`/proc/self/fd/${directory.fd}/${part}`;let next;try{next=await open(child,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW)}catch(e){if(e.code!=='ENOENT')throw e;try{await mkdir(child,{mode:0o700})}catch(mkdirError){if(mkdirError.code!=='EEXIST')throw mkdirError}next=await open(child,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW)}await directory.close();directory=next}const parent=`/proc/self/fd/${directory.fd}`;return {absolute:`${parent}/${basename(output)}`,parent,output,portable:false,close:()=>directory.close()}}catch(e){await directory?.close().catch(()=>{});throw filesystem(`Unsafe destination parent: ${e.message}`)}}
|
|
17
|
+
async function startTime(pid,platform){if(platform!=='linux'){if(pid===process.pid)return SELF_START;try{process.kill(pid,0);return 'alive'}catch(e){return e.code==='EPERM'?'alive':undefined}}try{const value=await readFile(`/proc/${pid}/stat`,'utf8'),tail=value.slice(value.lastIndexOf(')')+2).split(' ');return tail[19]}catch{return}}
|
|
18
|
+
const identity=s=>({dev:String(s.dev),ino:String(s.ino)});
|
|
19
|
+
const same=(s,id)=>!!s&&!!id&&!s.isSymbolicLink()&&String(s.dev)===id.dev&&String(s.ino)===id.ino;
|
|
20
|
+
async function assertParent(destination){if(destination.portable&&!same(await exists(destination.parent),destination.parentIdentity))throw filesystem('Destination parent identity changed');}
|
|
21
|
+
// This closes the staging window deterministically. Node has no portable rename-if-absent for
|
|
22
|
+
// directories, so the parent lock still only serializes cooperating AIGPU writers; a hostile writer
|
|
23
|
+
// with access to the parent could race between this final check and rename().
|
|
24
|
+
async function assertDestinationUnclaimed(initial,destination,reported){if(!initial&&await exists(destination))throw destinationExists(`Destination already exists: ${reported}`);}
|
|
25
|
+
function names(name,token){return {temp:`.${name}.aigpu-tmp-${token}`,backup:`.${name}.aigpu-old-${token}`,stamp:`.${name}.aigpu-published-${token}`};}
|
|
26
|
+
function validOwner(value,name){if(!value||!TOKEN.test(value.token)||!Number.isInteger(value.pid)||typeof value.start!=='string')return false;const expected=names(name,value.token);return value.temp===expected.temp&&value.backup===expected.backup&&value.stamp===expected.stamp;}
|
|
27
|
+
async function readOwner(lock,name){try{const s=await lstat(join(lock,'owner'));if(s.isSymbolicLink()||!s.isFile())return;const value=JSON.parse(await readFile(join(lock,'owner'),'utf8'));return validOwner(value,name)?value:undefined}catch{return}}
|
|
28
|
+
async function active(owner,platform){const current=await startTime(owner.pid,platform);return platform==='linux'||owner.pid===process.pid?current===owner.start:current!==undefined;}
|
|
29
|
+
async function writeExclusive(path,bytes,portable){let handle;try{handle=await open(path,constants.O_WRONLY|constants.O_CREAT|constants.O_EXCL|(portable?DARWIN_NOFOLLOW_ANY:constants.O_NOFOLLOW),0o600);await handle.writeFile(bytes);await handle.sync()}finally{await handle?.close()}}
|
|
30
|
+
async function writeOwner(lock,owner,portable){const path=join(lock,'owner'),tmp=join(lock,`.owner-${owner.token}`);await writeExclusive(tmp,Buffer.from(JSON.stringify(owner)+'\n'),portable);await rename(tmp,path);}
|
|
31
|
+
async function removeLock(lock,name,token){const owner=await readOwner(lock,name);if(!owner||owner.token!==token)return;const entries=await readdir(lock);if(entries.some(entry=>entry!=='owner'))throw filesystem('Unsafe destination lock contents');await rm(join(lock,'owner'));await rmdir(lock);}
|
|
32
|
+
async function acquire(lock,name,destination,platform,guard){let stale;const portable=platform==='darwin';for(let attempt=0;attempt<3;attempt++){const token=randomBytes(16).toString('hex'),owned=names(name,token),owner={pid:process.pid,start:await startTime(process.pid,platform),token,...owned,phase:'locked'};if(!owner.start)throw filesystem('Cannot determine process identity');try{await guard();await mkdir(lock,{mode:0o700});await guard();await writeExclusive(join(lock,'owner'),Buffer.from(JSON.stringify(owner)+'\n'),portable);return {owner,stale}}catch(e){if(e.code!=='EEXIST')throw filesystem(e.message);const s=await exists(lock);if(!s||s.isSymbolicLink()||!s.isDirectory())throw filesystem('Unsafe destination lock');const previous=await readOwner(lock,name);if(!previous)throw filesystem('Unauthenticated destination lock');if(await active(previous,platform))throw destinationExists(`Destination is busy: ${destination}`);const entries=await readdir(lock);if(entries.some(entry=>entry!=='owner'))throw filesystem('Unsafe stale destination lock');await rm(join(lock,'owner'));await rmdir(lock);stale=previous;}}throw filesystem('Cannot recover stale destination lock');}
|
|
33
|
+
async function ownedStamp(path,token){try{const s=await lstat(path);return s.isFile()&&!s.isSymbolicLink()&&(await readFile(path,'utf8'))===token}catch{return false}}
|
|
34
|
+
async function removeOwnedDirectory(path,id){const s=await exists(path);if(same(s,id)&&s.isDirectory())await rm(path,{recursive:true,force:true});}
|
|
35
|
+
async function writeStaged(root,path,parts,bytes,portable,beforeFileWrite){if(!portable){let directory=root,owned=false;try{for(const part of parts.slice(0,-1)){const child=`/proc/self/fd/${directory.fd}/${part}`;try{await mkdir(child,{mode:0o700})}catch(e){if(e.code!=='EEXIST')throw e}const next=await open(child,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW);if(owned)await directory.close();directory=next;owned=true}const visible=join(path,...parts.slice(0,-1)),directoryIdentity=identity(await directory.stat());if(beforeFileWrite)await beforeFileWrite();await writeFile(`/proc/self/fd/${directory.fd}/${parts.at(-1)}`,bytes,{mode:0o600,flag:'wx'});if(!same(await exists(visible),directoryIdentity))throw filesystem('Staging directory identity changed');}finally{if(owned)await directory.close().catch(()=>{})}return}const rootIdentity=identity(await root.stat());if(!same(await exists(path),rootIdentity))throw filesystem('Staging directory identity changed');let directory=path;for(const part of parts.slice(0,-1)){directory=join(directory,part);try{await mkdir(directory,{mode:0o700})}catch(e){if(e.code!=='EEXIST')throw e}const stat=await lstat(directory);if(stat.isSymbolicLink()||!stat.isDirectory())throw filesystem(`Unsafe staging directory: ${part}`);const checked=await open(directory,constants.O_RDONLY|constants.O_DIRECTORY|DARWIN_NOFOLLOW_ANY);await checked.close();if(!same(await exists(path),rootIdentity))throw filesystem('Staging directory identity changed')}const directoryIdentity=identity(await lstat(directory));if(beforeFileWrite)await beforeFileWrite();await writeExclusive(join(directory,parts.at(-1)),bytes,true);if(!same(await exists(directory),directoryIdentity)||!same(await exists(path),rootIdentity))throw filesystem('Staging directory identity changed');}
|
|
36
|
+
async function recover(parent,name,absolute,owner){if(!owner)return;const temp=join(parent,owner.temp),backup=join(parent,owner.backup),stamp=join(parent,owner.stamp),old=await exists(backup),current=await exists(absolute);if(old){if(!same(old,owner.backupIdentity)||!old.isDirectory())throw filesystem('Unauthenticated stale pull backup');const published=owner.phase==='published'&¤t?.isDirectory()&&!current.isSymbolicLink()&&await ownedStamp(stamp,owner.token);if(published){await rm(backup,{recursive:true});await rm(stamp,{force:true});}else{if(current){const quarantine=join(parent,`.${name}.aigpu-rejected-${randomBytes(16).toString('hex')}`);await rename(absolute,quarantine);}await rename(backup,absolute);await rm(stamp,{force:true});}}await removeOwnedDirectory(temp,owner.tempIdentity);}
|
|
37
|
+
function guardSignals(restore){let handled=false;const handlers={SIGINT:()=>handle('SIGINT'),SIGTERM:()=>handle('SIGTERM')},close=()=>{for(const [signal,handler] of Object.entries(handlers))process.off(signal,handler);},handle=signal=>{if(handled)return;handled=true;close();try{restore()}finally{process.kill(process.pid,signal)}};for(const [signal,handler] of Object.entries(handlers))process.once(signal,handler);return close;}
|
|
38
|
+
|
|
39
|
+
export async function pullExample(client,manifest,destination,{force=false,offline=false,beforeFileWrite,beforePublish,afterBackupRename,afterPublish,platform=process.platform,signal}={}){
|
|
40
|
+
signal?.throwIfAborted();
|
|
41
|
+
const total=manifest.files.reduce((n,f)=>n+f.size,0);if(total>MAX_PULL_BYTES)throw integrity('Example exceeds 32 MiB pull limit');
|
|
42
|
+
const safeDestination=await safeParent(destination,platform),guard=()=>assertParent(safeDestination),{absolute,parent,output}=safeDestination,name=basename(output),lock=join(parent,`.${name}.aigpu.lock`);let acquired;try{acquired=await acquire(lock,name,destination,platform,guard)}catch(e){await safeDestination.close().catch(()=>{});throw e}const owner=acquired.owner,temp=join(parent,owner.temp),backup=join(parent,owner.backup),stamp=join(parent,owner.stamp);
|
|
43
|
+
let moved=false,published=false,stage;
|
|
44
|
+
try{
|
|
45
|
+
await guard();
|
|
46
|
+
await recover(parent,name,absolute,acquired.stale);
|
|
47
|
+
const current=await exists(absolute);if(current&&!force)throw destinationExists(`Destination already exists: ${destination}`);if(current&&(current.isSymbolicLink()||!current.isDirectory()))throw filesystem('Destination must be a real directory');
|
|
48
|
+
await guard();await mkdir(temp,{mode:0o700});stage=await open(temp,constants.O_RDONLY|constants.O_DIRECTORY|(safeDestination.portable?DARWIN_NOFOLLOW_ANY:constants.O_NOFOLLOW));owner.tempIdentity=identity(await stage.stat());owner.phase='staging';await writeOwner(lock,owner,safeDestination.portable);
|
|
49
|
+
let next=0;async function worker(){while(true){signal?.throwIfAborted();const i=next++;if(i>=manifest.files.length)return;const file=manifest.files[i],bytes=await client.getFile(manifest,file,{offline,signal});signal?.throwIfAborted();await writeStaged(stage,temp,file.path.split('/'),bytes,safeDestination.portable,beforeFileWrite);}}
|
|
50
|
+
await Promise.all(Array.from({length:Math.min(4,manifest.files.length)},worker));
|
|
51
|
+
const unguard=guardSignals(()=>{if(!published&&existsSync(backup)&&!existsSync(absolute)){try{renameSync(backup,absolute)}catch{}moved=false;}try{rmSync(temp,{recursive:true,force:true})}catch{}try{rmSync(lock,{recursive:true,force:true})}catch{}});
|
|
52
|
+
try{if(current){await guard();owner.backupIdentity=identity(current);owner.phase='backup-planned';await writeOwner(lock,owner,safeDestination.portable);await rename(absolute,backup);moved=true;if(afterBackupRename)await afterBackupRename();if(!same(await lstat(backup),owner.backupIdentity))throw filesystem('Pull backup identity changed');owner.phase='backed-up';await writeOwner(lock,owner,safeDestination.portable);}if(beforePublish)await beforePublish();await guard();if(!same(await stage.stat(),owner.tempIdentity)||!same(await exists(temp),owner.tempIdentity))throw filesystem('Staging directory identity changed');await assertDestinationUnclaimed(current,absolute,destination);signal?.throwIfAborted();await rename(temp,absolute);if(!same(await exists(absolute),owner.tempIdentity)){const rejected=join(parent,`.${name}.aigpu-rejected-${randomBytes(16).toString('hex')}`);await rename(absolute,rejected).catch(()=>{});throw filesystem('Published staging identity changed')}published=true;moved=false;await writeExclusive(stamp,Buffer.from(owner.token),safeDestination.portable);owner.phase='published';await writeOwner(lock,owner,safeDestination.portable);}finally{unguard()}
|
|
53
|
+
if(afterPublish)await afterPublish();
|
|
54
|
+
if(await exists(backup)){const old=await exists(backup);if(!same(old,owner.backupIdentity))throw filesystem('Pull backup identity changed');await rm(backup,{recursive:true});}await rm(stamp,{force:true});
|
|
55
|
+
return {out:output,files:manifest.files.length,bytes:total};
|
|
56
|
+
}catch(e){if(moved&&!published)await rename(backup,absolute).catch(()=>{});else if(published)await recover(parent,name,absolute,owner).catch(()=>{});if(e instanceof ExamplesError||e?.name==='AbortError'||signal?.aborted&&e===signal.reason)throw e;throw filesystem(`Pull failed: ${e.message}`);
|
|
57
|
+
}finally{await stage?.close().catch(()=>{});await removeOwnedDirectory(temp,owner.tempIdentity).catch(()=>{});await removeLock(lock,name,owner.token).catch(()=>{});await safeDestination.close().catch(()=>{});}
|
|
58
|
+
}
|