picopilot 0.0.0 → 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/LICENSE +661 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +4 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/cli.d.ts +15 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +59 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/audio.d.ts +17 -0
  11. package/dist/commands/audio.d.ts.map +1 -0
  12. package/dist/commands/audio.js +315 -0
  13. package/dist/commands/audio.js.map +1 -0
  14. package/dist/commands/export.d.ts +36 -0
  15. package/dist/commands/export.d.ts.map +1 -0
  16. package/dist/commands/export.js +257 -0
  17. package/dist/commands/export.js.map +1 -0
  18. package/dist/commands/gfx.d.ts +28 -0
  19. package/dist/commands/gfx.d.ts.map +1 -0
  20. package/dist/commands/gfx.js +521 -0
  21. package/dist/commands/gfx.js.map +1 -0
  22. package/dist/commands/init.d.ts +34 -0
  23. package/dist/commands/init.d.ts.map +1 -0
  24. package/dist/commands/init.js +151 -0
  25. package/dist/commands/init.js.map +1 -0
  26. package/dist/commands/lint.d.ts +20 -0
  27. package/dist/commands/lint.d.ts.map +1 -0
  28. package/dist/commands/lint.js +90 -0
  29. package/dist/commands/lint.js.map +1 -0
  30. package/dist/commands/minify.d.ts +20 -0
  31. package/dist/commands/minify.d.ts.map +1 -0
  32. package/dist/commands/minify.js +129 -0
  33. package/dist/commands/minify.js.map +1 -0
  34. package/dist/commands/music.d.ts +13 -0
  35. package/dist/commands/music.d.ts.map +1 -0
  36. package/dist/commands/music.js +241 -0
  37. package/dist/commands/music.js.map +1 -0
  38. package/dist/commands/playtest.d.ts +29 -0
  39. package/dist/commands/playtest.d.ts.map +1 -0
  40. package/dist/commands/playtest.js +631 -0
  41. package/dist/commands/playtest.js.map +1 -0
  42. package/dist/commands/run.d.ts +30 -0
  43. package/dist/commands/run.d.ts.map +1 -0
  44. package/dist/commands/run.js +202 -0
  45. package/dist/commands/run.js.map +1 -0
  46. package/dist/commands/serve.d.ts +50 -0
  47. package/dist/commands/serve.d.ts.map +1 -0
  48. package/dist/commands/serve.js +213 -0
  49. package/dist/commands/serve.js.map +1 -0
  50. package/dist/commands/sfx.d.ts +13 -0
  51. package/dist/commands/sfx.d.ts.map +1 -0
  52. package/dist/commands/sfx.js +254 -0
  53. package/dist/commands/sfx.js.map +1 -0
  54. package/dist/commands/tokens.d.ts +36 -0
  55. package/dist/commands/tokens.d.ts.map +1 -0
  56. package/dist/commands/tokens.js +129 -0
  57. package/dist/commands/tokens.js.map +1 -0
  58. package/dist/commands/verify.d.ts +57 -0
  59. package/dist/commands/verify.d.ts.map +1 -0
  60. package/dist/commands/verify.js +213 -0
  61. package/dist/commands/verify.js.map +1 -0
  62. package/dist/commands/version.d.ts +12 -0
  63. package/dist/commands/version.d.ts.map +1 -0
  64. package/dist/commands/version.js +24 -0
  65. package/dist/commands/version.js.map +1 -0
  66. package/dist/engine/audio/index.d.ts +14 -0
  67. package/dist/engine/audio/index.d.ts.map +1 -0
  68. package/dist/engine/audio/index.js +14 -0
  69. package/dist/engine/audio/index.js.map +1 -0
  70. package/dist/engine/audio/music.d.ts +116 -0
  71. package/dist/engine/audio/music.d.ts.map +1 -0
  72. package/dist/engine/audio/music.js +138 -0
  73. package/dist/engine/audio/music.js.map +1 -0
  74. package/dist/engine/audio/section.d.ts +34 -0
  75. package/dist/engine/audio/section.d.ts.map +1 -0
  76. package/dist/engine/audio/section.js +67 -0
  77. package/dist/engine/audio/section.js.map +1 -0
  78. package/dist/engine/audio/sfx.d.ts +141 -0
  79. package/dist/engine/audio/sfx.d.ts.map +1 -0
  80. package/dist/engine/audio/sfx.js +506 -0
  81. package/dist/engine/audio/sfx.js.map +1 -0
  82. package/dist/engine/cart/cart.d.ts +75 -0
  83. package/dist/engine/cart/cart.d.ts.map +1 -0
  84. package/dist/engine/cart/cart.js +153 -0
  85. package/dist/engine/cart/cart.js.map +1 -0
  86. package/dist/engine/cart/errors.d.ts +29 -0
  87. package/dist/engine/cart/errors.d.ts.map +1 -0
  88. package/dist/engine/cart/errors.js +26 -0
  89. package/dist/engine/cart/errors.js.map +1 -0
  90. package/dist/engine/cart/gfx.d.ts +79 -0
  91. package/dist/engine/cart/gfx.d.ts.map +1 -0
  92. package/dist/engine/cart/gfx.js +171 -0
  93. package/dist/engine/cart/gfx.js.map +1 -0
  94. package/dist/engine/cart/index.d.ts +15 -0
  95. package/dist/engine/cart/index.d.ts.map +1 -0
  96. package/dist/engine/cart/index.js +15 -0
  97. package/dist/engine/cart/index.js.map +1 -0
  98. package/dist/engine/cart/map.d.ts +46 -0
  99. package/dist/engine/cart/map.d.ts.map +1 -0
  100. package/dist/engine/cart/map.js +118 -0
  101. package/dist/engine/cart/map.js.map +1 -0
  102. package/dist/engine/config/config.d.ts +74 -0
  103. package/dist/engine/config/config.d.ts.map +1 -0
  104. package/dist/engine/config/config.js +103 -0
  105. package/dist/engine/config/config.js.map +1 -0
  106. package/dist/engine/config/index.d.ts +7 -0
  107. package/dist/engine/config/index.d.ts.map +1 -0
  108. package/dist/engine/config/index.js +7 -0
  109. package/dist/engine/config/index.js.map +1 -0
  110. package/dist/engine/gfx/grid.d.ts +51 -0
  111. package/dist/engine/gfx/grid.d.ts.map +1 -0
  112. package/dist/engine/gfx/grid.js +116 -0
  113. package/dist/engine/gfx/grid.js.map +1 -0
  114. package/dist/engine/gfx/index.d.ts +17 -0
  115. package/dist/engine/gfx/index.d.ts.map +1 -0
  116. package/dist/engine/gfx/index.js +17 -0
  117. package/dist/engine/gfx/index.js.map +1 -0
  118. package/dist/engine/gfx/label.d.ts +34 -0
  119. package/dist/engine/gfx/label.d.ts.map +1 -0
  120. package/dist/engine/gfx/label.js +190 -0
  121. package/dist/engine/gfx/label.js.map +1 -0
  122. package/dist/engine/gfx/overlap.d.ts +42 -0
  123. package/dist/engine/gfx/overlap.d.ts.map +1 -0
  124. package/dist/engine/gfx/overlap.js +57 -0
  125. package/dist/engine/gfx/overlap.js.map +1 -0
  126. package/dist/engine/gfx/palette.d.ts +29 -0
  127. package/dist/engine/gfx/palette.d.ts.map +1 -0
  128. package/dist/engine/gfx/palette.js +46 -0
  129. package/dist/engine/gfx/palette.js.map +1 -0
  130. package/dist/engine/gfx/png.d.ts +31 -0
  131. package/dist/engine/gfx/png.d.ts.map +1 -0
  132. package/dist/engine/gfx/png.js +165 -0
  133. package/dist/engine/gfx/png.js.map +1 -0
  134. package/dist/engine/gfx/render.d.ts +42 -0
  135. package/dist/engine/gfx/render.d.ts.map +1 -0
  136. package/dist/engine/gfx/render.js +72 -0
  137. package/dist/engine/gfx/render.js.map +1 -0
  138. package/dist/engine/index.d.ts +22 -0
  139. package/dist/engine/index.d.ts.map +1 -0
  140. package/dist/engine/index.js +22 -0
  141. package/dist/engine/index.js.map +1 -0
  142. package/dist/engine/pico8/adapter.d.ts +267 -0
  143. package/dist/engine/pico8/adapter.d.ts.map +1 -0
  144. package/dist/engine/pico8/adapter.js +37 -0
  145. package/dist/engine/pico8/adapter.js.map +1 -0
  146. package/dist/engine/pico8/drive.d.ts +308 -0
  147. package/dist/engine/pico8/drive.d.ts.map +1 -0
  148. package/dist/engine/pico8/drive.js +581 -0
  149. package/dist/engine/pico8/drive.js.map +1 -0
  150. package/dist/engine/pico8/harness.d.ts +146 -0
  151. package/dist/engine/pico8/harness.d.ts.map +1 -0
  152. package/dist/engine/pico8/harness.js +251 -0
  153. package/dist/engine/pico8/harness.js.map +1 -0
  154. package/dist/engine/pico8/index.d.ts +21 -0
  155. package/dist/engine/pico8/index.d.ts.map +1 -0
  156. package/dist/engine/pico8/index.js +21 -0
  157. package/dist/engine/pico8/index.js.map +1 -0
  158. package/dist/engine/pico8/sentinel.d.ts +63 -0
  159. package/dist/engine/pico8/sentinel.d.ts.map +1 -0
  160. package/dist/engine/pico8/sentinel.js +86 -0
  161. package/dist/engine/pico8/sentinel.js.map +1 -0
  162. package/dist/engine/pico8/session-daemon-entry.d.ts +10 -0
  163. package/dist/engine/pico8/session-daemon-entry.d.ts.map +1 -0
  164. package/dist/engine/pico8/session-daemon-entry.js +11 -0
  165. package/dist/engine/pico8/session-daemon-entry.js.map +1 -0
  166. package/dist/engine/pico8/session-daemon-main.d.ts +44 -0
  167. package/dist/engine/pico8/session-daemon-main.d.ts.map +1 -0
  168. package/dist/engine/pico8/session-daemon-main.js +71 -0
  169. package/dist/engine/pico8/session-daemon-main.js.map +1 -0
  170. package/dist/engine/pico8/session-daemon.d.ts +68 -0
  171. package/dist/engine/pico8/session-daemon.d.ts.map +1 -0
  172. package/dist/engine/pico8/session-daemon.js +264 -0
  173. package/dist/engine/pico8/session-daemon.js.map +1 -0
  174. package/dist/engine/pico8/session.d.ts +140 -0
  175. package/dist/engine/pico8/session.d.ts.map +1 -0
  176. package/dist/engine/pico8/session.js +250 -0
  177. package/dist/engine/pico8/session.js.map +1 -0
  178. package/dist/engine/pico8/shell.d.ts +103 -0
  179. package/dist/engine/pico8/shell.d.ts.map +1 -0
  180. package/dist/engine/pico8/shell.js +324 -0
  181. package/dist/engine/pico8/shell.js.map +1 -0
  182. package/dist/engine/pico8/supervisor.d.ts +137 -0
  183. package/dist/engine/pico8/supervisor.d.ts.map +1 -0
  184. package/dist/engine/pico8/supervisor.js +140 -0
  185. package/dist/engine/pico8/supervisor.js.map +1 -0
  186. package/dist/engine/scaffold/index.d.ts +8 -0
  187. package/dist/engine/scaffold/index.d.ts.map +1 -0
  188. package/dist/engine/scaffold/index.js +8 -0
  189. package/dist/engine/scaffold/index.js.map +1 -0
  190. package/dist/engine/scaffold/scaffold.d.ts +49 -0
  191. package/dist/engine/scaffold/scaffold.d.ts.map +1 -0
  192. package/dist/engine/scaffold/scaffold.js +217 -0
  193. package/dist/engine/scaffold/scaffold.js.map +1 -0
  194. package/dist/engine/shrinko/adapter.d.ts +167 -0
  195. package/dist/engine/shrinko/adapter.d.ts.map +1 -0
  196. package/dist/engine/shrinko/adapter.js +107 -0
  197. package/dist/engine/shrinko/adapter.js.map +1 -0
  198. package/dist/engine/shrinko/index.d.ts +13 -0
  199. package/dist/engine/shrinko/index.d.ts.map +1 -0
  200. package/dist/engine/shrinko/index.js +13 -0
  201. package/dist/engine/shrinko/index.js.map +1 -0
  202. package/dist/engine/shrinko/shell.d.ts +69 -0
  203. package/dist/engine/shrinko/shell.d.ts.map +1 -0
  204. package/dist/engine/shrinko/shell.js +156 -0
  205. package/dist/engine/shrinko/shell.js.map +1 -0
  206. package/dist/engine/skills/index.d.ts +8 -0
  207. package/dist/engine/skills/index.d.ts.map +1 -0
  208. package/dist/engine/skills/index.js +8 -0
  209. package/dist/engine/skills/index.js.map +1 -0
  210. package/dist/engine/skills/skills.d.ts +104 -0
  211. package/dist/engine/skills/skills.d.ts.map +1 -0
  212. package/dist/engine/skills/skills.js +149 -0
  213. package/dist/engine/skills/skills.js.map +1 -0
  214. package/dist/index.d.ts +10 -0
  215. package/dist/index.d.ts.map +1 -0
  216. package/dist/index.js +10 -0
  217. package/dist/index.js.map +1 -0
  218. package/dist/version.d.ts +2 -0
  219. package/dist/version.d.ts.map +1 -0
  220. package/dist/version.js +19 -0
  221. package/dist/version.js.map +1 -0
  222. package/package.json +48 -2
  223. package/src/bin.ts +4 -0
  224. package/src/cli.ts +61 -0
  225. package/src/commands/audio.test.ts +280 -0
  226. package/src/commands/audio.ts +410 -0
  227. package/src/commands/export.test.ts +310 -0
  228. package/src/commands/export.ts +316 -0
  229. package/src/commands/gfx-io.test.ts +178 -0
  230. package/src/commands/gfx.test.ts +470 -0
  231. package/src/commands/gfx.ts +645 -0
  232. package/src/commands/init.test.ts +460 -0
  233. package/src/commands/init.ts +193 -0
  234. package/src/commands/lint.test.ts +137 -0
  235. package/src/commands/lint.ts +117 -0
  236. package/src/commands/minify.test.ts +166 -0
  237. package/src/commands/minify.ts +156 -0
  238. package/src/commands/music.test.ts +272 -0
  239. package/src/commands/music.ts +288 -0
  240. package/src/commands/playtest.test.ts +317 -0
  241. package/src/commands/playtest.ts +773 -0
  242. package/src/commands/run.test.ts +238 -0
  243. package/src/commands/run.ts +259 -0
  244. package/src/commands/serve.test.ts +218 -0
  245. package/src/commands/serve.ts +272 -0
  246. package/src/commands/sfx.test.ts +349 -0
  247. package/src/commands/sfx.ts +300 -0
  248. package/src/commands/tokens.test.ts +212 -0
  249. package/src/commands/tokens.ts +168 -0
  250. package/src/commands/verify.test.ts +238 -0
  251. package/src/commands/verify.ts +256 -0
  252. package/src/commands/version.test.ts +70 -0
  253. package/src/commands/version.ts +24 -0
  254. package/src/engine/audio/index.ts +47 -0
  255. package/src/engine/audio/music.test.ts +188 -0
  256. package/src/engine/audio/music.ts +218 -0
  257. package/src/engine/audio/section.test.ts +124 -0
  258. package/src/engine/audio/section.ts +79 -0
  259. package/src/engine/audio/sfx.test.ts +410 -0
  260. package/src/engine/audio/sfx.ts +694 -0
  261. package/src/engine/cart/cart.test.ts +304 -0
  262. package/src/engine/cart/cart.ts +211 -0
  263. package/src/engine/cart/errors.ts +39 -0
  264. package/src/engine/cart/fixtures/crlf.p8 +6 -0
  265. package/src/engine/cart/fixtures/full.p8 +174 -0
  266. package/src/engine/cart/fixtures/header-only.p8 +2 -0
  267. package/src/engine/cart/fixtures/malformed-no-header.p8 +2 -0
  268. package/src/engine/cart/fixtures/minimal.p8 +10 -0
  269. package/src/engine/cart/fixtures/no-trailing-newline.p8 +6 -0
  270. package/src/engine/cart/gfx.ts +195 -0
  271. package/src/engine/cart/index.ts +27 -0
  272. package/src/engine/cart/map.ts +134 -0
  273. package/src/engine/config/config.ts +115 -0
  274. package/src/engine/config/index.ts +12 -0
  275. package/src/engine/gfx/grid.test.ts +128 -0
  276. package/src/engine/gfx/grid.ts +136 -0
  277. package/src/engine/gfx/index.ts +34 -0
  278. package/src/engine/gfx/label.test.ts +105 -0
  279. package/src/engine/gfx/label.ts +229 -0
  280. package/src/engine/gfx/overlap.test.ts +71 -0
  281. package/src/engine/gfx/overlap.ts +66 -0
  282. package/src/engine/gfx/palette.test.ts +35 -0
  283. package/src/engine/gfx/palette.ts +54 -0
  284. package/src/engine/gfx/png.test.ts +111 -0
  285. package/src/engine/gfx/png.ts +196 -0
  286. package/src/engine/gfx/render.test.ts +180 -0
  287. package/src/engine/gfx/render.ts +106 -0
  288. package/src/engine/index.ts +21 -0
  289. package/src/engine/pico8/adapter.ts +303 -0
  290. package/src/engine/pico8/drive.test.ts +415 -0
  291. package/src/engine/pico8/drive.ts +729 -0
  292. package/src/engine/pico8/harness.test.ts +181 -0
  293. package/src/engine/pico8/harness.ts +354 -0
  294. package/src/engine/pico8/index.ts +20 -0
  295. package/src/engine/pico8/sentinel.test.ts +80 -0
  296. package/src/engine/pico8/sentinel.ts +93 -0
  297. package/src/engine/pico8/session-daemon-entry.ts +12 -0
  298. package/src/engine/pico8/session-daemon-main.ts +98 -0
  299. package/src/engine/pico8/session-daemon.test.ts +212 -0
  300. package/src/engine/pico8/session-daemon.ts +306 -0
  301. package/src/engine/pico8/session.test.ts +239 -0
  302. package/src/engine/pico8/session.ts +321 -0
  303. package/src/engine/pico8/shell.test.ts +583 -0
  304. package/src/engine/pico8/shell.ts +412 -0
  305. package/src/engine/pico8/supervisor.test.ts +105 -0
  306. package/src/engine/pico8/supervisor.ts +197 -0
  307. package/src/engine/scaffold/index.ts +15 -0
  308. package/src/engine/scaffold/scaffold.ts +236 -0
  309. package/src/engine/shrinko/adapter.ts +241 -0
  310. package/src/engine/shrinko/fixtures/count-no-compressed.txt +2 -0
  311. package/src/engine/shrinko/fixtures/count-over-budget.txt +3 -0
  312. package/src/engine/shrinko/fixtures/count-under-budget.txt +3 -0
  313. package/src/engine/shrinko/index.ts +33 -0
  314. package/src/engine/shrinko/shell.ts +224 -0
  315. package/src/engine/shrinko/shrinko.test.ts +182 -0
  316. package/src/engine/skills/index.ts +14 -0
  317. package/src/engine/skills/skills.test.ts +376 -0
  318. package/src/engine/skills/skills.ts +205 -0
  319. package/src/index.ts +13 -0
  320. package/src/skills/game-design-reference/SKILL.md +70 -0
  321. package/src/skills/game-jam/SKILL.md +45 -0
  322. package/src/skills/picopilot-art/SKILL.md +62 -0
  323. package/src/skills/picopilot-audio/SKILL.md +194 -0
  324. package/src/skills/picopilot-code/SKILL.md +97 -0
  325. package/src/skills/picopilot-code/reference/README.md +42 -0
  326. package/src/skills/picopilot-code/reference/mode7-racing.md +95 -0
  327. package/src/skills/picopilot-code/reference/platformer.md +119 -0
  328. package/src/skills/picopilot-code/reference/puzzle-grid.md +106 -0
  329. package/src/skills/picopilot-code/reference/rpg-menus-dialog.md +122 -0
  330. package/src/skills/picopilot-code/reference/top-down-adventure.md +117 -0
  331. package/src/skills/picopilot-code/reference/twin-stick-arcade.md +128 -0
  332. package/src/skills/picopilot-debug/SKILL.md +195 -0
  333. package/src/skills/picopilot-overview/SKILL.md +52 -0
  334. package/src/version.ts +22 -0
@@ -0,0 +1,30 @@
1
+ import { type Cli } from 'incur';
2
+ import { type Pico8Adapter } from '../engine/pico8/index.js';
3
+ /**
4
+ * Injects the PICO-8 adapter `run` uses (defaults to the shell adapter over
5
+ * native `pico8 -x`). Mirrors the shrinko `adapterFactory` seam: a test passes a
6
+ * stub to drive the sentinel/backstop/absent paths without the paid binary, and
7
+ * to isolate the child `env` (its `PICO8_PATH`/`PATH`).
8
+ */
9
+ export type Pico8AdapterFactory = (env: NodeJS.ProcessEnv) => Pico8Adapter;
10
+ /**
11
+ * Registers `picopilot run`, the THIN sentinel-watch + screenshot-collect
12
+ * orchestration command (US #14, ADR-0006). It is NOT a `pico8` wrapper: its
13
+ * value is the glue an agent should not hand-roll every run, launch PICO-8,
14
+ * stream stdout, kill on the cart's done-sentinel (backstop as the safety net),
15
+ * and collect screenshots + printh + exit reason into ONE structured result.
16
+ *
17
+ * The cart cooperates: it self-screenshots via `extcmd("screen")` on a timer and
18
+ * prints the done-sentinel via `printh` when finished (the `picopilot-debug`
19
+ * skill teaches the recipe). A cart cannot self-quit the app, so the external
20
+ * sentinel-kill IS the quit mechanism.
21
+ *
22
+ * PICO-8 absent → structured `pico8-not-found` + nonzero exit (mirrors the
23
+ * shrinko boundary), never a crash or a hang. That absent path is the one
24
+ * exercised in CI; live runs are a manual/opt-in tier.
25
+ *
26
+ * @param adapterFactory injects the adapter (defaults to the shell adapter);
27
+ * tests pass a stub to drive present/absent without the real binary.
28
+ */
29
+ export declare function registerRun(cli: Cli.Cli, adapterFactory?: Pico8AdapterFactory): void;
30
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAClC,OAAO,EAIN,KAAK,YAAY,EAEjB,MAAM,0BAA0B,CAAC;AAElC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,YAAY,CAAC;AAQ3E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,GACzD,IAAI,CA4MN"}
@@ -0,0 +1,202 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync, } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { isAbsolute, join, resolve } from 'node:path';
4
+ import { z } from 'incur';
5
+ import { buildRecordHarness, DONE_SENTINEL, HarnessError, ShellPico8Adapter, } from '../engine/pico8/index.js';
6
+ const defaultAdapterFactory = (env) => new ShellPico8Adapter({ env });
7
+ /** The default backstop: kill a cart that never signals/exits after this many ms. */
8
+ const DEFAULT_BACKSTOP_MS = 15_000;
9
+ /**
10
+ * Registers `picopilot run`, the THIN sentinel-watch + screenshot-collect
11
+ * orchestration command (US #14, ADR-0006). It is NOT a `pico8` wrapper: its
12
+ * value is the glue an agent should not hand-roll every run, launch PICO-8,
13
+ * stream stdout, kill on the cart's done-sentinel (backstop as the safety net),
14
+ * and collect screenshots + printh + exit reason into ONE structured result.
15
+ *
16
+ * The cart cooperates: it self-screenshots via `extcmd("screen")` on a timer and
17
+ * prints the done-sentinel via `printh` when finished (the `picopilot-debug`
18
+ * skill teaches the recipe). A cart cannot self-quit the app, so the external
19
+ * sentinel-kill IS the quit mechanism.
20
+ *
21
+ * PICO-8 absent → structured `pico8-not-found` + nonzero exit (mirrors the
22
+ * shrinko boundary), never a crash or a hang. That absent path is the one
23
+ * exercised in CI; live runs are a manual/opt-in tier.
24
+ *
25
+ * @param adapterFactory injects the adapter (defaults to the shell adapter);
26
+ * tests pass a stub to drive present/absent without the real binary.
27
+ */
28
+ export function registerRun(cli, adapterFactory = defaultAdapterFactory) {
29
+ cli.command('run', {
30
+ description: "Run a cart on the user's PICO-8, capture printh + screenshots, end on a done-sentinel. Requires PICO-8.",
31
+ args: z.object({
32
+ cart: z
33
+ .string()
34
+ .default('main.p8')
35
+ .describe('The .p8 cart to run. Defaults to main.p8 in the current folder.'),
36
+ }),
37
+ options: z.object({
38
+ shotDir: z
39
+ .string()
40
+ .optional()
41
+ .describe('Where PICO-8 writes screenshots (-desktop). Defaults to an isolated temp dir; NEVER ~/Desktop.'),
42
+ sentinel: z
43
+ .string()
44
+ .default(DONE_SENTINEL)
45
+ .describe('The stdout line that ends the run. The cart printh-es this when done.'),
46
+ backstopMs: z
47
+ .number()
48
+ .int()
49
+ .positive()
50
+ .default(DEFAULT_BACKSTOP_MS)
51
+ .describe('Safety backstop: kill PICO-8 after this many ms if it never signals.'),
52
+ input: z
53
+ .string()
54
+ .optional()
55
+ .describe('One-shot scripted input passed as `-p` (the cart reads it via stat(6)) for automated playtests.'),
56
+ recordAudio: z
57
+ .boolean()
58
+ .default(false)
59
+ .describe("Also capture a WAV of the run's audio alongside the screenshots (ADR-0009). A REAL-TIME recording that needs a real audio+video session (a separate -run pass, NOT the headless screenshot pass), never an offline export."),
60
+ }),
61
+ // incur resolves these from the (test-overridable) env source and hands
62
+ // them to the child, so PICO8_PATH/PATH locate the binary in isolation.
63
+ env: z.object({
64
+ PICO8_PATH: z
65
+ .string()
66
+ .optional()
67
+ .describe('Explicit path to the PICO-8 binary (else `pico8` on PATH).'),
68
+ PATH: z.string().optional().describe('Used to locate `pico8`.'),
69
+ }),
70
+ output: z.object({
71
+ screenshots: z
72
+ .array(z.string())
73
+ .describe('Absolute paths of the PNG screenshots the cart produced.'),
74
+ printh: z
75
+ .string()
76
+ .describe('Captured stdout (the cart printh output + PICO-8 lines).'),
77
+ exitReason: z
78
+ .enum(['sentinel', 'timeout', 'exit'])
79
+ .describe('sentinel = cart signalled done; timeout = backstop fired; exit = PICO-8 quit.'),
80
+ shotDir: z.string().describe('The dir screenshots were written to.'),
81
+ wav: z
82
+ .string()
83
+ .optional()
84
+ .describe('Absolute path of the captured WAV (only with --record-audio; omitted if no audio was captured).'),
85
+ audioCaptured: z
86
+ .boolean()
87
+ .describe('Whether a non-empty WAV was captured (false unless --record-audio ran and produced audio).'),
88
+ }),
89
+ examples: [
90
+ { description: 'Run main.p8' },
91
+ { description: 'Run a specific cart', args: { cart: 'game.p8' } },
92
+ {
93
+ description: 'Collect screenshots into a chosen dir',
94
+ args: { cart: 'game.p8' },
95
+ options: { shotDir: './shots' },
96
+ },
97
+ ],
98
+ async run({ args, options, env, error, ok }) {
99
+ const cartPath = isAbsolute(args.cart)
100
+ ? args.cart
101
+ : resolve(process.cwd(), args.cart);
102
+ // A missing cart is a picopilot-side error, distinct from PICO-8 absence.
103
+ if (!existsSync(cartPath)) {
104
+ return error({
105
+ code: 'cart-not-found',
106
+ message: `no cart at ${cartPath}`,
107
+ exitCode: 1,
108
+ });
109
+ }
110
+ // Screenshot dir: a user-given path, else an isolated temp dir. NEVER the
111
+ // default ~/Desktop (the `-desktop` flag is the isolation lever). Create
112
+ // it so PICO-8 has somewhere to write.
113
+ const shotDir = options.shotDir !== undefined
114
+ ? isAbsolute(options.shotDir)
115
+ ? options.shotDir
116
+ : resolve(process.cwd(), options.shotDir)
117
+ : mkdtempSync(join(tmpdir(), 'picopilot-run-'));
118
+ if (!existsSync(shotDir))
119
+ mkdirSync(shotDir, { recursive: true });
120
+ const adapter = adapterFactory(env);
121
+ const result = await adapter.run({
122
+ cartPath,
123
+ shotDir,
124
+ sentinel: options.sentinel,
125
+ backstopMs: options.backstopMs,
126
+ input: options.input,
127
+ });
128
+ if (!result.ok) {
129
+ // The two-tier structured failure (US #19, ADR-0006): PICO-8 absent.
130
+ // The message carries the exact remedy + needs; the code is the handle.
131
+ return error({
132
+ code: result.reason,
133
+ message: `PICO-8 is not installed. ${result.remedy} (needs: ${result.needs.join(', ')})`,
134
+ exitCode: 1,
135
+ });
136
+ }
137
+ const { screenshots, printh, exitReason } = result.value;
138
+ // --record-audio: an ADDITIVE second pass that records the run's audio to a
139
+ // WAV. It cannot reuse the headless `-x` screenshot pass (which mixes no
140
+ // audio -> an empty WAV, ADR-0009), so it does a separate `-run` capture via
141
+ // the record seam, injecting the same cooperative recorder `audio record`
142
+ // uses. The WAV lands in the isolated shotDir alongside the screenshots.
143
+ let wav;
144
+ let audioCaptured = false;
145
+ if (options.recordAudio) {
146
+ try {
147
+ const harness = buildRecordHarness(readFileSync(cartPath, 'utf8'), {
148
+ sentinel: options.sentinel,
149
+ });
150
+ const harnessCart = join(shotDir, 'record-audio-harness.p8');
151
+ writeFileSync(harnessCart, harness.cartText);
152
+ const rec = await adapter.record({
153
+ cartPath: harnessCart,
154
+ wavDir: shotDir,
155
+ wavBasename: harness.wavBasename,
156
+ sentinel: options.sentinel,
157
+ backstopMs: options.backstopMs,
158
+ });
159
+ if (rec.ok) {
160
+ wav = rec.value.wavPath;
161
+ audioCaptured = rec.value.wavPath !== undefined;
162
+ }
163
+ // A record-pass pico8-not-found is impossible here: the screenshot
164
+ // pass above already returned pico8-not-found if PICO-8 were absent.
165
+ }
166
+ catch (e) {
167
+ if (!(e instanceof HarnessError))
168
+ throw e;
169
+ // A harness-build failure (unparseable cart) leaves audioCaptured false;
170
+ // the screenshot run's report still stands.
171
+ }
172
+ }
173
+ // A backstop timeout is a soft warning: the cart never signalled done, so
174
+ // it may have hung or errored. CTA the agent toward the static checks
175
+ // (lint/tokens) to catch a code fault before re-running.
176
+ const cta = exitReason === 'timeout'
177
+ ? {
178
+ description: 'The cart never printed the done-sentinel (backstop fired). Check for a fault:',
179
+ commands: [
180
+ {
181
+ command: 'verify',
182
+ description: 'Run the static gate (tokens + lint + integrity).',
183
+ },
184
+ {
185
+ command: 'tokens',
186
+ description: 'Confirm the cart is under the token budget.',
187
+ },
188
+ ],
189
+ }
190
+ : undefined;
191
+ return ok({
192
+ screenshots: [...screenshots],
193
+ printh,
194
+ exitReason,
195
+ shotDir,
196
+ wav,
197
+ audioCaptured,
198
+ }, cta === undefined ? undefined : { cta });
199
+ },
200
+ });
201
+ }
202
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { type Server } from 'node:http';
2
+ import { type Cli } from 'incur';
3
+ import { type Pico8Adapter } from '../engine/pico8/index.js';
4
+ /**
5
+ * Injects the PICO-8 adapter `serve` uses (defaults to the shell adapter). Same
6
+ * seam as `export`/`run`: a test passes a stub so the export step needs no paid
7
+ * binary, and the child `env` (`PICO8_PATH`/`PATH`) stays isolated.
8
+ */
9
+ export type Pico8AdapterFactory = (env: NodeJS.ProcessEnv) => Pico8Adapter;
10
+ /**
11
+ * Injects the HTTP-server factory `serve` binds. The default is a zero-dep
12
+ * `node:http` static server over the export dir; a test injects a fake to assert
13
+ * the serve loop WITHOUT opening a real socket. The factory returns the started
14
+ * server plus the bound port (so `:0` -> an OS-assigned port is observable).
15
+ */
16
+ export type ServerFactory = (rootDir: string, port: number) => Promise<{
17
+ server: Server;
18
+ port: number;
19
+ }>;
20
+ /**
21
+ * Registers a one-shot cleanup for the serving case: `serve` runs until the user
22
+ * interrupts it (the socket keeps the process alive), so the temp export dir is
23
+ * removed on SIGINT/SIGTERM and on normal `exit`. The handler fires at most once.
24
+ * The default uses `process`; a test injects a fake to assert the wiring without
25
+ * touching real signal handlers.
26
+ */
27
+ export type CleanupRegistrar = (cleanup: () => void) => void;
28
+ /**
29
+ * Registers `picopilot serve`, the "play this cart in a browser" loop. It ALWAYS
30
+ * exports first (the quick round-trip: cart -> standalone HTML bundle -> local
31
+ * server -> URL you open), so it takes a cart path, not a pre-built dir. The
32
+ * export step hard-requires PICO-8 (absence is the structured `pico8-not-found`
33
+ * value + a nonzero exit, mirroring `run`/`export`); the static server itself is
34
+ * a zero-dep `node:http` file server over the temp export dir, so no extra
35
+ * dependency and no ~/Desktop pollution.
36
+ *
37
+ * It is distinct from `export`: `export` PRODUCES a bundle at a chosen dest (for
38
+ * the showcase); `serve` throws the bundle in a temp dir and serves it for a
39
+ * quick manual play. It runs until interrupted (the server keeps the process
40
+ * alive), which is why the injected {@link ServerFactory} seam lets a test drive
41
+ * the export + bind path without leaving a real socket open.
42
+ *
43
+ * @param adapterFactory injects the PICO-8 adapter (defaults to the shell one).
44
+ * @param serverFactory injects the HTTP server (defaults to the zero-dep static
45
+ * server); a test passes a fake to assert the loop without a real socket.
46
+ * @param registerCleanup injects the exit/signal cleanup wiring (defaults to the
47
+ * real `process` handlers); a test passes a fake to capture the cleanup fn.
48
+ */
49
+ export declare function registerServe(cli: Cli.Cli, adapterFactory?: Pico8AdapterFactory, serverFactory?: ServerFactory, registerCleanup?: CleanupRegistrar): void;
50
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAOA,OAAO,EAAe,KAAK,MAAM,EAAC,MAAM,WAAW,CAAC;AAGpD,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAClC,OAAO,EAEN,KAAK,YAAY,EAEjB,MAAM,0BAA0B,CAAC;AAElC;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,YAAY,CAAC;AAK3E;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,KACR,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC,CAAC;AAiB7C;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AA6E7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,EAC3D,aAAa,GAAE,aAAoC,EACnD,eAAe,GAAE,gBAA0C,GACzD,IAAI,CA4GN"}
@@ -0,0 +1,213 @@
1
+ import { createReadStream, existsSync, mkdtempSync, rmSync, statSync, } from 'node:fs';
2
+ import { createServer } from 'node:http';
3
+ import { tmpdir } from 'node:os';
4
+ import { extname, isAbsolute, join, normalize, resolve } from 'node:path';
5
+ import { z } from 'incur';
6
+ import { EXPORT_HTML_NAME, ShellPico8Adapter, } from '../engine/pico8/index.js';
7
+ const defaultAdapterFactory = (env) => new ShellPico8Adapter({ env });
8
+ const DEFAULT_PORT = 5858;
9
+ const DEFAULT_BACKSTOP_MS = 30_000;
10
+ /**
11
+ * Best-effort recursive removal of the temp export dir, swallowing any error (a
12
+ * cleanup failure must never mask the command's own result or crash on exit).
13
+ */
14
+ function removeDir(dir) {
15
+ try {
16
+ rmSync(dir, { recursive: true, force: true });
17
+ }
18
+ catch {
19
+ // A stubborn temp dir is not fatal; the OS reaps it eventually.
20
+ }
21
+ }
22
+ const defaultCleanupRegistrar = (cleanup) => {
23
+ let done = false;
24
+ const once = () => {
25
+ if (done)
26
+ return;
27
+ done = true;
28
+ cleanup();
29
+ };
30
+ process.once('exit', once);
31
+ // On a signal, run cleanup then re-exit so the temp dir is gone even on Ctrl-C.
32
+ process.once('SIGINT', () => {
33
+ once();
34
+ process.exit(130);
35
+ });
36
+ process.once('SIGTERM', () => {
37
+ once();
38
+ process.exit(143);
39
+ });
40
+ };
41
+ /** MIME types the static server needs for a PICO-8 export (html + the runtime js). */
42
+ const MIME = {
43
+ '.html': 'text/html; charset=utf-8',
44
+ '.js': 'text/javascript; charset=utf-8',
45
+ '.wasm': 'application/wasm',
46
+ '.png': 'image/png',
47
+ '.json': 'application/json; charset=utf-8',
48
+ '.css': 'text/css; charset=utf-8',
49
+ };
50
+ /**
51
+ * The default {@link ServerFactory}: a minimal, zero-dependency `node:http`
52
+ * static file server rooted at `rootDir`. It resolves `/` to `index.html`,
53
+ * path-traversal-guards every request against `rootDir`, and streams files with
54
+ * a best-effort content type. It exists to PLAY an export locally, not to be a
55
+ * production server. Binding `:0` yields an OS-assigned port, reported back so
56
+ * the command can print the real URL.
57
+ */
58
+ const defaultServerFactory = (rootDir, port) => new Promise((resolvePromise, reject) => {
59
+ const server = createServer((req, res) => {
60
+ const urlPath = decodeURIComponent((req.url ?? '/').split('?')[0]);
61
+ // Resolve the request against the root and guard against traversal: the
62
+ // resolved path must stay inside rootDir.
63
+ const rel = normalize(urlPath).replace(/^(\.\.[/\\])+/, '');
64
+ let filePath = join(rootDir, rel);
65
+ if (!filePath.startsWith(rootDir)) {
66
+ res.writeHead(403).end('forbidden');
67
+ return;
68
+ }
69
+ try {
70
+ if (existsSync(filePath) && statSync(filePath).isDirectory()) {
71
+ filePath = join(filePath, 'index.html');
72
+ }
73
+ if (!existsSync(filePath)) {
74
+ res.writeHead(404).end('not found');
75
+ return;
76
+ }
77
+ res.writeHead(200, {
78
+ 'content-type': MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream',
79
+ });
80
+ createReadStream(filePath).pipe(res);
81
+ }
82
+ catch {
83
+ res.writeHead(500).end('server error');
84
+ }
85
+ });
86
+ server.on('error', reject);
87
+ server.listen(port, '127.0.0.1', () => {
88
+ const addr = server.address();
89
+ const bound = typeof addr === 'object' && addr !== null ? addr.port : port;
90
+ resolvePromise({ server, port: bound });
91
+ });
92
+ });
93
+ /**
94
+ * Registers `picopilot serve`, the "play this cart in a browser" loop. It ALWAYS
95
+ * exports first (the quick round-trip: cart -> standalone HTML bundle -> local
96
+ * server -> URL you open), so it takes a cart path, not a pre-built dir. The
97
+ * export step hard-requires PICO-8 (absence is the structured `pico8-not-found`
98
+ * value + a nonzero exit, mirroring `run`/`export`); the static server itself is
99
+ * a zero-dep `node:http` file server over the temp export dir, so no extra
100
+ * dependency and no ~/Desktop pollution.
101
+ *
102
+ * It is distinct from `export`: `export` PRODUCES a bundle at a chosen dest (for
103
+ * the showcase); `serve` throws the bundle in a temp dir and serves it for a
104
+ * quick manual play. It runs until interrupted (the server keeps the process
105
+ * alive), which is why the injected {@link ServerFactory} seam lets a test drive
106
+ * the export + bind path without leaving a real socket open.
107
+ *
108
+ * @param adapterFactory injects the PICO-8 adapter (defaults to the shell one).
109
+ * @param serverFactory injects the HTTP server (defaults to the zero-dep static
110
+ * server); a test passes a fake to assert the loop without a real socket.
111
+ * @param registerCleanup injects the exit/signal cleanup wiring (defaults to the
112
+ * real `process` handlers); a test passes a fake to capture the cleanup fn.
113
+ */
114
+ export function registerServe(cli, adapterFactory = defaultAdapterFactory, serverFactory = defaultServerFactory, registerCleanup = defaultCleanupRegistrar) {
115
+ cli.command('serve', {
116
+ description: 'Export a cart and serve it locally so you can play it in a browser. Requires PICO-8.',
117
+ args: z.object({
118
+ cart: z
119
+ .string()
120
+ .default('main.p8')
121
+ .describe('The .p8 cart to export and serve. Defaults to main.p8 in the current folder.'),
122
+ }),
123
+ options: z.object({
124
+ port: z
125
+ .number()
126
+ .int()
127
+ .min(0)
128
+ .max(65535)
129
+ .default(DEFAULT_PORT)
130
+ .describe('Port to serve on (0 = an OS-assigned free port).'),
131
+ backstopMs: z
132
+ .number()
133
+ .int()
134
+ .positive()
135
+ .default(DEFAULT_BACKSTOP_MS)
136
+ .describe('Safety backstop for the export step.'),
137
+ }),
138
+ env: z.object({
139
+ PICO8_PATH: z
140
+ .string()
141
+ .optional()
142
+ .describe('Explicit path to the PICO-8 binary (else `pico8` on PATH).'),
143
+ PATH: z.string().optional().describe('Used to locate `pico8`.'),
144
+ }),
145
+ output: z.object({
146
+ url: z.string().describe('The local URL to open in a browser.'),
147
+ port: z.number().describe('The port the server bound to.'),
148
+ serveDir: z
149
+ .string()
150
+ .describe('The temp dir the export bundle was served from.'),
151
+ }),
152
+ examples: [
153
+ { description: 'Export and serve main.p8' },
154
+ { description: 'Serve a specific cart', args: { cart: 'game.p8' } },
155
+ {
156
+ description: 'Serve on a chosen port',
157
+ args: { cart: 'game.p8' },
158
+ options: { port: 8080 },
159
+ },
160
+ ],
161
+ async run({ args, options, env, error, ok }) {
162
+ const cartPath = isAbsolute(args.cart)
163
+ ? args.cart
164
+ : resolve(process.cwd(), args.cart);
165
+ if (!existsSync(cartPath)) {
166
+ return error({
167
+ code: 'cart-not-found',
168
+ message: `no cart at ${cartPath}`,
169
+ exitCode: 1,
170
+ });
171
+ }
172
+ // Always export first, into an isolated temp dir (never a user path or
173
+ // ~/Desktop). The bundle is the standalone pair the static server plays.
174
+ const serveDir = mkdtempSync(join(tmpdir(), 'picopilot-serve-'));
175
+ const adapter = adapterFactory(env);
176
+ const result = await adapter.export({
177
+ cartPath,
178
+ outDir: serveDir,
179
+ htmlName: EXPORT_HTML_NAME,
180
+ backstopMs: options.backstopMs,
181
+ });
182
+ if (!result.ok) {
183
+ // Nothing is being served: remove the temp dir now, not on exit.
184
+ removeDir(serveDir);
185
+ return error({
186
+ code: result.reason,
187
+ message: `PICO-8 is not installed. ${result.remedy} (needs: ${result.needs.join(', ')})`,
188
+ exitCode: 1,
189
+ });
190
+ }
191
+ if (result.value.jsPath === undefined) {
192
+ removeDir(serveDir);
193
+ return error({
194
+ code: 'export-failed',
195
+ message: result.value.labelWarning
196
+ ? 'PICO-8 produced no bundle (the cart has no __label__). Capture a label first, then re-serve.'
197
+ : 'PICO-8 produced no export bundle. Check the cart runs without a boot error.',
198
+ exitCode: 1,
199
+ });
200
+ }
201
+ const { server, port } = await serverFactory(serveDir, options.port);
202
+ const url = `http://127.0.0.1:${port}/`;
203
+ // The server keeps the process alive until interrupted; the temp export dir
204
+ // is reaped on SIGINT/SIGTERM/exit so a long-lived serve leaves no leak.
205
+ // `server` is referenced so its lifetime is the command's; a test's fake
206
+ // resolves without a real socket.
207
+ registerCleanup(() => removeDir(serveDir));
208
+ void server;
209
+ return ok({ url, port, serveDir });
210
+ },
211
+ });
212
+ }
213
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { Cli } from 'incur';
2
+ /**
3
+ * Registers the `sfx` command group (`sfx from-mml`) on the root CLI.
4
+ *
5
+ * `sfx` is a shrinko-FREE and PICO-8-FREE command group: it transpiles
6
+ * picopilot-MML to `__sfx__` and merges it through the cart model entirely in
7
+ * TS, so it works with both absent and never spawns a child process. The
8
+ * picopilot-MML dialect (ADR-0005 + the audio spike finding) is TRACKER-ROW
9
+ * based: durations are row counts at one SFX speed, NOT tempo (ADR-0008), and
10
+ * out-of-range pitch / >32 rows are structured refusals, never silent clamps.
11
+ */
12
+ export declare function registerSfx(cli: Cli.Cli): void;
13
+ //# sourceMappingURL=sfx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sfx.d.ts","sourceRoot":"","sources":["../../src/commands/sfx.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,GAAG,EAAI,MAAM,OAAO,CAAC;AA6R7B;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,IAAI,CAE9C"}