modwright 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 (455) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +212 -0
  3. package/assets/claude/agents/mw-explore.md +46 -0
  4. package/assets/claude/agents/mw-fixture.md +33 -0
  5. package/assets/claude/agents/mw-rule-author.md +51 -0
  6. package/assets/claude/agents/mw-scaffold-author.md +48 -0
  7. package/assets/claude/skills/mw-author.md +59 -0
  8. package/assets/claude/skills/mw-new-item.md +51 -0
  9. package/assets/claude/skills/mw-new-spell.md +50 -0
  10. package/assets/claude/skills/mw-scaffold.md +59 -0
  11. package/assets/claude/skills/mw-ship.md +57 -0
  12. package/assets/claude/skills/mw-validate.md +51 -0
  13. package/assets/claude/skills/mw-verify.md +59 -0
  14. package/assets/nivalisnights/extract_resources.py +177 -0
  15. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
  16. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
  17. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
  18. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
  19. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
  20. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
  21. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
  22. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
  23. package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
  24. package/bridges/bg3-se/README.md +99 -0
  25. package/bridges/bg3-se/bridge.json +68 -0
  26. package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
  27. package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
  28. package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
  29. package/bridges/cp2077-cet/README.md +165 -0
  30. package/bridges/cp2077-cet/bridge.json +53 -0
  31. package/dist/core/bridge/arm.js +175 -0
  32. package/dist/core/bridge/cet-console.js +26 -0
  33. package/dist/core/bridge/cet-log.js +362 -0
  34. package/dist/core/bridge/index.js +11 -0
  35. package/dist/core/bridge/locate.js +35 -0
  36. package/dist/core/bridge/paths.js +34 -0
  37. package/dist/core/bridge/pending.js +89 -0
  38. package/dist/core/bridge/poll.js +155 -0
  39. package/dist/core/bridge/stage.js +100 -0
  40. package/dist/core/bridge/status.js +97 -0
  41. package/dist/core/bridge/types.js +145 -0
  42. package/dist/core/bridge/validate.js +107 -0
  43. package/dist/core/build/index.js +6 -0
  44. package/dist/core/build/manifest.js +47 -0
  45. package/dist/core/build/paths.js +32 -0
  46. package/dist/core/build/run.js +342 -0
  47. package/dist/core/build/stage.js +265 -0
  48. package/dist/core/build/steps/bg3.js +129 -0
  49. package/dist/core/build/steps/core.js +195 -0
  50. package/dist/core/build/steps/cyberpunk.js +95 -0
  51. package/dist/core/build/steps/listing.js +35 -0
  52. package/dist/core/build/types.js +13 -0
  53. package/dist/core/build/zip.js +148 -0
  54. package/dist/core/compat/check.js +113 -0
  55. package/dist/core/compat/pe.js +220 -0
  56. package/dist/core/compat/types.js +1 -0
  57. package/dist/core/compat/version.js +70 -0
  58. package/dist/core/deploy/guards.js +292 -0
  59. package/dist/core/deploy/index.js +5 -0
  60. package/dist/core/deploy/plan.js +696 -0
  61. package/dist/core/deploy/reload-apply.js +361 -0
  62. package/dist/core/deploy/reload.js +358 -0
  63. package/dist/core/deploy/sources.js +169 -0
  64. package/dist/core/deploy/types.js +1 -0
  65. package/dist/core/epic.js +54 -0
  66. package/dist/core/fsutil.js +191 -0
  67. package/dist/core/home.js +70 -0
  68. package/dist/core/knowledge/facts.js +229 -0
  69. package/dist/core/knowledge/index.js +4 -0
  70. package/dist/core/knowledge/search.js +112 -0
  71. package/dist/core/knowledge/store.js +123 -0
  72. package/dist/core/knowledge/types.js +1 -0
  73. package/dist/core/ledger/check.js +42 -0
  74. package/dist/core/ledger/hooks.js +80 -0
  75. package/dist/core/ledger/index.js +7 -0
  76. package/dist/core/ledger/query.js +17 -0
  77. package/dist/core/ledger/record.js +84 -0
  78. package/dist/core/ledger/status.js +53 -0
  79. package/dist/core/ledger/store.js +98 -0
  80. package/dist/core/ledger/types.js +108 -0
  81. package/dist/core/logs/index.js +4 -0
  82. package/dist/core/logs/registry.js +50 -0
  83. package/dist/core/logs/scan.js +195 -0
  84. package/dist/core/logs/triage.js +160 -0
  85. package/dist/core/logs/types.js +1 -0
  86. package/dist/core/lsx.js +10 -0
  87. package/dist/core/lz4.js +66 -0
  88. package/dist/core/mods.js +229 -0
  89. package/dist/core/process.js +49 -0
  90. package/dist/core/project/build-steps.js +83 -0
  91. package/dist/core/project/describe.js +74 -0
  92. package/dist/core/project/index.js +5 -0
  93. package/dist/core/project/load.js +146 -0
  94. package/dist/core/project/schema.js +264 -0
  95. package/dist/core/project/types.js +3 -0
  96. package/dist/core/prompts.js +72 -0
  97. package/dist/core/registry.js +35 -0
  98. package/dist/core/safety/backup.js +206 -0
  99. package/dist/core/safety/execute.js +188 -0
  100. package/dist/core/safety/index.js +5 -0
  101. package/dist/core/safety/plan.js +122 -0
  102. package/dist/core/safety/restore.js +172 -0
  103. package/dist/core/safety/types.js +1 -0
  104. package/dist/core/scaffold/bootstrap.js +7 -0
  105. package/dist/core/scaffold/catalog.js +28 -0
  106. package/dist/core/scaffold/claude-assets.js +271 -0
  107. package/dist/core/scaffold/collisions.js +64 -0
  108. package/dist/core/scaffold/emit.js +356 -0
  109. package/dist/core/scaffold/engine.js +680 -0
  110. package/dist/core/scaffold/ids.js +78 -0
  111. package/dist/core/scaffold/index.js +10 -0
  112. package/dist/core/scaffold/jsonpath.js +121 -0
  113. package/dist/core/scaffold/load.js +81 -0
  114. package/dist/core/scaffold/render.js +85 -0
  115. package/dist/core/scaffold/sequence.js +396 -0
  116. package/dist/core/scaffold/types.js +309 -0
  117. package/dist/core/steam.js +63 -0
  118. package/dist/core/surfaceutil.js +36 -0
  119. package/dist/core/testplan/check.js +67 -0
  120. package/dist/core/testplan/index.js +5 -0
  121. package/dist/core/testplan/load.js +100 -0
  122. package/dist/core/testplan/registry.js +317 -0
  123. package/dist/core/testplan/run.js +389 -0
  124. package/dist/core/testplan/types.js +82 -0
  125. package/dist/core/text/converters/bg3.js +50 -0
  126. package/dist/core/text/converters/cyberpunk.js +36 -0
  127. package/dist/core/text/converters/index.js +261 -0
  128. package/dist/core/text/diff/cr2w.js +180 -0
  129. package/dist/core/text/diff/index.js +16 -0
  130. package/dist/core/text/diff/loca.js +43 -0
  131. package/dist/core/text/diff/lsx.js +126 -0
  132. package/dist/core/text/handles.js +86 -0
  133. package/dist/core/text/index.js +6 -0
  134. package/dist/core/text/json.js +46 -0
  135. package/dist/core/text/templates/cache.js +71 -0
  136. package/dist/core/text/templates/index.js +3 -0
  137. package/dist/core/text/templates/library.js +245 -0
  138. package/dist/core/text/templates/types.js +51 -0
  139. package/dist/core/text/toolchain.js +16 -0
  140. package/dist/core/text/types.js +1 -0
  141. package/dist/core/text/validate.js +149 -0
  142. package/dist/core/text/xml.js +58 -0
  143. package/dist/core/toolchain/datafetch.js +151 -0
  144. package/dist/core/toolchain/env.js +63 -0
  145. package/dist/core/toolchain/features.js +170 -0
  146. package/dist/core/toolchain/index.js +8 -0
  147. package/dist/core/toolchain/install.js +191 -0
  148. package/dist/core/toolchain/locate.js +275 -0
  149. package/dist/core/toolchain/remember.js +84 -0
  150. package/dist/core/toolchain/run.js +318 -0
  151. package/dist/core/toolchain/specs.js +351 -0
  152. package/dist/core/toolchain/types.js +1 -0
  153. package/dist/core/types.js +1 -0
  154. package/dist/core/userconfig.js +66 -0
  155. package/dist/core/validate/context.js +139 -0
  156. package/dist/core/validate/index.js +5 -0
  157. package/dist/core/validate/registry.js +58 -0
  158. package/dist/core/validate/run.js +131 -0
  159. package/dist/core/validate/suppressions.js +91 -0
  160. package/dist/core/validate/types.js +2 -0
  161. package/dist/core/yamledit.js +51 -0
  162. package/dist/index.js +389 -0
  163. package/dist/server.js +1914 -0
  164. package/dist/surfaces/baldursgate3/compat.js +164 -0
  165. package/dist/surfaces/baldursgate3/identity.js +110 -0
  166. package/dist/surfaces/baldursgate3/index/build.js +331 -0
  167. package/dist/surfaces/baldursgate3/index/index.js +17 -0
  168. package/dist/surfaces/baldursgate3/index/info.js +40 -0
  169. package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
  170. package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
  171. package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
  172. package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
  173. package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
  174. package/dist/surfaces/baldursgate3/index/queries.js +327 -0
  175. package/dist/surfaces/baldursgate3/index/schema.js +62 -0
  176. package/dist/surfaces/baldursgate3/index.js +3 -0
  177. package/dist/surfaces/baldursgate3/logs.js +278 -0
  178. package/dist/surfaces/baldursgate3/modsettings.js +115 -0
  179. package/dist/surfaces/baldursgate3/pak.js +153 -0
  180. package/dist/surfaces/baldursgate3/settings.js +49 -0
  181. package/dist/surfaces/baldursgate3/surface.js +232 -0
  182. package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
  183. package/dist/surfaces/baldursgate3/validators/files.js +476 -0
  184. package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
  185. package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
  186. package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
  187. package/dist/surfaces/baldursgate3/validators/story.js +790 -0
  188. package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
  189. package/dist/surfaces/cyberpunk2077/compat.js +223 -0
  190. package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
  191. package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
  192. package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
  193. package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
  194. package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
  195. package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
  196. package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
  197. package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
  198. package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
  199. package/dist/surfaces/cyberpunk2077/index.js +3 -0
  200. package/dist/surfaces/cyberpunk2077/logs.js +742 -0
  201. package/dist/surfaces/cyberpunk2077/paths.js +12 -0
  202. package/dist/surfaces/cyberpunk2077/surface.js +218 -0
  203. package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
  204. package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
  205. package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
  206. package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
  207. package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
  208. package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
  209. package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
  210. package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
  211. package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
  212. package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
  213. package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
  214. package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
  215. package/dist/surfaces/eldenring/binders.js +111 -0
  216. package/dist/surfaces/eldenring/compat.js +166 -0
  217. package/dist/surfaces/eldenring/dcx.js +49 -0
  218. package/dist/surfaces/eldenring/index.js +13 -0
  219. package/dist/surfaces/eldenring/loaders.js +132 -0
  220. package/dist/surfaces/eldenring/logs.js +67 -0
  221. package/dist/surfaces/eldenring/paramdef.js +256 -0
  222. package/dist/surfaces/eldenring/params.js +129 -0
  223. package/dist/surfaces/eldenring/surface.js +121 -0
  224. package/dist/surfaces/eldenring/tools.js +52 -0
  225. package/dist/surfaces/eldenring/validators/binders.js +193 -0
  226. package/dist/surfaces/eldenring/validators/packages.js +159 -0
  227. package/dist/surfaces/eldenring/validators/params.js +334 -0
  228. package/dist/surfaces/eldenring/validators/profiles.js +287 -0
  229. package/dist/surfaces/index.js +34 -0
  230. package/dist/surfaces/nivalisnights/compat.js +229 -0
  231. package/dist/surfaces/nivalisnights/index/build.js +310 -0
  232. package/dist/surfaces/nivalisnights/index/index.js +5 -0
  233. package/dist/surfaces/nivalisnights/index/queries.js +231 -0
  234. package/dist/surfaces/nivalisnights/index.js +4 -0
  235. package/dist/surfaces/nivalisnights/logs.js +473 -0
  236. package/dist/surfaces/nivalisnights/surface.js +195 -0
  237. package/dist/surfaces/skyrimse/archives.js +229 -0
  238. package/dist/surfaces/skyrimse/compat.js +163 -0
  239. package/dist/surfaces/skyrimse/index.js +12 -0
  240. package/dist/surfaces/skyrimse/loadorder.js +174 -0
  241. package/dist/surfaces/skyrimse/logs.js +241 -0
  242. package/dist/surfaces/skyrimse/plugins.js +149 -0
  243. package/dist/surfaces/skyrimse/records.js +94 -0
  244. package/dist/surfaces/skyrimse/surface.js +240 -0
  245. package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
  246. package/dist/surfaces/skyrimse/validators/archives.js +110 -0
  247. package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
  248. package/dist/surfaces/skyrimse/validators/records.js +117 -0
  249. package/dist/surfaces/stardewvalley/compat.js +190 -0
  250. package/dist/surfaces/stardewvalley/config.js +42 -0
  251. package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
  252. package/dist/surfaces/stardewvalley/index/index.js +229 -0
  253. package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
  254. package/dist/surfaces/stardewvalley/index.js +12 -0
  255. package/dist/surfaces/stardewvalley/logs.js +307 -0
  256. package/dist/surfaces/stardewvalley/mods.js +182 -0
  257. package/dist/surfaces/stardewvalley/surface.js +244 -0
  258. package/dist/surfaces/stardewvalley/validators/content.js +321 -0
  259. package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
  260. package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
  261. package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
  262. package/dist/surfaces/subnautica2/compat.js +138 -0
  263. package/dist/surfaces/subnautica2/index.js +5 -0
  264. package/dist/surfaces/subnautica2/logs.js +129 -0
  265. package/dist/surfaces/subnautica2/paks.js +173 -0
  266. package/dist/surfaces/subnautica2/surface.js +151 -0
  267. package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
  268. package/dist/surfaces/valheim/compat.js +171 -0
  269. package/dist/surfaces/valheim/index/index.js +301 -0
  270. package/dist/surfaces/valheim/index/tables.js +103 -0
  271. package/dist/surfaces/valheim/index.js +11 -0
  272. package/dist/surfaces/valheim/logs.js +197 -0
  273. package/dist/surfaces/valheim/packaging.js +131 -0
  274. package/dist/surfaces/valheim/plugins.js +270 -0
  275. package/dist/surfaces/valheim/references.js +121 -0
  276. package/dist/surfaces/valheim/surface.js +223 -0
  277. package/dist/surfaces/valheim/validators/packaging.js +218 -0
  278. package/dist/surfaces/valheim/validators/plugins.js +287 -0
  279. package/dist/surfaces/valheim/validators/references.js +169 -0
  280. package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
  281. package/knowledge/baldursgate3/audio.voice.yaml +182 -0
  282. package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
  283. package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
  284. package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
  285. package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
  286. package/knowledge/baldursgate3/crash.causes.yaml +90 -0
  287. package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
  288. package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
  289. package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
  290. package/knowledge/baldursgate3/dialogue.yaml +122 -0
  291. package/knowledge/baldursgate3/feats.classes.yaml +164 -0
  292. package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
  293. package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
  294. package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
  295. package/knowledge/baldursgate3/journal.quests.yaml +214 -0
  296. package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
  297. package/knowledge/baldursgate3/localization.handles.yaml +92 -0
  298. package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
  299. package/knowledge/baldursgate3/origins.companions.yaml +144 -0
  300. package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
  301. package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
  302. package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
  303. package/knowledge/baldursgate3/process.launch.yaml +34 -0
  304. package/knowledge/baldursgate3/progressions.yaml +92 -0
  305. package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
  306. package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
  307. package/knowledge/baldursgate3/races.progression.yaml +228 -0
  308. package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
  309. package/knowledge/baldursgate3/scriptextender.yaml +252 -0
  310. package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
  311. package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
  312. package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
  313. package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
  314. package/knowledge/baldursgate3/stats.spells.yaml +293 -0
  315. package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
  316. package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
  317. package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
  318. package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
  319. package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
  320. package/knowledge/baldursgate3/ui.icons.yaml +412 -0
  321. package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
  322. package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
  323. package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
  324. package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
  325. package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
  326. package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
  327. package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
  328. package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
  329. package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
  330. package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
  331. package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
  332. package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
  333. package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
  334. package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
  335. package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
  336. package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
  337. package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
  338. package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
  339. package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
  340. package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
  341. package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
  342. package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
  343. package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
  344. package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
  345. package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
  346. package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
  347. package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
  348. package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
  349. package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
  350. package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
  351. package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
  352. package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
  353. package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
  354. package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
  355. package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
  356. package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
  357. package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
  358. package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
  359. package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
  360. package/knowledge/cyberpunk2077/player.control.yaml +918 -0
  361. package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
  362. package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
  363. package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
  364. package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
  365. package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
  366. package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
  367. package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
  368. package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
  369. package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
  370. package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
  371. package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
  372. package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
  373. package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
  374. package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
  375. package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
  376. package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
  377. package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
  378. package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
  379. package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
  380. package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
  381. package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
  382. package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
  383. package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
  384. package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
  385. package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
  386. package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
  387. package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
  388. package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
  389. package/knowledge/eldenring/formats.binders.yaml +237 -0
  390. package/knowledge/eldenring/launcher.me3.yaml +230 -0
  391. package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
  392. package/knowledge/eldenring/params.paramdef.yaml +149 -0
  393. package/knowledge/eldenring/params.regulation.yaml +142 -0
  394. package/knowledge/eldenring/saves.online.yaml +118 -0
  395. package/knowledge/eldenring/toolchain.cli.yaml +256 -0
  396. package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
  397. package/knowledge/skyrimse/archives.bsa.yaml +212 -0
  398. package/knowledge/skyrimse/install.detection.yaml +41 -0
  399. package/knowledge/skyrimse/loadorder.files.yaml +242 -0
  400. package/knowledge/skyrimse/plugins.esl.yaml +189 -0
  401. package/knowledge/skyrimse/plugins.format.yaml +224 -0
  402. package/knowledge/skyrimse/skse.runtime.yaml +283 -0
  403. package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
  404. package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
  405. package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
  406. package/knowledge/stardewvalley/game.versions.yaml +202 -0
  407. package/knowledge/stardewvalley/install.layout.yaml +277 -0
  408. package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
  409. package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
  410. package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
  411. package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
  412. package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
  413. package/knowledge/subnautica2/game.build.yaml +331 -0
  414. package/knowledge/subnautica2/modding.state.yaml +210 -0
  415. package/knowledge/subnautica2/ue5.containers.yaml +72 -0
  416. package/knowledge/subnautica2/ue5.paks.yaml +203 -0
  417. package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
  418. package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
  419. package/knowledge/valheim/bepinex.loader.yaml +247 -0
  420. package/knowledge/valheim/bepinex.plugins.yaml +251 -0
  421. package/knowledge/valheim/game.versions.yaml +321 -0
  422. package/knowledge/valheim/install.layout.yaml +216 -0
  423. package/knowledge/valheim/jotunn.library.yaml +334 -0
  424. package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
  425. package/knowledge/valheim/toolchain.build.yaml +111 -0
  426. package/package.json +29 -0
  427. package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
  428. package/scaffolds/baldursgate3/new-item.yaml +264 -0
  429. package/scaffolds/baldursgate3/new-mod.yaml +174 -0
  430. package/scaffolds/baldursgate3/new-passive.yaml +100 -0
  431. package/scaffolds/baldursgate3/new-project.yaml +118 -0
  432. package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
  433. package/scaffolds/baldursgate3/new-spell.yaml +102 -0
  434. package/scaffolds/baldursgate3/new-status.yaml +119 -0
  435. package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
  436. package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
  437. package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
  438. package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
  439. package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
  440. package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
  441. package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
  442. package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
  443. package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
  444. package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
  445. package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
  446. package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
  447. package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
  448. package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
  449. package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
  450. package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
  451. package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
  452. package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
  453. package/scaffolds/stardewvalley/new-project.yaml +184 -0
  454. package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
  455. package/scripts/mw-tool.mjs +184 -0
@@ -0,0 +1,165 @@
1
+ # cp2077-cet bridge
2
+
3
+ The Cyberpunk 2077 Cyber Engine Tweaks bridge: ModWright's author-only verification mod. At
4
+ load it feature-detects the CET sandbox; when a request reaches it, it runs the probes that
5
+ request names and prints the result plus a `done` marker carrying the result's byte length. It
6
+ is staged by `stageBridge(project, "cyberpunk2077")` (`src/core/bridge/stage.ts`) into
7
+ `<project>/.modwright/bridge/cp2077-cet/`, and `deploy --variant withBridge` copies
8
+ `ModWrightBridge` from there into the project's `cet` root
9
+ (`bin/x64/plugins/cyber_engine_tweaks/mods/ModWrightBridge`).
10
+
11
+ The protocol is implemented on the Node side in `src/core/bridge/`.
12
+
13
+ ## Two transports: files first, then console-armed and log-polled
14
+
15
+ An in-game check (2026-09-08) ran `io.open` in the CET **console**: `io` is nil there (CET
16
+ v1.37.1, game v3.0.80.51928). CET's changelog says that is by design and is not a fact about
17
+ mods: 1.10 "stripped ImGui, json, io and dofile and such from console sandbox", in the same
18
+ release that made mod pathing relative — "All pathing is now relative to mod (breaking change,
19
+ includes all io functions, dofile() and such included)" (CET Nexus changelog, mod 107). Mods
20
+ have `io`; the console does not. `cp2077.bridge.io` stays **unverified**, and the bridge's own
21
+ capability line (`m.where()`'s `io`, which rides in every heartbeat and lands in the mod's own
22
+ log) is what settles it. So:
23
+
24
+ * **The file transport below stays first choice.** Whenever the bridge reports a live write
25
+ root, `bridge arm` writes the request there and `bridge poll` reads the result files, exactly
26
+ as for BG3.
27
+ * **The console-and-log path is the fallback**, and it is what runs when the bridge
28
+ reports log-only. Both are tried on every arm and every poll, in that order.
29
+
30
+ Reading that capability line is the first thing to do on a new install — it needs no game
31
+ launch, because CET 1.9.1 ("Mods will now log to
32
+ their own log files") means it is already in the newest `.log` under
33
+ `bin/x64/plugins/cyber_engine_tweaks/mods/ModWrightBridge/`.
34
+
35
+ * **Arming** — `bridge arm --game cyberpunk2077 --path <mod repo>` builds the same `Request`
36
+ it would for BG3, writes it into the write root when there is one, and always returns the
37
+ console line that hands it to the bridge:
38
+ `GetMod("ModWrightBridge").arm('<compact request JSON>')`, escaped for a Lua single-quoted
39
+ string. It records the nonce under `<project>/.modwright/bridge/pending/<id>.json`; a dry
40
+ run shows the line and records nothing. The Lua needed no change for this —
41
+ `init.lua`'s `armFn` → `Protocol.armFromString` already accepts that payload verbatim.
42
+ * **Results** — a finished run writes `modwright_res_<id>.json` plus its `done` marker when it
43
+ can, and in log-only mode prints `[MODWRIGHT-RESULT] <json>` and then
44
+ `[MODWRIGHT-DONE] <json>` carrying the result's byte length. `bridge poll` checks the result
45
+ files first and then tails
46
+ the newest `.log` in the mod's own folder first (CET 1.9.1), then
47
+ `bin/x64/plugins/cyber_engine_tweaks/cyber_engine_tweaks.log` and `…/gamelog.log`, applies the same byte-length
48
+ completeness rule the file transport uses, matches the pending nonce, ignores lines older
49
+ than the arm, and says which file it read.
50
+ * **Heartbeat** — written to `modwright_hb.json` every 2 s, or printed as
51
+ `[MODWRIGHT-HEARTBEAT] <json>` every 15 s in log-only mode. `bridge status` reads the file
52
+ first and the log lines second, judges freshness against whichever answered (15 s / 45 s),
53
+ and reports **`sandboxIo`** — the bridge's own `io` capability, which is the fact the whole
54
+ transport question turns on.
55
+ * **`run_tests` can drive this game only when the sandbox has `io`.** It must run unattended:
56
+ against a file-writing bridge it arms and polls normally; against a log-only one it refuses
57
+ with `console-arm-required` naming the three-command flow instead.
58
+
59
+ ### The `io` write-root discovery
60
+
61
+ Nothing on disk attests that CET's Lua sandbox has `io` at all (the first row of the probe
62
+ catalogue), so at `onInit` the bridge tries three candidates in order —
63
+
64
+ ```
65
+ modwright_hb.json
66
+ mods/ModWrightBridge/modwright_hb.json
67
+ plugins/cyber_engine_tweaks/mods/ModWrightBridge/modwright_hb.json
68
+ ```
69
+
70
+ — keeps the first that opens **and accepts a write**, and reports the winner in every
71
+ heartbeat's `writeRoot` with every failure string in `writeRootErrors`. ModWright polls the
72
+ matching absolute directories under the install (`bridgeCandidateDirs`,
73
+ `src/core/bridge/paths.ts`). If `io` turns out to be absent in the mod sandbox too, all three
74
+ fail before they are tried and the bridge falls into the log-only mode described above.
75
+
76
+ ## protocolVersion
77
+
78
+ **1** — the current value of `PROTOCOL_VERSION` in `src/core/bridge/types.ts`. The Lua defines
79
+ the identical integer literal as `Protocol.PROTOCOL_VERSION` (`ModWrightBridge/protocol.lua`),
80
+ "`protocolVersion` is an integer duplicated in the Lua and the Node constant, checked at arm
81
+ time". `test/unit/bridge/cp2077-lua-parity.test.ts` asserts the two literals
82
+ agree.
83
+
84
+ ## The tree
85
+
86
+ ```
87
+ bridges/cp2077-cet/
88
+ bridge.json # id, protocolVersion, version, deploy target, file list, sha256s
89
+ README.md # this file
90
+ ModWrightBridge/
91
+ init.lua # registerForEvent onInit/onUpdate/onShutdown; returns the console table
92
+ protocol.lua # write-root discovery, JSON framing, nonce arming, heartbeat, run loop
93
+ probes.lua # the probe dispatch table
94
+ ```
95
+
96
+ Three files. `init.lua` is CET's entry point and **must
97
+ return a table** — knowledge fact `cp2077.process.cet-mods-sandboxed`, status `verified` on
98
+ 2.31 — so that `GetMod("ModWrightBridge")` is reachable from the console. The CET mods surveyed
99
+ ship no manifest file, so none is invented.
100
+
101
+ **How `init.lua` loads the other two comes from CET's changelog, not from a sibling on disk.**
102
+ The CET mods surveyed have no second file. CET 1.10 made every mod path relative to the mod's
103
+ own folder and `require` paths `/`-separated, and 1.9.1 deprecated `dofile` without removing
104
+ it, so `init.lua` tries three routes in order — `require("protocol")`,
105
+ `require("ModWrightBridge/protocol")`, `dofile("protocol.lua")` — reports the winner in the
106
+ capability line, and, if all three fail, still returns a console table whose
107
+ `status()`/`where()` print the three error strings. A CET mod that returns nothing cannot even
108
+ be diagnosed.
109
+
110
+ ## The dispatch table
111
+
112
+ Fenced with `-- MODWRIGHT-PROBE-TABLE-BEGIN` / `-- MODWRIGHT-PROBE-TABLE-END` in
113
+ `probes.lua`, so the parity test reads only the table and never an id that appears in a
114
+ comment. Thirteen ids, one per catalogue row, each carrying that row's own status word:
115
+
116
+ | id | Status | If its call is missing |
117
+ |---|---|---|
118
+ | `cp2077.bridge.io` | unverified — the 2026-09-08 check ran in the console sandbox, which CET strips by design | `error` — and the bridge's own capability line says which case this install is in |
119
+ | `cp2077.mod.loaded` | attested(engine) | — |
120
+ | `cp2077.player.present` | attested(engine) | `error` if `Game` is absent |
121
+ | `cp2077.item.equipped` | attested(engine) | — |
122
+ | `cp2077.inventory.add` | attested(doc), **mutate** | `error`; skipped without `allowMutate` |
123
+ | `cp2077.entity.tagged` | attested(engine) | — |
124
+ | `cp2077.entity.components` | attested(engine) for the call; names unverified | `error` without `params.names` |
125
+ | `cp2077.entity.transform` | attested(engine) for item objects; unverified for a sector prop | says which, in `evidence.attestationForThisCall` |
126
+ | `cp2077.entity.appearance` | unverified (composite) | `skipped` without a baseline |
127
+ | `cp2077.tweakdb.record` | unverified | `error` |
128
+ | `cp2077.tweakdb.flat` | unverified | `error` |
129
+ | `cp2077.interaction.present` | unverified (the component *name*) | `error` without `params.names` |
130
+ | `cp2077.loot.roll` | **not designed** | always `unsupported` |
131
+
132
+ Every probe result carries its row's `evidence.attestation` and `evidence.citation`, so the
133
+ provenance of an answer travels with the answer.
134
+
135
+ ## The content banner
136
+
137
+ `BRIDGE_BANNER` in `src/core/bridge/types.ts` (`"MODWRIGHT-BRIDGE-DO-NOT-SHIP"`) appears,
138
+ verbatim, as the first line of every `.lua` file here. This is what lets
139
+ `cp2077.history.bridge-never-ships` (`src/surfaces/cyberpunk2077/validators/bridge.ts`) catch a
140
+ bridge file that reached a build output under a path that no longer says "ModWrightBridge" —
141
+ e.g. flattened during packaging — not just one that still does.
142
+
143
+ ## Checking the Lua
144
+
145
+ No Lua parser is vendored. Three checks:
146
+
147
+ * `npm run bridge:lint` — shells to `luac -p` under 5.1 and 5.4 when either is on PATH, and
148
+ prints a one-line skip when neither is.
149
+ * `test/unit/bridge/cp2077-lua-parity.test.ts` — reads the Lua as text: the dispatch-table keys
150
+ equal the catalogue's ids, every file carries the banner, the `PROTOCOL_VERSION` literals agree, and
151
+ `bridge.json` lists every file. It also runs `luac -p` when one is installed.
152
+ * The CI job installing `lua5.1`/`lua5.4`.
153
+
154
+ The Lua is kept inside the 5.1-compatible subset shipped CET mods use, and checked
155
+ against both dialects — CET embeds a 5.4-era sol2, but nothing on disk pins which dialect
156
+ features are available, so the intersection is the safe target.
157
+
158
+ ## What this bridge never does
159
+
160
+ * It never observes: no CET listener API is attested, so `effect: "observe"` is `unsupported`
161
+ and `window.seconds` is ignored with a line in the result's `errors`.
162
+ * It never calls anything that mutates except `Game.AddToInventory`, and only when the request
163
+ sets `allowMutate: true`. `m.probe()` from the console always passes `allowMutate: false`.
164
+ * It never claims a `pass` for a call that is not there: an `unverified` row whose call is
165
+ missing returns `error` with a message naming what was missing and which check settles it.
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "cp2077-cet",
3
+ "game": "cyberpunk2077",
4
+ "protocolVersion": 1,
5
+ "version": "0.1.0",
6
+ "modFolder": "ModWrightBridge",
7
+ "entryPoint": "ModWrightBridge/init.lua",
8
+ "banner": "MODWRIGHT-BRIDGE-DO-NOT-SHIP",
9
+ "transport": {
10
+ "primary": "file",
11
+ "fallback": "log",
12
+ "writeRootCandidates": [
13
+ "modwright_hb.json",
14
+ "mods/ModWrightBridge/modwright_hb.json",
15
+ "plugins/cyber_engine_tweaks/mods/ModWrightBridge/modwright_hb.json"
16
+ ],
17
+ "note": "The write root is DISCOVERED at onInit: the first candidate whose `io.open(candidate, \"w\")` succeeds wins, and the winner is reported in every heartbeat's `writeRoot`. The first candidate is the empty prefix, which since CET 1.10 (\"All pathing is now relative to mod\", CET Nexus changelog) means this mod's own folder — the directory ModWright polls first. Whether any candidate resolves is still `unverified`: the `io.open` check of 2026-09-08 was typed in the CET console, whose sandbox has io stripped by design (same changelog), so it tested the wrong half. If none does, the bridge prints each result as one `[MODWRIGHT-RESULT] <json>` line for `triage_logs` to recover."
18
+ },
19
+ "deploy": {
20
+ "rootId": "cet",
21
+ "from": ".modwright/bridge/cp2077-cet/ModWrightBridge",
22
+ "into": "ModWrightBridge",
23
+ "variant": "withBridge",
24
+ "locked": false,
25
+ "backup": true,
26
+ "prune": [
27
+ "ModWrightBridge/**"
28
+ ],
29
+ "note": "The deploy target `bridge stage` adds to the project's deploy targets when no target already names this rootId, into and variant, so a fresh project deploys the bridge without a hand edit; paste it in yourself only if you want it there before staging. A deploy WITHOUT `--variant withBridge` prunes the folder back out, with backup."
30
+ },
31
+ "sha256": {
32
+ "algorithm": "sha256",
33
+ "encoding": "hex",
34
+ "over": "the file's raw bytes, unmodified",
35
+ "paths": "POSIX-separated, relative to bridges/cp2077-cet/",
36
+ "regenerate": "python3 -c \"import hashlib,sys;print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())\" <path>",
37
+ "note": "Recomputed by hand whenever a file here changes; `bridge stage` (src/core/bridge/stage.ts) copies the tree verbatim and does not fill these in, and nothing in ModWright reads them yet. They exist so a staged or deployed copy can be compared against the source of truth by hand."
38
+ },
39
+ "files": [
40
+ {
41
+ "path": "ModWrightBridge/init.lua",
42
+ "sha256": "269528f9a81576f345ec1c679ace5331ae6ccfbb174d4d09efd09d3fb4fc91d8"
43
+ },
44
+ {
45
+ "path": "ModWrightBridge/probes.lua",
46
+ "sha256": "a9dc28d3fd949f353f17404366bd2bb329813b9ca639e06a9b51aa34c104fb7d"
47
+ },
48
+ {
49
+ "path": "ModWrightBridge/protocol.lua",
50
+ "sha256": "f4a59d07883ac9bcdb2da2d5d4cdcd3af42c322a40751515c551c9057ac24210"
51
+ }
52
+ ]
53
+ }
@@ -0,0 +1,175 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as path from "node:path";
3
+ import { executePlan, planFile } from "../safety/index.js";
4
+ import { cetArmConsoleLine, cetArmSteps, compactRequestJson } from "./cet-console.js";
5
+ import { buildPendingArm, pendingArmFile, planPruneExpiredPendingArms, planRecordPendingArm } from "./pending.js";
6
+ import { bridgeFileNames } from "./paths.js";
7
+ import { PROTOCOL_VERSION, bridgeRequestSchema } from "./types.js";
8
+ import { bridgeStatus } from "./status.js";
9
+ export function generateNonce() {
10
+ return crypto.randomBytes(8).toString("hex");
11
+ }
12
+ export function generateRequestId() {
13
+ return `req-${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
14
+ }
15
+ export function buildRequest(opts) {
16
+ const now = opts.now ?? (() => new Date());
17
+ const issuedAt = now();
18
+ const armForMs = (opts.armForSeconds ?? 60) * 1000;
19
+ const grant = opts.grant ?? (opts.allowMutate ? ["session"] : undefined);
20
+ const allowMutate = grant ? grant.includes("session") : opts.allowMutate ?? false;
21
+ const request = {
22
+ protocolVersion: PROTOCOL_VERSION,
23
+ id: opts.id ?? generateRequestId(),
24
+ nonce: opts.nonce ?? generateNonce(),
25
+ issuedAt: issuedAt.toISOString(),
26
+ armedUntil: new Date(issuedAt.getTime() + armForMs).toISOString(),
27
+ game: opts.game,
28
+ allowMutate,
29
+ ...(grant ? { grant } : {}),
30
+ ...(opts.window ? { window: opts.window } : {}),
31
+ ...(opts.plan ? { plan: opts.plan } : {}),
32
+ probes: opts.probes,
33
+ };
34
+ return bridgeRequestSchema.parse(request);
35
+ }
36
+ export function planArmRequest(dirAbs, request) {
37
+ const files = bridgeFileNames(request.game);
38
+ const reqPath = path.join(dirAbs, files.request);
39
+ const markerPath = path.join(dirAbs, files.requestMarker);
40
+ const reqJson = `${JSON.stringify(request, null, 2)}\n`;
41
+ const marker = {
42
+ protocolVersion: PROTOCOL_VERSION,
43
+ id: request.id,
44
+ nonce: request.nonce,
45
+ bytes: Buffer.byteLength(reqJson, "utf8"),
46
+ };
47
+ return {
48
+ purpose: `arm bridge request ${request.id} (${request.game})`,
49
+ operations: [planFile(reqPath, reqJson), planFile(markerPath, `${JSON.stringify(marker, null, 2)}\n`)],
50
+ };
51
+ }
52
+ export async function armBridge(input) {
53
+ const status = await bridgeStatus(input.game, input.install, input.surface, { now: input.now, readFile: input.readFile });
54
+ if (status.found && !status.protocolVersionMatch) {
55
+ return {
56
+ game: input.game,
57
+ transport: input.game === "cyberpunk2077" ? "console" : "file",
58
+ status,
59
+ refused: { reason: "protocol-mismatch", message: status.message },
60
+ };
61
+ }
62
+ if (input.game === "cyberpunk2077")
63
+ return armCyberpunk(input, status);
64
+ const dir = status.dir ?? (status.candidates.length === 1 ? status.candidates[0] : undefined);
65
+ if (!dir) {
66
+ return {
67
+ game: input.game,
68
+ transport: "file",
69
+ status,
70
+ refused: {
71
+ reason: "unknown-write-root",
72
+ message: `No heartbeat was found and ${input.game} has more than one possible write root, so ModWright cannot tell ` +
73
+ `which one the bridge will use. Launch the game once with the bridge deployed and staged, then retry.`,
74
+ },
75
+ };
76
+ }
77
+ const now = input.now ?? (() => new Date());
78
+ const request = buildRequest(input);
79
+ const plan = planArmRequest(dir, request);
80
+ if (input.projectRoot) {
81
+ const armedAt = now();
82
+ plan.operations.push(...planRecordPendingArm(input.projectRoot, buildPendingArm(request, undefined, armedAt, input.window?.seconds)).operations, ...(await planPruneExpiredPendingArms(input.projectRoot, input.game, armedAt)));
83
+ }
84
+ const confidence = {
85
+ level: status.found ? "high" : "medium",
86
+ assumptions: status.found
87
+ ? []
88
+ : [`No live heartbeat was seen; armed into the only possible directory for ${input.game} ahead of the bridge starting.`],
89
+ verifiedBy: status.found ? ["bridge heartbeat"] : [],
90
+ };
91
+ const write = await executePlan(input.projectRoot ? { ...plan, backupRoot: path.join(input.projectRoot, ".modwright", "backups") } : plan, { mode: input.mode ?? "dry-run", now: input.now, confidence });
92
+ return {
93
+ game: input.game,
94
+ transport: "file",
95
+ status,
96
+ dir,
97
+ request,
98
+ write,
99
+ ...(input.projectRoot && (input.mode ?? "dry-run") === "apply" ? { pendingFile: pendingArmFile(input.projectRoot, request.id) } : {}),
100
+ };
101
+ }
102
+ async function armCyberpunk(input, status) {
103
+ const now = input.now ?? (() => new Date());
104
+ const request = buildRequest(input);
105
+ const consoleLine = cetArmConsoleLine(request);
106
+ const fileDir = status.transport === "file" ? status.dir : undefined;
107
+ if (!fileDir && !input.console) {
108
+ return {
109
+ game: input.game,
110
+ transport: "console",
111
+ status,
112
+ request,
113
+ refused: {
114
+ reason: "console-arm-required",
115
+ message: `This bridge is not writing files ModWright can find (${status.sandboxIo === false ? "its capability line reports no io in the mod sandbox" : "no heartbeat file was found"}), ` +
116
+ `so the request only reaches it when a human pastes one CET console line. This caller cannot wait ` +
117
+ `for that. Run the "bridge" tool's "arm" action with the project path to get the line and record the nonce, ` +
118
+ `paste it into the CET console, then "bridge poll".`,
119
+ },
120
+ };
121
+ }
122
+ if (!fileDir && !input.projectRoot) {
123
+ return {
124
+ game: input.game,
125
+ transport: "console",
126
+ status,
127
+ request,
128
+ refused: {
129
+ reason: "no-project-root",
130
+ message: `A console-armed Cyberpunk request records its pending nonce under <project>/.modwright/bridge/pending/ so ` +
131
+ `"bridge poll" can match the result lines in the CET log. Pass the mod project's path.`,
132
+ },
133
+ };
134
+ }
135
+ const armedAt = now();
136
+ const mode = input.mode ?? "dry-run";
137
+ const operations = [
138
+ ...(fileDir ? planArmRequest(fileDir, request).operations : []),
139
+ ...(input.projectRoot
140
+ ? planRecordPendingArm(input.projectRoot, buildPendingArm(request, consoleLine, armedAt, input.window?.seconds)).operations
141
+ : []),
142
+ ...(input.projectRoot ? await planPruneExpiredPendingArms(input.projectRoot, input.game, armedAt) : []),
143
+ ];
144
+ const confidence = {
145
+ level: status.found ? "high" : "medium",
146
+ assumptions: [
147
+ ...(fileDir
148
+ ? [`The bridge reports a live write root, so the request was written there as well; if it never picks it up, paste the console line instead.`]
149
+ : [`The request reaches the bridge only when a human pastes the console line; ModWright cannot confirm that it was run.`]),
150
+ ...(status.found
151
+ ? []
152
+ : ["No heartbeat was found in either transport, so the bridge may not be loaded yet — the console line can still be pasted once it is."]),
153
+ ],
154
+ verifiedBy: status.found ? [`bridge heartbeat (${status.transport} transport)`] : [],
155
+ };
156
+ const write = await executePlan({
157
+ purpose: `arm bridge request ${request.id} (cyberpunk2077, ${fileDir ? "file + console" : "console"})`,
158
+ operations,
159
+ ...(input.projectRoot ? { backupRoot: path.join(input.projectRoot, ".modwright", "backups") } : {}),
160
+ }, { mode, now: input.now, confidence });
161
+ return {
162
+ game: input.game,
163
+ transport: fileDir ? "file" : "console",
164
+ status,
165
+ ...(fileDir ? { dir: fileDir } : {}),
166
+ request,
167
+ write,
168
+ console: {
169
+ consoleLine,
170
+ requestJson: compactRequestJson(request),
171
+ ...(mode === "apply" && input.projectRoot ? { pendingFile: pendingArmFile(input.projectRoot, request.id) } : {}),
172
+ steps: cetArmSteps(request, mode === "apply" && input.projectRoot ? pendingArmFile(input.projectRoot, request.id) : undefined, Boolean(fileDir)),
173
+ },
174
+ };
175
+ }
@@ -0,0 +1,26 @@
1
+ import { CET_DONE_TAG, CET_RESULT_TAG } from "./cet-log.js";
2
+ export const CET_CONSOLE_MOD = "ModWrightBridge";
3
+ export function compactRequestJson(request) {
4
+ return JSON.stringify(request).replace(/[\u007f-\uffff]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
5
+ }
6
+ export function luaSingleQuoted(value) {
7
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
8
+ }
9
+ export function cetArmConsoleLine(request) {
10
+ return `GetMod("${CET_CONSOLE_MOD}").arm('${luaSingleQuoted(compactRequestJson(request))}')`;
11
+ }
12
+ export function cetArmSteps(request, pendingFile, alsoWrittenAsFile = false) {
13
+ return [
14
+ ...(alsoWrittenAsFile
15
+ ? [
16
+ `The request was also written as a file into the write root this bridge reported, so it may be picked up on its own within a second — poll first, and only paste the line below if nothing comes back.`,
17
+ ]
18
+ : []),
19
+ `Open the CET console (~) with a save loaded and paste the line in "consoleLine" exactly as it is, on one line.`,
20
+ `The bridge prints "[ModWrightBridge] armed by hand" and then, when the probes finish, one ${CET_RESULT_TAG} line ` +
21
+ `followed by one ${CET_DONE_TAG} line.`,
22
+ `Close the console if any probe reads pose, animation or equipment: the weapon drops into the holstered pose while the console is open.`,
23
+ `Then run bridge poll — id "${request.id}"${pendingFile ? ` (recorded in ${pendingFile})` : ""} — which tails the CET log for those two lines.`,
24
+ `The request stays armed until ${request.armedUntil}; after that the bridge refuses it with disarm.reason "expired".`,
25
+ ];
26
+ }