@auraindustry/aurajs 0.0.7 → 0.1.0

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 (334) hide show
  1. package/README.md +98 -2
  2. package/benchmarks/perf-thresholds.json +54 -0
  3. package/package.json +4 -7
  4. package/src/asset-pack.mjs +5 -1
  5. package/src/authored-project.mjs +1449 -0
  6. package/src/authored-runtime.mjs +2016 -0
  7. package/src/authoring/avatar-animation-graph.mjs +648 -0
  8. package/src/bin-integrity.mjs +272 -0
  9. package/src/build-contract/assets.mjs +130 -0
  10. package/src/build-contract/capabilities.mjs +116 -0
  11. package/src/build-contract/constants.mjs +6 -0
  12. package/src/build-contract/helpers.mjs +44 -0
  13. package/src/build-contract/web-templates.mjs +5993 -0
  14. package/src/build-contract.mjs +27 -2910
  15. package/src/bundler.mjs +188 -55
  16. package/src/cli.mjs +4825 -1512
  17. package/src/commands/project-authoring.mjs +434 -0
  18. package/src/config.mjs +27 -0
  19. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +3309 -0
  20. package/src/conformance/cases/core-runtime-cases.mjs +1431 -0
  21. package/src/conformance/cases/index.mjs +11 -0
  22. package/src/conformance/cases/scene3d-and-media-cases.mjs +2094 -0
  23. package/src/conformance/cases/systems-and-gameplay-cases.mjs +1776 -0
  24. package/src/conformance/shared.mjs +27 -0
  25. package/src/conformance-runner.mjs +25 -13
  26. package/src/conformance.mjs +619 -4020
  27. package/src/cutscene.mjs +362 -5
  28. package/src/dev-cli-action.mjs +249 -0
  29. package/src/dev-cli-inspect.mjs +92 -0
  30. package/src/dev-cli-state.mjs +80 -0
  31. package/src/external-asset-cache.mjs +587 -0
  32. package/src/external-asset-policy.mjs +217 -0
  33. package/src/external-package-surface.mjs +206 -0
  34. package/src/game-action-runtime.mjs +869 -0
  35. package/src/game-state-runtime.mjs +206 -6
  36. package/src/headless-action.mjs +186 -0
  37. package/src/headless-test/runtime-animation.mjs +1173 -0
  38. package/src/headless-test/runtime-coordinator.mjs +1514 -0
  39. package/src/headless-test/runtime-primitives.mjs +320 -0
  40. package/src/headless-test/runtime-world.mjs +2253 -0
  41. package/src/headless-test.mjs +392 -4298
  42. package/src/host-binary.mjs +342 -14
  43. package/src/icon-discovery.mjs +64 -0
  44. package/src/make-catalog.mjs +109 -0
  45. package/src/make.mjs +197 -0
  46. package/src/package-integrity.mjs +586 -0
  47. package/src/perf-benchmark.mjs +353 -0
  48. package/src/postinstall.mjs +5 -5
  49. package/src/prefabs/index.mjs +34 -0
  50. package/src/prefabs/scene-serialization.mjs +184 -0
  51. package/src/project-importer.mjs +620 -0
  52. package/src/project-registry.mjs +24 -0
  53. package/src/publish-command.mjs +195 -0
  54. package/src/publish-env-example.mjs +83 -0
  55. package/src/publish-validation.mjs +708 -0
  56. package/src/retro/assets/compile.mjs +232 -0
  57. package/src/retro/backend-gba/authoring.mjs +1029 -0
  58. package/src/retro/backend-gba/rom.mjs +363 -0
  59. package/src/retro/backend-gbc/rom.mjs +85 -0
  60. package/src/retro/build.mjs +278 -0
  61. package/src/retro/cli/commands.mjs +292 -0
  62. package/src/retro/cli/templates.mjs +84 -0
  63. package/src/retro/diagnostics/catalog.mjs +110 -0
  64. package/src/retro/diagnostics/emit.mjs +72 -0
  65. package/src/retro/emulator/case-overlay.mjs +64 -0
  66. package/src/retro/emulator/discovery.mjs +158 -0
  67. package/src/retro/emulator/macos-case-overlay.swift +220 -0
  68. package/src/retro/emulator/profiles.mjs +146 -0
  69. package/src/retro/emulator/runner.mjs +289 -0
  70. package/src/retro/frontend/load-project.mjs +98 -0
  71. package/src/retro/index.mjs +30 -0
  72. package/src/retro/ir/build-ir.mjs +108 -0
  73. package/src/retro/runtime-gba/contract.mjs +151 -0
  74. package/src/retro/runtime-gbc/contract.mjs +117 -0
  75. package/src/retro/shared/span.mjs +26 -0
  76. package/src/retro/shared/targets.mjs +64 -0
  77. package/src/retro/validator/check-project.mjs +114 -0
  78. package/src/runtime-hotspot-audit.mjs +707 -0
  79. package/src/scaffold/config.mjs +1000 -0
  80. package/src/scaffold/fs.mjs +56 -0
  81. package/src/scaffold/layout.mjs +318 -0
  82. package/src/scaffold/project-docs.mjs +438 -0
  83. package/src/scaffold.mjs +93 -596
  84. package/src/scene-composition/index.mjs +326 -0
  85. package/src/scene-composition/runtime.mjs +751 -0
  86. package/src/self-hosted-assets.mjs +604 -0
  87. package/src/session-client.mjs +750 -0
  88. package/src/session-native-launcher.mjs +74 -0
  89. package/src/session-protocol.mjs +75 -0
  90. package/src/session-runtime.mjs +321 -0
  91. package/src/session-server.mjs +360 -0
  92. package/src/shader-kits/index.mjs +773 -0
  93. package/src/starter-content-registry.mjs +292 -0
  94. package/src/state-artifacts.mjs +662 -24
  95. package/src/state-dev-reload.mjs +99 -2
  96. package/src/terminal-ui.mjs +245 -0
  97. package/src/web-conformance.mjs +219 -0
  98. package/templates/create/2d/config/gameplay/shooter.config.js +26 -0
  99. package/templates/create/2d/content/gameplay/waves.json +26 -0
  100. package/templates/create/2d/content/registries/.gitkeep +1 -0
  101. package/templates/create/2d/docs/design/.gitkeep +1 -0
  102. package/templates/create/2d/docs/design/loop.md +5 -0
  103. package/templates/create/2d/prefabs/enemies.prefab.js +90 -0
  104. package/templates/create/2d/prefabs/enemy-basic.prefab.js +18 -0
  105. package/templates/create/2d/prefabs/player.prefab.js +36 -0
  106. package/templates/create/2d/prefabs/projectiles.prefab.js +35 -0
  107. package/templates/create/2d/scenes/boot.scene.js +12 -0
  108. package/templates/create/2d/scenes/gameplay.scene.js +230 -0
  109. package/templates/create/2d/scenes/menu.scene.js +9 -0
  110. package/templates/create/2d/src/main.js +6 -185
  111. package/templates/create/2d/src/runtime/app.js +49 -0
  112. package/templates/create/2d/src/runtime/capabilities.js +35 -0
  113. package/templates/create/2d/ui/hud.screen.js +40 -0
  114. package/templates/create/2d/ui/pause.screen.js +149 -0
  115. package/templates/create/2d/ui/settings.screen.js +347 -0
  116. package/templates/create/2d/ui/title.screen.js +13 -0
  117. package/templates/create/2d-adventure/aura.config.json +28 -0
  118. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +14 -0
  119. package/templates/create/2d-adventure/content/gameplay/world.js +46 -0
  120. package/templates/create/2d-adventure/content/registries/.gitkeep +1 -0
  121. package/templates/create/2d-adventure/docs/design/loop.md +5 -0
  122. package/templates/create/2d-adventure/prefabs/player.prefab.js +54 -0
  123. package/templates/create/2d-adventure/prefabs/relic.prefab.js +38 -0
  124. package/templates/create/2d-adventure/prefabs/world.prefab.js +125 -0
  125. package/templates/create/2d-adventure/scenes/gameplay.scene.js +256 -0
  126. package/templates/create/2d-adventure/src/runtime/capabilities.js +34 -0
  127. package/templates/create/2d-adventure/ui/hud.screen.js +60 -0
  128. package/templates/create/2d-survivor/config/gameplay/survivor.config.js +33 -0
  129. package/templates/create/2d-survivor/content/gameplay/spawn-zones.json +29 -0
  130. package/templates/create/2d-survivor/content/registries/.gitkeep +1 -0
  131. package/templates/create/2d-survivor/docs/design/.gitkeep +1 -0
  132. package/templates/create/2d-survivor/docs/design/loop.md +5 -0
  133. package/templates/create/2d-survivor/prefabs/enemies.prefab.js +178 -0
  134. package/templates/create/2d-survivor/prefabs/enemy-swarm.prefab.js +18 -0
  135. package/templates/create/2d-survivor/prefabs/player.prefab.js +42 -0
  136. package/templates/create/2d-survivor/prefabs/projectiles.prefab.js +56 -0
  137. package/templates/create/2d-survivor/scenes/boot.scene.js +12 -0
  138. package/templates/create/2d-survivor/scenes/gameplay.scene.js +314 -0
  139. package/templates/create/2d-survivor/scenes/menu.scene.js +9 -0
  140. package/templates/create/2d-survivor/src/main.js +5 -332
  141. package/templates/create/2d-survivor/src/runtime/app.js +49 -0
  142. package/templates/create/2d-survivor/src/runtime/capabilities.js +35 -0
  143. package/templates/create/2d-survivor/ui/hud.screen.js +45 -0
  144. package/templates/create/2d-survivor/ui/title.screen.js +13 -0
  145. package/templates/create/3d/assets/models/starter-avatar.gltf +184 -0
  146. package/templates/create/3d/config/gameplay/.gitkeep +1 -0
  147. package/templates/create/3d/content/gameplay/checkpoints.json +33 -0
  148. package/templates/create/3d/content/gameplay/course.js +40 -0
  149. package/templates/create/3d/content/registries/.gitkeep +1 -0
  150. package/templates/create/3d/docs/design/.gitkeep +1 -0
  151. package/templates/create/3d/docs/design/loop.md +5 -0
  152. package/templates/create/3d/prefabs/checkpoint.prefab.js +15 -0
  153. package/templates/create/3d/prefabs/player.prefab.js +204 -0
  154. package/templates/create/3d/prefabs/world.prefab.js +112 -0
  155. package/templates/create/3d/scenes/boot.scene.js +12 -0
  156. package/templates/create/3d/scenes/checkpoint.scene.js +9 -0
  157. package/templates/create/3d/scenes/gameplay.scene.js +292 -0
  158. package/templates/create/3d/src/main.js +6 -295
  159. package/templates/create/3d/src/runtime/app.js +49 -0
  160. package/templates/create/3d/src/runtime/capabilities.js +53 -0
  161. package/templates/create/3d/src/runtime/materials.js +34 -0
  162. package/templates/create/3d/src/runtime/state.js +39 -0
  163. package/templates/create/3d/ui/hud.screen.js +75 -0
  164. package/templates/create/3d/ui/pause.screen.js +166 -0
  165. package/templates/create/3d/ui/settings.screen.js +387 -0
  166. package/templates/create/3d-adventure/assets/models/starter-avatar.gltf +184 -0
  167. package/templates/create/3d-adventure/aura.config.json +28 -0
  168. package/templates/create/3d-adventure/config/gameplay/adventure.config.js +9 -0
  169. package/templates/create/3d-adventure/content/gameplay/course.js +62 -0
  170. package/templates/create/3d-adventure/content/registries/.gitkeep +1 -0
  171. package/templates/create/3d-adventure/docs/design/loop.md +5 -0
  172. package/templates/create/3d-adventure/prefabs/player.prefab.js +168 -0
  173. package/templates/create/3d-adventure/prefabs/relic.prefab.js +35 -0
  174. package/templates/create/3d-adventure/prefabs/world.prefab.js +119 -0
  175. package/templates/create/3d-adventure/scenes/gameplay.scene.js +358 -0
  176. package/templates/create/3d-adventure/src/runtime/capabilities.js +56 -0
  177. package/templates/create/3d-adventure/src/runtime/materials.js +39 -0
  178. package/templates/create/3d-adventure/src/runtime/state.js +31 -0
  179. package/templates/create/3d-adventure/ui/hud.screen.js +70 -0
  180. package/templates/create/3d-adventure/ui/pause.screen.js +437 -0
  181. package/templates/create/3d-collectathon/assets/models/starter-avatar.gltf +184 -0
  182. package/templates/create/3d-collectathon/config/gameplay/.gitkeep +1 -0
  183. package/templates/create/3d-collectathon/content/gameplay/collectibles.json +26 -0
  184. package/templates/create/3d-collectathon/content/gameplay/course.js +46 -0
  185. package/templates/create/3d-collectathon/content/registries/.gitkeep +1 -0
  186. package/templates/create/3d-collectathon/docs/design/.gitkeep +1 -0
  187. package/templates/create/3d-collectathon/docs/design/loop.md +5 -0
  188. package/templates/create/3d-collectathon/prefabs/collectible.prefab.js +15 -0
  189. package/templates/create/3d-collectathon/prefabs/player.prefab.js +207 -0
  190. package/templates/create/3d-collectathon/prefabs/world.prefab.js +112 -0
  191. package/templates/create/3d-collectathon/scenes/boot.scene.js +12 -0
  192. package/templates/create/3d-collectathon/scenes/checkpoint.scene.js +9 -0
  193. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +200 -0
  194. package/templates/create/3d-collectathon/src/main.js +5 -355
  195. package/templates/create/3d-collectathon/src/runtime/app.js +49 -0
  196. package/templates/create/3d-collectathon/src/runtime/capabilities.js +53 -0
  197. package/templates/create/3d-collectathon/src/runtime/materials.js +34 -0
  198. package/templates/create/3d-collectathon/src/runtime/state.js +27 -0
  199. package/templates/create/3d-collectathon/ui/hud.screen.js +66 -0
  200. package/templates/create/3d-collectathon/ui/pause.screen.js +13 -0
  201. package/templates/create/blank/config/gameplay/.gitkeep +1 -0
  202. package/templates/create/blank/content/gameplay/.gitkeep +1 -0
  203. package/templates/create/blank/content/registries/.gitkeep +1 -0
  204. package/templates/create/blank/docs/design/.gitkeep +1 -0
  205. package/templates/create/blank/docs/design/loop.md +5 -0
  206. package/templates/create/blank/prefabs/.gitkeep +1 -0
  207. package/templates/create/blank/scenes/.gitkeep +1 -0
  208. package/templates/create/blank/src/runtime/.gitkeep +1 -0
  209. package/templates/create/blank/ui/.gitkeep +1 -0
  210. package/templates/create/deckbuilder-2d/assets/audio/.gitkeep +1 -0
  211. package/templates/create/deckbuilder-2d/assets/fonts/.gitkeep +1 -0
  212. package/templates/create/deckbuilder-2d/assets/sprites/.gitkeep +1 -0
  213. package/templates/create/deckbuilder-2d/assets/starter/README.md +11 -0
  214. package/templates/create/deckbuilder-2d/assets/ui/.gitkeep +1 -0
  215. package/templates/create/deckbuilder-2d/aura.config.json +28 -0
  216. package/templates/create/deckbuilder-2d/config/gameplay/deckbuilder.config.js +26 -0
  217. package/templates/create/deckbuilder-2d/content/cards/guard.card.js +19 -0
  218. package/templates/create/deckbuilder-2d/content/cards/spark.card.js +20 -0
  219. package/templates/create/deckbuilder-2d/content/cards/starter.deck.js +69 -0
  220. package/templates/create/deckbuilder-2d/content/cards/strike.card.js +19 -0
  221. package/templates/create/deckbuilder-2d/content/cards/survey.card.js +20 -0
  222. package/templates/create/deckbuilder-2d/content/encounters/training-battle.encounter.js +14 -0
  223. package/templates/create/deckbuilder-2d/content/encounters/training-battle.js +65 -0
  224. package/templates/create/deckbuilder-2d/content/enemies/training-automaton.enemy.js +48 -0
  225. package/templates/create/deckbuilder-2d/content/gameplay/.gitkeep +1 -0
  226. package/templates/create/deckbuilder-2d/content/registries/cards.registry.js +26 -0
  227. package/templates/create/deckbuilder-2d/content/registries/encounters.registry.js +20 -0
  228. package/templates/create/deckbuilder-2d/content/registries/enemies.registry.js +20 -0
  229. package/templates/create/deckbuilder-2d/content/registries/relics.registry.js +20 -0
  230. package/templates/create/deckbuilder-2d/content/relics/ember-charm.relic.js +18 -0
  231. package/templates/create/deckbuilder-2d/docs/design/loop.md +12 -0
  232. package/templates/create/deckbuilder-2d/prefabs/.gitkeep +1 -0
  233. package/templates/create/deckbuilder-2d/scenes/boot.scene.js +84 -0
  234. package/templates/create/deckbuilder-2d/scenes/gameplay.scene.js +641 -0
  235. package/templates/create/deckbuilder-2d/src/components/.gitkeep +1 -0
  236. package/templates/create/deckbuilder-2d/src/main.js +17 -0
  237. package/templates/create/deckbuilder-2d/src/runtime/capabilities.js +22 -0
  238. package/templates/create/deckbuilder-2d/src/shared/.gitkeep +1 -0
  239. package/templates/create/deckbuilder-2d/src/systems/.gitkeep +1 -0
  240. package/templates/create/deckbuilder-2d/tests/smoke/.gitkeep +1 -0
  241. package/templates/create/deckbuilder-2d/ui/hud.screen.js +80 -0
  242. package/templates/create/deckbuilder-2d/ui/pause.screen.js +146 -0
  243. package/templates/create/deckbuilder-2d/ui/settings.screen.js +342 -0
  244. package/templates/create/local-multiplayer/aura.config.json +40 -0
  245. package/templates/create/local-multiplayer/config/gameplay/local-multiplayer.config.js +26 -0
  246. package/templates/create/local-multiplayer/content/gameplay/room-layout.js +13 -0
  247. package/templates/create/local-multiplayer/content/registries/.gitkeep +1 -0
  248. package/templates/create/local-multiplayer/docs/design/loop.md +14 -0
  249. package/templates/create/local-multiplayer/prefabs/player.prefab.js +99 -0
  250. package/templates/create/local-multiplayer/scenes/boot.scene.js +12 -0
  251. package/templates/create/local-multiplayer/scenes/gameplay.scene.js +443 -0
  252. package/templates/create/local-multiplayer/src/main.js +17 -0
  253. package/templates/create/local-multiplayer/src/runtime/capabilities.js +28 -0
  254. package/templates/create/local-multiplayer/ui/hud.screen.js +60 -0
  255. package/templates/create/shared/src/runtime/project-inspector.js +105 -0
  256. package/templates/create/shared/src/runtime/scene-flow.js +290 -0
  257. package/templates/create/shared/src/runtime/screen-shell.js +222 -0
  258. package/templates/create/shared/src/runtime/ui-forms.js +209 -0
  259. package/templates/create/shared/src/runtime/ui-settings.js +237 -0
  260. package/templates/create/shared/src/runtime/ui-theme.js +352 -0
  261. package/templates/create/shared/src/starter-utils/adventure-objectives.js +102 -0
  262. package/templates/create/shared/src/starter-utils/animation-2d.js +337 -0
  263. package/templates/create/shared/src/starter-utils/avatar-3d.js +404 -0
  264. package/templates/create/shared/src/starter-utils/combat-feedback-2d.js +320 -0
  265. package/templates/create/shared/src/starter-utils/core.js +39 -3
  266. package/templates/create/shared/src/starter-utils/index.js +8 -2
  267. package/templates/create/shared/src/starter-utils/platformer-3d.js +34 -3
  268. package/templates/create/shared/src/starter-utils/triggers.js +662 -0
  269. package/templates/create/shared/src/starter-utils/tween-2d.js +615 -0
  270. package/templates/create/video-cutscene/assets/video/.gitkeep +0 -0
  271. package/templates/create/video-cutscene/aura.config.json +28 -0
  272. package/templates/create/video-cutscene/config/gameplay/.gitkeep +0 -0
  273. package/templates/create/video-cutscene/content/gameplay/.gitkeep +0 -0
  274. package/templates/create/video-cutscene/content/registries/.gitkeep +0 -0
  275. package/templates/create/video-cutscene/docs/design/loop.md +22 -0
  276. package/templates/create/video-cutscene/prefabs/.gitkeep +0 -0
  277. package/templates/create/video-cutscene/scenes/boot.scene.js +11 -0
  278. package/templates/create/video-cutscene/scenes/cutscene.scene.js +113 -0
  279. package/templates/create/video-cutscene/scenes/gameplay.scene.js +50 -0
  280. package/templates/create/video-cutscene/src/main.js +17 -0
  281. package/templates/create/video-cutscene/src/runtime/app.js +52 -0
  282. package/templates/create/video-cutscene/src/runtime/capabilities.js +35 -0
  283. package/templates/create/video-cutscene/src/runtime/state.js +13 -0
  284. package/templates/create/video-cutscene/ui/.gitkeep +0 -0
  285. package/templates/create-bin/play.js +1187 -0
  286. package/templates/make/README.md +46 -0
  287. package/templates/make/catalog.json +51 -0
  288. package/templates/make/component/files/{{MAKE_NAME}}.component.js +20 -0
  289. package/templates/make/component/manifest.json +9 -0
  290. package/templates/make/data/files/{{MAKE_NAME}}.json +14 -0
  291. package/templates/make/data/manifest.json +9 -0
  292. package/templates/make/material/files/{{MAKE_NAME}}.material.json +17 -0
  293. package/templates/make/material/manifest.json +9 -0
  294. package/templates/make/prefab/files/{{MAKE_NAME}}.prefab.js +20 -0
  295. package/templates/make/prefab/manifest.json +9 -0
  296. package/templates/make/scene/files/{{MAKE_NAME}}.scene.js +31 -0
  297. package/templates/make/scene/manifest.json +9 -0
  298. package/templates/make/shader/files/{{MAKE_NAME}}.shader.js +23 -0
  299. package/templates/make/shader/manifest.json +9 -0
  300. package/templates/make/system/files/{{MAKE_NAME}}.system.js +15 -0
  301. package/templates/make/system/manifest.json +9 -0
  302. package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.js +16 -0
  303. package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.json +23 -0
  304. package/templates/make/ui-screen/manifest.json +10 -0
  305. package/templates/make-starters/deckbuilder-2d/card/files/{{MAKE_NAME}}.card.js +22 -0
  306. package/templates/make-starters/deckbuilder-2d/card/manifest.json +9 -0
  307. package/templates/make-starters/deckbuilder-2d/catalog.json +34 -0
  308. package/templates/make-starters/deckbuilder-2d/encounter/files/{{MAKE_NAME}}.encounter.js +18 -0
  309. package/templates/make-starters/deckbuilder-2d/encounter/manifest.json +9 -0
  310. package/templates/make-starters/deckbuilder-2d/enemy/files/{{MAKE_NAME}}.enemy.js +28 -0
  311. package/templates/make-starters/deckbuilder-2d/enemy/manifest.json +9 -0
  312. package/templates/make-starters/deckbuilder-2d/relic/files/{{MAKE_NAME}}.relic.js +23 -0
  313. package/templates/make-starters/deckbuilder-2d/relic/manifest.json +9 -0
  314. package/templates/retro/platformer/README.md +10 -0
  315. package/templates/retro/platformer/assets/retro/assets.json +91 -0
  316. package/templates/retro/platformer/aura.config.json +7 -0
  317. package/templates/retro/platformer/package.json +5 -0
  318. package/templates/retro/platformer/src/main.js +40 -0
  319. package/templates/retro/puzzle-grid/README.md +10 -0
  320. package/templates/retro/puzzle-grid/assets/retro/assets.json +90 -0
  321. package/templates/retro/puzzle-grid/aura.config.json +7 -0
  322. package/templates/retro/puzzle-grid/package.json +5 -0
  323. package/templates/retro/puzzle-grid/src/main.js +29 -0
  324. package/templates/retro/tactics-grid/README.md +10 -0
  325. package/templates/retro/tactics-grid/assets/retro/assets.json +90 -0
  326. package/templates/retro/tactics-grid/aura.config.json +7 -0
  327. package/templates/retro/tactics-grid/package.json +5 -0
  328. package/templates/retro/tactics-grid/src/main.js +35 -0
  329. package/templates/retro/topdown-adventure/README.md +10 -0
  330. package/templates/retro/topdown-adventure/assets/retro/assets.json +95 -0
  331. package/templates/retro/topdown-adventure/aura.config.json +7 -0
  332. package/templates/retro/topdown-adventure/package.json +5 -0
  333. package/templates/retro/topdown-adventure/src/main.js +29 -0
  334. package/templates/skills/aurajs/SKILL.md +61 -5
@@ -0,0 +1,750 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash, randomBytes } from 'node:crypto';
3
+ import {
4
+ closeSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ openSync,
8
+ readFileSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from 'node:fs';
12
+ import net from 'node:net';
13
+ import { tmpdir } from 'node:os';
14
+ import { dirname, resolve } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ import {
18
+ DEFAULT_SESSION_HEIGHT,
19
+ DEFAULT_SESSION_REQUEST_TIMEOUT_MS,
20
+ DEFAULT_SESSION_START_TIMEOUT_MS,
21
+ DEFAULT_SESSION_WIDTH,
22
+ DEV_SESSION_REGISTRY_SCHEMA_VERSION,
23
+ DEV_SESSION_REPORT_SCHEMA_VERSION,
24
+ DEV_SESSION_REQUEST_SCHEMA_VERSION,
25
+ DEV_SESSION_RESPONSE_SCHEMA_VERSION,
26
+ orderSessionRecord,
27
+ orderSessionRegistry,
28
+ orderSessionReport,
29
+ } from './session-protocol.mjs';
30
+
31
+ const SESSION_SERVER_PATH = fileURLToPath(new URL('./session-server.mjs', import.meta.url));
32
+ const SESSION_NATIVE_LAUNCHER_PATH = fileURLToPath(new URL('./session-native-launcher.mjs', import.meta.url));
33
+ const SESSION_REFRESH_RETRY_TIMEOUT_MS = 1000;
34
+ const SESSION_REFRESH_RETRY_INTERVAL_MS = 50;
35
+
36
+ export class SessionError extends Error {
37
+ constructor(reasonCode, message, details = {}) {
38
+ super(message);
39
+ this.name = 'SessionError';
40
+ this.reasonCode = reasonCode;
41
+ this.details = details;
42
+ }
43
+ }
44
+
45
+ function normalizeNonEmptyString(value) {
46
+ const normalized = String(value || '').trim();
47
+ return normalized.length > 0 ? normalized : null;
48
+ }
49
+
50
+ function normalizePid(value) {
51
+ const parsed = Number(value);
52
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
53
+ }
54
+
55
+ function createDefaultRegistry() {
56
+ return {
57
+ schemaVersion: DEV_SESSION_REGISTRY_SCHEMA_VERSION,
58
+ currentSessionId: null,
59
+ sessions: [],
60
+ };
61
+ }
62
+
63
+ function sortSessions(records) {
64
+ return [...records].sort((a, b) => String(a.sessionId).localeCompare(String(b.sessionId)));
65
+ }
66
+
67
+ export function resolveSessionRoot(projectRoot) {
68
+ return resolve(projectRoot, '.aura', 'session');
69
+ }
70
+
71
+ export function resolveSessionRegistryPath(projectRoot) {
72
+ return resolve(resolveSessionRoot(projectRoot), 'registry.json');
73
+ }
74
+
75
+ function resolveSessionSocketsDir(projectRoot) {
76
+ if (process.platform === 'win32') {
77
+ return null;
78
+ }
79
+ return tmpdir();
80
+ }
81
+
82
+ function resolveSessionLogsDir(projectRoot) {
83
+ return resolve(resolveSessionRoot(projectRoot), 'logs');
84
+ }
85
+
86
+ export function resolveSessionLogPath(projectRoot, sessionId) {
87
+ return resolve(resolveSessionLogsDir(projectRoot), `${sessionId}.log`);
88
+ }
89
+
90
+ export function resolveSessionSocketPath(projectRoot, sessionId) {
91
+ if (process.platform === 'win32') {
92
+ const digest = createHash('sha1')
93
+ .update(resolve(projectRoot))
94
+ .digest('hex')
95
+ .slice(0, 8);
96
+ return `\\\\.\\pipe\\aurajs-session-${digest}-${sessionId}`;
97
+ }
98
+ const digest = createHash('sha1')
99
+ .update(resolve(projectRoot))
100
+ .digest('hex')
101
+ .slice(0, 8);
102
+ const slug = String(sessionId || 'session')
103
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
104
+ .slice(0, 40);
105
+ return resolve(resolveSessionSocketsDir(projectRoot), `aurajs-${digest}-${slug}.sock`);
106
+ }
107
+
108
+ function normalizeRegistryPayload(payload) {
109
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
110
+ return createDefaultRegistry();
111
+ }
112
+
113
+ const sessions = Array.isArray(payload.sessions)
114
+ ? payload.sessions
115
+ .filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
116
+ .map((entry) => orderSessionRecord({
117
+ sessionId: normalizeNonEmptyString(entry.sessionId),
118
+ name: normalizeNonEmptyString(entry.name) || normalizeNonEmptyString(entry.sessionId),
119
+ mode: normalizeNonEmptyString(entry.mode) || 'headless',
120
+ origin: normalizeNonEmptyString(entry.origin) || 'session.start',
121
+ launchReasonCode: normalizeNonEmptyString(entry.launchReasonCode)
122
+ || (normalizeNonEmptyString(entry.mode) === 'native'
123
+ ? 'session_start_native_detached'
124
+ : 'session_start_headless_detached'),
125
+ controllerPid: normalizePid(entry.controllerPid),
126
+ pid: normalizePid(entry.pid),
127
+ projectRoot: normalizeNonEmptyString(entry.projectRoot),
128
+ entryFile: normalizeNonEmptyString(entry.entryFile),
129
+ socketPath: normalizeNonEmptyString(entry.socketPath),
130
+ logPath: normalizeNonEmptyString(entry.logPath),
131
+ createdAt: normalizeNonEmptyString(entry.createdAt),
132
+ }))
133
+ .filter((entry) => entry.sessionId && entry.socketPath)
134
+ : [];
135
+
136
+ const currentSessionId = normalizeNonEmptyString(payload.currentSessionId);
137
+
138
+ return orderSessionRegistry({
139
+ schemaVersion: DEV_SESSION_REGISTRY_SCHEMA_VERSION,
140
+ currentSessionId,
141
+ sessions: sortSessions(sessions),
142
+ });
143
+ }
144
+
145
+ export function readSessionRegistry(projectRoot) {
146
+ const registryPath = resolveSessionRegistryPath(projectRoot);
147
+ if (!existsSync(registryPath)) {
148
+ return createDefaultRegistry();
149
+ }
150
+
151
+ try {
152
+ const text = readFileSync(registryPath, 'utf8');
153
+ return normalizeRegistryPayload(JSON.parse(text));
154
+ } catch {
155
+ return createDefaultRegistry();
156
+ }
157
+ }
158
+
159
+ export function writeSessionRegistry(projectRoot, registry) {
160
+ const registryPath = resolveSessionRegistryPath(projectRoot);
161
+ const normalized = normalizeRegistryPayload(registry);
162
+ mkdirSync(dirname(registryPath), { recursive: true });
163
+ writeFileSync(registryPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
164
+ return normalized;
165
+ }
166
+
167
+ function cleanupSocketPath(socketPath) {
168
+ if (!socketPath || process.platform === 'win32') return;
169
+ if (existsSync(socketPath)) {
170
+ rmSync(socketPath, { force: true });
171
+ }
172
+ }
173
+
174
+ function isProcessRunning(pid) {
175
+ const normalizedPid = normalizePid(pid);
176
+ if (!normalizedPid) {
177
+ return false;
178
+ }
179
+
180
+ try {
181
+ process.kill(normalizedPid, 0);
182
+ return true;
183
+ } catch (error) {
184
+ return error?.code === 'EPERM' || error?.code === 'EACCES';
185
+ }
186
+ }
187
+
188
+ function isForegroundControllerManagedRecord(record) {
189
+ if (!record || typeof record !== 'object') {
190
+ return false;
191
+ }
192
+ if (normalizeNonEmptyString(record.mode) !== 'native') {
193
+ return false;
194
+ }
195
+ const origin = normalizeNonEmptyString(record.origin);
196
+ return origin === 'dev' || origin === 'run';
197
+ }
198
+
199
+ function hasLiveSessionProcess(record) {
200
+ return isProcessRunning(record?.controllerPid) || isProcessRunning(record?.pid);
201
+ }
202
+
203
+ function shouldRetrySessionRefresh(record, error) {
204
+ if (!hasLiveSessionProcess(record)) {
205
+ return false;
206
+ }
207
+ if (!(error instanceof SessionError)) {
208
+ return true;
209
+ }
210
+ return [
211
+ 'session_unavailable',
212
+ 'session_request_timeout',
213
+ 'session_invalid_response',
214
+ ].includes(error.reasonCode);
215
+ }
216
+
217
+ function shouldShutdownUnavailableDetachedSession(record) {
218
+ return normalizeNonEmptyString(record?.origin) === 'session.start' && isProcessRunning(record?.pid);
219
+ }
220
+
221
+ async function shutdownSessionRecord(record) {
222
+ try {
223
+ await sendSessionRequest(record, 'session.shutdown', {});
224
+ } catch {}
225
+ const sessionPid = normalizePid(record?.pid);
226
+ if (sessionPid && isProcessRunning(sessionPid)) {
227
+ try {
228
+ process.kill(sessionPid, 'SIGTERM');
229
+ } catch {}
230
+ const deadline = Date.now() + 1000;
231
+ while (Date.now() < deadline && isProcessRunning(sessionPid)) {
232
+ await sleep(50);
233
+ }
234
+ if (isProcessRunning(sessionPid)) {
235
+ try {
236
+ process.kill(sessionPid, 'SIGKILL');
237
+ } catch {}
238
+ }
239
+ }
240
+ cleanupSocketPath(record.socketPath);
241
+ }
242
+
243
+ async function probeSessionRecord(record) {
244
+ try {
245
+ await sendSessionRequest(record, 'session.ping', {});
246
+ return true;
247
+ } catch (error) {
248
+ if (!shouldRetrySessionRefresh(record, error)) {
249
+ return false;
250
+ }
251
+
252
+ const deadline = Date.now() + SESSION_REFRESH_RETRY_TIMEOUT_MS;
253
+ while (Date.now() < deadline) {
254
+ await sleep(SESSION_REFRESH_RETRY_INTERVAL_MS);
255
+ try {
256
+ await sendSessionRequest(record, 'session.ping', {});
257
+ return true;
258
+ } catch (retryError) {
259
+ if (!shouldRetrySessionRefresh(record, retryError)) {
260
+ return false;
261
+ }
262
+ }
263
+ }
264
+
265
+ return false;
266
+ }
267
+ }
268
+
269
+ function createSessionRequest(method, params = {}) {
270
+ return {
271
+ schemaVersion: DEV_SESSION_REQUEST_SCHEMA_VERSION,
272
+ method,
273
+ params,
274
+ };
275
+ }
276
+
277
+ export async function sendSessionRequest(record, method, params = {}) {
278
+ if (!record || typeof record !== 'object') {
279
+ throw new SessionError('session_required', 'Session record is required.');
280
+ }
281
+ if (!record.socketPath) {
282
+ throw new SessionError('session_unavailable', 'Session socket path is unavailable.');
283
+ }
284
+
285
+ const request = createSessionRequest(method, params);
286
+
287
+ return new Promise((resolveRequest, rejectRequest) => {
288
+ let settled = false;
289
+ let buffer = '';
290
+ const socket = net.createConnection(record.socketPath);
291
+
292
+ const fail = (reasonCode, message, details = {}) => {
293
+ if (settled) return;
294
+ settled = true;
295
+ socket.destroy();
296
+ rejectRequest(new SessionError(reasonCode, message, details));
297
+ };
298
+
299
+ const succeed = (response) => {
300
+ if (settled) return;
301
+ settled = true;
302
+ resolveRequest(response);
303
+ };
304
+
305
+ socket.setEncoding('utf8');
306
+ socket.setTimeout(DEFAULT_SESSION_REQUEST_TIMEOUT_MS, () => {
307
+ fail('session_request_timeout', `Session request timed out for "${record.sessionId}".`);
308
+ });
309
+
310
+ socket.on('connect', () => {
311
+ socket.end(`${JSON.stringify(request)}\n`);
312
+ });
313
+ socket.on('data', (chunk) => {
314
+ buffer += chunk;
315
+ });
316
+ socket.on('error', (error) => {
317
+ fail(
318
+ 'session_unavailable',
319
+ `Failed to connect to session "${record.sessionId}": ${error.message}`,
320
+ { code: error.code || null },
321
+ );
322
+ });
323
+ socket.on('end', () => {
324
+ const text = buffer.trim();
325
+ if (!text) {
326
+ fail('session_invalid_response', `Session "${record.sessionId}" returned an empty response.`);
327
+ return;
328
+ }
329
+
330
+ let response;
331
+ try {
332
+ response = JSON.parse(text);
333
+ } catch (error) {
334
+ fail('session_invalid_response', `Session "${record.sessionId}" returned invalid JSON: ${error.message}`);
335
+ return;
336
+ }
337
+
338
+ if (!response || typeof response !== 'object' || Array.isArray(response)) {
339
+ fail('session_invalid_response', `Session "${record.sessionId}" returned an invalid response object.`);
340
+ return;
341
+ }
342
+ if (response.schemaVersion !== DEV_SESSION_RESPONSE_SCHEMA_VERSION) {
343
+ fail('schema_version_mismatch', `Session response schema "${response.schemaVersion}" is unsupported.`);
344
+ return;
345
+ }
346
+ if (response.ok !== true) {
347
+ fail(
348
+ typeof response.reasonCode === 'string' ? response.reasonCode : 'session_request_failed',
349
+ typeof response.detail === 'string' && response.detail
350
+ ? response.detail
351
+ : `Session request "${method}" failed.`,
352
+ { response },
353
+ );
354
+ return;
355
+ }
356
+
357
+ succeed(response);
358
+ });
359
+ });
360
+ }
361
+
362
+ function sleep(ms) {
363
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
364
+ }
365
+
366
+ function createSessionId(name) {
367
+ const slug = (normalizeNonEmptyString(name) || 'session')
368
+ .toLowerCase()
369
+ .replace(/[^a-z0-9._-]+/g, '-')
370
+ .replace(/-+/g, '-')
371
+ .replace(/^[-._]+|[-._]+$/g, '')
372
+ .slice(0, 24) || 'session';
373
+ const suffix = randomBytes(3).toString('hex');
374
+ return `${slug}-${suffix}`;
375
+ }
376
+
377
+ function createSessionRecord({
378
+ sessionId,
379
+ name,
380
+ projectRoot,
381
+ file,
382
+ socketPath,
383
+ logPath,
384
+ controllerPid = null,
385
+ pid,
386
+ mode = 'headless',
387
+ origin = 'session.start',
388
+ launchReasonCode = null,
389
+ }) {
390
+ const sessionMode = normalizeNonEmptyString(mode) === 'native' ? 'native' : 'headless';
391
+ return orderSessionRecord({
392
+ sessionId,
393
+ name: normalizeNonEmptyString(name) || sessionId,
394
+ mode: sessionMode,
395
+ origin: normalizeNonEmptyString(origin) || 'session.start',
396
+ launchReasonCode: normalizeNonEmptyString(launchReasonCode)
397
+ || (sessionMode === 'native'
398
+ ? 'session_start_native_detached'
399
+ : 'session_start_headless_detached'),
400
+ controllerPid: normalizePid(controllerPid),
401
+ pid: normalizePid(pid),
402
+ projectRoot: resolve(projectRoot),
403
+ entryFile: resolve(projectRoot, file),
404
+ socketPath,
405
+ logPath,
406
+ createdAt: new Date().toISOString(),
407
+ });
408
+ }
409
+
410
+ async function waitForSessionReady(record) {
411
+ const startedAt = Date.now();
412
+ let lastError = null;
413
+
414
+ while ((Date.now() - startedAt) < DEFAULT_SESSION_START_TIMEOUT_MS) {
415
+ try {
416
+ const response = await sendSessionRequest(record, 'session.ping', {});
417
+ return response.result || null;
418
+ } catch (error) {
419
+ lastError = error;
420
+ await sleep(50);
421
+ }
422
+ }
423
+
424
+ throw new SessionError(
425
+ 'session_start_timeout',
426
+ `Timed out waiting for session "${record.sessionId}" to start. See ${record.logPath}.`,
427
+ { lastError: lastError instanceof Error ? lastError.message : String(lastError) },
428
+ );
429
+ }
430
+
431
+ export async function refreshSessionRegistry(projectRoot) {
432
+ const report = await refreshSessionRegistryWithReport(projectRoot);
433
+ return report.registry;
434
+ }
435
+
436
+ export async function refreshSessionRegistryWithReport(projectRoot) {
437
+ const registry = readSessionRegistry(projectRoot);
438
+ const activeSessions = [];
439
+ const staleSessions = [];
440
+ const reapedForegroundSessions = [];
441
+
442
+ for (const record of registry.sessions) {
443
+ if (isForegroundControllerManagedRecord(record) && record.controllerPid && !isProcessRunning(record.controllerPid)) {
444
+ await shutdownSessionRecord(record);
445
+ reapedForegroundSessions.push(record);
446
+ continue;
447
+ }
448
+
449
+ if (await probeSessionRecord(record)) {
450
+ activeSessions.push(record);
451
+ continue;
452
+ }
453
+
454
+ if (shouldShutdownUnavailableDetachedSession(record)) {
455
+ await shutdownSessionRecord(record);
456
+ } else {
457
+ cleanupSocketPath(record.socketPath);
458
+ }
459
+ staleSessions.push(record);
460
+ }
461
+
462
+ const nextRegistry = orderSessionRegistry({
463
+ schemaVersion: DEV_SESSION_REGISTRY_SCHEMA_VERSION,
464
+ currentSessionId: activeSessions.some((entry) => entry.sessionId === registry.currentSessionId)
465
+ ? registry.currentSessionId
466
+ : null,
467
+ sessions: sortSessions(activeSessions),
468
+ });
469
+
470
+ writeSessionRegistry(projectRoot, nextRegistry);
471
+ return {
472
+ registry: nextRegistry,
473
+ staleSessions: staleSessions.map((record) => orderSessionRecord(record)),
474
+ reapedForegroundSessions: reapedForegroundSessions.map((record) => orderSessionRecord(record)),
475
+ };
476
+ }
477
+
478
+ export async function listSessions(projectRoot) {
479
+ const registry = await refreshSessionRegistry(projectRoot);
480
+ const sessions = [];
481
+
482
+ for (const record of registry.sessions) {
483
+ try {
484
+ const response = await sendSessionRequest(record, 'session.describe', {});
485
+ sessions.push(orderSessionRecord({
486
+ ...record,
487
+ frameIndex: response.result?.frameIndex ?? null,
488
+ elapsedSeconds: response.result?.elapsedSeconds ?? null,
489
+ requestCount: response.result?.requestCount ?? null,
490
+ current: record.sessionId === registry.currentSessionId,
491
+ }));
492
+ } catch {
493
+ sessions.push(orderSessionRecord({
494
+ ...record,
495
+ current: record.sessionId === registry.currentSessionId,
496
+ }));
497
+ }
498
+ }
499
+
500
+ return orderSessionReport({
501
+ schemaVersion: DEV_SESSION_REPORT_SCHEMA_VERSION,
502
+ ok: true,
503
+ reasonCode: 'session_list_ok',
504
+ currentSessionId: registry.currentSessionId || null,
505
+ sessions,
506
+ });
507
+ }
508
+
509
+ export async function startSession({
510
+ projectRoot,
511
+ file,
512
+ name = null,
513
+ mode = 'native',
514
+ width = DEFAULT_SESSION_WIDTH,
515
+ height = DEFAULT_SESSION_HEIGHT,
516
+ nativeLaunch = null,
517
+ }) {
518
+ const resolvedProjectRoot = resolve(projectRoot || process.cwd());
519
+ const sessionMode = normalizeNonEmptyString(mode) === 'headless' ? 'headless' : 'native';
520
+ if (sessionMode === 'native' && process.platform === 'win32') {
521
+ throw new SessionError(
522
+ 'session_native_unsupported',
523
+ 'Native sessions are not supported on Windows yet. Use "aura session start --headless".',
524
+ );
525
+ }
526
+ const sessionId = createSessionId(name);
527
+ const socketPath = resolveSessionSocketPath(resolvedProjectRoot, sessionId);
528
+ const logPath = resolveSessionLogPath(resolvedProjectRoot, sessionId);
529
+ const launchReasonCode = sessionMode === 'native'
530
+ ? 'session_start_native_detached'
531
+ : 'session_start_headless_detached';
532
+ const record = createSessionRecord({
533
+ sessionId,
534
+ name,
535
+ projectRoot: resolvedProjectRoot,
536
+ file,
537
+ socketPath,
538
+ logPath,
539
+ pid: null,
540
+ mode: sessionMode,
541
+ origin: 'session.start',
542
+ launchReasonCode,
543
+ });
544
+
545
+ cleanupSocketPath(socketPath);
546
+ mkdirSync(dirname(logPath), { recursive: true });
547
+ if (process.platform !== 'win32') {
548
+ mkdirSync(dirname(socketPath), { recursive: true });
549
+ }
550
+
551
+ const logFd = openSync(logPath, 'a');
552
+ let child;
553
+ if (sessionMode === 'headless') {
554
+ child = spawn(process.execPath, [
555
+ SESSION_SERVER_PATH,
556
+ '--project-root', resolvedProjectRoot,
557
+ '--file', file,
558
+ '--session-id', sessionId,
559
+ '--socket-path', socketPath,
560
+ '--width', String(width),
561
+ '--height', String(height),
562
+ ...(normalizeNonEmptyString(name) ? ['--name', name] : []),
563
+ ], {
564
+ cwd: resolvedProjectRoot,
565
+ detached: true,
566
+ stdio: ['ignore', logFd, logFd],
567
+ env: process.env,
568
+ });
569
+ } else {
570
+ const launchExecutablePath = normalizeNonEmptyString(nativeLaunch?.launchExecutablePath);
571
+ const launchArgs = Array.isArray(nativeLaunch?.launchArgs)
572
+ ? nativeLaunch.launchArgs.map((entry) => String(entry))
573
+ : [];
574
+ if (!launchExecutablePath || launchArgs.length === 0) {
575
+ closeSync(logFd);
576
+ throw new SessionError(
577
+ 'session_native_launch_invalid',
578
+ 'Native session launch contract is incomplete.',
579
+ );
580
+ }
581
+ child = spawn(process.execPath, [
582
+ SESSION_NATIVE_LAUNCHER_PATH,
583
+ '--cwd', resolvedProjectRoot,
584
+ '--executable', launchExecutablePath,
585
+ '--',
586
+ ...launchArgs,
587
+ ], {
588
+ cwd: resolvedProjectRoot,
589
+ detached: true,
590
+ stdio: ['ignore', logFd, logFd],
591
+ env: {
592
+ ...process.env,
593
+ ...(nativeLaunch?.env && typeof nativeLaunch.env === 'object' ? nativeLaunch.env : {}),
594
+ AURA_SESSION_ID: sessionId,
595
+ AURA_SESSION_NAME: normalizeNonEmptyString(name) || sessionId,
596
+ AURA_SESSION_MODE: sessionMode,
597
+ AURA_SESSION_PROJECT_ROOT: resolvedProjectRoot,
598
+ AURA_SESSION_SOCKET_PATH: socketPath,
599
+ AURA_WINDOW_WIDTH: String(width),
600
+ AURA_WINDOW_HEIGHT: String(height),
601
+ },
602
+ });
603
+ }
604
+ closeSync(logFd);
605
+ child.unref();
606
+
607
+ const nextRecord = createSessionRecord({
608
+ sessionId,
609
+ name,
610
+ projectRoot: resolvedProjectRoot,
611
+ file,
612
+ socketPath,
613
+ logPath,
614
+ pid: child.pid,
615
+ mode: sessionMode,
616
+ origin: 'session.start',
617
+ launchReasonCode,
618
+ });
619
+
620
+ await waitForSessionReady(nextRecord);
621
+
622
+ const registry = await refreshSessionRegistry(resolvedProjectRoot);
623
+ const sessions = registry.sessions.filter((entry) => entry.sessionId !== sessionId);
624
+ sessions.push(nextRecord);
625
+ const nextRegistry = writeSessionRegistry(resolvedProjectRoot, {
626
+ schemaVersion: DEV_SESSION_REGISTRY_SCHEMA_VERSION,
627
+ currentSessionId: sessionId,
628
+ sessions: sortSessions(sessions),
629
+ });
630
+
631
+ return orderSessionReport({
632
+ schemaVersion: DEV_SESSION_REPORT_SCHEMA_VERSION,
633
+ ok: true,
634
+ reasonCode: 'session_started',
635
+ sessionId,
636
+ currentSessionId: nextRegistry.currentSessionId,
637
+ session: orderSessionRecord({
638
+ ...nextRecord,
639
+ current: true,
640
+ }),
641
+ });
642
+ }
643
+
644
+ export async function resolveSessionRecord(projectRoot, requestedSessionId = null) {
645
+ const registry = await refreshSessionRegistry(projectRoot);
646
+ const requestedId = normalizeNonEmptyString(requestedSessionId);
647
+
648
+ if (requestedId) {
649
+ const byId = registry.sessions.find((entry) => entry.sessionId === requestedId);
650
+ if (!byId) {
651
+ throw new SessionError('session_not_found', `No active session found for "${requestedId}".`);
652
+ }
653
+ return { registry, record: byId };
654
+ }
655
+
656
+ if (registry.currentSessionId) {
657
+ const current = registry.sessions.find((entry) => entry.sessionId === registry.currentSessionId);
658
+ if (current) {
659
+ return { registry, record: current };
660
+ }
661
+ }
662
+
663
+ if (registry.sessions.length === 1) {
664
+ return { registry, record: registry.sessions[0] };
665
+ }
666
+
667
+ if (registry.sessions.length === 0) {
668
+ throw new SessionError('session_required', 'No active session. Use "aura session start" first.');
669
+ }
670
+
671
+ throw new SessionError(
672
+ 'session_required',
673
+ 'Multiple sessions exist and no current session is attached. Use "aura session attach <id>".',
674
+ );
675
+ }
676
+
677
+ export async function attachSession(projectRoot, sessionId) {
678
+ const { registry, record } = await resolveSessionRecord(projectRoot, sessionId);
679
+ const nextRegistry = writeSessionRegistry(projectRoot, {
680
+ schemaVersion: DEV_SESSION_REGISTRY_SCHEMA_VERSION,
681
+ currentSessionId: record.sessionId,
682
+ sessions: registry.sessions,
683
+ });
684
+
685
+ return orderSessionReport({
686
+ schemaVersion: DEV_SESSION_REPORT_SCHEMA_VERSION,
687
+ ok: true,
688
+ reasonCode: 'session_attached',
689
+ sessionId: record.sessionId,
690
+ currentSessionId: nextRegistry.currentSessionId,
691
+ session: orderSessionRecord({
692
+ ...record,
693
+ current: true,
694
+ }),
695
+ });
696
+ }
697
+
698
+ export async function detachSession(projectRoot) {
699
+ const registry = await refreshSessionRegistry(projectRoot);
700
+ const nextRegistry = writeSessionRegistry(projectRoot, {
701
+ schemaVersion: DEV_SESSION_REGISTRY_SCHEMA_VERSION,
702
+ currentSessionId: null,
703
+ sessions: registry.sessions,
704
+ });
705
+
706
+ return orderSessionReport({
707
+ schemaVersion: DEV_SESSION_REPORT_SCHEMA_VERSION,
708
+ ok: true,
709
+ reasonCode: 'session_detached',
710
+ currentSessionId: nextRegistry.currentSessionId,
711
+ });
712
+ }
713
+
714
+ export async function closeSession(projectRoot, sessionId = null) {
715
+ const { registry, record } = await resolveSessionRecord(projectRoot, sessionId);
716
+ let stale = false;
717
+
718
+ try {
719
+ await sendSessionRequest(record, 'session.shutdown', {});
720
+ } catch {
721
+ stale = true;
722
+ }
723
+
724
+ cleanupSocketPath(record.socketPath);
725
+
726
+ const nextRegistry = writeSessionRegistry(projectRoot, {
727
+ schemaVersion: DEV_SESSION_REGISTRY_SCHEMA_VERSION,
728
+ currentSessionId: registry.currentSessionId === record.sessionId
729
+ ? null
730
+ : registry.currentSessionId,
731
+ sessions: registry.sessions.filter((entry) => entry.sessionId !== record.sessionId),
732
+ });
733
+
734
+ return orderSessionReport({
735
+ schemaVersion: DEV_SESSION_REPORT_SCHEMA_VERSION,
736
+ ok: true,
737
+ reasonCode: stale ? 'session_closed_stale' : 'session_closed',
738
+ sessionId: record.sessionId,
739
+ currentSessionId: nextRegistry.currentSessionId,
740
+ closed: true,
741
+ stale,
742
+ });
743
+ }
744
+
745
+ export function serializeSessionReport(report, compact = false) {
746
+ const ordered = orderSessionReport(report);
747
+ return compact
748
+ ? `${JSON.stringify(ordered)}\n`
749
+ : `${JSON.stringify(ordered, null, 2)}\n`;
750
+ }