@lampdotdev/lamp 0.0.0-stage → 0.36.2-nightly.16

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 (470) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +397 -2
  3. package/Resources/Devices/roku.json +410 -0
  4. package/Resources/Devices/xbox-one.json +367 -0
  5. package/Resources/GameKit/NOTICE.md +63 -0
  6. package/Resources/GameKit/apple/LampPage.swift +1813 -0
  7. package/Resources/GameKit/apple/LampPlayer.swift +46 -0
  8. package/Resources/GameKit/apple/LampWebApp.swift +63 -0
  9. package/Resources/GameKit/apple/LampWebGame.swift +87 -0
  10. package/Resources/GameKit/apple/Native.swift +12 -0
  11. package/Resources/GameKit/build.sh +55 -0
  12. package/Resources/GameKit/lamp_game.h +116 -0
  13. package/Resources/GameKit/lamp_host.h +19 -0
  14. package/Resources/GameKit/lamp_kit.cpp +61 -0
  15. package/Resources/GameKit/lamp_sound.cpp +455 -0
  16. package/Resources/GameKit/lamp_xbox.cpp +374 -0
  17. package/Resources/GameKit/libraries/HandmadeMath.h +3940 -0
  18. package/Resources/GameKit/libraries/cgltf.h +7240 -0
  19. package/Resources/GameKit/libraries/dr_mp3.h +5430 -0
  20. package/Resources/GameKit/libraries/dr_wav.h +9209 -0
  21. package/Resources/GameKit/libraries/sokol_debugtext.h +4754 -0
  22. package/Resources/GameKit/libraries/sokol_gfx.h +29558 -0
  23. package/Resources/GameKit/libraries/sokol_gl.h +4538 -0
  24. package/Resources/GameKit/libraries/sokol_log.h +335 -0
  25. package/Resources/GameKit/libraries/sokol_shape.h +1531 -0
  26. package/Resources/GameKit/libraries/stb_image.h +7988 -0
  27. package/Resources/GameKit/libraries/stb_vorbis.c +5584 -0
  28. package/Resources/GameKit/page/lamp-live.js +672 -0
  29. package/Resources/GameKit/scaffold/Game/game.cpp +142 -0
  30. package/Resources/GameKit/scaffold/Game/scene.glsl +49 -0
  31. package/Resources/GameKit/scaffold-web/Web/game.js +31 -0
  32. package/Resources/GameKit/scaffold-web/Web/index.html +13 -0
  33. package/Resources/GameKit/scaffold-web/Web/making.js +33 -0
  34. package/Resources/GameKit/scaffold-web/Web/world.js +33 -0
  35. package/Resources/GameKit/web/lamp-game.js +3042 -0
  36. package/Resources/GameKit/web/lamp-phaser.js +128 -0
  37. package/Resources/WebHost/App.exe +0 -0
  38. package/Resources/WebHost/Microsoft.Web.WebView2.Core.dll +0 -0
  39. package/Resources/WebHost/WebView2-LICENSE.txt +27 -0
  40. package/Resources/WebHost/lamp_controllers.h +1506 -0
  41. package/Resources/WebHost/lamp_web_host.cpp +1173 -0
  42. package/Resources/WebLibraries/earlier.txt +318 -0
  43. package/Resources/WebLibraries/phaser/LICENSE.md +20 -0
  44. package/Resources/WebLibraries/phaser/NOTICE.md +2 -0
  45. package/Resources/WebLibraries/phaser/phaser.esm.min.js +1 -0
  46. package/Resources/WebLibraries/qrcode/LICENSE +21 -0
  47. package/Resources/WebLibraries/qrcode/NOTICE.md +4 -0
  48. package/Resources/WebLibraries/qrcode/qrcode.js +2297 -0
  49. package/Resources/WebLibraries/rapier/LICENSE +201 -0
  50. package/Resources/WebLibraries/rapier/NOTICE.md +3 -0
  51. package/Resources/WebLibraries/rapier/rapier.js +2 -0
  52. package/Resources/WebLibraries/three/LICENSE +21 -0
  53. package/Resources/WebLibraries/three/NOTICE.md +4 -0
  54. package/Resources/WebLibraries/three/addons/animation/AnimationClipCreator.js +168 -0
  55. package/Resources/WebLibraries/three/addons/animation/CCDIKSolver.js +607 -0
  56. package/Resources/WebLibraries/three/addons/controls/ArcballControls.js +3543 -0
  57. package/Resources/WebLibraries/three/addons/controls/DragControls.js +452 -0
  58. package/Resources/WebLibraries/three/addons/controls/FirstPersonControls.js +512 -0
  59. package/Resources/WebLibraries/three/addons/controls/FlyControls.js +384 -0
  60. package/Resources/WebLibraries/three/addons/controls/MapControls.js +116 -0
  61. package/Resources/WebLibraries/three/addons/controls/OrbitControls.js +1985 -0
  62. package/Resources/WebLibraries/three/addons/controls/PointerLockControls.js +264 -0
  63. package/Resources/WebLibraries/three/addons/controls/TrackballControls.js +1106 -0
  64. package/Resources/WebLibraries/three/addons/controls/TransformControls.js +2003 -0
  65. package/Resources/WebLibraries/three/addons/curves/CurveExtras.js +694 -0
  66. package/Resources/WebLibraries/three/addons/curves/NURBSCurve.js +169 -0
  67. package/Resources/WebLibraries/three/addons/curves/NURBSSurface.js +98 -0
  68. package/Resources/WebLibraries/three/addons/curves/NURBSUtils.js +532 -0
  69. package/Resources/WebLibraries/three/addons/curves/NURBSVolume.js +82 -0
  70. package/Resources/WebLibraries/three/addons/effects/AnaglyphEffect.js +274 -0
  71. package/Resources/WebLibraries/three/addons/effects/AsciiEffect.js +323 -0
  72. package/Resources/WebLibraries/three/addons/effects/OutlineEffect.js +489 -0
  73. package/Resources/WebLibraries/three/addons/effects/ParallaxBarrierEffect.js +155 -0
  74. package/Resources/WebLibraries/three/addons/effects/StereoEffect.js +91 -0
  75. package/Resources/WebLibraries/three/addons/environments/ColorEnvironment.js +59 -0
  76. package/Resources/WebLibraries/three/addons/environments/DebugEnvironment.js +102 -0
  77. package/Resources/WebLibraries/three/addons/environments/RoomEnvironment.js +185 -0
  78. package/Resources/WebLibraries/three/addons/geometries/BoxLineGeometry.js +92 -0
  79. package/Resources/WebLibraries/three/addons/geometries/ConvexGeometry.js +72 -0
  80. package/Resources/WebLibraries/three/addons/geometries/DecalGeometry.js +420 -0
  81. package/Resources/WebLibraries/three/addons/geometries/LoftGeometry.js +355 -0
  82. package/Resources/WebLibraries/three/addons/geometries/ParametricFunctions.js +100 -0
  83. package/Resources/WebLibraries/three/addons/geometries/ParametricGeometry.js +172 -0
  84. package/Resources/WebLibraries/three/addons/geometries/RoundedBoxGeometry.js +216 -0
  85. package/Resources/WebLibraries/three/addons/geometries/TeapotGeometry.js +689 -0
  86. package/Resources/WebLibraries/three/addons/geometries/TextGeometry.js +102 -0
  87. package/Resources/WebLibraries/three/addons/helpers/AnimationPathHelper.js +302 -0
  88. package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelper.js +166 -0
  89. package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelperWebGL.js +221 -0
  90. package/Resources/WebLibraries/three/addons/helpers/LightProbeHelper.js +166 -0
  91. package/Resources/WebLibraries/three/addons/helpers/LightProbeHelperGPU.js +102 -0
  92. package/Resources/WebLibraries/three/addons/helpers/OctreeHelper.js +109 -0
  93. package/Resources/WebLibraries/three/addons/helpers/PositionalAudioHelper.js +169 -0
  94. package/Resources/WebLibraries/three/addons/helpers/RapierHelper.js +59 -0
  95. package/Resources/WebLibraries/three/addons/helpers/RectAreaLightHelper.js +118 -0
  96. package/Resources/WebLibraries/three/addons/helpers/TextureHelper.js +265 -0
  97. package/Resources/WebLibraries/three/addons/helpers/TextureHelperGPU.js +214 -0
  98. package/Resources/WebLibraries/three/addons/helpers/VertexNormalsHelper.js +155 -0
  99. package/Resources/WebLibraries/three/addons/helpers/VertexTangentsHelper.js +133 -0
  100. package/Resources/WebLibraries/three/addons/helpers/ViewHelper.js +529 -0
  101. package/Resources/WebLibraries/three/addons/interactive/HTMLMesh.js +617 -0
  102. package/Resources/WebLibraries/three/addons/interactive/InteractiveGroup.js +224 -0
  103. package/Resources/WebLibraries/three/addons/interactive/SelectionBox.js +324 -0
  104. package/Resources/WebLibraries/three/addons/interactive/SelectionHelper.js +150 -0
  105. package/Resources/WebLibraries/three/addons/libs/fflate.module.js +2699 -0
  106. package/Resources/WebLibraries/three/addons/libs/lil-gui.module.min.js +8 -0
  107. package/Resources/WebLibraries/three/addons/libs/meshopt_decoder.module.js +196 -0
  108. package/Resources/WebLibraries/three/addons/libs/potpack.module.js +125 -0
  109. package/Resources/WebLibraries/three/addons/libs/stats.module.js +167 -0
  110. package/Resources/WebLibraries/three/addons/libs/tween.module.js +876 -0
  111. package/Resources/WebLibraries/three/addons/lighting/ClusteredLighting.js +153 -0
  112. package/Resources/WebLibraries/three/addons/lighting/DynamicLighting.js +82 -0
  113. package/Resources/WebLibraries/three/addons/lighting/LightMapper.js +295 -0
  114. package/Resources/WebLibraries/three/addons/lighting/LightProbeGrid.js +923 -0
  115. package/Resources/WebLibraries/three/addons/lighting/LightProbeGridUtils.js +107 -0
  116. package/Resources/WebLibraries/three/addons/lighting/LightProbeGridWebGL.js +846 -0
  117. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIConeTracer.js +243 -0
  118. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIIrradiance.js +43 -0
  119. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGINode.js +617 -0
  120. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGISceneCollector.js +793 -0
  121. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIVolume.js +1440 -0
  122. package/Resources/WebLibraries/three/addons/lights/LightProbeGenerator.js +337 -0
  123. package/Resources/WebLibraries/three/addons/lights/RectAreaLightTexturesLib.js +127 -0
  124. package/Resources/WebLibraries/three/addons/lights/RectAreaLightUniformsLib.js +40 -0
  125. package/Resources/WebLibraries/three/addons/lights/SunLight.js +100 -0
  126. package/Resources/WebLibraries/three/addons/lights/SunLightNode.js +56 -0
  127. package/Resources/WebLibraries/three/addons/lights/SunLightShadow.js +319 -0
  128. package/Resources/WebLibraries/three/addons/lights/SunShadowNode.js +312 -0
  129. package/Resources/WebLibraries/three/addons/lines/Line2.js +56 -0
  130. package/Resources/WebLibraries/three/addons/lines/LineGeometry.js +157 -0
  131. package/Resources/WebLibraries/three/addons/lines/LineMaterial.js +742 -0
  132. package/Resources/WebLibraries/three/addons/lines/LineSegments2.js +434 -0
  133. package/Resources/WebLibraries/three/addons/lines/LineSegmentsGeometry.js +298 -0
  134. package/Resources/WebLibraries/three/addons/lines/Wireframe.js +108 -0
  135. package/Resources/WebLibraries/three/addons/lines/WireframeGeometry2.js +49 -0
  136. package/Resources/WebLibraries/three/addons/lines/webgpu/Line2.js +46 -0
  137. package/Resources/WebLibraries/three/addons/lines/webgpu/LineSegments2.js +419 -0
  138. package/Resources/WebLibraries/three/addons/lines/webgpu/Wireframe.js +86 -0
  139. package/Resources/WebLibraries/three/addons/loaders/EXRLoader.js +3411 -0
  140. package/Resources/WebLibraries/three/addons/loaders/FBXLoader.js +4660 -0
  141. package/Resources/WebLibraries/three/addons/loaders/FontLoader.js +262 -0
  142. package/Resources/WebLibraries/three/addons/loaders/GLTFLoader.js +5006 -0
  143. package/Resources/WebLibraries/three/addons/loaders/HDRLoader.js +463 -0
  144. package/Resources/WebLibraries/three/addons/loaders/MTLLoader.js +601 -0
  145. package/Resources/WebLibraries/three/addons/loaders/OBJLoader.js +961 -0
  146. package/Resources/WebLibraries/three/addons/loaders/PLYLoader.js +992 -0
  147. package/Resources/WebLibraries/three/addons/loaders/RGBELoader.js +18 -0
  148. package/Resources/WebLibraries/three/addons/loaders/STLLoader.js +423 -0
  149. package/Resources/WebLibraries/three/addons/loaders/SVGLoader.js +3297 -0
  150. package/Resources/WebLibraries/three/addons/loaders/TGALoader.js +539 -0
  151. package/Resources/WebLibraries/three/addons/loaders/UltraHDRLoader.js +757 -0
  152. package/Resources/WebLibraries/three/addons/math/Capsule.js +159 -0
  153. package/Resources/WebLibraries/three/addons/math/ColorConverter.js +62 -0
  154. package/Resources/WebLibraries/three/addons/math/ColorSpaces.js +147 -0
  155. package/Resources/WebLibraries/three/addons/math/ConvexHull.js +1695 -0
  156. package/Resources/WebLibraries/three/addons/math/ImprovedNoise.js +88 -0
  157. package/Resources/WebLibraries/three/addons/math/Lut.js +317 -0
  158. package/Resources/WebLibraries/three/addons/math/MeshSurfaceSampler.js +315 -0
  159. package/Resources/WebLibraries/three/addons/math/OBB.js +535 -0
  160. package/Resources/WebLibraries/three/addons/math/Octree.js +825 -0
  161. package/Resources/WebLibraries/three/addons/math/SimplexNoise.js +470 -0
  162. package/Resources/WebLibraries/three/addons/misc/ConvexObjectBreaker.js +539 -0
  163. package/Resources/WebLibraries/three/addons/misc/GPUComputationRenderer.js +508 -0
  164. package/Resources/WebLibraries/three/addons/misc/Gyroscope.js +78 -0
  165. package/Resources/WebLibraries/three/addons/misc/MorphAnimMesh.js +119 -0
  166. package/Resources/WebLibraries/three/addons/misc/MorphBlendMesh.js +425 -0
  167. package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMap.js +370 -0
  168. package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMapGPU.js +322 -0
  169. package/Resources/WebLibraries/three/addons/misc/RollerCoaster.js +661 -0
  170. package/Resources/WebLibraries/three/addons/misc/Sculptor.js +1679 -0
  171. package/Resources/WebLibraries/three/addons/misc/SculptorMesh.js +1504 -0
  172. package/Resources/WebLibraries/three/addons/misc/SculptorTools.js +1180 -0
  173. package/Resources/WebLibraries/three/addons/misc/SculptorUtils.js +345 -0
  174. package/Resources/WebLibraries/three/addons/misc/TileCreasedNormalsPlugin.js +270 -0
  175. package/Resources/WebLibraries/three/addons/misc/TubePainter.js +611 -0
  176. package/Resources/WebLibraries/three/addons/misc/Volume.js +520 -0
  177. package/Resources/WebLibraries/three/addons/misc/VolumeSlice.js +275 -0
  178. package/Resources/WebLibraries/three/addons/modifiers/CurveModifier.js +374 -0
  179. package/Resources/WebLibraries/three/addons/modifiers/CurveModifierGPU.js +256 -0
  180. package/Resources/WebLibraries/three/addons/modifiers/EdgeSplitModifier.js +299 -0
  181. package/Resources/WebLibraries/three/addons/modifiers/TessellateModifier.js +340 -0
  182. package/Resources/WebLibraries/three/addons/objects/GroundedSkybox.js +69 -0
  183. package/Resources/WebLibraries/three/addons/objects/Lensflare.js +489 -0
  184. package/Resources/WebLibraries/three/addons/objects/LensflareMesh.js +376 -0
  185. package/Resources/WebLibraries/three/addons/objects/MarchingCubes.js +1261 -0
  186. package/Resources/WebLibraries/three/addons/objects/Reflector.js +390 -0
  187. package/Resources/WebLibraries/three/addons/objects/ReflectorForSSRPass.js +393 -0
  188. package/Resources/WebLibraries/three/addons/objects/Refractor.js +389 -0
  189. package/Resources/WebLibraries/three/addons/objects/ShadowMesh.js +130 -0
  190. package/Resources/WebLibraries/three/addons/objects/Sky.js +342 -0
  191. package/Resources/WebLibraries/three/addons/objects/SkyMesh.js +403 -0
  192. package/Resources/WebLibraries/three/addons/objects/Water.js +374 -0
  193. package/Resources/WebLibraries/three/addons/objects/Water2.js +403 -0
  194. package/Resources/WebLibraries/three/addons/objects/Water2Mesh.js +199 -0
  195. package/Resources/WebLibraries/three/addons/objects/WaterMesh.js +194 -0
  196. package/Resources/WebLibraries/three/addons/postprocessing/AfterimagePass.js +187 -0
  197. package/Resources/WebLibraries/three/addons/postprocessing/BloomPass.js +274 -0
  198. package/Resources/WebLibraries/three/addons/postprocessing/BokehPass.js +218 -0
  199. package/Resources/WebLibraries/three/addons/postprocessing/ClearPass.js +97 -0
  200. package/Resources/WebLibraries/three/addons/postprocessing/CubeTexturePass.js +146 -0
  201. package/Resources/WebLibraries/three/addons/postprocessing/DotScreenPass.js +114 -0
  202. package/Resources/WebLibraries/three/addons/postprocessing/EffectComposer.js +365 -0
  203. package/Resources/WebLibraries/three/addons/postprocessing/FXAAPass.js +40 -0
  204. package/Resources/WebLibraries/three/addons/postprocessing/FilmPass.js +113 -0
  205. package/Resources/WebLibraries/three/addons/postprocessing/GTAOPass.js +735 -0
  206. package/Resources/WebLibraries/three/addons/postprocessing/GlitchPass.js +177 -0
  207. package/Resources/WebLibraries/three/addons/postprocessing/HalftonePass.js +134 -0
  208. package/Resources/WebLibraries/three/addons/postprocessing/LUTPass.js +138 -0
  209. package/Resources/WebLibraries/three/addons/postprocessing/MaskPass.js +195 -0
  210. package/Resources/WebLibraries/three/addons/postprocessing/OutlinePass.js +776 -0
  211. package/Resources/WebLibraries/three/addons/postprocessing/OutputPass.js +148 -0
  212. package/Resources/WebLibraries/three/addons/postprocessing/Pass.js +191 -0
  213. package/Resources/WebLibraries/three/addons/postprocessing/RenderPass.js +193 -0
  214. package/Resources/WebLibraries/three/addons/postprocessing/RenderPixelatedPass.js +314 -0
  215. package/Resources/WebLibraries/three/addons/postprocessing/RenderTransitionPass.js +267 -0
  216. package/Resources/WebLibraries/three/addons/postprocessing/SAOPass.js +407 -0
  217. package/Resources/WebLibraries/three/addons/postprocessing/SMAAPass.js +230 -0
  218. package/Resources/WebLibraries/three/addons/postprocessing/SSAARenderPass.js +313 -0
  219. package/Resources/WebLibraries/three/addons/postprocessing/SSAOPass.js +527 -0
  220. package/Resources/WebLibraries/three/addons/postprocessing/SSRPass.js +858 -0
  221. package/Resources/WebLibraries/three/addons/postprocessing/SavePass.js +132 -0
  222. package/Resources/WebLibraries/three/addons/postprocessing/ShaderPass.js +135 -0
  223. package/Resources/WebLibraries/three/addons/postprocessing/TAARenderPass.js +243 -0
  224. package/Resources/WebLibraries/three/addons/postprocessing/TexturePass.js +131 -0
  225. package/Resources/WebLibraries/three/addons/postprocessing/UnrealBloomPass.js +541 -0
  226. package/Resources/WebLibraries/three/addons/shaders/ACESFilmicToneMappingShader.js +96 -0
  227. package/Resources/WebLibraries/three/addons/shaders/AfterimageShader.js +63 -0
  228. package/Resources/WebLibraries/three/addons/shaders/BasicShader.js +36 -0
  229. package/Resources/WebLibraries/three/addons/shaders/BleachBypassShader.js +68 -0
  230. package/Resources/WebLibraries/three/addons/shaders/BlendShader.js +56 -0
  231. package/Resources/WebLibraries/three/addons/shaders/BokehShader.js +151 -0
  232. package/Resources/WebLibraries/three/addons/shaders/BokehShader2.js +404 -0
  233. package/Resources/WebLibraries/three/addons/shaders/BrightnessContrastShader.js +62 -0
  234. package/Resources/WebLibraries/three/addons/shaders/ColorCorrectionShader.js +59 -0
  235. package/Resources/WebLibraries/three/addons/shaders/ColorifyShader.js +57 -0
  236. package/Resources/WebLibraries/three/addons/shaders/ConvolutionShader.js +74 -0
  237. package/Resources/WebLibraries/three/addons/shaders/CopyShader.js +52 -0
  238. package/Resources/WebLibraries/three/addons/shaders/DOFMipMapShader.js +63 -0
  239. package/Resources/WebLibraries/three/addons/shaders/DepthLimitedBlurShader.js +183 -0
  240. package/Resources/WebLibraries/three/addons/shaders/DigitalGlitch.js +104 -0
  241. package/Resources/WebLibraries/three/addons/shaders/DotScreenShader.js +75 -0
  242. package/Resources/WebLibraries/three/addons/shaders/ExposureShader.js +51 -0
  243. package/Resources/WebLibraries/three/addons/shaders/FXAAShader.js +298 -0
  244. package/Resources/WebLibraries/three/addons/shaders/FilmShader.js +73 -0
  245. package/Resources/WebLibraries/three/addons/shaders/FocusShader.js +94 -0
  246. package/Resources/WebLibraries/three/addons/shaders/FreiChenShader.js +103 -0
  247. package/Resources/WebLibraries/three/addons/shaders/GTAOShader.js +437 -0
  248. package/Resources/WebLibraries/three/addons/shaders/GammaCorrectionShader.js +52 -0
  249. package/Resources/WebLibraries/three/addons/shaders/HalftoneShader.js +332 -0
  250. package/Resources/WebLibraries/three/addons/shaders/HorizontalBlurShader.js +68 -0
  251. package/Resources/WebLibraries/three/addons/shaders/HorizontalTiltShiftShader.js +70 -0
  252. package/Resources/WebLibraries/three/addons/shaders/HueSaturationShader.js +74 -0
  253. package/Resources/WebLibraries/three/addons/shaders/KaleidoShader.js +65 -0
  254. package/Resources/WebLibraries/three/addons/shaders/LuminosityHighPassShader.js +68 -0
  255. package/Resources/WebLibraries/three/addons/shaders/LuminosityShader.js +54 -0
  256. package/Resources/WebLibraries/three/addons/shaders/MirrorShader.js +62 -0
  257. package/Resources/WebLibraries/three/addons/shaders/NormalMapShader.js +60 -0
  258. package/Resources/WebLibraries/three/addons/shaders/OutputShader.js +103 -0
  259. package/Resources/WebLibraries/three/addons/shaders/PoissonDenoiseShader.js +230 -0
  260. package/Resources/WebLibraries/three/addons/shaders/RGBShiftShader.js +61 -0
  261. package/Resources/WebLibraries/three/addons/shaders/SAOShader.js +201 -0
  262. package/Resources/WebLibraries/three/addons/shaders/SMAAShader.js +489 -0
  263. package/Resources/WebLibraries/three/addons/shaders/SSAOShader.js +331 -0
  264. package/Resources/WebLibraries/three/addons/shaders/SSRShader.js +396 -0
  265. package/Resources/WebLibraries/three/addons/shaders/SepiaShader.js +57 -0
  266. package/Resources/WebLibraries/three/addons/shaders/SobelOperatorShader.js +98 -0
  267. package/Resources/WebLibraries/three/addons/shaders/SubsurfaceScatteringShader.js +95 -0
  268. package/Resources/WebLibraries/three/addons/shaders/TechnicolorShader.js +51 -0
  269. package/Resources/WebLibraries/three/addons/shaders/ToonShader.js +349 -0
  270. package/Resources/WebLibraries/three/addons/shaders/TriangleBlurShader.js +79 -0
  271. package/Resources/WebLibraries/three/addons/shaders/UnpackDepthRGBAShader.js +60 -0
  272. package/Resources/WebLibraries/three/addons/shaders/VelocityShader.js +137 -0
  273. package/Resources/WebLibraries/three/addons/shaders/VerticalBlurShader.js +66 -0
  274. package/Resources/WebLibraries/three/addons/shaders/VerticalTiltShiftShader.js +70 -0
  275. package/Resources/WebLibraries/three/addons/shaders/VignetteShader.js +56 -0
  276. package/Resources/WebLibraries/three/addons/shaders/VolumeShader.js +285 -0
  277. package/Resources/WebLibraries/three/addons/shaders/WaterRefractionShader.js +106 -0
  278. package/Resources/WebLibraries/three/addons/textures/FlakesTexture.js +54 -0
  279. package/Resources/WebLibraries/three/addons/tsl/WebGLNodesHandler.js +607 -0
  280. package/Resources/WebLibraries/three/addons/tsl/display/AfterImageNode.js +256 -0
  281. package/Resources/WebLibraries/three/addons/tsl/display/AnaglyphPassNode.js +550 -0
  282. package/Resources/WebLibraries/three/addons/tsl/display/BilateralBlurNode.js +377 -0
  283. package/Resources/WebLibraries/three/addons/tsl/display/BleachBypass.js +33 -0
  284. package/Resources/WebLibraries/three/addons/tsl/display/BloomNode.js +599 -0
  285. package/Resources/WebLibraries/three/addons/tsl/display/CRT.js +150 -0
  286. package/Resources/WebLibraries/three/addons/tsl/display/ChromaticAberrationNode.js +163 -0
  287. package/Resources/WebLibraries/three/addons/tsl/display/DenoiseNode.js +354 -0
  288. package/Resources/WebLibraries/three/addons/tsl/display/DepthOfFieldNode.js +572 -0
  289. package/Resources/WebLibraries/three/addons/tsl/display/DotScreenNode.js +104 -0
  290. package/Resources/WebLibraries/three/addons/tsl/display/FSR1Node.js +481 -0
  291. package/Resources/WebLibraries/three/addons/tsl/display/FXAANode.js +357 -0
  292. package/Resources/WebLibraries/three/addons/tsl/display/FilmNode.js +101 -0
  293. package/Resources/WebLibraries/three/addons/tsl/display/GTAONode.js +727 -0
  294. package/Resources/WebLibraries/three/addons/tsl/display/GaussianBlurNode.js +412 -0
  295. package/Resources/WebLibraries/three/addons/tsl/display/GodraysNode.js +604 -0
  296. package/Resources/WebLibraries/three/addons/tsl/display/ImportanceSampledEnvironment.js +564 -0
  297. package/Resources/WebLibraries/three/addons/tsl/display/LensflareNode.js +282 -0
  298. package/Resources/WebLibraries/three/addons/tsl/display/Lut3DNode.js +109 -0
  299. package/Resources/WebLibraries/three/addons/tsl/display/MotionBlur.js +33 -0
  300. package/Resources/WebLibraries/three/addons/tsl/display/OITPassNode.js +341 -0
  301. package/Resources/WebLibraries/three/addons/tsl/display/OutlineNode.js +916 -0
  302. package/Resources/WebLibraries/three/addons/tsl/display/ParallaxBarrierPassNode.js +90 -0
  303. package/Resources/WebLibraries/three/addons/tsl/display/PixelationPassNode.js +227 -0
  304. package/Resources/WebLibraries/three/addons/tsl/display/RGBShiftNode.js +96 -0
  305. package/Resources/WebLibraries/three/addons/tsl/display/RecurrentDenoiseNode.js +812 -0
  306. package/Resources/WebLibraries/three/addons/tsl/display/RetroPassNode.js +263 -0
  307. package/Resources/WebLibraries/three/addons/tsl/display/SMAANode.js +729 -0
  308. package/Resources/WebLibraries/three/addons/tsl/display/SSAAPassNode.js +344 -0
  309. package/Resources/WebLibraries/three/addons/tsl/display/SSAONode.js +422 -0
  310. package/Resources/WebLibraries/three/addons/tsl/display/SSGINode.js +698 -0
  311. package/Resources/WebLibraries/three/addons/tsl/display/SSRNode.js +1430 -0
  312. package/Resources/WebLibraries/three/addons/tsl/display/SSSNode.js +479 -0
  313. package/Resources/WebLibraries/three/addons/tsl/display/Sepia.js +24 -0
  314. package/Resources/WebLibraries/three/addons/tsl/display/Shape.js +29 -0
  315. package/Resources/WebLibraries/three/addons/tsl/display/SharpenNode.js +284 -0
  316. package/Resources/WebLibraries/three/addons/tsl/display/SobelOperatorNode.js +168 -0
  317. package/Resources/WebLibraries/three/addons/tsl/display/StereoCompositePassNode.js +190 -0
  318. package/Resources/WebLibraries/three/addons/tsl/display/StereoPassNode.js +117 -0
  319. package/Resources/WebLibraries/three/addons/tsl/display/TAAUNode.js +700 -0
  320. package/Resources/WebLibraries/three/addons/tsl/display/TRAANode.js +609 -0
  321. package/Resources/WebLibraries/three/addons/tsl/display/TemporalReprojectNode.js +1024 -0
  322. package/Resources/WebLibraries/three/addons/tsl/display/TransitionNode.js +141 -0
  323. package/Resources/WebLibraries/three/addons/tsl/display/boxBlur.js +65 -0
  324. package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlend.js +80 -0
  325. package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlur.js +63 -0
  326. package/Resources/WebLibraries/three/addons/tsl/display/hashBlur.js +55 -0
  327. package/Resources/WebLibraries/three/addons/tsl/display/lensDirt.js +30 -0
  328. package/Resources/WebLibraries/three/addons/tsl/display/radialBlur.js +68 -0
  329. package/Resources/WebLibraries/three/addons/tsl/lighting/ClusteredLightsNode.js +994 -0
  330. package/Resources/WebLibraries/three/addons/tsl/lighting/DynamicLightsNode.js +201 -0
  331. package/Resources/WebLibraries/three/addons/tsl/lighting/LightProbeGridNode.js +183 -0
  332. package/Resources/WebLibraries/three/addons/tsl/lighting/data/AmbientLightDataNode.js +67 -0
  333. package/Resources/WebLibraries/three/addons/tsl/lighting/data/DirectionalLightDataNode.js +117 -0
  334. package/Resources/WebLibraries/three/addons/tsl/lighting/data/HemisphereLightDataNode.js +105 -0
  335. package/Resources/WebLibraries/three/addons/tsl/lighting/data/PointLightDataNode.js +140 -0
  336. package/Resources/WebLibraries/three/addons/tsl/lighting/data/SpotLightDataNode.js +167 -0
  337. package/Resources/WebLibraries/three/addons/tsl/math/Bayer.js +84 -0
  338. package/Resources/WebLibraries/three/addons/tsl/math/curlNoise.js +107 -0
  339. package/Resources/WebLibraries/three/addons/tsl/math/voronoiNoise.js +112 -0
  340. package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNode.js +542 -0
  341. package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNodeHelper.js +212 -0
  342. package/Resources/WebLibraries/three/addons/tsl/utils/GroundedSkybox.js +62 -0
  343. package/Resources/WebLibraries/three/addons/tsl/utils/RNoise.js +51 -0
  344. package/Resources/WebLibraries/three/addons/tsl/utils/Raymarching.js +70 -0
  345. package/Resources/WebLibraries/three/addons/tsl/utils/SoftParticles.js +53 -0
  346. package/Resources/WebLibraries/three/addons/tsl/utils/SpecularHelpers.js +302 -0
  347. package/Resources/WebLibraries/three/addons/tsl/utils/TAAUtils.js +184 -0
  348. package/Resources/WebLibraries/three/addons/utils/BufferGeometryUtils.js +1523 -0
  349. package/Resources/WebLibraries/three/addons/utils/CameraUtils.js +82 -0
  350. package/Resources/WebLibraries/three/addons/utils/ColorUtils.js +76 -0
  351. package/Resources/WebLibraries/three/addons/utils/GaussianSplatUtils.js +229 -0
  352. package/Resources/WebLibraries/three/addons/utils/GeometryCompressionUtils.js +547 -0
  353. package/Resources/WebLibraries/three/addons/utils/GeometryUtils.js +226 -0
  354. package/Resources/WebLibraries/three/addons/utils/LDrawUtils.js +211 -0
  355. package/Resources/WebLibraries/three/addons/utils/SceneOptimizer.js +459 -0
  356. package/Resources/WebLibraries/three/addons/utils/SceneUtils.js +363 -0
  357. package/Resources/WebLibraries/three/addons/utils/ShadowMapViewer.js +244 -0
  358. package/Resources/WebLibraries/three/addons/utils/ShadowMapViewerGPU.js +233 -0
  359. package/Resources/WebLibraries/three/addons/utils/SkeletonUtils.js +496 -0
  360. package/Resources/WebLibraries/three/addons/utils/SortUtils.js +175 -0
  361. package/Resources/WebLibraries/three/addons/utils/UVsDebug.js +173 -0
  362. package/Resources/WebLibraries/three/addons/utils/WebGLTextureUtils.js +115 -0
  363. package/Resources/WebLibraries/three/addons/utils/WebGPUTextureUtils.js +81 -0
  364. package/Resources/WebLibraries/three/addons/utils/WorkerPool.js +167 -0
  365. package/Resources/WebLibraries/three/addons/utils/unwrapUVs.js +865 -0
  366. package/Resources/WebLibraries/three/three.core.js +61162 -0
  367. package/Resources/WebLibraries/three/three.module.js +19537 -0
  368. package/Resources/WebLibraries/three/three.tsl.js +41903 -0
  369. package/Resources/WebLibraries/three/three.webgpu.js +53714 -0
  370. package/Resources/share-rules.json +64 -0
  371. package/Resources/skills/2d-game/SKILL.md +305 -0
  372. package/Resources/skills/3d-game/SKILL.md +274 -0
  373. package/Resources/skills/accounts/SKILL.md +41 -0
  374. package/Resources/skills/android-design/SKILL.md +159 -0
  375. package/Resources/skills/app-ai/SKILL.md +154 -0
  376. package/Resources/skills/app-intents/SKILL.md +31 -0
  377. package/Resources/skills/app-kinds/SKILL.md +122 -0
  378. package/Resources/skills/apple-watch/SKILL.md +43 -0
  379. package/Resources/skills/assets/SKILL.md +183 -0
  380. package/Resources/skills/building-blocks/SKILL.md +97 -0
  381. package/Resources/skills/device-control/SKILL.md +56 -0
  382. package/Resources/skills/device-integrations/SKILL.md +278 -0
  383. package/Resources/skills/finder-files/SKILL.md +19 -0
  384. package/Resources/skills/game/SKILL.md +416 -0
  385. package/Resources/skills/game-3d/SKILL.md +12 -0
  386. package/Resources/skills/game-art/SKILL.md +11 -0
  387. package/Resources/skills/game-feel/SKILL.md +13 -0
  388. package/Resources/skills/game-levels/SKILL.md +12 -0
  389. package/Resources/skills/game-live/SKILL.md +12 -0
  390. package/Resources/skills/game-physics/SKILL.md +13 -0
  391. package/Resources/skills/game-playtest/SKILL.md +26 -0
  392. package/Resources/skills/game-ui/SKILL.md +278 -0
  393. package/Resources/skills/github-projects/SKILL.md +62 -0
  394. package/Resources/skills/icloud-sync/SKILL.md +30 -0
  395. package/Resources/skills/iphone-hardware/SKILL.md +57 -0
  396. package/Resources/skills/iphone-widgets/SKILL.md +53 -0
  397. package/Resources/skills/lg-developer-mode/SKILL.md +152 -0
  398. package/Resources/skills/mac-widgets/SKILL.md +60 -0
  399. package/Resources/skills/maps/SKILL.md +8 -0
  400. package/Resources/skills/menu-bar/SKILL.md +36 -0
  401. package/Resources/skills/more-apps/SKILL.md +53 -0
  402. package/Resources/skills/phone-controllers/SKILL.md +79 -0
  403. package/Resources/skills/phone-pages/SKILL.md +173 -0
  404. package/Resources/skills/roku-developer-mode/SKILL.md +106 -0
  405. package/Resources/skills/scene-3d/SKILL.md +27 -0
  406. package/Resources/skills/scripting/SKILL.md +17 -0
  407. package/Resources/skills/share-extension/SKILL.md +22 -0
  408. package/Resources/skills/tv-design/SKILL.md +72 -0
  409. package/Resources/skills/web-app/SKILL.md +170 -0
  410. package/Resources/skills/web-game/SKILL.md +95 -0
  411. package/Resources/skills/web-or-native/SKILL.md +108 -0
  412. package/Resources/skills/wish-links/SKILL.md +156 -0
  413. package/THIRD_PARTY_NOTICES.md +170 -0
  414. package/cli/ai.mjs +237 -0
  415. package/cli/android.mjs +941 -0
  416. package/cli/app-ai.mjs +473 -0
  417. package/cli/browser.mjs +284 -0
  418. package/cli/calls.mjs +594 -0
  419. package/cli/dev.mjs +820 -0
  420. package/cli/devices.mjs +984 -0
  421. package/cli/lamp.mjs +1326 -0
  422. package/cli/lampapp.mjs +332 -0
  423. package/cli/lamps/android-host/LampPageGateway.kt +345 -0
  424. package/cli/lamps/android-host/app/build.gradle.kts +40 -0
  425. package/cli/lamps/android-host/build.gradle.kts +4 -0
  426. package/cli/lamps/android-host/game/AndroidManifest.xml +33 -0
  427. package/cli/lamps/android-host/game/MainActivity.kt +173 -0
  428. package/cli/lamps/android-host/gradle/libs.versions.toml +39 -0
  429. package/cli/lamps/android-host/gradle.properties +8 -0
  430. package/cli/lamps/android-host/page/AndroidManifest.xml +35 -0
  431. package/cli/lamps/android-host/page/MainActivity.kt +193 -0
  432. package/cli/lamps/android-host/settings.gradle.kts +30 -0
  433. package/cli/lamps/android-host/tools.json +43 -0
  434. package/cli/lamps/lamp-controllers.js +2 -0
  435. package/cli/lamps/lamp-for-games.js +6922 -0
  436. package/cli/lamps/lamp.js +7082 -0
  437. package/cli/lamps/mac-starter/.lamp/meta.json +1 -0
  438. package/cli/lamps/mac-starter/APP.md +12 -0
  439. package/cli/lamps/mac-starter/Package.swift +8 -0
  440. package/cli/lamps/mac-starter/Sources/App.swift +11 -0
  441. package/cli/lamps/mac-starter/Sources/ContentView.swift +17 -0
  442. package/cli/lamps/mac-starter/Sources/LampSupport.swift +18215 -0
  443. package/cli/lamps/mac-starter/build.sh +336 -0
  444. package/cli/lamps/mac-starter/gitignore +3 -0
  445. package/cli/lamps/mac-starter/lamp-starter.json +6 -0
  446. package/cli/lamps/web-starter/APP.md +8 -0
  447. package/cli/lamps/web-starter/app.js +1 -0
  448. package/cli/lamps/web-starter/index.html +17 -0
  449. package/cli/lamps/web-starter/lamp-starter.json +5 -0
  450. package/cli/lamps/web-starter/lamp.js +7082 -0
  451. package/cli/lamps/web-starter/style.css +25 -0
  452. package/cli/link.mjs +202 -0
  453. package/cli/local-ai.mjs +193 -0
  454. package/cli/mac.mjs +621 -0
  455. package/cli/mdns.mjs +205 -0
  456. package/cli/msix.mjs +413 -0
  457. package/cli/network.mjs +209 -0
  458. package/cli/openrouter.mjs +133 -0
  459. package/cli/outside.mjs +203 -0
  460. package/cli/page.js +317 -0
  461. package/cli/place.mjs +298 -0
  462. package/cli/png.mjs +184 -0
  463. package/cli/share.mjs +270 -0
  464. package/cli/skill/SKILL.md +280 -0
  465. package/cli/skills.mjs +71 -0
  466. package/cli/state.mjs +176 -0
  467. package/cli/update.mjs +162 -0
  468. package/package.json +20 -4
  469. package/site/lib/quota.js +59 -0
  470. package/site/lib/shared-wishes.js +1727 -0
@@ -0,0 +1,209 @@
1
+ // Everything on the person's home network, one record per address, for when they're looking for devices: SSDP and the
2
+ // integrations find what Lamp can work with (devices.mjs); this finds the rest, so a list can say what else is there.
3
+ // It only reads: one small datagram to each address of this computer's own subnet, so its neighbor table fills (a
4
+ // phone that ignores everything else still answers ARP), that table, and every Bonjour service on the network
5
+ // (mdns.mjs). And the TVs: what a Samsung or an LG TV says of itself. Nothing is kept on disk: the names a network
6
+ // holds are often people's ("…'s iPhone").
7
+
8
+ import { execFile } from "node:child_process";
9
+ import dgram from "node:dgram";
10
+ import { readFileSync } from "node:fs";
11
+ import { networkInterfaces } from "node:os";
12
+ import { isPrivate, onNetwork, ownAddresses, send, xmlAt } from "./devices.mjs";
13
+ import { everything } from "./mdns.mjs";
14
+
15
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
16
+
17
+ /** What a system program says, or "" when it says nothing (it isn't there, or it failed). Never through a shell. */
18
+ const said = (file, args) => new Promise((done) => execFile(file, args, { timeout: 5000 }, (error, out) => done(error ? "" : String(out))));
19
+
20
+ // MARK: The subnet
21
+
22
+ /** An IPv4 address as a number, or null. */
23
+ export function valueOf(address) {
24
+ const parts = String(address).split(".");
25
+ if (parts.length !== 4 || !parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) < 256)) return null;
26
+ return parts.reduce((value, part) => (value * 256) + Number(part), 0);
27
+ }
28
+ const textOf = (value) => [24, 16, 8, 0].map((shift) => Math.floor(value / 2 ** shift) % 256).join(".");
29
+
30
+ /**
31
+ * The subnet to sweep for an address and its mask: no more than the 256 addresses around this computer's on a bigger
32
+ * network, and none for an address that isn't on a home network.
33
+ */
34
+ export function subnetOf(address, netmask) {
35
+ const mine = valueOf(address), mask = valueOf(netmask);
36
+ if (mine === null || mask === null) return null;
37
+ const actual = [...mask.toString(2)].filter((bit) => bit === "1").length;
38
+ if (actual < 8 || actual > 30 || !onNetwork(address)) return null;
39
+ const prefix = Math.max(actual, 24), size = 2 ** (32 - prefix);
40
+ return { network: textOf(mine - (mine % size)), prefix, actualPrefix: actual, mine: address };
41
+ }
42
+
43
+ const inSubnet = (subnet, address) => {
44
+ const value = valueOf(address), size = 2 ** (32 - subnet.prefix);
45
+ return value !== null && value - (value % size) === valueOf(subnet.network);
46
+ };
47
+
48
+ /** The default route: the interface it uses and the gateway it goes through. Null when there's none. */
49
+ export async function defaultRoute() {
50
+ if (process.platform === "darwin") {
51
+ const text = await said("/sbin/route", ["-n", "get", "default"]);
52
+ const gateway = /^\s*gateway:\s*(\S+)/m.exec(text)?.[1], name = /^\s*interface:\s*(\S+)/m.exec(text)?.[1];
53
+ return name ? { interface: name, gateway: valueOf(gateway) === null ? null : gateway } : null;
54
+ }
55
+ if (process.platform === "linux") {
56
+ let table = "";
57
+ try { table = readFileSync("/proc/net/route", "utf8"); } catch { return null; }
58
+ for (const line of table.split("\n").slice(1)) {
59
+ const [name, destination, gateway, flags] = line.trim().split(/\s+/);
60
+ if (destination !== "00000000" || !(parseInt(flags, 16) & 2)) continue;
61
+ const bytes = gateway.match(/../g)?.reverse().map((byte) => parseInt(byte, 16));
62
+ return { interface: name, gateway: bytes?.length === 4 ? bytes.join(".") : null };
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /** The subnet of the interface the default route uses, when it's a home network's. */
69
+ export function subnetOn(name) {
70
+ const entry = (networkInterfaces()[name] ?? []).find((address) => address.family === "IPv4" && !address.internal);
71
+ return entry ? subnetOf(entry.address, entry.netmask) : null;
72
+ }
73
+
74
+ /** One datagram to each address of the subnet (one byte to the discard port, which nothing answers), a few at a time, paced, then a moment for the answers to ARP. */
75
+ export async function sweep(subnet, { batch = 16, pause = 25, settle = 1500 } = {}) {
76
+ const socket = dgram.createSocket("udp4"), size = 2 ** (32 - subnet.prefix), network = valueOf(subnet.network);
77
+ socket.on("error", () => {});
78
+ await new Promise((done) => socket.bind(0, done));
79
+ const addresses = [];
80
+ for (let offset = 1; offset < size - 1; offset++) if (textOf(network + offset) !== subnet.mine) addresses.push(textOf(network + offset));
81
+ try {
82
+ for (let start = 0; start < addresses.length; start += batch) {
83
+ for (const address of addresses.slice(start, start + batch)) socket.send(Buffer.from([0]), 9, address, () => {});
84
+ await sleep(pause);
85
+ }
86
+ await sleep(settle);
87
+ } finally { try { socket.close(); } catch {} }
88
+ }
89
+
90
+ /** A hardware address with two digits to each part ("ae:b0:a9:01:eb:44"); null for a multicast or broadcast one, or none. */
91
+ function hardware(text) {
92
+ const parts = String(text).toLowerCase().split(":");
93
+ if (parts.length !== 6 || !parts.every((part) => /^[0-9a-f]{1,2}$/.test(part))) return null;
94
+ const mac = parts.map((part) => part.padStart(2, "0"));
95
+ if (parseInt(mac[0], 16) & 1 || mac.every((part) => part === "ff")) return null;
96
+ return mac.join(":");
97
+ }
98
+
99
+ /**
100
+ * This computer's neighbor table: each neighbor's IPv4 address and hardware address, for those that answered. macOS
101
+ * shows it only to a process it lets on the local network: run on the runtime Lamp's app carries, it's empty, so
102
+ * Lamp's app reads its own (`Neighbours`).
103
+ */
104
+ export async function neighbours() {
105
+ const table = {};
106
+ if (process.platform === "darwin") {
107
+ for (const [, address, mac] of (await said("/usr/sbin/arp", ["-an"])).matchAll(/\((\d+\.\d+\.\d+\.\d+)\) at ([0-9a-fA-F:]+) on /g)) {
108
+ const known = hardware(mac);
109
+ if (known) table[address] = known;
110
+ }
111
+ } else if (process.platform === "linux") {
112
+ let text = "";
113
+ try { text = readFileSync("/proc/net/arp", "utf8"); } catch {}
114
+ for (const line of text.split("\n").slice(1)) {
115
+ const [address, , flags, mac] = line.trim().split(/\s+/);
116
+ const known = (parseInt(flags, 16) & 2) && hardware(mac);
117
+ if (known && valueOf(address) !== null) table[address] = known;
118
+ }
119
+ }
120
+ return table;
121
+ }
122
+
123
+ /**
124
+ * The hosts of this computer's subnet, each with its hardware address and its Bonjour services, from a sweep, the
125
+ * neighbor table and every Bonjour service, browsed while the sweep runs. `swept` says which subnet was swept: on a
126
+ * network bigger than a /24 only the 256 addresses around this computer's are.
127
+ */
128
+ export async function homeNetwork({ seconds = 3 } = {}) {
129
+ const services = everything({ seconds });
130
+ const route = await defaultRoute(), subnet = route ? subnetOn(route.interface) : null;
131
+ if (subnet) await sweep(subnet);
132
+ const table = await neighbours(), mine = ownAddresses();
133
+ const inside = (address) => (subnet ? inSubnet(subnet, address) : isPrivate(address)) || mine.has(address);
134
+ const hosts = new Map(), hostAt = (address) => hosts.get(address) ?? hosts.set(address, { address, mac: null, services: [] }).get(address);
135
+ for (const [address, mac] of Object.entries(table)) if (inside(address)) hostAt(address).mac = mac;
136
+ for (const { service, addresses } of await services) for (const address of addresses) if (inside(address)) hostAt(address).services.push(service);
137
+ for (const host of hosts.values()) {
138
+ host.isThisMac = mine.has(host.address);
139
+ host.isGateway = host.address === route?.gateway;
140
+ }
141
+ return { hosts: [...hosts.values()].sort((a, b) => (valueOf(a.address) ?? Infinity) - (valueOf(b.address) ?? Infinity)), swept: subnet, gateway: route?.gateway ?? null };
142
+ }
143
+
144
+ // MARK: TVs
145
+
146
+ /** The service an LG TV announces for itself; the description it points at is the TV's own. */
147
+ export const LG_TV = "urn:lge:device:tv:1";
148
+
149
+ /** A page on the TV, read with every guard a request to a device has: its body, or null. */
150
+ const fetched = (address) => (request) => send(request, address, {}, { timeout: 3000 }).then((answer) => (answer.status === 200 ? answer.body : null), () => null);
151
+
152
+ /** A Samsung TV's own description of itself, from its local API; null when it isn't one. */
153
+ export async function samsungAt(address, { get = fetched(address) } = {}) {
154
+ let device;
155
+ try { device = JSON.parse(String(await get({ port: 8001, path: "/api/v2/" }))).device; } catch { return null; }
156
+ if (device?.OS !== "Tizen") return null;
157
+ const modelID = typeof device.modelName === "string" ? device.modelName : "", frame = device.FrameTVSupport === "true";
158
+ // The size is in the model code (QN50…, UN65…).
159
+ const size = /^\d{2}$/.test(modelID.slice(2, 4)) ? modelID.slice(2, 4) : "";
160
+ const model = frame ? `The Frame${size ? ` ${size}` : ""}` : size ? `Smart TV ${size}` : modelID;
161
+ // The year is the first part of its internal model ("22_PONTUSM_FTV"), and each year has one Tizen.
162
+ const year = Number(String(device.model ?? "").slice(0, 2)) || 0;
163
+ const tizen = { 17: "3.0", 18: "4.0", 19: "5.0", 20: "5.5", 21: "6.0", 22: "6.5", 23: "7.0", 24: "8.0", 25: "9.0", 26: "10.0" }[year] ?? "";
164
+ const id = String(device.id ?? device.duid ?? address).replace("uuid:", "");
165
+ return { id: `samsung:${id}`, maker: "Samsung", name: typeof device.name === "string" ? device.name : "Samsung TV", model, modelID, address, os: "Tizen",
166
+ osVersion: tizen, resolution: typeof device.resolution === "string" ? device.resolution : "", frame,
167
+ developerIP: typeof device.developerIP === "string" ? device.developerIP : null, developerMode: device.developerMode === "1" };
168
+ }
169
+
170
+ /**
171
+ * An LG TV, from the UPnP descriptions its answers point at. It answers for several services, each with a
172
+ * description and an id of its own, and the first to answer is often one that says nothing of the TV (a streaming
173
+ * service's, a few bytes of "status=ok"), so each is tried until one is an LG TV's: the TV's own service first, which
174
+ * gives it the same id at every search. `udns` is every id it answered for, so a TV kept under another one is known.
175
+ */
176
+ export async function lgAt(address, answers, { get = fetched(address) } = {}) {
177
+ const locations = [];
178
+ const own = answers.filter((answer) => String(answer.target).toLowerCase() === LG_TV).map((answer) => answer.location);
179
+ for (const location of [...own, ...answers.map((answer) => answer.location)]) {
180
+ let url;
181
+ try { url = new URL(location); } catch { continue; }
182
+ if (url.hostname === address && url.protocol === "http:" && !locations.includes(location)) locations.push(location);
183
+ }
184
+ // A USN is "uuid:<udn>" or "uuid:<udn>::<type>".
185
+ const udns = new Set(answers.map((answer) => String(answer.usn)).filter((usn) => usn.toLowerCase().startsWith("uuid:")).map((usn) => usn.slice(5).split("::")[0]).filter(Boolean));
186
+ for (const location of locations) {
187
+ const url = new URL(location), body = await get({ port: Number(url.port) || 80, path: (url.pathname || "/") + url.search });
188
+ if (!body) continue;
189
+ const tag = (name) => xmlAt(body, name) ?? null;
190
+ if (!(tag("manufacturer") ?? "").toUpperCase().startsWith("LG")) continue;
191
+ const udn = (tag("UDN") ?? address).replace("uuid:", "");
192
+ return { id: `lg:${udn}`, maker: "LG", name: tag("friendlyName") ?? "LG TV", model: tag("modelName") ?? "webOS TV", modelID: tag("modelNumber") ?? "",
193
+ address, os: "webOS", osVersion: "", resolution: "", udns: [...new Set([...udns, udn])].sort() };
194
+ }
195
+ return null;
196
+ }
197
+
198
+ /** The TVs among a search's answers, each asked who it is: one whose answers say Samsung, or webOS or LG. */
199
+ export async function tvsIn(answers, { get } = {}) {
200
+ const byAddress = new Map();
201
+ for (const answer of answers) byAddress.set(answer.address, [...(byAddress.get(answer.address) ?? []), answer]);
202
+ const read = await Promise.all([...byAddress].map(([address, said]) => {
203
+ const words = said.map((answer) => `${answer.server}\n${answer.location ?? ""}\n${answer.target}`).join("\n").toLowerCase();
204
+ if (words.includes("samsung")) return samsungAt(address, get ? { get } : {});
205
+ if (words.includes("webos") || words.includes("lge")) return lgAt(address, said, get ? { get } : {});
206
+ return null;
207
+ }));
208
+ return read.filter(Boolean).filter((tv, index, all) => all.findIndex((other) => other.id === tv.id) === index);
209
+ }
@@ -0,0 +1,133 @@
1
+ // An app's AI asked of OpenRouter with the person's own key: the one place it's sent, for Lamp's app (`openRouterText`,
2
+ // `openRouterChat`, `openRouterDecide` and `openRouterImage` in calls.mjs; `AppAI.viaOpenRouter`, `AppAct.openRouterStep`,
3
+ // `AppAI.decide` and `AppAI.image`) and for lamp's own answers
4
+ // (ai.mjs, with OPENROUTER_API_KEY). The key comes with each request and is kept nowhere here.
5
+
6
+ const ENDPOINT = "https://openrouter.ai/api/v1/chat/completions";
7
+ /** The model lamp answers with when none is named: the one Lamp's app offers first for apps' text. */
8
+ export const DEFAULT_MODEL = "anthropic/claude-sonnet-5.5";
9
+ const FILTERED = "The AI provider's content filter cut the reply off. Try rewording the request.";
10
+
11
+ /** Why OpenRouter gave no answer: `kind` "refused" (with its `status` and `reply`), "filtered", "timeout" or "stopped". */
12
+ export class OpenRouterTrouble extends Error {
13
+ constructor(kind, message, { status = 0, reply = {}, cost = 0 } = {}) {
14
+ super(message);
15
+ Object.assign(this, { kind, status, reply, cost });
16
+ }
17
+ }
18
+
19
+ /**
20
+ * A request for text: the prompt with its pictures (base64, with their `media_type`) and the system prompt. With
21
+ * `json` the answer must be a JSON object, with room for a long one (a cut-off reply is what breaks apps), and with a
22
+ * `schema` it must fit it too.
23
+ */
24
+ export function textBody({ prompt, images = [], system = "", model = DEFAULT_MODEL, json = false, schema } = {}) {
25
+ const messages = [];
26
+ if (String(system).trim()) messages.push({ role: "system", content: system });
27
+ messages.push({ role: "user", content: [{ type: "text", text: prompt },
28
+ ...(Array.isArray(images) ? images : []).filter((image) => image?.data).map((image) => ({ type: "image_url", image_url: { url: `data:${image.media_type || "image/jpeg"};base64,${image.data}` } }))] });
29
+ const body = { model, messages, usage: { include: true } };
30
+ if (json) {
31
+ body.response_format = schema ? { type: "json_schema", json_schema: { name: "answer", strict: true, schema } } : { type: "json_object" };
32
+ body.max_tokens = 16000;
33
+ }
34
+ return body;
35
+ }
36
+
37
+ /**
38
+ * One chat completion: `{message, cost}`, the model's message as OpenRouter gives it (its text, or its tool calls) and
39
+ * what it cost. A provider's safety filter sometimes refuses an ordinary request at random (finish_reason
40
+ * "content_filter", an empty reply, HTTP 200), so it's asked again, twice at most, within the same `seconds`.
41
+ */
42
+ export async function chat({ key, body, seconds = 120, signal, fetcher = fetch }) {
43
+ const deadline = Date.now() + seconds * 1000;
44
+ let cost = 0;
45
+ for (let attempt = 0; attempt < 3; attempt++) {
46
+ // Whole milliseconds: a timer takes nothing finer, and the seconds given may have more.
47
+ const left = Math.floor(deadline - Date.now());
48
+ if (left < 1000) throw new OpenRouterTrouble("timeout", "This ran out of time.", { cost });
49
+ let answer;
50
+ try {
51
+ answer = await fetcher(ENDPOINT, { method: "POST", body: JSON.stringify(body),
52
+ headers: { Authorization: `Bearer ${key || ""}`, "Content-Type": "application/json", "X-Title": "Lamp" },
53
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(left)]) : AbortSignal.timeout(left) });
54
+ } catch (error) {
55
+ if (signal?.aborted) throw new OpenRouterTrouble("stopped", "Stopped.", { cost });
56
+ if (error?.name === "TimeoutError" || Date.now() >= deadline) throw new OpenRouterTrouble("timeout", "This ran out of time.", { cost });
57
+ throw new OpenRouterTrouble("refused", "Couldn't reach OpenRouter.", { cost });
58
+ }
59
+ let reply = {};
60
+ try { reply = await answer.json(); } catch {}
61
+ cost += Number(reply?.usage?.cost) || 0;
62
+ const choice = Array.isArray(reply?.choices) ? reply.choices[0] : null;
63
+ if (answer.status !== 200 || !choice) throw new OpenRouterTrouble("refused", "OpenRouter didn't answer.", { status: answer.status, reply, cost });
64
+ if (choice.finish_reason === "content_filter") {
65
+ if (attempt < 2) { await new Promise((done) => setTimeout(done, Math.min(400 << attempt, Math.max(0, deadline - Date.now())))); continue; }
66
+ throw new OpenRouterTrouble("filtered", FILTERED, { cost });
67
+ }
68
+ return { message: choice.message ?? {}, cost };
69
+ }
70
+ throw new OpenRouterTrouble("refused", "The AI didn't answer.", { cost });
71
+ }
72
+
73
+ /** A text answer: `{text, cost}`. */
74
+ export async function text(given) {
75
+ const { message, cost } = await chat({ ...given, body: textBody(given) });
76
+ return { text: typeof message.content === "string" ? message.content : "", cost };
77
+ }
78
+
79
+ /** One request to OpenRouter, within `seconds`: its status and its JSON, or why there was none. */
80
+ async function post(path, { key, body, seconds, signal, fetcher = fetch }) {
81
+ const timeout = AbortSignal.timeout(Math.floor(seconds * 1000));
82
+ let answer;
83
+ try {
84
+ answer = await fetcher(`https://openrouter.ai/api/v1/${path}`, { method: "POST", body: JSON.stringify(body),
85
+ headers: { Authorization: `Bearer ${key || ""}`, "Content-Type": "application/json", "X-Title": "Lamp" },
86
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout });
87
+ } catch (error) {
88
+ if (signal?.aborted) throw new OpenRouterTrouble("stopped", "Stopped.");
89
+ if (error?.name === "TimeoutError") throw new OpenRouterTrouble("timeout", "This ran out of time.");
90
+ throw new OpenRouterTrouble("refused", "Couldn't reach OpenRouter.");
91
+ }
92
+ let reply = {};
93
+ try { reply = await answer.json(); } catch {}
94
+ return { status: answer.status, reply: reply && typeof reply === "object" ? reply : {} };
95
+ }
96
+
97
+ /**
98
+ * Decisions from a decision model (`LampAI.decide`, `AppAI.decide`): `{answers, cost}`. Rate limits (429) and overload
99
+ * (529) are expected under load, so it backs off and asks again three times at most. A request the model can't take
100
+ * (422) is "invalid", with what it said.
101
+ */
102
+ export async function decide({ key, model, state, questions, seconds = 60, signal, fetcher }) {
103
+ for (let attempt = 0; attempt < 4; attempt++) {
104
+ const { status, reply } = await post("systemone", { key, body: { state, model, questions }, seconds, signal, fetcher });
105
+ if ((status === 429 || status === 529) && attempt < 3) { await new Promise((done) => setTimeout(done, 500 << attempt)); continue; }
106
+ if (status !== 200 || !reply.answers || typeof reply.answers !== "object") {
107
+ const detail = reply.error?.message ?? (reply.detail !== undefined ? String(typeof reply.detail === "string" ? reply.detail : JSON.stringify(reply.detail)) : `HTTP ${status}`);
108
+ throw new OpenRouterTrouble(status === 422 ? "invalid" : "refused", status === 422 ? `The decision request was invalid: ${detail}` : "The decision model didn't answer.", { status, reply });
109
+ }
110
+ // Jev charges per input token ($0.042 per million); OpenRouter may report the exact cost.
111
+ // A cost OpenRouter reports stands, nothing included; the estimate is only for when it reports none.
112
+ const reported = reply.usage?.cost;
113
+ const cost = typeof reported === "number" && Number.isFinite(reported) ? reported : (Number(reply.usage?.input_tokens) || 0) * 0.042 / 1_000_000;
114
+ return { answers: reply.answers, cost };
115
+ }
116
+ throw new OpenRouterTrouble("busy", "The decision model is busy. Try again in a moment.");
117
+ }
118
+
119
+ /**
120
+ * A picture from an image model (`LampAI.makeImage` and its edits, `AppAI.image`): `{image, cost}`, the PNG in base64.
121
+ * `references` are the pictures to edit or build on (base64, with their `media_type`).
122
+ */
123
+ export async function image({ key, model, prompt, references = [], aspectRatio, seconds = 240, signal, fetcher }) {
124
+ const body = { model, prompt, n: 1, output_format: "png" };
125
+ if (aspectRatio) body.aspect_ratio = aspectRatio;
126
+ const given = (Array.isArray(references) ? references : []).filter((reference) => reference?.data);
127
+ if (given.length) body.input_references = given.map((reference) => ({ type: "image_url", image_url: { url: `data:${reference.media_type || "image/png"};base64,${reference.data}` } }));
128
+ const { status, reply } = await post("images", { key, body, seconds, signal, fetcher });
129
+ if (status !== 200) throw new OpenRouterTrouble("refused", "The image model didn't answer.", { status, reply });
130
+ const picture = Array.isArray(reply.data) ? reply.data[0]?.b64_json : null;
131
+ if (typeof picture !== "string" || !picture) throw new OpenRouterTrouble("empty", "The image model didn't return a picture.", { reply });
132
+ return { image: picture, cost: Number(reply.usage?.cost) || 0 };
133
+ }
@@ -0,0 +1,203 @@
1
+ // Lamp's app, reached the way the person's own coding agent reaches it (`OutsideServer`, Lamp's MCP endpoint): a Unix
2
+ // socket in that Lamp's own folder, private to the person, a hello line each way, then JSON-RPC a line at a time. lamp
3
+ // asks it to take a Mac app's folder on (`open_project`, which Lamp reviews with the person first), to put each change
4
+ // into the app it has open (`apply_change`, which says how it went in: live, held for a Reload, reopened…), and to
5
+ // look at it (`look_at_app`). What a change does to an open app is Lamp's app's to work out (`LiveReload`); this only
6
+ // carries the folder's path there and the answer back.
7
+
8
+ import { createConnection } from "node:net";
9
+ import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ import { Problem, stateOf } from "./state.mjs";
13
+
14
+ // The handshake's version, as Lamp's helper sends it (`OutsideKit.protocolVersion`).
15
+ const PROTOCOL = 1;
16
+
17
+ /** What a Lamp is called where its folders are, by its bundle id (`OutsideKit.editionName`). */
18
+ export function editionName(bundle) {
19
+ if (bundle === "dev.lamp.host.dev" || bundle.startsWith("dev.lamp.host.dev.")) {
20
+ const instance = bundle.startsWith("dev.lamp.host.dev.") ? bundle.slice("dev.lamp.host.dev.".length) : "";
21
+ return "Lamp Dev" + (instance ? ` ${instance}` : "");
22
+ }
23
+ return bundle === "dev.lamp.host.nightly" ? "Lamp Nightly" : "Lamp";
24
+ }
25
+
26
+ const socketOf = (bundle) => join(homedir(), "Library", "Application Support", editionName(bundle), "MCP", "lamp.sock");
27
+
28
+ /** The Lamps lamp looks for, in order: the one LAMP_APP names (a bundle id), else the release and then Lamp Nightly. */
29
+ const candidates = () => process.env.LAMP_APP ? [process.env.LAMP_APP] : ["dev.lamp.host", "dev.lamp.host.nightly"];
30
+
31
+ /**
32
+ * A connection to a running Lamp, greeted: `call(tool, args)` answers what the tool said (its outcome, its words and
33
+ * its pictures). Null when no Lamp lamp knows of is answering.
34
+ */
35
+ export async function reachLamp(bundles = candidates()) {
36
+ for (const bundle of bundles) {
37
+ if (!/^dev\.lamp\.host(\.[A-Za-z0-9-]+)*$/.test(bundle) || !existsSync(socketOf(bundle))) continue;
38
+ const lamp = await connected(bundle).catch(() => null);
39
+ if (lamp) return lamp;
40
+ }
41
+ return null;
42
+ }
43
+
44
+ function connected(bundle) {
45
+ return new Promise((done, failed) => {
46
+ const socket = createConnection(socketOf(bundle));
47
+ let buffer = "", greeted = false, next = 1, ended = null;
48
+ const waiting = new Map();
49
+ // Once the connection is gone, every call is refused with why, those under way and those asked later alike.
50
+ const fail = (problem) => {
51
+ ended ??= problem;
52
+ for (const { failed: no } of waiting.values()) no(problem);
53
+ waiting.clear();
54
+ failed(problem);
55
+ };
56
+ socket.setEncoding("utf8");
57
+ socket.on("error", (error) => fail(new Problem(`couldn't reach ${editionName(bundle)}: ${error.message}`)));
58
+ socket.on("close", () => fail(new Problem(`${editionName(bundle)} closed the connection`)));
59
+ socket.on("connect", () => socket.write(JSON.stringify({ lamp_hello: { protocol: PROTOCOL, client: "lamp" } }) + "\n"));
60
+ const send = (method, params) => new Promise((answered, no) => {
61
+ if (ended) return no(ended);
62
+ const id = next++;
63
+ waiting.set(id, { answered, failed: no });
64
+ socket.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
65
+ });
66
+ socket.on("data", async (chunk) => {
67
+ buffer += chunk;
68
+ for (let at; (at = buffer.indexOf("\n")) >= 0; ) {
69
+ const line = buffer.slice(0, at);
70
+ buffer = buffer.slice(at + 1);
71
+ if (!line.trim()) continue;
72
+ let said;
73
+ try { said = JSON.parse(line); } catch { continue; }
74
+ if (!greeted) {
75
+ if (said.lamp_hello?.error) { socket.destroy(); return failed(new Problem(said.lamp_hello.error)); }
76
+ if (!said.lamp_hello) { socket.destroy(); return failed(new Problem(`${editionName(bundle)} didn't answer as Lamp does`)); }
77
+ greeted = true;
78
+ try {
79
+ await send("initialize", { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "lamp", version: "1" } });
80
+ socket.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
81
+ } catch (error) { return failed(error); }
82
+ done({
83
+ name: editionName(bundle), bundle,
84
+ async call(tool, args) {
85
+ const result = await send("tools/call", { name: tool, arguments: args });
86
+ return answerOf(result);
87
+ },
88
+ close() { ended ??= new Problem("the connection to Lamp was closed"); socket.removeAllListeners("close"); socket.end(); },
89
+ });
90
+ continue;
91
+ }
92
+ const one = waiting.get(said.id);
93
+ if (!one) continue;
94
+ waiting.delete(said.id);
95
+ if (said.error) one.failed(new Problem(said.error.message ?? "Lamp refused it"));
96
+ else one.answered(said.result ?? {});
97
+ }
98
+ });
99
+ });
100
+ }
101
+
102
+ /** A tool's answer as lamp reads it: the outcome its words lead with, the words after it, and its pictures. */
103
+ export function answerOf(result) {
104
+ const content = Array.isArray(result?.content) ? result.content : [];
105
+ const text = content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
106
+ const outcome = /^Outcome: ([a-z_]+)\./.exec(text)?.[1] ?? null;
107
+ // The facts are one line of JSON under the outcome: kept for whoever reads them, out of the words.
108
+ const lines = text.split("\n");
109
+ let facts = {};
110
+ if (outcome && lines[1]?.startsWith("{")) { try { facts = JSON.parse(lines[1]); } catch {} }
111
+ const words = (outcome ? lines.slice(lines[1]?.startsWith("{") ? 2 : 1) : lines).join("\n").trim();
112
+ const pictures = content.filter((part) => part.type === "image" && part.data).map((part) => Buffer.from(part.data, "base64"));
113
+ return { outcome, facts, words, pictures, failed: result?.isError === true };
114
+ }
115
+
116
+ /**
117
+ * The Lamp whose own folder an app's is in (`~/Library/Application Support/<Lamp>/Apps/…`, followed through a link to
118
+ * it), by its bundle id, or null for a folder of anyone else's.
119
+ */
120
+ export function lampOwning(folder) {
121
+ let real = folder;
122
+ try { real = realpathSync(folder); } catch {}
123
+ const name = new RegExp(`${join(homedir(), "Library", "Application Support").replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/(Lamp(?: Nightly| Dev(?: [A-Za-z0-9-]+)?)?)/Apps/`).exec(real)?.[1];
124
+ if (!name) return null;
125
+ if (name === "Lamp") return "dev.lamp.host";
126
+ if (name === "Lamp Nightly") return "dev.lamp.host.nightly";
127
+ return name === "Lamp Dev" ? "dev.lamp.host.dev" : `dev.lamp.host.dev.${name.slice("Lamp Dev ".length)}`;
128
+ }
129
+
130
+ // MARK: Which folders are Lamp's app's
131
+
132
+ const markOf = (folder) => join(stateOf(folder), "in-lamp.json");
133
+
134
+ /** The Lamp a folder was given to (`lamp open --in-lamp`), by its bundle id, or null for one lamp runs itself. */
135
+ export function givenTo(folder) {
136
+ try { return JSON.parse(readFileSync(markOf(folder), "utf8")).bundle ?? null; } catch { return null; }
137
+ }
138
+
139
+ export function giveTo(folder, bundle) {
140
+ mkdirSync(stateOf(folder), { recursive: true });
141
+ writeFileSync(markOf(folder), JSON.stringify({ bundle }));
142
+ }
143
+
144
+ export function takeBack(folder) { rmSync(markOf(folder), { force: true }); }
145
+
146
+ /** The pictures an answer brought, kept where lamp keeps a look's, by their paths. */
147
+ export function keepPictures(folder, pictures) {
148
+ mkdirSync(stateOf(folder), { recursive: true });
149
+ return pictures.map((bytes, n) => {
150
+ const path = join(stateOf(folder), `lamp-look${n ? `-${n + 1}` : ""}.png`);
151
+ writeFileSync(path, bytes);
152
+ return path;
153
+ });
154
+ }
155
+
156
+ /**
157
+ * The link Lamp's app keeps for an app in its own folder (its "Wish Links.json", by the app's instance id), as lamp
158
+ * keeps a link (`readLink`), or null. lamp shares such an app itself, to the same link, whether Lamp is open or not.
159
+ */
160
+ export function lampsKeptLink(folder) {
161
+ const where = keptLinkOf(folder);
162
+ if (!where) return null;
163
+ let kept;
164
+ try { kept = JSON.parse(readFileSync(where.file, "utf8"))[where.key]; } catch { return null; }
165
+ if (typeof kept?.id !== "string" || typeof kept.secret !== "string" || typeof kept.url !== "string") return null;
166
+ const shown = kept.shown ?? {};
167
+ let site = "https://lamp.dev";
168
+ try { site = new URL(kept.url).origin; } catch {}
169
+ return { site, id: kept.id, url: kept.url, secret: kept.secret, name: shown.name ?? "", words: shown.words ?? "", line: shown.line ?? "",
170
+ by: shown.by ?? "", asks: shown.asks ?? [], ...(shown.file ? { file: true } : {}) };
171
+ }
172
+
173
+ /** Tells Lamp's app what lamp shared to an app's link (or, with none, that it was taken down), so its Share Link card
174
+ * shows the link as it is now. */
175
+ export function keepInLamp(folder, link) {
176
+ const where = keptLinkOf(folder);
177
+ if (!where) return;
178
+ let all = {};
179
+ try { all = JSON.parse(readFileSync(where.file, "utf8")) || {}; } catch {}
180
+ const was = all[where.key] ?? {};
181
+ // Taken down: Lamp's card has no link for it any more.
182
+ if (!link) delete all[where.key];
183
+ else all[where.key] = { ...was, id: link.id, url: link.url, secret: link.secret,
184
+ // Its picture isn't the one Lamp took (Lamp knows its own by a digest), so the card offers to send Lamp's again.
185
+ shown: { ...(({ picture: _picture, ...rest }) => rest)(was.shown ?? {}), name: link.name, words: link.words, line: link.line, by: link.by,
186
+ asks: link.asks ?? [], file: link.file ? true : undefined,
187
+ ...(link.shape !== undefined ? { shape: link.shape === "fill" ? "fill" : Number(link.shape).toFixed(4) } : {}) } };
188
+ mkdirSync(dirname(where.file), { recursive: true });
189
+ writeFileSync(where.file, JSON.stringify(all), { mode: 0o600 });
190
+ }
191
+
192
+ /** Where Lamp's app keeps an app's link: its Lamp's "Wish Links.json", under the app's instance id (`.lamp/meta.json`). */
193
+ function keptLinkOf(folder) {
194
+ let real = folder;
195
+ try { real = realpathSync(folder); } catch {}
196
+ const at = real.lastIndexOf("/Apps/");
197
+ if (!lampOwning(real) || at < 0) return null;
198
+ const app = join(real.slice(0, at), "Apps", real.slice(at + 6).split("/")[0]);
199
+ let meta;
200
+ try { meta = JSON.parse(readFileSync(join(app, ".lamp", "meta.json"), "utf8")); } catch { return null; }
201
+ const key = typeof meta?.instance === "string" && meta.instance ? meta.instance : meta?.id;
202
+ return typeof key === "string" && key ? { file: join(real.slice(0, at), "Wish Links.json"), key } : null;
203
+ }