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,324 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, statSync, } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { EXPORT_HTML_NAME, pico8NotFound, RECORD_WAV_BASENAME, } from './adapter.js';
6
+ import { SentinelWatcher } from './sentinel.js';
7
+ /**
8
+ * The default {@link SpawnRunner}: `spawn` with stdin detached (`ignore`) so the
9
+ * cart never blocks on console input, stdout/stderr piped for the sentinel
10
+ * watch. A spawn failure surfaces via `onError` (ENOENT ⇒ absent). `kill` sends
11
+ * SIGKILL to the whole tree (`detached` + negative pid) so a hung PICO-8 dies.
12
+ */
13
+ /**
14
+ * An isolated directory to hand PICO-8 as its `-home`, so it writes its config /
15
+ * data tree (`config.txt`, `log.txt`, `sdl_controllers.txt`, `backup/`, `carts/`,
16
+ * `bbs/`, `cdata/`, `cstore/`, `plates/`) THERE and never litters the caller's
17
+ * CWD. Without `-home`, PICO-8 creates that tree in whatever directory it was
18
+ * spawned from, e.g. a user's repo root (see
19
+ * work/notes/observations/pico8-appimage-writes-config-into-cwd.md). Verified:
20
+ * `-home <dir>` fully redirects every one of those files/dirs.
21
+ *
22
+ * One dir per process, created lazily under the OS temp dir and reused by every
23
+ * launch (run/drive/export/record/session). It is throwaway config, so OS temp
24
+ * cleanup reclaims it; picopilot never needs its contents.
25
+ */
26
+ let cachedHome;
27
+ export function pico8HomeDir() {
28
+ if (cachedHome !== undefined && existsSync(cachedHome))
29
+ return cachedHome;
30
+ cachedHome = mkdtempSync(join(tmpdir(), 'picopilot-pico8-home-'));
31
+ return cachedHome;
32
+ }
33
+ /**
34
+ * Prepend `-home <isolatedDir>` to a PICO-8 arg list so its config/data never
35
+ * land in the CWD. Applied at every spawn site.
36
+ */
37
+ export function withPico8Home(args) {
38
+ const home = pico8HomeDir();
39
+ mkdirSync(home, { recursive: true });
40
+ return ['-home', home, ...args];
41
+ }
42
+ export const spawnRunner = (file, args, env, options) => {
43
+ // `drive` pipes fixed-size command blocks to stdin; `run`/`record` detach it so
44
+ // the cart never blocks on console input.
45
+ const stdin = options?.stdin === true ? 'pipe' : 'ignore';
46
+ const child = spawn(file, args, {
47
+ env,
48
+ stdio: [stdin, 'pipe', 'pipe'],
49
+ detached: true,
50
+ });
51
+ child.stdout?.setEncoding('utf8');
52
+ child.stderr?.setEncoding('utf8');
53
+ return {
54
+ onStdout(cb) {
55
+ child.stdout?.on('data', cb);
56
+ child.stderr?.on('data', cb);
57
+ },
58
+ onClose(cb) {
59
+ child.on('close', cb);
60
+ },
61
+ onError(cb) {
62
+ child.on('error', cb);
63
+ },
64
+ writeStdin(bytes) {
65
+ try {
66
+ child.stdin?.write(Buffer.from(bytes));
67
+ }
68
+ catch {
69
+ // A closed stdin (the cart exited) is not fatal to the run.
70
+ }
71
+ },
72
+ kill() {
73
+ try {
74
+ // Negative pid kills the process GROUP (detached), so any child dies too.
75
+ if (child.pid !== undefined)
76
+ process.kill(-child.pid, 'SIGKILL');
77
+ }
78
+ catch {
79
+ child.kill('SIGKILL'); // fall back to killing just the process
80
+ }
81
+ },
82
+ };
83
+ };
84
+ /** The candidate ways to launch PICO-8, tried in order: `PICO8_PATH`, then `pico8` on `PATH`. */
85
+ export function pico8Candidates(env) {
86
+ const fromEnv = env.PICO8_PATH?.trim();
87
+ return fromEnv !== undefined && fromEnv.length > 0
88
+ ? [fromEnv, 'pico8']
89
+ : ['pico8'];
90
+ }
91
+ /**
92
+ * The files an export produced in `outDir`, absolute + sorted. PICO-8 writes the
93
+ * `<name>.html` shell and the `<name>.js` runtime payload; we return every file
94
+ * present so the report is faithful even if a future PICO-8 adds siblings.
95
+ */
96
+ function collectExportFiles(outDir) {
97
+ if (!existsSync(outDir))
98
+ return [];
99
+ return readdirSync(outDir)
100
+ .sort()
101
+ .map((f) => join(outDir, f))
102
+ .filter((p) => {
103
+ try {
104
+ return statSync(p).isFile();
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ });
110
+ }
111
+ /** PNG files a run produced in its `-desktop` dir, sorted for deterministic order. */
112
+ function collectScreenshots(shotDir) {
113
+ if (!existsSync(shotDir))
114
+ return [];
115
+ return readdirSync(shotDir)
116
+ .filter((f) => f.toLowerCase().endsWith('.png'))
117
+ .sort()
118
+ .map((f) => join(shotDir, f));
119
+ }
120
+ /**
121
+ * The WAV a record run produced in its `wavDir`, or `undefined`. Prefers the
122
+ * expected `<basename>.wav` (what the harness named via `set_filename`); falls
123
+ * back to any single `.wav` present. A well-formed but EMPTY WAV (a headless
124
+ * `-x` capture yields a 44-byte 0-frame RIFF header, ADR-0009) is treated as
125
+ * "no usable WAV" so a silent capture is not reported as a real recording.
126
+ */
127
+ function collectWav(wavDir, basename) {
128
+ if (!existsSync(wavDir))
129
+ return undefined;
130
+ const wavs = readdirSync(wavDir).filter((f) => f.toLowerCase().endsWith('.wav'));
131
+ if (wavs.length === 0)
132
+ return undefined;
133
+ const expected = `${basename}.wav`;
134
+ const chosen = wavs.includes(expected) ? expected : wavs.sort()[0];
135
+ const path = join(wavDir, chosen);
136
+ // A 44-byte RIFF header with no PCM data is the empty/silent (headless) WAV.
137
+ try {
138
+ if (statSync(path).size <= 44)
139
+ return undefined;
140
+ }
141
+ catch {
142
+ return undefined;
143
+ }
144
+ return path;
145
+ }
146
+ /**
147
+ * The v1 {@link Pico8Adapter}: shells out to the user's native `pico8 -x`
148
+ * (ADR-0006, native path first). It owns the ORCHESTRATION the command should
149
+ * not hand-roll: launch, stream stdout, kill on the done-sentinel, arm the
150
+ * backstop, and collect screenshots + printh + exit reason into one result.
151
+ *
152
+ * PICO-8 absent (the first candidate ENOENTs and there is no fallback) returns
153
+ * the structured {@link pico8NotFound} value, never a throw or a hang. That
154
+ * absent path is the CI-testable one; live runs are a manual/opt-in tier.
155
+ */
156
+ export class ShellPico8Adapter {
157
+ env;
158
+ spawnRun;
159
+ constructor(options) {
160
+ this.env = options.env;
161
+ this.spawnRun = options.spawn ?? spawnRunner;
162
+ }
163
+ run(options) {
164
+ const [file] = pico8Candidates(this.env);
165
+ const args = [
166
+ '-desktop',
167
+ options.shotDir,
168
+ '-x',
169
+ options.cartPath,
170
+ // One-shot scripted input (the cart reads it via stat(6)); omitted if absent.
171
+ ...(options.input !== undefined ? ['-p', options.input] : []),
172
+ ];
173
+ return this.orchestrate(file, args, options, (exitReason, printh) => ({
174
+ ok: true,
175
+ value: {
176
+ screenshots: collectScreenshots(options.shotDir),
177
+ printh,
178
+ exitReason,
179
+ },
180
+ }));
181
+ }
182
+ /**
183
+ * Records a running cart's audio to a WAV (ADR-0009). Launches PICO-8 in a
184
+ * REAL A/V session (`-run`, NOT the headless `-x` that mixes no audio) and
185
+ * controls the WAV location with `-root_path <wavDir>`, because
186
+ * `extcmd("audio_end", 1)` saves to PICO-8's CURRENT folder, not `-desktop`
187
+ * (the `audio_end(1)` trap). The cooperating cart (a harness for `record`, or
188
+ * the whole render harness) does the `audio_rec`/`audio_end`/sentinel dance; we
189
+ * watch the sentinel, arm the backstop, and collect `<basename>.wav` from the
190
+ * isolated `wavDir`. Live capture is a manual/opt-in tier; the absent path is
191
+ * the CI-testable one.
192
+ */
193
+ /**
194
+ * Drives a throwaway cart through scripted input and captures live gameplay
195
+ * (ADR-0011). Launches `-desktop <shotDir> -x <cart>` with a LIVE stdin, writes
196
+ * the whole encoded FIXED-SIZE command block stream up front (the one-shot; the
197
+ * cart drains one block per frame and screenshots at the SHOT points), watches
198
+ * the sentinel, arms the backstop, and collects the SHOT PNGs + printh + exit
199
+ * reason. Returns {@link pico8NotFound} when PICO-8 is absent. Live capture is a
200
+ * manual/opt-in tier; the absent path is the CI-testable one.
201
+ */
202
+ drive(options) {
203
+ const [file] = pico8Candidates(this.env);
204
+ const args = ['-desktop', options.shotDir, '-x', options.cartPath];
205
+ return this.orchestrate(file, args, { sentinel: options.sentinel, backstopMs: options.backstopMs }, (exitReason, printh) => ({
206
+ ok: true,
207
+ value: {
208
+ screenshots: collectScreenshots(options.shotDir),
209
+ printh,
210
+ exitReason,
211
+ },
212
+ }), { stdin: true, onSpawn: (proc) => proc.writeStdin?.(options.blocks) });
213
+ }
214
+ /**
215
+ * Exports the cart as a PICO-8 HTML bundle over the HEADLESS `-x` path (no
216
+ * display needed: `pico8 <cart> -export <outDir>/<name> -x` runs the cart, writes
217
+ * `<name>.html` + `<name>.js`, and exits). PICO-8 derives both file names from
218
+ * the `.html` name, so passing `index.html` yields a directly-serveable pair.
219
+ * A labelless cart prints "please capture a label first" and still exports (a
220
+ * blank splash), surfaced as `labelWarning`. PICO-8 absent -> {@link pico8NotFound}.
221
+ */
222
+ export(options) {
223
+ const [file] = pico8Candidates(this.env);
224
+ const htmlName = options.htmlName ?? EXPORT_HTML_NAME;
225
+ const outPath = join(options.outDir, htmlName);
226
+ // `-export <name>.html` is a load-and-export command; `-x` makes it headless
227
+ // (run the cart, perform the export, exit) so no window/display is needed.
228
+ const args = ['-export', outPath, '-x', options.cartPath];
229
+ return this.orchestrate(file, args,
230
+ // The export has no cooperative done-sentinel; `-x` exits on its own when
231
+ // finished, and the backstop is the net for a cart that hangs at boot.
232
+ { backstopMs: options.backstopMs }, (_exitReason, printh) => {
233
+ const files = collectExportFiles(options.outDir);
234
+ const htmlPath = files.find((p) => p.endsWith('.html'));
235
+ const jsPath = files.find((p) => p.endsWith('.js'));
236
+ return {
237
+ ok: true,
238
+ value: {
239
+ htmlPath,
240
+ jsPath,
241
+ files,
242
+ // PICO-8 warns when the cart has no `__label__` to embed as the splash.
243
+ labelWarning: /please capture a label first/i.test(printh),
244
+ },
245
+ };
246
+ });
247
+ }
248
+ record(options) {
249
+ const [file] = pico8Candidates(this.env);
250
+ const basename = options.wavBasename ?? RECORD_WAV_BASENAME;
251
+ // `-root_path <wavDir>` steers the `audio_end(1)`-to-current-folder WAV into
252
+ // the isolated temp dir (never ~/Desktop or the carts root). `-run` is a real
253
+ // A/V session (recording needs audio to capture; `-x` yields an empty WAV).
254
+ const args = ['-root_path', options.wavDir, '-run', options.cartPath];
255
+ return this.orchestrate(file, args, { sentinel: options.sentinel, backstopMs: options.backstopMs }, (exitReason, printh) => ({
256
+ ok: true,
257
+ value: {
258
+ wavPath: collectWav(options.wavDir, basename),
259
+ printh,
260
+ exitReason,
261
+ },
262
+ }));
263
+ }
264
+ /**
265
+ * The shared launch/sentinel-watch/backstop/collect loop behind both
266
+ * {@link run} and {@link record}. `buildOk` turns the exit reason + captured
267
+ * stdout into the command-specific success value; a spawn ENOENT with no
268
+ * fallback resolves the structured {@link pico8NotFound}. `printh` for `run` is
269
+ * filled by the caller from the run report path (kept out of `buildOk` there so
270
+ * the two report shapes stay distinct).
271
+ */
272
+ orchestrate(file, args, opts, buildOk, spawnOpts) {
273
+ const watcher = new SentinelWatcher(opts.sentinel);
274
+ return new Promise((resolve) => {
275
+ let settled = false;
276
+ let backstop;
277
+ const proc = this.spawnRun(file, withPico8Home(args), this.env, {
278
+ stdin: spawnOpts?.stdin,
279
+ });
280
+ const finish = (exitReason) => {
281
+ if (settled)
282
+ return;
283
+ settled = true;
284
+ if (backstop !== undefined)
285
+ clearTimeout(backstop);
286
+ resolve(buildOk(exitReason, watcher.text));
287
+ };
288
+ proc.onError((err) => {
289
+ // A spawn ENOENT ⇒ PICO-8 is absent (a bad PICO8_PATH surfaces as absence
290
+ // with the same remedy, not a silent fall-through). Any other spawn error
291
+ // is a failed run, not a crash.
292
+ if (settled)
293
+ return;
294
+ if (err.code === 'ENOENT') {
295
+ settled = true;
296
+ if (backstop !== undefined)
297
+ clearTimeout(backstop);
298
+ resolve(pico8NotFound());
299
+ }
300
+ else {
301
+ finish('exit');
302
+ }
303
+ });
304
+ proc.onStdout((chunk) => {
305
+ if (watcher.push(chunk) && !settled) {
306
+ proc.kill(); // sentinel matched: end the run promptly
307
+ finish('sentinel');
308
+ }
309
+ });
310
+ proc.onClose(() => finish('exit')); // PICO-8 exited on its own
311
+ // For `drive`: write the command blocks to the live stdin now that it is
312
+ // spawned. A spawn ENOENT resolves via onError before this matters.
313
+ spawnOpts?.onSpawn?.(proc);
314
+ // Backstop: a cart that neither signals nor exits gets killed.
315
+ backstop = setTimeout(() => {
316
+ if (settled)
317
+ return;
318
+ proc.kill();
319
+ finish('timeout');
320
+ }, opts.backstopMs);
321
+ });
322
+ }
323
+ }
324
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/engine/pico8/shell.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The playtest SESSION SUPERVISOR (ADR-0011, prd US #6): the daemon that keeps a
3
+ * driven PICO-8 process ALIVE + PAUSED across the agent's separate CLI turns, and
4
+ * the client the per-verb sub-invocations use to address it BY ID.
5
+ *
6
+ * The problem this solves: `playtest step <id>` / `input <id>` / `shot <id>` are
7
+ * SEPARATE process invocations, but the driven PICO-8 must survive between them
8
+ * (staying alive + paused). A one-shot CLI process cannot hold another process's
9
+ * pipe, so `start` spawns a small DETACHED daemon that owns the live process + its
10
+ * stdin/stdout (via {@link DriveSession}) and listens on a per-session Unix domain
11
+ * socket; each later verb CONNECTS to that socket, sends its request, and gets the
12
+ * structured result back. The session is addressed by an id = the socket + a
13
+ * session dir under a controlled temp base (never `~/Desktop`, never the carts
14
+ * root).
15
+ *
16
+ * Lifecycle robustness (US #6 acceptance):
17
+ * - ORPHAN reaping: the daemon self-reaps after an IDLE timeout (no verb within
18
+ * the window), so a session whose agent never `stop`s it does not leak a live
19
+ * PICO-8 forever. `stop <id>` tears it down promptly.
20
+ * - PICO-8 dying mid-session surfaces as a STRUCTURED error (via
21
+ * {@link SessionError}), not a hang; the next verb reports `process-dead`.
22
+ * - `start` with PICO-8 ABSENT returns the same structured `pico8-not-found`
23
+ * boundary as `run`/`audio render`.
24
+ *
25
+ * ONE session per id (the id IS the socket). Multiple concurrent sessions ARE
26
+ * allowed (distinct ids/dirs/daemons); the tool does not serialize them, an agent
27
+ * that wants isolation uses distinct ids (the default id is random, so parallel
28
+ * `start`s never collide).
29
+ *
30
+ * The pure, CI-testable pieces (the id/path registry, the request/response codec)
31
+ * live here alongside the IO daemon; the live daemon end-to-end is the
32
+ * manual/opt-in tier. The handshake core it drives ({@link DriveSession}) is
33
+ * separately unit-tested against a fake process.
34
+ */
35
+ /** The controlled base dir all session dirs live under (never `~/Desktop`). */
36
+ export declare const SESSION_BASE_DIRNAME: "picopilot-playtest-sessions";
37
+ /**
38
+ * The default IDLE-reap window: a daemon with no verb for this long tears its
39
+ * session (and the live PICO-8) down, so an orphaned session (the agent walked
40
+ * away without `stop`) cannot leak a process indefinitely. Every verb resets it.
41
+ */
42
+ export declare const DEFAULT_IDLE_TIMEOUT_MS: number;
43
+ /** The session verbs a client can send the daemon over the socket. */
44
+ export type SessionVerb = 'input' | 'step' | 'shot' | 'stop' | 'status';
45
+ /** A request sent from a verb sub-invocation to the session daemon. */
46
+ export type SessionRequest = {
47
+ readonly verb: 'input';
48
+ readonly held: number;
49
+ } | {
50
+ readonly verb: 'step';
51
+ readonly frames: number;
52
+ } | {
53
+ readonly verb: 'shot';
54
+ } | {
55
+ readonly verb: 'stop';
56
+ } | {
57
+ readonly verb: 'status';
58
+ };
59
+ /** A structured daemon response: either an ok payload or a structured error. */
60
+ export type SessionResponse = {
61
+ readonly ok: true;
62
+ readonly value: SessionResponseValue;
63
+ } | {
64
+ readonly ok: false;
65
+ readonly code: string;
66
+ readonly message: string;
67
+ };
68
+ /** The ok payload shape (a superset; each verb fills the relevant fields). */
69
+ export interface SessionResponseValue {
70
+ /** The session id. */
71
+ readonly id: string;
72
+ /** Total frames advanced so far. */
73
+ readonly frame: number;
74
+ /** The ACK line the cart printed for this command (settled-frame proof). */
75
+ readonly ack?: string;
76
+ /** For `shot`: the absolute screenshot path the cart just wrote. */
77
+ readonly screenshot?: string;
78
+ /** For `shot`: the basename the cart named the capture. */
79
+ readonly shotName?: string;
80
+ /** Whether the session is still alive after this verb. */
81
+ readonly alive: boolean;
82
+ }
83
+ /**
84
+ * Encodes a request/response as a single newline-terminated JSON frame (the
85
+ * socket protocol). Newline-framed so a reader can split a stream into whole
86
+ * messages; kept pure so the codec is unit-tested without a socket.
87
+ */
88
+ export declare function encodeFrame(msg: SessionRequest | SessionResponse): string;
89
+ /**
90
+ * A stateful frame decoder mirroring {@link encodeFrame}: feed it raw socket
91
+ * chunks, it yields whole parsed messages (buffering partial lines). Typed loose
92
+ * (`T`) because a client decodes responses and the daemon decodes requests.
93
+ */
94
+ export declare class FrameDecoder<T> {
95
+ private buffer;
96
+ push(chunk: string): T[];
97
+ }
98
+ /** A resolved session's on-disk layout (all under the controlled base). */
99
+ export interface SessionPaths {
100
+ /** The session id (also the dir name under the base). */
101
+ readonly id: string;
102
+ /** The session dir: `<base>/<id>`. */
103
+ readonly dir: string;
104
+ /** The Unix domain socket the daemon listens on. */
105
+ readonly socket: string;
106
+ /** The `-desktop` dir PICO-8 writes SHOT screenshots into. */
107
+ readonly shotDir: string;
108
+ /** The pidfile written by the daemon (for reap/liveness checks). */
109
+ readonly pidfile: string;
110
+ }
111
+ /**
112
+ * The session REGISTRY: the pure id/path logic mapping a session id to its
113
+ * on-disk layout under a controlled base dir. No IO beyond the base-dir join, so
114
+ * the id validation + path derivation are unit-tested. The daemon and the client
115
+ * both resolve a session THROUGH this, so they agree on where the socket is.
116
+ */
117
+ export declare class SessionRegistry {
118
+ readonly base: string;
119
+ constructor(base?: string);
120
+ /** A fresh random session id (short, filesystem-safe, collision-free enough). */
121
+ static freshId(): string;
122
+ /**
123
+ * Validates a caller-supplied id: filesystem-safe, no path traversal, so an id
124
+ * can never escape the controlled base. Returns the id or throws a plain Error
125
+ * (the command maps it to a structured refusal).
126
+ */
127
+ static validateId(id: string): string;
128
+ /** Resolves a session id to its {@link SessionPaths} under the base. */
129
+ resolve(id: string): SessionPaths;
130
+ /** Creates the session dir + shot dir (idempotent), returning the paths. */
131
+ create(id: string): SessionPaths;
132
+ /** The ids of all sessions with a dir present under the base (for listing/reap). */
133
+ list(): string[];
134
+ /** Removes a session's whole dir (the reap/cleanup, after the daemon exits). */
135
+ remove(id: string): void;
136
+ }
137
+ //# sourceMappingURL=supervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../../src/engine/pico8/supervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * The playtest SESSION SUPERVISOR (ADR-0011, prd US #6): the daemon that keeps a
3
+ * driven PICO-8 process ALIVE + PAUSED across the agent's separate CLI turns, and
4
+ * the client the per-verb sub-invocations use to address it BY ID.
5
+ *
6
+ * The problem this solves: `playtest step <id>` / `input <id>` / `shot <id>` are
7
+ * SEPARATE process invocations, but the driven PICO-8 must survive between them
8
+ * (staying alive + paused). A one-shot CLI process cannot hold another process's
9
+ * pipe, so `start` spawns a small DETACHED daemon that owns the live process + its
10
+ * stdin/stdout (via {@link DriveSession}) and listens on a per-session Unix domain
11
+ * socket; each later verb CONNECTS to that socket, sends its request, and gets the
12
+ * structured result back. The session is addressed by an id = the socket + a
13
+ * session dir under a controlled temp base (never `~/Desktop`, never the carts
14
+ * root).
15
+ *
16
+ * Lifecycle robustness (US #6 acceptance):
17
+ * - ORPHAN reaping: the daemon self-reaps after an IDLE timeout (no verb within
18
+ * the window), so a session whose agent never `stop`s it does not leak a live
19
+ * PICO-8 forever. `stop <id>` tears it down promptly.
20
+ * - PICO-8 dying mid-session surfaces as a STRUCTURED error (via
21
+ * {@link SessionError}), not a hang; the next verb reports `process-dead`.
22
+ * - `start` with PICO-8 ABSENT returns the same structured `pico8-not-found`
23
+ * boundary as `run`/`audio render`.
24
+ *
25
+ * ONE session per id (the id IS the socket). Multiple concurrent sessions ARE
26
+ * allowed (distinct ids/dirs/daemons); the tool does not serialize them, an agent
27
+ * that wants isolation uses distinct ids (the default id is random, so parallel
28
+ * `start`s never collide).
29
+ *
30
+ * The pure, CI-testable pieces (the id/path registry, the request/response codec)
31
+ * live here alongside the IO daemon; the live daemon end-to-end is the
32
+ * manual/opt-in tier. The handshake core it drives ({@link DriveSession}) is
33
+ * separately unit-tested against a fake process.
34
+ */
35
+ import { randomBytes } from 'node:crypto';
36
+ import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
37
+ import { tmpdir } from 'node:os';
38
+ import { join } from 'node:path';
39
+ /** The controlled base dir all session dirs live under (never `~/Desktop`). */
40
+ export const SESSION_BASE_DIRNAME = 'picopilot-playtest-sessions';
41
+ /**
42
+ * The default IDLE-reap window: a daemon with no verb for this long tears its
43
+ * session (and the live PICO-8) down, so an orphaned session (the agent walked
44
+ * away without `stop`) cannot leak a process indefinitely. Every verb resets it.
45
+ */
46
+ export const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60_000;
47
+ /**
48
+ * Encodes a request/response as a single newline-terminated JSON frame (the
49
+ * socket protocol). Newline-framed so a reader can split a stream into whole
50
+ * messages; kept pure so the codec is unit-tested without a socket.
51
+ */
52
+ export function encodeFrame(msg) {
53
+ return `${JSON.stringify(msg)}\n`;
54
+ }
55
+ /**
56
+ * A stateful frame decoder mirroring {@link encodeFrame}: feed it raw socket
57
+ * chunks, it yields whole parsed messages (buffering partial lines). Typed loose
58
+ * (`T`) because a client decodes responses and the daemon decodes requests.
59
+ */
60
+ export class FrameDecoder {
61
+ buffer = '';
62
+ push(chunk) {
63
+ this.buffer += chunk;
64
+ const out = [];
65
+ let nl;
66
+ while ((nl = this.buffer.indexOf('\n')) !== -1) {
67
+ const line = this.buffer.slice(0, nl);
68
+ this.buffer = this.buffer.slice(nl + 1);
69
+ if (line.trim().length > 0)
70
+ out.push(JSON.parse(line));
71
+ }
72
+ return out;
73
+ }
74
+ }
75
+ /**
76
+ * The session REGISTRY: the pure id/path logic mapping a session id to its
77
+ * on-disk layout under a controlled base dir. No IO beyond the base-dir join, so
78
+ * the id validation + path derivation are unit-tested. The daemon and the client
79
+ * both resolve a session THROUGH this, so they agree on where the socket is.
80
+ */
81
+ export class SessionRegistry {
82
+ base;
83
+ constructor(base) {
84
+ this.base = base ?? join(tmpdir(), SESSION_BASE_DIRNAME);
85
+ }
86
+ /** A fresh random session id (short, filesystem-safe, collision-free enough). */
87
+ static freshId() {
88
+ return `s-${randomBytes(6).toString('hex')}`;
89
+ }
90
+ /**
91
+ * Validates a caller-supplied id: filesystem-safe, no path traversal, so an id
92
+ * can never escape the controlled base. Returns the id or throws a plain Error
93
+ * (the command maps it to a structured refusal).
94
+ */
95
+ static validateId(id) {
96
+ if (!/^[A-Za-z0-9._-]{1,64}$/.test(id) || id === '.' || id === '..') {
97
+ throw new Error(`invalid session id "${id}": use 1-64 chars of [A-Za-z0-9._-]`);
98
+ }
99
+ return id;
100
+ }
101
+ /** Resolves a session id to its {@link SessionPaths} under the base. */
102
+ resolve(id) {
103
+ SessionRegistry.validateId(id);
104
+ const dir = join(this.base, id);
105
+ return {
106
+ id,
107
+ dir,
108
+ socket: join(dir, 'sock'),
109
+ shotDir: join(dir, 'shots'),
110
+ pidfile: join(dir, 'pid'),
111
+ };
112
+ }
113
+ /** Creates the session dir + shot dir (idempotent), returning the paths. */
114
+ create(id) {
115
+ const paths = this.resolve(id);
116
+ mkdirSync(paths.shotDir, { recursive: true });
117
+ return paths;
118
+ }
119
+ /** The ids of all sessions with a dir present under the base (for listing/reap). */
120
+ list() {
121
+ if (!existsSync(this.base))
122
+ return [];
123
+ return readdirSync(this.base)
124
+ .filter((name) => {
125
+ try {
126
+ return statSync(join(this.base, name)).isDirectory();
127
+ }
128
+ catch {
129
+ return false;
130
+ }
131
+ })
132
+ .sort();
133
+ }
134
+ /** Removes a session's whole dir (the reap/cleanup, after the daemon exits). */
135
+ remove(id) {
136
+ const { dir } = this.resolve(id);
137
+ rmSync(dir, { recursive: true, force: true });
138
+ }
139
+ }
140
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/engine/pico8/supervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The static content `picopilot init` scaffolds: the `#include`-discipline
3
+ * `main.p8` (built through the cart model), the editable `main.lua`, the curated
4
+ * `AGENTS.md` PICO-8 reference, and the `picopilot.json` config. See
5
+ * `scaffold.ts` for the single scaffolding model and why these are the files.
6
+ */
7
+ export { agentsMd, LUA_INCLUDE_LINE, mainLua, mainP8, picopilotJson, type ScaffoldFile, scaffoldFiles, } from './scaffold.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/scaffold/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACN,QAAQ,EACR,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,aAAa,EACb,KAAK,YAAY,EACjB,aAAa,GACb,MAAM,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The static content `picopilot init` scaffolds: the `#include`-discipline
3
+ * `main.p8` (built through the cart model), the editable `main.lua`, the curated
4
+ * `AGENTS.md` PICO-8 reference, and the `picopilot.json` config. See
5
+ * `scaffold.ts` for the single scaffolding model and why these are the files.
6
+ */
7
+ export { agentsMd, LUA_INCLUDE_LINE, mainLua, mainP8, picopilotJson, scaffoldFiles, } from './scaffold.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/scaffold/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACN,QAAQ,EACR,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,aAAa,EAEb,aAAa,GACb,MAAM,eAAe,CAAC"}