@amg555/frameforge-mcp 2.7.6 → 2.7.7

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 (1129) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/package.json +1 -1
  4. package/plugins/video-pipeline/.claude-plugin/plugin.json +1 -1
  5. package/plugins/video-pipeline/skills/.upstream-skills.json +1 -1
  6. package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +1 -1
  7. package/scripts/deep-validate.js +2 -2
  8. package/src/mcp-server/server.js +5 -5
  9. package/src/skills/sync-upstream.js +3 -3
  10. package/.agent/skills/ai-video-editor/README.md +0 -32
  11. package/.agent/skills/ai-video-editor/SKILL.md +0 -152
  12. package/.agent/skills/ai-video-editor/assets/fonts/fonts.css +0 -2409
  13. package/.agent/skills/ai-video-editor/assets/fonts/ttf/anton-400.ttf +0 -0
  14. package/.agent/skills/ai-video-editor/assets/fonts/ttf/archivo-800.ttf +0 -0
  15. package/.agent/skills/ai-video-editor/assets/fonts/ttf/cabin-sketch-700.ttf +0 -0
  16. package/.agent/skills/ai-video-editor/assets/fonts/ttf/dm-sans-700.ttf +0 -0
  17. package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-600.ttf +0 -0
  18. package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-700.ttf +0 -0
  19. package/.agent/skills/ai-video-editor/assets/fonts/ttf/fredoka-700.ttf +0 -0
  20. package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-700.ttf +0 -0
  21. package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-800.ttf +0 -0
  22. package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-tight-800.ttf +0 -0
  23. package/.agent/skills/ai-video-editor/assets/fonts/ttf/kalam-700.ttf +0 -0
  24. package/.agent/skills/ai-video-editor/assets/fonts/ttf/noto-sans-devanagari-700.ttf +0 -0
  25. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-400-normal.woff2 +0 -0
  26. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-500-normal.woff2 +0 -0
  27. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-600-normal.woff2 +0 -0
  28. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-700-normal.woff2 +0 -0
  29. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-800-normal.woff2 +0 -0
  30. package/.agent/skills/ai-video-editor/engine/hidpi.js +0 -117
  31. package/.agent/skills/ai-video-editor/engine/page.html +0 -43
  32. package/.agent/skills/ai-video-editor/engine/player.js +0 -145
  33. package/.agent/skills/ai-video-editor/references/example-story-warm-canvas.js +0 -95
  34. package/.agent/skills/ai-video-editor/references/story-guide.md +0 -112
  35. package/.agent/skills/ai-video-editor/requirements.txt +0 -4
  36. package/.agent/skills/ai-video-editor/styles/chalk-talk/base.js +0 -490
  37. package/.agent/skills/ai-video-editor/styles/chalk-talk/style.json +0 -14
  38. package/.agent/skills/ai-video-editor/styles/chalk-talk/style.md +0 -41
  39. package/.agent/skills/ai-video-editor/styles/clean-tech/base.js +0 -447
  40. package/.agent/skills/ai-video-editor/styles/clean-tech/style.json +0 -15
  41. package/.agent/skills/ai-video-editor/styles/clean-tech/style.md +0 -38
  42. package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/base.js +0 -698
  43. package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.json +0 -16
  44. package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.md +0 -39
  45. package/.agent/skills/ai-video-editor/styles/data-story/base.js +0 -432
  46. package/.agent/skills/ai-video-editor/styles/data-story/style.json +0 -14
  47. package/.agent/skills/ai-video-editor/styles/data-story/style.md +0 -38
  48. package/.agent/skills/ai-video-editor/styles/index.json +0 -103
  49. package/.agent/skills/ai-video-editor/styles/pop-info/base.js +0 -570
  50. package/.agent/skills/ai-video-editor/styles/pop-info/style.json +0 -13
  51. package/.agent/skills/ai-video-editor/styles/pop-info/style.md +0 -37
  52. package/.agent/skills/ai-video-editor/styles/software-walkthrough/base.js +0 -544
  53. package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.json +0 -15
  54. package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.md +0 -38
  55. package/.agent/skills/ai-video-editor/styles/warm-canvas/base.js +0 -610
  56. package/.agent/skills/ai-video-editor/styles/warm-canvas/style.json +0 -14
  57. package/.agent/skills/ai-video-editor/styles/warm-canvas/style.md +0 -37
  58. package/.agent/skills/faceless-shorts/README.md +0 -34
  59. package/.agent/skills/faceless-shorts/SKILL.md +0 -124
  60. package/.agent/skills/faceless-shorts/styles/deep-dive/STYLE.md +0 -58
  61. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/cues.json +0 -140
  62. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/lines.json +0 -67
  63. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.json +0 -614
  64. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.txt +0 -102
  65. package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-Black.otf +0 -0
  66. package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-ExtraBold.otf +0 -0
  67. package/.agent/skills/faceless-shorts/styles/deep-dive/example/index.html +0 -16
  68. package/.agent/skills/faceless-shorts/styles/deep-dive/example/script.json +0 -27
  69. package/.agent/skills/faceless-shorts/styles/deep-dive/example/sheet.html +0 -10
  70. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/board.js +0 -62
  71. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/captions.js +0 -68
  72. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/engine.js +0 -65
  73. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/human.js +0 -128
  74. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/lib.js +0 -44
  75. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/look2d.js +0 -33
  76. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sdf.js +0 -79
  77. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sets.js +0 -107
  78. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/timing.js +0 -23
  79. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/main.js +0 -56
  80. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/sheet.js +0 -79
  81. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/shots.js +0 -136
  82. package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/capture.mjs +0 -122
  83. package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/mix.py +0 -172
  84. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/LICENSE-three.txt +0 -21
  85. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/environments/RoomEnvironment.js +0 -144
  86. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/BokehPass.js +0 -141
  87. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
  88. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/MaskPass.js +0 -104
  89. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/OutputPass.js +0 -97
  90. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/Pass.js +0 -95
  91. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/RenderPass.js +0 -99
  92. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
  93. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
  94. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/BokehShader.js +0 -145
  95. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/CopyShader.js +0 -45
  96. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
  97. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/OutputShader.js +0 -85
  98. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/three.module.js +0 -6
  99. package/.agent/skills/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
  100. package/.agent/skills/faceless-shorts/styles/inside-out/STYLE.md +0 -97
  101. package/.agent/skills/faceless-shorts/styles/inside-out/example/data/cues.json +0 -178
  102. package/.agent/skills/faceless-shorts/styles/inside-out/example/data/lines.json +0 -73
  103. package/.agent/skills/faceless-shorts/styles/inside-out/example/data/words.json +0 -662
  104. package/.agent/skills/faceless-shorts/styles/inside-out/example/fonts/Inter-Black.otf +0 -0
  105. package/.agent/skills/faceless-shorts/styles/inside-out/example/index.html +0 -16
  106. package/.agent/skills/faceless-shorts/styles/inside-out/example/script.json +0 -26
  107. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/captions.js +0 -68
  108. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/kit.js +0 -345
  109. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/lib.js +0 -44
  110. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/timing.js +0 -23
  111. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/main.js +0 -62
  112. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/shots.js +0 -195
  113. package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/capture.mjs +0 -122
  114. package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/mix.py +0 -172
  115. package/.agent/skills/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
  116. package/.agent/skills/faceless-shorts/styles/night-file/STYLE.md +0 -36
  117. package/.agent/skills/faceless-shorts/styles/night-file/example/data/cues.json +0 -146
  118. package/.agent/skills/faceless-shorts/styles/night-file/example/data/lines.json +0 -73
  119. package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.json +0 -572
  120. package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.txt +0 -95
  121. package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/FreeMonoBold.ttf +0 -0
  122. package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/Inter-Black.otf +0 -0
  123. package/.agent/skills/faceless-shorts/styles/night-file/example/index.html +0 -16
  124. package/.agent/skills/faceless-shorts/styles/night-file/example/script.json +0 -26
  125. package/.agent/skills/faceless-shorts/styles/night-file/example/sheet.html +0 -11
  126. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/board.js +0 -62
  127. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/captions.js +0 -49
  128. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/engine.js +0 -65
  129. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/human.js +0 -128
  130. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/kit.js +0 -151
  131. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/lib.js +0 -44
  132. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sdf.js +0 -79
  133. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sets3d.js +0 -106
  134. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/timing.js +0 -23
  135. package/.agent/skills/faceless-shorts/styles/night-file/example/src/main.js +0 -56
  136. package/.agent/skills/faceless-shorts/styles/night-file/example/src/sheet.js +0 -86
  137. package/.agent/skills/faceless-shorts/styles/night-file/example/src/shots.js +0 -130
  138. package/.agent/skills/faceless-shorts/styles/night-file/example/tools/capture.mjs +0 -122
  139. package/.agent/skills/faceless-shorts/styles/night-file/example/tools/mix.py +0 -172
  140. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/LICENSE-three.txt +0 -21
  141. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/environments/RoomEnvironment.js +0 -144
  142. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/BokehPass.js +0 -141
  143. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
  144. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/MaskPass.js +0 -104
  145. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/OutputPass.js +0 -97
  146. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/Pass.js +0 -95
  147. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/RenderPass.js +0 -99
  148. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
  149. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
  150. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/BokehShader.js +0 -145
  151. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/CopyShader.js +0 -45
  152. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
  153. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/OutputShader.js +0 -85
  154. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/three.module.js +0 -6
  155. package/.agent/skills/faceless-shorts/styles/night-file/style-sheet.png +0 -0
  156. package/.agent/skills/faceless-shorts/styles/paper-atlas/STYLE.md +0 -30
  157. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/cues.json +0 -167
  158. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/lines.json +0 -67
  159. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.json +0 -614
  160. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.txt +0 -102
  161. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Caladea-Bold.ttf +0 -0
  162. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Inter-Black.otf +0 -0
  163. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/index.html +0 -13
  164. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/script.json +0 -26
  165. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/sheet.html +0 -10
  166. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/board.js +0 -62
  167. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/captions.js +0 -47
  168. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/kit.js +0 -152
  169. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/lib.js +0 -44
  170. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/timing.js +0 -23
  171. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/main.js +0 -62
  172. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/sheet.js +0 -61
  173. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/shots.js +0 -139
  174. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/capture.mjs +0 -122
  175. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/mix.py +0 -172
  176. package/.agent/skills/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
  177. package/.agent/skills/faceless-shorts/styles/stick-story/STYLE.md +0 -32
  178. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/cues.json +0 -182
  179. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/lines.json +0 -79
  180. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.json +0 -662
  181. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.txt +0 -110
  182. package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-Black.otf +0 -0
  183. package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-ExtraBold.otf +0 -0
  184. package/.agent/skills/faceless-shorts/styles/stick-story/example/index.html +0 -12
  185. package/.agent/skills/faceless-shorts/styles/stick-story/example/script.json +0 -27
  186. package/.agent/skills/faceless-shorts/styles/stick-story/example/sheet.html +0 -9
  187. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/board.js +0 -62
  188. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/captions.js +0 -47
  189. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/kit.js +0 -166
  190. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/lib.js +0 -44
  191. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/timing.js +0 -23
  192. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/main.js +0 -62
  193. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/sheet.js +0 -64
  194. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/shots.js +0 -148
  195. package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/capture.mjs +0 -122
  196. package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/mix.py +0 -172
  197. package/.agent/skills/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
  198. package/.agent/skills/faceless-shorts/styles/tall-tale/STYLE.md +0 -51
  199. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/cues.json +0 -271
  200. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/lines.json +0 -127
  201. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.json +0 -908
  202. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.txt +0 -151
  203. package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Caladea-Bold.ttf +0 -0
  204. package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-Black.otf +0 -0
  205. package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-ExtraBold.otf +0 -0
  206. package/.agent/skills/faceless-shorts/styles/tall-tale/example/index.html +0 -17
  207. package/.agent/skills/faceless-shorts/styles/tall-tale/example/script.json +0 -37
  208. package/.agent/skills/faceless-shorts/styles/tall-tale/example/sheet.html +0 -11
  209. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/board.js +0 -62
  210. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/captions.js +0 -56
  211. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/engine.js +0 -65
  212. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/lib.js +0 -44
  213. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/props.js +0 -166
  214. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/timing.js +0 -23
  215. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/toon.js +0 -352
  216. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/world.js +0 -340
  217. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/main.js +0 -56
  218. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/sheet.js +0 -99
  219. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/shots.js +0 -377
  220. package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/capture.mjs +0 -122
  221. package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/mix.py +0 -204
  222. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/LICENSE-three.txt +0 -21
  223. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/environments/RoomEnvironment.js +0 -144
  224. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/BokehPass.js +0 -141
  225. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
  226. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/MaskPass.js +0 -104
  227. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/OutputPass.js +0 -97
  228. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/Pass.js +0 -95
  229. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/RenderPass.js +0 -99
  230. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
  231. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
  232. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/BokehShader.js +0 -145
  233. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/CopyShader.js +0 -45
  234. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
  235. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/OutputShader.js +0 -85
  236. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/three.module.js +0 -6
  237. package/.agent/skills/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
  238. package/.agent/skills/gstack-autoplan/SKILL.md +0 -972
  239. package/.agent/skills/gstack-autoplan/SKILL.md.tmpl +0 -468
  240. package/.agent/skills/gstack-autoplan/bin/guard-journal.ts +0 -115
  241. package/.agent/skills/gstack-autoplan/bin/guard-log.ts +0 -40
  242. package/.agent/skills/gstack-autoplan/bin/guard-reasons.ts +0 -92
  243. package/.agent/skills/gstack-autoplan/bin/owned-read.ts +0 -72
  244. package/.agent/skills/gstack-autoplan/bin/phase-publication-hook +0 -15
  245. package/.agent/skills/gstack-autoplan/bin/phase-publication-hook.ts +0 -769
  246. package/.agent/skills/gstack-autoplan/sections/ceo-phase.md +0 -195
  247. package/.agent/skills/gstack-autoplan/sections/ceo-phase.md.tmpl +0 -147
  248. package/.agent/skills/gstack-autoplan/sections/design-phase.md +0 -128
  249. package/.agent/skills/gstack-autoplan/sections/design-phase.md.tmpl +0 -82
  250. package/.agent/skills/gstack-autoplan/sections/dx-phase.md +0 -155
  251. package/.agent/skills/gstack-autoplan/sections/dx-phase.md.tmpl +0 -109
  252. package/.agent/skills/gstack-autoplan/sections/eng-phase.md +0 -164
  253. package/.agent/skills/gstack-autoplan/sections/eng-phase.md.tmpl +0 -116
  254. package/.agent/skills/gstack-autoplan/sections/manifest.json +0 -50
  255. package/.agent/skills/gstack-autoplan/sections/phase-close.md +0 -70
  256. package/.agent/skills/gstack-autoplan/sections/phase-close.md.tmpl +0 -68
  257. package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md +0 -71
  258. package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md.tmpl +0 -1
  259. package/.agent/skills/gstack-autoplan/sections/unattended.md +0 -11
  260. package/.agent/skills/gstack-autoplan/sections/unattended.md.tmpl +0 -9
  261. package/.agent/skills/gstack-benchmark/SKILL.md +0 -456
  262. package/.agent/skills/gstack-benchmark/SKILL.md.tmpl +0 -249
  263. package/.agent/skills/gstack-benchmark-models/SKILL.md +0 -285
  264. package/.agent/skills/gstack-benchmark-models/SKILL.md.tmpl +0 -155
  265. package/.agent/skills/gstack-browse/PLAN-snapshot-dropdown-interactive.md +0 -102
  266. package/.agent/skills/gstack-browse/SKILL.md +0 -470
  267. package/.agent/skills/gstack-browse/SKILL.md.tmpl +0 -129
  268. package/.agent/skills/gstack-browse/bin/find-browse +0 -21
  269. package/.agent/skills/gstack-browse/bin/remote-slug +0 -18
  270. package/.agent/skills/gstack-browse/sections/command-list.md +0 -176
  271. package/.agent/skills/gstack-browse/sections/command-list.md.tmpl +0 -7
  272. package/.agent/skills/gstack-browse/sections/manifest.json +0 -14
  273. package/.agent/skills/gstack-browse/src/activity.ts +0 -209
  274. package/.agent/skills/gstack-browse/src/audit.ts +0 -69
  275. package/.agent/skills/gstack-browse/src/browse-client.ts +0 -264
  276. package/.agent/skills/gstack-browse/src/browser-manager.ts +0 -2334
  277. package/.agent/skills/gstack-browse/src/browser-skill-commands.ts +0 -466
  278. package/.agent/skills/gstack-browse/src/browser-skill-write.ts +0 -216
  279. package/.agent/skills/gstack-browse/src/browser-skills.ts +0 -420
  280. package/.agent/skills/gstack-browse/src/buffers.ts +0 -137
  281. package/.agent/skills/gstack-browse/src/bun-child-config.ts +0 -35
  282. package/.agent/skills/gstack-browse/src/bun-polyfill.cjs +0 -276
  283. package/.agent/skills/gstack-browse/src/cdp-allowlist.ts +0 -235
  284. package/.agent/skills/gstack-browse/src/cdp-bridge.ts +0 -180
  285. package/.agent/skills/gstack-browse/src/cdp-commands.ts +0 -64
  286. package/.agent/skills/gstack-browse/src/cdp-inspector.ts +0 -824
  287. package/.agent/skills/gstack-browse/src/chromium-profiles.ts +0 -212
  288. package/.agent/skills/gstack-browse/src/claude-bin.ts +0 -2
  289. package/.agent/skills/gstack-browse/src/cli.ts +0 -2254
  290. package/.agent/skills/gstack-browse/src/commands.ts +0 -296
  291. package/.agent/skills/gstack-browse/src/config.ts +0 -307
  292. package/.agent/skills/gstack-browse/src/content-security.ts +0 -373
  293. package/.agent/skills/gstack-browse/src/cookie-auth-verification.ts +0 -225
  294. package/.agent/skills/gstack-browse/src/cookie-database.ts +0 -73
  295. package/.agent/skills/gstack-browse/src/cookie-import-browser.ts +0 -929
  296. package/.agent/skills/gstack-browse/src/cookie-import-native-integrity.ts +0 -95
  297. package/.agent/skills/gstack-browse/src/cookie-import-native-job.ts +0 -161
  298. package/.agent/skills/gstack-browse/src/cookie-import-native-qualification.json +0 -1
  299. package/.agent/skills/gstack-browse/src/cookie-import-native-worker.ts +0 -268
  300. package/.agent/skills/gstack-browse/src/cookie-import-native.ts +0 -166
  301. package/.agent/skills/gstack-browse/src/cookie-import-operation.ts +0 -232
  302. package/.agent/skills/gstack-browse/src/cookie-picker-routes.ts +0 -343
  303. package/.agent/skills/gstack-browse/src/cookie-picker-ui.ts +0 -717
  304. package/.agent/skills/gstack-browse/src/domain-skill-commands.ts +0 -301
  305. package/.agent/skills/gstack-browse/src/domain-skills.ts +0 -438
  306. package/.agent/skills/gstack-browse/src/error-handling.ts +0 -2
  307. package/.agent/skills/gstack-browse/src/extension-id.ts +0 -25
  308. package/.agent/skills/gstack-browse/src/file-permissions.ts +0 -370
  309. package/.agent/skills/gstack-browse/src/find-browse.ts +0 -118
  310. package/.agent/skills/gstack-browse/src/find-security-sidecar.ts +0 -78
  311. package/.agent/skills/gstack-browse/src/media-extract.ts +0 -177
  312. package/.agent/skills/gstack-browse/src/memory-command.ts +0 -115
  313. package/.agent/skills/gstack-browse/src/memory-snapshot.ts +0 -73
  314. package/.agent/skills/gstack-browse/src/meta-commands.ts +0 -1150
  315. package/.agent/skills/gstack-browse/src/network-capture.ts +0 -179
  316. package/.agent/skills/gstack-browse/src/path-security.ts +0 -132
  317. package/.agent/skills/gstack-browse/src/platform.ts +0 -38
  318. package/.agent/skills/gstack-browse/src/port-allocator.ts +0 -140
  319. package/.agent/skills/gstack-browse/src/project-slug.ts +0 -36
  320. package/.agent/skills/gstack-browse/src/proxy-config.ts +0 -283
  321. package/.agent/skills/gstack-browse/src/proxy-redact.ts +0 -46
  322. package/.agent/skills/gstack-browse/src/pty-session-cookie.ts +0 -64
  323. package/.agent/skills/gstack-browse/src/pty-session-lease.ts +0 -137
  324. package/.agent/skills/gstack-browse/src/read-commands.ts +0 -756
  325. package/.agent/skills/gstack-browse/src/routes/activity.ts +0 -63
  326. package/.agent/skills/gstack-browse/src/routes/commands.ts +0 -154
  327. package/.agent/skills/gstack-browse/src/routes/core.ts +0 -123
  328. package/.agent/skills/gstack-browse/src/routes/files.ts +0 -47
  329. package/.agent/skills/gstack-browse/src/routes/index.ts +0 -27
  330. package/.agent/skills/gstack-browse/src/routes/inspector.ts +0 -132
  331. package/.agent/skills/gstack-browse/src/routes/pairing.ts +0 -158
  332. package/.agent/skills/gstack-browse/src/routes/pty.ts +0 -254
  333. package/.agent/skills/gstack-browse/src/routes/table.ts +0 -182
  334. package/.agent/skills/gstack-browse/src/routes/tokens.ts +0 -86
  335. package/.agent/skills/gstack-browse/src/routes/tunnel.ts +0 -62
  336. package/.agent/skills/gstack-browse/src/sanitize.ts +0 -49
  337. package/.agent/skills/gstack-browse/src/screenshot-size-guard.ts +0 -106
  338. package/.agent/skills/gstack-browse/src/security-classifier.ts +0 -258
  339. package/.agent/skills/gstack-browse/src/security-sidecar-client.ts +0 -234
  340. package/.agent/skills/gstack-browse/src/security-sidecar-entry.ts +0 -120
  341. package/.agent/skills/gstack-browse/src/security.ts +0 -328
  342. package/.agent/skills/gstack-browse/src/server.ts +0 -2220
  343. package/.agent/skills/gstack-browse/src/session-cookie-store.ts +0 -124
  344. package/.agent/skills/gstack-browse/src/session-persist.ts +0 -178
  345. package/.agent/skills/gstack-browse/src/skill-token.ts +0 -91
  346. package/.agent/skills/gstack-browse/src/snapshot-flags.ts +0 -30
  347. package/.agent/skills/gstack-browse/src/snapshot.ts +0 -656
  348. package/.agent/skills/gstack-browse/src/socks-bridge.ts +0 -314
  349. package/.agent/skills/gstack-browse/src/sse-helpers.ts +0 -147
  350. package/.agent/skills/gstack-browse/src/sse-session-cookie.ts +0 -65
  351. package/.agent/skills/gstack-browse/src/stealth.ts +0 -574
  352. package/.agent/skills/gstack-browse/src/tab-session.ts +0 -210
  353. package/.agent/skills/gstack-browse/src/telemetry.ts +0 -115
  354. package/.agent/skills/gstack-browse/src/terminal-agent-control.ts +0 -379
  355. package/.agent/skills/gstack-browse/src/terminal-agent.ts +0 -1124
  356. package/.agent/skills/gstack-browse/src/terminal-pty-lifecycle.ts +0 -144
  357. package/.agent/skills/gstack-browse/src/token-registry.ts +0 -721
  358. package/.agent/skills/gstack-browse/src/tunnel-denial-log.ts +0 -97
  359. package/.agent/skills/gstack-browse/src/url-validation.ts +0 -299
  360. package/.agent/skills/gstack-browse/src/welcome.html +0 -237
  361. package/.agent/skills/gstack-browse/src/write-commands.ts +0 -1438
  362. package/.agent/skills/gstack-browse/src/xprotect-heal.ts +0 -439
  363. package/.agent/skills/gstack-browse/src/xvfb.ts +0 -313
  364. package/.agent/skills/gstack-browse/test/activity.test.ts +0 -120
  365. package/.agent/skills/gstack-browse/test/adversarial-security.test.ts +0 -32
  366. package/.agent/skills/gstack-browse/test/batch.test.ts +0 -267
  367. package/.agent/skills/gstack-browse/test/bridge-chromium-e2e.test.ts +0 -205
  368. package/.agent/skills/gstack-browse/test/browse-client.test.ts +0 -300
  369. package/.agent/skills/gstack-browse/test/browse-eval-wrapping.test.ts +0 -86
  370. package/.agent/skills/gstack-browse/test/browser-manager-custom-chromium.test.ts +0 -67
  371. package/.agent/skills/gstack-browse/test/browser-manager-unit.test.ts +0 -335
  372. package/.agent/skills/gstack-browse/test/browser-skill-commands.test.ts +0 -433
  373. package/.agent/skills/gstack-browse/test/browser-skill-write.test.ts +0 -350
  374. package/.agent/skills/gstack-browse/test/browser-skills-e2e.test.ts +0 -101
  375. package/.agent/skills/gstack-browse/test/browser-skills-storage.test.ts +0 -283
  376. package/.agent/skills/gstack-browse/test/build-command-response.test.ts +0 -68
  377. package/.agent/skills/gstack-browse/test/build.test.ts +0 -52
  378. package/.agent/skills/gstack-browse/test/bun-child-config.test.ts +0 -83
  379. package/.agent/skills/gstack-browse/test/bun-polyfill.test.ts +0 -402
  380. package/.agent/skills/gstack-browse/test/busy-daemon-iron-rule.test.ts +0 -270
  381. package/.agent/skills/gstack-browse/test/busy-daemon-recovery.test.ts +0 -134
  382. package/.agent/skills/gstack-browse/test/cdp-allowlist.test.ts +0 -109
  383. package/.agent/skills/gstack-browse/test/cdp-e2e.test.ts +0 -117
  384. package/.agent/skills/gstack-browse/test/cdp-inspector-history-cap.test.ts +0 -95
  385. package/.agent/skills/gstack-browse/test/cdp-mutex.test.ts +0 -113
  386. package/.agent/skills/gstack-browse/test/cdp-session-cleanup.test.ts +0 -171
  387. package/.agent/skills/gstack-browse/test/chromium-profile-isolation.test.ts +0 -418
  388. package/.agent/skills/gstack-browse/test/claude-bin.test.ts +0 -95
  389. package/.agent/skills/gstack-browse/test/cli-chain-stdin.test.ts +0 -122
  390. package/.agent/skills/gstack-browse/test/cli-config-mismatch-message.test.ts +0 -72
  391. package/.agent/skills/gstack-browse/test/cli-lock.test.ts +0 -120
  392. package/.agent/skills/gstack-browse/test/cli-loopback-no-proxy.test.ts +0 -77
  393. package/.agent/skills/gstack-browse/test/cli-setsid-daemonize.test.ts +0 -75
  394. package/.agent/skills/gstack-browse/test/cli-start-final-healthcheck.test.ts +0 -77
  395. package/.agent/skills/gstack-browse/test/cli-supervisor.test.ts +0 -194
  396. package/.agent/skills/gstack-browse/test/commands.test.ts +0 -2779
  397. package/.agent/skills/gstack-browse/test/compare-board.test.ts +0 -382
  398. package/.agent/skills/gstack-browse/test/config.test.ts +0 -519
  399. package/.agent/skills/gstack-browse/test/content-security.test.ts +0 -681
  400. package/.agent/skills/gstack-browse/test/cookie-auth-verification.test.ts +0 -760
  401. package/.agent/skills/gstack-browse/test/cookie-credential-deadline.test.ts +0 -225
  402. package/.agent/skills/gstack-browse/test/cookie-database.test.ts +0 -277
  403. package/.agent/skills/gstack-browse/test/cookie-fixture-delete-lease.test.ts +0 -256
  404. package/.agent/skills/gstack-browse/test/cookie-import-browser.test.ts +0 -523
  405. package/.agent/skills/gstack-browse/test/cookie-import-native-job.test.ts +0 -1645
  406. package/.agent/skills/gstack-browse/test/cookie-import-native-qualification.ts +0 -128
  407. package/.agent/skills/gstack-browse/test/cookie-import-native.test.ts +0 -436
  408. package/.agent/skills/gstack-browse/test/cookie-import-node.test.ts +0 -166
  409. package/.agent/skills/gstack-browse/test/cookie-import-operation.test.ts +0 -452
  410. package/.agent/skills/gstack-browse/test/cookie-import-reliability.test.ts +0 -659
  411. package/.agent/skills/gstack-browse/test/cookie-import-transport.test.ts +0 -38
  412. package/.agent/skills/gstack-browse/test/cookie-picker-binding-browser.test.ts +0 -96
  413. package/.agent/skills/gstack-browse/test/cookie-picker-binding.test.ts +0 -155
  414. package/.agent/skills/gstack-browse/test/cookie-picker-csrf-browser.test.ts +0 -50
  415. package/.agent/skills/gstack-browse/test/cookie-picker-routes.test.ts +0 -460
  416. package/.agent/skills/gstack-browse/test/cookie-picker-ui.test.ts +0 -535
  417. package/.agent/skills/gstack-browse/test/daemon-log-hygiene.test.ts +0 -135
  418. package/.agent/skills/gstack-browse/test/daemon-mismatch-refuse.test.ts +0 -178
  419. package/.agent/skills/gstack-browse/test/data-platform.test.ts +0 -176
  420. package/.agent/skills/gstack-browse/test/dia-gui-readiness.test.ts +0 -174
  421. package/.agent/skills/gstack-browse/test/dia-launch-comparison.test.ts +0 -180
  422. package/.agent/skills/gstack-browse/test/dia-macos-qualification.test.ts +0 -1695
  423. package/.agent/skills/gstack-browse/test/domain-skills-e2e.test.ts +0 -142
  424. package/.agent/skills/gstack-browse/test/domain-skills-storage.test.ts +0 -263
  425. package/.agent/skills/gstack-browse/test/dotenv-isolation.test.ts +0 -121
  426. package/.agent/skills/gstack-browse/test/dual-listener.test.ts +0 -411
  427. package/.agent/skills/gstack-browse/test/dx-polish.test.ts +0 -101
  428. package/.agent/skills/gstack-browse/test/error-handling.test.ts +0 -47
  429. package/.agent/skills/gstack-browse/test/extension-sender-auth.test.ts +0 -285
  430. package/.agent/skills/gstack-browse/test/extension-token.test.ts +0 -232
  431. package/.agent/skills/gstack-browse/test/file-drop.test.ts +0 -271
  432. package/.agent/skills/gstack-browse/test/file-permissions.test.ts +0 -346
  433. package/.agent/skills/gstack-browse/test/fill-change-event.test.ts +0 -78
  434. package/.agent/skills/gstack-browse/test/find-browse.test.ts +0 -61
  435. package/.agent/skills/gstack-browse/test/findport.test.ts +0 -232
  436. package/.agent/skills/gstack-browse/test/fixtures/basic.html +0 -33
  437. package/.agent/skills/gstack-browse/test/fixtures/change-only-validator.html +0 -31
  438. package/.agent/skills/gstack-browse/test/fixtures/cursor-interactive.html +0 -22
  439. package/.agent/skills/gstack-browse/test/fixtures/dialog.html +0 -15
  440. package/.agent/skills/gstack-browse/test/fixtures/dropdown.html +0 -61
  441. package/.agent/skills/gstack-browse/test/fixtures/empty.html +0 -2
  442. package/.agent/skills/gstack-browse/test/fixtures/forms.html +0 -55
  443. package/.agent/skills/gstack-browse/test/fixtures/iframe.html +0 -30
  444. package/.agent/skills/gstack-browse/test/fixtures/injection-combined.html +0 -58
  445. package/.agent/skills/gstack-browse/test/fixtures/injection-hidden.html +0 -62
  446. package/.agent/skills/gstack-browse/test/fixtures/injection-social.html +0 -25
  447. package/.agent/skills/gstack-browse/test/fixtures/injection-visible.html +0 -26
  448. package/.agent/skills/gstack-browse/test/fixtures/media-page.html +0 -67
  449. package/.agent/skills/gstack-browse/test/fixtures/mock-claude/claude +0 -185
  450. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-delete-lease.ts +0 -125
  451. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-file-owners.ts +0 -132
  452. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-launch.cjs +0 -139
  453. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process-observer.ts +0 -150
  454. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process.cjs +0 -13
  455. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-remove-fixture.cjs +0 -42
  456. package/.agent/skills/gstack-browse/test/fixtures/network-idle.html +0 -30
  457. package/.agent/skills/gstack-browse/test/fixtures/qa-eval-checkout.html +0 -108
  458. package/.agent/skills/gstack-browse/test/fixtures/qa-eval-spa.html +0 -98
  459. package/.agent/skills/gstack-browse/test/fixtures/qa-eval.html +0 -51
  460. package/.agent/skills/gstack-browse/test/fixtures/qa-only.html +0 -13
  461. package/.agent/skills/gstack-browse/test/fixtures/responsive.html +0 -49
  462. package/.agent/skills/gstack-browse/test/fixtures/route-dispatch-allowlist.json +0 -22
  463. package/.agent/skills/gstack-browse/test/fixtures/snapshot-ambiguous.html +0 -24
  464. package/.agent/skills/gstack-browse/test/fixtures/snapshot.html +0 -55
  465. package/.agent/skills/gstack-browse/test/fixtures/spa.html +0 -24
  466. package/.agent/skills/gstack-browse/test/fixtures/states.html +0 -17
  467. package/.agent/skills/gstack-browse/test/fixtures/upload-path-validation.ts +0 -91
  468. package/.agent/skills/gstack-browse/test/fixtures/upload.html +0 -25
  469. package/.agent/skills/gstack-browse/test/from-file-path-validation.test.ts +0 -68
  470. package/.agent/skills/gstack-browse/test/gstack-config.test.ts +0 -252
  471. package/.agent/skills/gstack-browse/test/gstack-update-check.test.ts +0 -614
  472. package/.agent/skills/gstack-browse/test/handoff.test.ts +0 -508
  473. package/.agent/skills/gstack-browse/test/headless-gpu-and-reap.test.ts +0 -286
  474. package/.agent/skills/gstack-browse/test/headless-launch-options.test.ts +0 -93
  475. package/.agent/skills/gstack-browse/test/launch-signal-flags.test.ts +0 -75
  476. package/.agent/skills/gstack-browse/test/learnings-injection.test.ts +0 -33
  477. package/.agent/skills/gstack-browse/test/media-extract-unit.test.ts +0 -294
  478. package/.agent/skills/gstack-browse/test/memory-command.test.ts +0 -219
  479. package/.agent/skills/gstack-browse/test/memory-leak-reproducer.test.ts +0 -133
  480. package/.agent/skills/gstack-browse/test/navigation-guard.test.ts +0 -106
  481. package/.agent/skills/gstack-browse/test/output-path-symlinks.test.ts +0 -76
  482. package/.agent/skills/gstack-browse/test/pair-agent-e2e.test.ts +0 -793
  483. package/.agent/skills/gstack-browse/test/pair-agent-optin-gate.test.ts +0 -171
  484. package/.agent/skills/gstack-browse/test/pair-agent-tunnel-eval.test.ts +0 -501
  485. package/.agent/skills/gstack-browse/test/path-security-realpath.test.ts +0 -89
  486. package/.agent/skills/gstack-browse/test/path-validation.test.ts +0 -268
  487. package/.agent/skills/gstack-browse/test/pdf-flags.test.ts +0 -86
  488. package/.agent/skills/gstack-browse/test/platform.test.ts +0 -37
  489. package/.agent/skills/gstack-browse/test/playwright-core-patch.test.ts +0 -62
  490. package/.agent/skills/gstack-browse/test/poisoned-bundle-probe.test.ts +0 -148
  491. package/.agent/skills/gstack-browse/test/process-liveness-windows.test.ts +0 -176
  492. package/.agent/skills/gstack-browse/test/proxy-config.test.ts +0 -189
  493. package/.agent/skills/gstack-browse/test/proxy-env-daemon.test.ts +0 -137
  494. package/.agent/skills/gstack-browse/test/proxy-env.test.ts +0 -189
  495. package/.agent/skills/gstack-browse/test/proxy-redact.test.ts +0 -64
  496. package/.agent/skills/gstack-browse/test/pty-inject-scan.test.ts +0 -153
  497. package/.agent/skills/gstack-browse/test/pty-session-lease.test.ts +0 -98
  498. package/.agent/skills/gstack-browse/test/rebrand-signed-bundle.test.ts +0 -53
  499. package/.agent/skills/gstack-browse/test/regression-pr1169-pdf-from-file-invalid-json.test.ts +0 -84
  500. package/.agent/skills/gstack-browse/test/restart-env.test.ts +0 -39
  501. package/.agent/skills/gstack-browse/test/route-test-harness.ts +0 -121
  502. package/.agent/skills/gstack-browse/test/runtime-root-server-bundle.test.ts +0 -100
  503. package/.agent/skills/gstack-browse/test/sanitize.test.ts +0 -112
  504. package/.agent/skills/gstack-browse/test/screenshot-size-guard.test.ts +0 -118
  505. package/.agent/skills/gstack-browse/test/security-adversarial-fixes.test.ts +0 -101
  506. package/.agent/skills/gstack-browse/test/security-adversarial.test.ts +0 -342
  507. package/.agent/skills/gstack-browse/test/security-audit-r2.test.ts +0 -432
  508. package/.agent/skills/gstack-browse/test/security-bench.test.ts +0 -159
  509. package/.agent/skills/gstack-browse/test/security-classifier-download-cleanup.test.ts +0 -138
  510. package/.agent/skills/gstack-browse/test/security-classifier.test.ts +0 -29
  511. package/.agent/skills/gstack-browse/test/security-integration.test.ts +0 -184
  512. package/.agent/skills/gstack-browse/test/security-live-playwright.test.ts +0 -195
  513. package/.agent/skills/gstack-browse/test/security-sidecar-client.test.ts +0 -66
  514. package/.agent/skills/gstack-browse/test/security.test.ts +0 -268
  515. package/.agent/skills/gstack-browse/test/server-auth.test.ts +0 -600
  516. package/.agent/skills/gstack-browse/test/server-embedder-terminal-port.test.ts +0 -301
  517. package/.agent/skills/gstack-browse/test/server-factory.test.ts +0 -640
  518. package/.agent/skills/gstack-browse/test/server-flush-trackers.test.ts +0 -73
  519. package/.agent/skills/gstack-browse/test/server-launch-resolution.test.ts +0 -49
  520. package/.agent/skills/gstack-browse/test/server-lock-errors.test.ts +0 -124
  521. package/.agent/skills/gstack-browse/test/server-no-import-side-effects.test.ts +0 -110
  522. package/.agent/skills/gstack-browse/test/server-proxy-fail-fast.test.ts +0 -98
  523. package/.agent/skills/gstack-browse/test/server-pty-lease-routes.test.ts +0 -156
  524. package/.agent/skills/gstack-browse/test/server-route-auth-blackbox.test.ts +0 -270
  525. package/.agent/skills/gstack-browse/test/server-route-dispatch-ratchet.test.ts +0 -129
  526. package/.agent/skills/gstack-browse/test/server-route-table.test.ts +0 -238
  527. package/.agent/skills/gstack-browse/test/server-sanitize-surrogates.test.ts +0 -182
  528. package/.agent/skills/gstack-browse/test/server-tmp-state-path.test.ts +0 -110
  529. package/.agent/skills/gstack-browse/test/session-cookie-store.test.ts +0 -159
  530. package/.agent/skills/gstack-browse/test/session-persist.test.ts +0 -287
  531. package/.agent/skills/gstack-browse/test/sidebar-tabs.test.ts +0 -268
  532. package/.agent/skills/gstack-browse/test/sidebar-ux.test.ts +0 -1006
  533. package/.agent/skills/gstack-browse/test/sidepanel-font-size.test.ts +0 -147
  534. package/.agent/skills/gstack-browse/test/sidepanel-patient-autoconnect.test.ts +0 -70
  535. package/.agent/skills/gstack-browse/test/sidepanel-reattach.test.ts +0 -93
  536. package/.agent/skills/gstack-browse/test/sidepanel-restart-dispose.test.ts +0 -106
  537. package/.agent/skills/gstack-browse/test/skill-token.test.ts +0 -167
  538. package/.agent/skills/gstack-browse/test/snapshot.test.ts +0 -593
  539. package/.agent/skills/gstack-browse/test/socks-bridge.test.ts +0 -461
  540. package/.agent/skills/gstack-browse/test/sse-helpers.test.ts +0 -194
  541. package/.agent/skills/gstack-browse/test/sse-session-cookie.test.ts +0 -155
  542. package/.agent/skills/gstack-browse/test/state-ttl.test.ts +0 -35
  543. package/.agent/skills/gstack-browse/test/stealth-extended.test.ts +0 -127
  544. package/.agent/skills/gstack-browse/test/stealth-layer-c.test.ts +0 -284
  545. package/.agent/skills/gstack-browse/test/stealth-webdriver.test.ts +0 -394
  546. package/.agent/skills/gstack-browse/test/stop-ack-before-shutdown.test.ts +0 -23
  547. package/.agent/skills/gstack-browse/test/stop-dead-daemon.test.ts +0 -153
  548. package/.agent/skills/gstack-browse/test/tab-each.test.ts +0 -196
  549. package/.agent/skills/gstack-browse/test/tab-guardrail.test.ts +0 -118
  550. package/.agent/skills/gstack-browse/test/tab-isolation.test.ts +0 -309
  551. package/.agent/skills/gstack-browse/test/tab-session-frame-detach.test.ts +0 -154
  552. package/.agent/skills/gstack-browse/test/telemetry-optout.test.ts +0 -171
  553. package/.agent/skills/gstack-browse/test/telemetry.test.ts +0 -77
  554. package/.agent/skills/gstack-browse/test/temp-dirs.test.ts +0 -129
  555. package/.agent/skills/gstack-browse/test/terminal-agent-detach-reattach.test.ts +0 -84
  556. package/.agent/skills/gstack-browse/test/terminal-agent-import.test.ts +0 -42
  557. package/.agent/skills/gstack-browse/test/terminal-agent-integration.test.ts +0 -565
  558. package/.agent/skills/gstack-browse/test/terminal-agent-keepalive.test.ts +0 -88
  559. package/.agent/skills/gstack-browse/test/terminal-agent-lifecycle.test.ts +0 -371
  560. package/.agent/skills/gstack-browse/test/terminal-agent-native-observation.test.ts +0 -66
  561. package/.agent/skills/gstack-browse/test/terminal-agent-owner-watchdog.test.ts +0 -81
  562. package/.agent/skills/gstack-browse/test/terminal-agent-pid-identity.test.ts +0 -161
  563. package/.agent/skills/gstack-browse/test/terminal-agent-port-range.test.ts +0 -80
  564. package/.agent/skills/gstack-browse/test/terminal-agent-publication-lock.test.ts +0 -289
  565. package/.agent/skills/gstack-browse/test/terminal-agent-ring-buffer-runtime.test.ts +0 -157
  566. package/.agent/skills/gstack-browse/test/terminal-agent-session-routing.test.ts +0 -96
  567. package/.agent/skills/gstack-browse/test/terminal-agent-watchdog.test.ts +0 -101
  568. package/.agent/skills/gstack-browse/test/terminal-agent.test.ts +0 -259
  569. package/.agent/skills/gstack-browse/test/terminal-pty-lifecycle.test.ts +0 -212
  570. package/.agent/skills/gstack-browse/test/test-server.ts +0 -57
  571. package/.agent/skills/gstack-browse/test/token-registry.test.ts +0 -615
  572. package/.agent/skills/gstack-browse/test/tunnel-gate-unit.test.ts +0 -129
  573. package/.agent/skills/gstack-browse/test/tunnel-revoke-cli.test.ts +0 -455
  574. package/.agent/skills/gstack-browse/test/url-validation.test.ts +0 -384
  575. package/.agent/skills/gstack-browse/test/watch.test.ts +0 -129
  576. package/.agent/skills/gstack-browse/test/watchdog.test.ts +0 -352
  577. package/.agent/skills/gstack-browse/test/welcome-page.test.ts +0 -143
  578. package/.agent/skills/gstack-browse/test/windows-spawn-hide.test.ts +0 -167
  579. package/.agent/skills/gstack-browse/test/xprotect-heal.test.ts +0 -457
  580. package/.agent/skills/gstack-browse/test/xvfb.test.ts +0 -603
  581. package/.agent/skills/gstack-canary/SKILL.md +0 -699
  582. package/.agent/skills/gstack-canary/SKILL.md.tmpl +0 -254
  583. package/.agent/skills/gstack-careful/SKILL.md +0 -96
  584. package/.agent/skills/gstack-careful/SKILL.md.tmpl +0 -107
  585. package/.agent/skills/gstack-careful/bin/check-careful.sh +0 -407
  586. package/.agent/skills/gstack-careful/bin/hook-extract.sh +0 -198
  587. package/.agent/skills/gstack-codex/SKILL.md +0 -889
  588. package/.agent/skills/gstack-codex/SKILL.md.tmpl +0 -358
  589. package/.agent/skills/gstack-codex/sections/challenge-mode.md +0 -150
  590. package/.agent/skills/gstack-codex/sections/challenge-mode.md.tmpl +0 -136
  591. package/.agent/skills/gstack-codex/sections/consult-mode.md +0 -221
  592. package/.agent/skills/gstack-codex/sections/consult-mode.md.tmpl +0 -207
  593. package/.agent/skills/gstack-codex/sections/manifest.json +0 -26
  594. package/.agent/skills/gstack-codex/sections/review-mode.md +0 -265
  595. package/.agent/skills/gstack-codex/sections/review-mode.md.tmpl +0 -246
  596. package/.agent/skills/gstack-context-restore/SKILL.md +0 -612
  597. package/.agent/skills/gstack-context-restore/SKILL.md.tmpl +0 -289
  598. package/.agent/skills/gstack-context-save/SKILL.md +0 -631
  599. package/.agent/skills/gstack-context-save/SKILL.md.tmpl +0 -307
  600. package/.agent/skills/gstack-cso/ACKNOWLEDGEMENTS.md +0 -17
  601. package/.agent/skills/gstack-cso/SKILL.md +0 -166
  602. package/.agent/skills/gstack-cso/SKILL.md.tmpl +0 -162
  603. package/.agent/skills/gstack-cso/sections/audit-phases.md +0 -116
  604. package/.agent/skills/gstack-cso/sections/audit-phases.md.tmpl +0 -114
  605. package/.agent/skills/gstack-cso/sections/manifest.json +0 -14
  606. package/.agent/skills/gstack-design-consultation/SKILL.md +0 -795
  607. package/.agent/skills/gstack-design-consultation/SKILL.md.tmpl +0 -234
  608. package/.agent/skills/gstack-design-consultation/sections/manifest.json +0 -14
  609. package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md +0 -595
  610. package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md.tmpl +0 -350
  611. package/.agent/skills/gstack-design-html/SKILL.md +0 -866
  612. package/.agent/skills/gstack-design-html/SKILL.md.tmpl +0 -476
  613. package/.agent/skills/gstack-design-html/sections/detector-install-offer.md +0 -40
  614. package/.agent/skills/gstack-design-html/sections/detector-install-offer.md.tmpl +0 -6
  615. package/.agent/skills/gstack-design-html/sections/doctrine.md +0 -86
  616. package/.agent/skills/gstack-design-html/sections/doctrine.md.tmpl +0 -1
  617. package/.agent/skills/gstack-design-html/sections/manifest.json +0 -26
  618. package/.agent/skills/gstack-design-html/sections/pretext-patterns.md +0 -145
  619. package/.agent/skills/gstack-design-html/sections/pretext-patterns.md.tmpl +0 -143
  620. package/.agent/skills/gstack-design-html/vendor/pretext.js +0 -5
  621. package/.agent/skills/gstack-design-review/SKILL.md +0 -1957
  622. package/.agent/skills/gstack-design-review/SKILL.md.tmpl +0 -344
  623. package/.agent/skills/gstack-design-shotgun/SKILL.md +0 -885
  624. package/.agent/skills/gstack-design-shotgun/SKILL.md.tmpl +0 -310
  625. package/.agent/skills/gstack-design-shotgun/sections/doctrine.md +0 -86
  626. package/.agent/skills/gstack-design-shotgun/sections/doctrine.md.tmpl +0 -1
  627. package/.agent/skills/gstack-design-shotgun/sections/manifest.json +0 -14
  628. package/.agent/skills/gstack-deslop-shared-libs/SKILL.md +0 -209
  629. package/.agent/skills/gstack-deslop-shared-libs/SKILL.md.tmpl +0 -181
  630. package/.agent/skills/gstack-devex-review/SKILL.md +0 -1107
  631. package/.agent/skills/gstack-devex-review/SKILL.md.tmpl +0 -241
  632. package/.agent/skills/gstack-diagram/SKILL.md +0 -295
  633. package/.agent/skills/gstack-diagram/SKILL.md.tmpl +0 -175
  634. package/.agent/skills/gstack-document-generate/SKILL.md +0 -814
  635. package/.agent/skills/gstack-document-generate/SKILL.md.tmpl +0 -460
  636. package/.agent/skills/gstack-document-release/SKILL.md +0 -557
  637. package/.agent/skills/gstack-document-release/SKILL.md.tmpl +0 -188
  638. package/.agent/skills/gstack-document-release/sections/audit-scope.md +0 -51
  639. package/.agent/skills/gstack-document-release/sections/audit-scope.md.tmpl +0 -49
  640. package/.agent/skills/gstack-document-release/sections/manifest.json +0 -20
  641. package/.agent/skills/gstack-document-release/sections/release-body.md +0 -640
  642. package/.agent/skills/gstack-document-release/sections/release-body.md.tmpl +0 -428
  643. package/.agent/skills/gstack-eval-plan/SKILL.md +0 -472
  644. package/.agent/skills/gstack-eval-plan/SKILL.md.tmpl +0 -157
  645. package/.agent/skills/gstack-freeze/SKILL.md +0 -79
  646. package/.agent/skills/gstack-freeze/SKILL.md.tmpl +0 -96
  647. package/.agent/skills/gstack-freeze/bin/check-freeze.sh +0 -210
  648. package/.agent/skills/gstack-freeze/bin/freeze-state.sh +0 -68
  649. package/.agent/skills/gstack-gstack-upgrade/SKILL.md +0 -410
  650. package/.agent/skills/gstack-gstack-upgrade/SKILL.md.tmpl +0 -413
  651. package/.agent/skills/gstack-gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
  652. package/.agent/skills/gstack-gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
  653. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
  654. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
  655. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
  656. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
  657. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
  658. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
  659. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
  660. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
  661. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
  662. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
  663. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
  664. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
  665. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
  666. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
  667. package/.agent/skills/gstack-guard/SKILL.md +0 -60
  668. package/.agent/skills/gstack-guard/SKILL.md.tmpl +0 -88
  669. package/.agent/skills/gstack-health/SKILL.md +0 -710
  670. package/.agent/skills/gstack-health/SKILL.md.tmpl +0 -386
  671. package/.agent/skills/gstack-investigate/SKILL.md +0 -706
  672. package/.agent/skills/gstack-investigate/SKILL.md.tmpl +0 -305
  673. package/.agent/skills/gstack-investigate/sections/backlog.md +0 -31
  674. package/.agent/skills/gstack-investigate/sections/backlog.md.tmpl +0 -29
  675. package/.agent/skills/gstack-investigate/sections/eval-bisect.md +0 -25
  676. package/.agent/skills/gstack-investigate/sections/eval-bisect.md.tmpl +0 -23
  677. package/.agent/skills/gstack-investigate/sections/flake.md +0 -50
  678. package/.agent/skills/gstack-investigate/sections/flake.md.tmpl +0 -43
  679. package/.agent/skills/gstack-investigate/sections/manifest.json +0 -38
  680. package/.agent/skills/gstack-investigate/sections/polluter.md +0 -33
  681. package/.agent/skills/gstack-investigate/sections/polluter.md.tmpl +0 -31
  682. package/.agent/skills/gstack-investigate/sections/race.md +0 -27
  683. package/.agent/skills/gstack-investigate/sections/race.md.tmpl +0 -25
  684. package/.agent/skills/gstack-ios-clean/SKILL.md +0 -427
  685. package/.agent/skills/gstack-ios-clean/SKILL.md.tmpl +0 -107
  686. package/.agent/skills/gstack-ios-design-review/SKILL.md +0 -434
  687. package/.agent/skills/gstack-ios-design-review/SKILL.md.tmpl +0 -111
  688. package/.agent/skills/gstack-ios-fix/SKILL.md +0 -423
  689. package/.agent/skills/gstack-ios-fix/SKILL.md.tmpl +0 -103
  690. package/.agent/skills/gstack-ios-qa/SKILL.md +0 -643
  691. package/.agent/skills/gstack-ios-qa/SKILL.md.tmpl +0 -297
  692. package/.agent/skills/gstack-ios-qa/daemon/src/allowlist.ts +0 -114
  693. package/.agent/skills/gstack-ios-qa/daemon/src/audit.ts +0 -97
  694. package/.agent/skills/gstack-ios-qa/daemon/src/auth-mint.ts +0 -89
  695. package/.agent/skills/gstack-ios-qa/daemon/src/cli-mint.ts +0 -149
  696. package/.agent/skills/gstack-ios-qa/daemon/src/devicectl.ts +0 -335
  697. package/.agent/skills/gstack-ios-qa/daemon/src/index.ts +0 -697
  698. package/.agent/skills/gstack-ios-qa/daemon/src/proxy.ts +0 -160
  699. package/.agent/skills/gstack-ios-qa/daemon/src/session-tokens.ts +0 -126
  700. package/.agent/skills/gstack-ios-qa/daemon/src/single-instance.ts +0 -171
  701. package/.agent/skills/gstack-ios-qa/daemon/src/tailscale-localapi.ts +0 -120
  702. package/.agent/skills/gstack-ios-qa/daemon/src/tunnel-bootstrap.ts +0 -459
  703. package/.agent/skills/gstack-ios-qa/daemon/src/types.ts +0 -91
  704. package/.agent/skills/gstack-ios-qa/daemon/test/allowlist.test.ts +0 -146
  705. package/.agent/skills/gstack-ios-qa/daemon/test/audit.test.ts +0 -111
  706. package/.agent/skills/gstack-ios-qa/daemon/test/auth-mint.test.ts +0 -106
  707. package/.agent/skills/gstack-ios-qa/daemon/test/cli-mint.test.ts +0 -120
  708. package/.agent/skills/gstack-ios-qa/daemon/test/daemon-integration.test.ts +0 -855
  709. package/.agent/skills/gstack-ios-qa/daemon/test/fake-device.ts +0 -238
  710. package/.agent/skills/gstack-ios-qa/daemon/test/proxy-classify.test.ts +0 -47
  711. package/.agent/skills/gstack-ios-qa/daemon/test/proxy-transport.test.ts +0 -499
  712. package/.agent/skills/gstack-ios-qa/daemon/test/session-tokens.test.ts +0 -156
  713. package/.agent/skills/gstack-ios-qa/daemon/test/single-instance.test.ts +0 -96
  714. package/.agent/skills/gstack-ios-qa/daemon/test/tailscale-localapi.test.ts +0 -55
  715. package/.agent/skills/gstack-ios-qa/daemon/test/tunnel-bootstrap.test.ts +0 -1030
  716. package/.agent/skills/gstack-ios-qa/templates/Bridges.swift.template +0 -474
  717. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeManager.swift.template +0 -38
  718. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.h.template +0 -38
  719. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.m.template +0 -365
  720. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeWiring.swift.template +0 -59
  721. package/.agent/skills/gstack-ios-qa/templates/DebugOverlay.swift.template +0 -137
  722. package/.agent/skills/gstack-ios-qa/templates/Package.swift.template +0 -82
  723. package/.agent/skills/gstack-ios-qa/templates/StateAccessor.swift.template +0 -41
  724. package/.agent/skills/gstack-ios-qa/templates/StateServer.swift.template +0 -625
  725. package/.agent/skills/gstack-ios-sync/SKILL.md +0 -418
  726. package/.agent/skills/gstack-ios-sync/SKILL.md.tmpl +0 -99
  727. package/.agent/skills/gstack-land-and-deploy/SKILL.md +0 -1028
  728. package/.agent/skills/gstack-land-and-deploy/SKILL.md.tmpl +0 -509
  729. package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md +0 -188
  730. package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md.tmpl +0 -150
  731. package/.agent/skills/gstack-land-and-deploy/sections/manifest.json +0 -26
  732. package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md +0 -250
  733. package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md.tmpl +0 -248
  734. package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md +0 -308
  735. package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md.tmpl +0 -306
  736. package/.agent/skills/gstack-landing-report/SKILL.md +0 -488
  737. package/.agent/skills/gstack-landing-report/SKILL.md.tmpl +0 -164
  738. package/.agent/skills/gstack-learn/SKILL.md +0 -519
  739. package/.agent/skills/gstack-learn/SKILL.md.tmpl +0 -197
  740. package/.agent/skills/gstack-make-pdf/SKILL.md +0 -409
  741. package/.agent/skills/gstack-make-pdf/SKILL.md.tmpl +0 -253
  742. package/.agent/skills/gstack-make-pdf/src/asideClient.ts +0 -114
  743. package/.agent/skills/gstack-make-pdf/src/cli.ts +0 -294
  744. package/.agent/skills/gstack-make-pdf/src/commands.ts +0 -62
  745. package/.agent/skills/gstack-make-pdf/src/diagram-prepass.ts +0 -834
  746. package/.agent/skills/gstack-make-pdf/src/html-to-docx.d.ts +0 -7
  747. package/.agent/skills/gstack-make-pdf/src/image-policy.ts +0 -236
  748. package/.agent/skills/gstack-make-pdf/src/image-size.ts +0 -117
  749. package/.agent/skills/gstack-make-pdf/src/orchestrator.ts +0 -339
  750. package/.agent/skills/gstack-make-pdf/src/pdftotext.ts +0 -318
  751. package/.agent/skills/gstack-make-pdf/src/print-css.ts +0 -452
  752. package/.agent/skills/gstack-make-pdf/src/render.ts +0 -636
  753. package/.agent/skills/gstack-make-pdf/src/setup.ts +0 -113
  754. package/.agent/skills/gstack-make-pdf/src/smartypants.ts +0 -105
  755. package/.agent/skills/gstack-make-pdf/src/toc-pages.ts +0 -197
  756. package/.agent/skills/gstack-make-pdf/src/types.ts +0 -109
  757. package/.agent/skills/gstack-make-pdf/test/asideClient.test.ts +0 -153
  758. package/.agent/skills/gstack-make-pdf/test/cli-args.test.ts +0 -94
  759. package/.agent/skills/gstack-make-pdf/test/cli-exit-codes.test.ts +0 -329
  760. package/.agent/skills/gstack-make-pdf/test/diagram-prepass.test.ts +0 -743
  761. package/.agent/skills/gstack-make-pdf/test/e2e/ci-prereqs.test.ts +0 -48
  762. package/.agent/skills/gstack-make-pdf/test/e2e/combined-gate.test.ts +0 -74
  763. package/.agent/skills/gstack-make-pdf/test/e2e/diagram-gate.test.ts +0 -173
  764. package/.agent/skills/gstack-make-pdf/test/e2e/emoji-gate.test.ts +0 -191
  765. package/.agent/skills/gstack-make-pdf/test/e2e/format-gate.test.ts +0 -130
  766. package/.agent/skills/gstack-make-pdf/test/e2e/landscape-gate.test.ts +0 -150
  767. package/.agent/skills/gstack-make-pdf/test/e2e/toc-gate.test.ts +0 -143
  768. package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.expected.txt +0 -20
  769. package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.md +0 -30
  770. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/huge-noise.png +0 -0
  771. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/red-box.png +0 -0
  772. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-arch.png +0 -0
  773. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-screenshot.png +0 -0
  774. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-gate.md +0 -48
  775. package/.agent/skills/gstack-make-pdf/test/fixtures/emoji-gate.md +0 -12
  776. package/.agent/skills/gstack-make-pdf/test/fixtures/landscape-gate.md +0 -52
  777. package/.agent/skills/gstack-make-pdf/test/image-policy.test.ts +0 -215
  778. package/.agent/skills/gstack-make-pdf/test/pdftotext.test.ts +0 -327
  779. package/.agent/skills/gstack-make-pdf/test/render-offline-sanitize.test.ts +0 -268
  780. package/.agent/skills/gstack-make-pdf/test/render.test.ts +0 -603
  781. package/.agent/skills/gstack-make-pdf/test/setup-smoke.test.ts +0 -259
  782. package/.agent/skills/gstack-make-pdf/test/toc-anchors.test.ts +0 -95
  783. package/.agent/skills/gstack-make-pdf/test/toc-pages.test.ts +0 -148
  784. package/.agent/skills/gstack-office-hours/SKILL.md +0 -1324
  785. package/.agent/skills/gstack-office-hours/SKILL.md.tmpl +0 -372
  786. package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md +0 -706
  787. package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md.tmpl +0 -547
  788. package/.agent/skills/gstack-office-hours/sections/manifest.json +0 -26
  789. package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md +0 -163
  790. package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md.tmpl +0 -161
  791. package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md +0 -43
  792. package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md.tmpl +0 -41
  793. package/.agent/skills/gstack-open-gstack-browser/SKILL.md +0 -414
  794. package/.agent/skills/gstack-open-gstack-browser/SKILL.md.tmpl +0 -248
  795. package/.agent/skills/gstack-pair-agent/SKILL.md +0 -730
  796. package/.agent/skills/gstack-pair-agent/SKILL.md.tmpl +0 -366
  797. package/.agent/skills/gstack-plan-ceo-review/SKILL.md +0 -1245
  798. package/.agent/skills/gstack-plan-ceo-review/SKILL.md.tmpl +0 -596
  799. package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook +0 -5
  800. package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook.ts +0 -34
  801. package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md +0 -37
  802. package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md.tmpl +0 -35
  803. package/.agent/skills/gstack-plan-ceo-review/sections/manifest.json +0 -23
  804. package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md +0 -1207
  805. package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md.tmpl +0 -605
  806. package/.agent/skills/gstack-plan-design-review/SKILL.md +0 -1230
  807. package/.agent/skills/gstack-plan-design-review/SKILL.md.tmpl +0 -344
  808. package/.agent/skills/gstack-plan-design-review/sections/checklist.md +0 -27
  809. package/.agent/skills/gstack-plan-design-review/sections/checklist.md.tmpl +0 -25
  810. package/.agent/skills/gstack-plan-design-review/sections/manifest.json +0 -23
  811. package/.agent/skills/gstack-plan-design-review/sections/review-sections.md +0 -727
  812. package/.agent/skills/gstack-plan-design-review/sections/review-sections.md.tmpl +0 -281
  813. package/.agent/skills/gstack-plan-devex-review/SKILL.md +0 -1072
  814. package/.agent/skills/gstack-plan-devex-review/SKILL.md.tmpl +0 -459
  815. package/.agent/skills/gstack-plan-devex-review/dx-hall-of-fame.md +0 -127
  816. package/.agent/skills/gstack-plan-devex-review/sections/checklist.md +0 -33
  817. package/.agent/skills/gstack-plan-devex-review/sections/checklist.md.tmpl +0 -31
  818. package/.agent/skills/gstack-plan-devex-review/sections/manifest.json +0 -23
  819. package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md +0 -1037
  820. package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md.tmpl +0 -446
  821. package/.agent/skills/gstack-plan-eng-review/SKILL.md +0 -702
  822. package/.agent/skills/gstack-plan-eng-review/SKILL.md.tmpl +0 -191
  823. package/.agent/skills/gstack-plan-eng-review/sections/checklist.md +0 -34
  824. package/.agent/skills/gstack-plan-eng-review/sections/checklist.md.tmpl +0 -32
  825. package/.agent/skills/gstack-plan-eng-review/sections/manifest.json +0 -23
  826. package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md +0 -1545
  827. package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md.tmpl +0 -614
  828. package/.agent/skills/gstack-plan-tune/SKILL.md +0 -986
  829. package/.agent/skills/gstack-plan-tune/SKILL.md.tmpl +0 -669
  830. package/.agent/skills/gstack-qa/SKILL.md +0 -781
  831. package/.agent/skills/gstack-qa/SKILL.md.tmpl +0 -305
  832. package/.agent/skills/gstack-qa/references/issue-taxonomy.md +0 -85
  833. package/.agent/skills/gstack-qa/sections/browser-setup.md +0 -114
  834. package/.agent/skills/gstack-qa/sections/browser-setup.md.tmpl +0 -28
  835. package/.agent/skills/gstack-qa/sections/browser-verify.md +0 -19
  836. package/.agent/skills/gstack-qa/sections/browser-verify.md.tmpl +0 -17
  837. package/.agent/skills/gstack-qa/sections/exploratory.md +0 -92
  838. package/.agent/skills/gstack-qa/sections/exploratory.md.tmpl +0 -1
  839. package/.agent/skills/gstack-qa/sections/manifest.json +0 -50
  840. package/.agent/skills/gstack-qa/sections/qa-patterns.md +0 -226
  841. package/.agent/skills/gstack-qa/sections/qa-patterns.md.tmpl +0 -1
  842. package/.agent/skills/gstack-qa/sections/scope.md +0 -23
  843. package/.agent/skills/gstack-qa/sections/scope.md.tmpl +0 -1
  844. package/.agent/skills/gstack-qa/sections/system-functional.md +0 -61
  845. package/.agent/skills/gstack-qa/sections/system-functional.md.tmpl +0 -1
  846. package/.agent/skills/gstack-qa/sections/test-bootstrap.md +0 -86
  847. package/.agent/skills/gstack-qa/sections/test-bootstrap.md.tmpl +0 -74
  848. package/.agent/skills/gstack-qa/templates/functional-report-template.md +0 -55
  849. package/.agent/skills/gstack-qa/templates/qa-report-template.md +0 -124
  850. package/.agent/skills/gstack-qa-only/SKILL.md +0 -595
  851. package/.agent/skills/gstack-qa-only/SKILL.md.tmpl +0 -214
  852. package/.agent/skills/gstack-qa-only/sections/exploratory.md +0 -107
  853. package/.agent/skills/gstack-qa-only/sections/exploratory.md.tmpl +0 -1
  854. package/.agent/skills/gstack-qa-only/sections/manifest.json +0 -19
  855. package/.agent/skills/gstack-qa-only/sections/reporting.md +0 -78
  856. package/.agent/skills/gstack-qa-only/sections/reporting.md.tmpl +0 -76
  857. package/.agent/skills/gstack-retro/SKILL.md +0 -1178
  858. package/.agent/skills/gstack-retro/SKILL.md.tmpl +0 -769
  859. package/.agent/skills/gstack-retro/sections/manifest.json +0 -14
  860. package/.agent/skills/gstack-retro/sections/report-format.md +0 -127
  861. package/.agent/skills/gstack-retro/sections/report-format.md.tmpl +0 -125
  862. package/.agent/skills/gstack-review/SKILL.md +0 -1199
  863. package/.agent/skills/gstack-review/SKILL.md.tmpl +0 -482
  864. package/.agent/skills/gstack-review/TODOS-format.md +0 -61
  865. package/.agent/skills/gstack-review/checklist.md +0 -222
  866. package/.agent/skills/gstack-review/design-checklist.md +0 -193
  867. package/.agent/skills/gstack-review/gate-integrity.md +0 -171
  868. package/.agent/skills/gstack-review/greptile-triage.md +0 -261
  869. package/.agent/skills/gstack-review/sections/adversarial.md +0 -296
  870. package/.agent/skills/gstack-review/sections/adversarial.md.tmpl +0 -1
  871. package/.agent/skills/gstack-review/sections/manifest.json +0 -32
  872. package/.agent/skills/gstack-review/sections/plan-completion.md +0 -232
  873. package/.agent/skills/gstack-review/sections/plan-completion.md.tmpl +0 -3
  874. package/.agent/skills/gstack-review/sections/review-army.md +0 -263
  875. package/.agent/skills/gstack-review/sections/review-army.md.tmpl +0 -1
  876. package/.agent/skills/gstack-review/sections/shared-code-reuse.md +0 -34
  877. package/.agent/skills/gstack-review/sections/shared-code-reuse.md.tmpl +0 -1
  878. package/.agent/skills/gstack-review/specialists/api-contract.md +0 -49
  879. package/.agent/skills/gstack-review/specialists/data-migration.md +0 -48
  880. package/.agent/skills/gstack-review/specialists/maintainability.md +0 -46
  881. package/.agent/skills/gstack-review/specialists/performance.md +0 -52
  882. package/.agent/skills/gstack-review/specialists/red-team.md +0 -45
  883. package/.agent/skills/gstack-review/specialists/security.md +0 -65
  884. package/.agent/skills/gstack-review/specialists/simplification.md +0 -49
  885. package/.agent/skills/gstack-review/specialists/testing.md +0 -160
  886. package/.agent/skills/gstack-scrape/SKILL.md +0 -431
  887. package/.agent/skills/gstack-scrape/SKILL.md.tmpl +0 -187
  888. package/.agent/skills/gstack-setup-browser-cookies/SKILL.md +0 -231
  889. package/.agent/skills/gstack-setup-browser-cookies/SKILL.md.tmpl +0 -64
  890. package/.agent/skills/gstack-setup-deploy/SKILL.md +0 -605
  891. package/.agent/skills/gstack-setup-deploy/SKILL.md.tmpl +0 -250
  892. package/.agent/skills/gstack-setup-gbrain/SKILL.md +0 -1074
  893. package/.agent/skills/gstack-setup-gbrain/SKILL.md.tmpl +0 -742
  894. package/.agent/skills/gstack-setup-gbrain/memory.md +0 -426
  895. package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md +0 -269
  896. package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md.tmpl +0 -267
  897. package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md +0 -40
  898. package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md.tmpl +0 -38
  899. package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md +0 -71
  900. package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md.tmpl +0 -69
  901. package/.agent/skills/gstack-setup-gbrain/sections/manifest.json +0 -32
  902. package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md +0 -105
  903. package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md.tmpl +0 -103
  904. package/.agent/skills/gstack-ship/SKILL.md +0 -1220
  905. package/.agent/skills/gstack-ship/SKILL.md.tmpl +0 -694
  906. package/.agent/skills/gstack-ship/sections/adversarial.md +0 -362
  907. package/.agent/skills/gstack-ship/sections/adversarial.md.tmpl +0 -22
  908. package/.agent/skills/gstack-ship/sections/apple-release.md +0 -98
  909. package/.agent/skills/gstack-ship/sections/apple-release.md.tmpl +0 -96
  910. package/.agent/skills/gstack-ship/sections/changelog.md +0 -47
  911. package/.agent/skills/gstack-ship/sections/changelog.md.tmpl +0 -5
  912. package/.agent/skills/gstack-ship/sections/documentation.md +0 -118
  913. package/.agent/skills/gstack-ship/sections/documentation.md.tmpl +0 -116
  914. package/.agent/skills/gstack-ship/sections/greptile-early.md +0 -57
  915. package/.agent/skills/gstack-ship/sections/greptile-early.md.tmpl +0 -48
  916. package/.agent/skills/gstack-ship/sections/greptile.md +0 -95
  917. package/.agent/skills/gstack-ship/sections/greptile.md.tmpl +0 -93
  918. package/.agent/skills/gstack-ship/sections/handoff.md +0 -22
  919. package/.agent/skills/gstack-ship/sections/handoff.md.tmpl +0 -20
  920. package/.agent/skills/gstack-ship/sections/manifest.json +0 -92
  921. package/.agent/skills/gstack-ship/sections/measure.md +0 -66
  922. package/.agent/skills/gstack-ship/sections/measure.md.tmpl +0 -64
  923. package/.agent/skills/gstack-ship/sections/plan-completion.md +0 -387
  924. package/.agent/skills/gstack-ship/sections/plan-completion.md.tmpl +0 -67
  925. package/.agent/skills/gstack-ship/sections/pr-body.md +0 -258
  926. package/.agent/skills/gstack-ship/sections/pr-body.md.tmpl +0 -241
  927. package/.agent/skills/gstack-ship/sections/review-army.md +0 -728
  928. package/.agent/skills/gstack-ship/sections/review-army.md.tmpl +0 -171
  929. package/.agent/skills/gstack-ship/sections/shared-code-reuse.md +0 -34
  930. package/.agent/skills/gstack-ship/sections/shared-code-reuse.md.tmpl +0 -1
  931. package/.agent/skills/gstack-ship/sections/test-coverage.md +0 -478
  932. package/.agent/skills/gstack-ship/sections/test-coverage.md.tmpl +0 -118
  933. package/.agent/skills/gstack-ship/sections/tests.md +0 -453
  934. package/.agent/skills/gstack-ship/sections/tests.md.tmpl +0 -144
  935. package/.agent/skills/gstack-skillify/SKILL.md +0 -790
  936. package/.agent/skills/gstack-skillify/SKILL.md.tmpl +0 -437
  937. package/.agent/skills/gstack-spec/SKILL.md +0 -893
  938. package/.agent/skills/gstack-spec/SKILL.md.tmpl +0 -498
  939. package/.agent/skills/gstack-spec/sections/gate-and-file.md +0 -470
  940. package/.agent/skills/gstack-spec/sections/gate-and-file.md.tmpl +0 -319
  941. package/.agent/skills/gstack-spec/sections/manifest.json +0 -14
  942. package/.agent/skills/gstack-sync-gbrain/SKILL.md +0 -935
  943. package/.agent/skills/gstack-sync-gbrain/SKILL.md.tmpl +0 -615
  944. package/.agent/skills/gstack-test-audit/SKILL.md +0 -500
  945. package/.agent/skills/gstack-test-audit/SKILL.md.tmpl +0 -149
  946. package/.agent/skills/gstack-unfreeze/SKILL.md +0 -39
  947. package/.agent/skills/gstack-unfreeze/SKILL.md.tmpl +0 -39
  948. package/.agent/skills/gstack-upgrade/SKILL.md +0 -410
  949. package/.agent/skills/gstack-upgrade/SKILL.md.tmpl +0 -413
  950. package/.agent/skills/gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
  951. package/.agent/skills/gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
  952. package/.agent/skills/gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
  953. package/.agent/skills/gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
  954. package/.agent/skills/gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
  955. package/.agent/skills/gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
  956. package/.agent/skills/gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
  957. package/.agent/skills/gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
  958. package/.agent/skills/gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
  959. package/.agent/skills/gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
  960. package/.agent/skills/gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
  961. package/.agent/skills/gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
  962. package/.agent/skills/gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
  963. package/.agent/skills/gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
  964. package/.agent/skills/gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
  965. package/.agent/skills/gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
  966. package/.agent/skills/html-renderer/SKILL.md +0 -336
  967. package/.agent/skills/html-renderer/references/animation-patterns.md +0 -609
  968. package/.agent/skills/html-renderer/references/character-animation.md +0 -388
  969. package/.agent/skills/html-renderer/references/collage-broll.md +0 -41
  970. package/.agent/skills/html-renderer/references/design.md +0 -217
  971. package/.agent/skills/html-renderer/references/directing.md +0 -165
  972. package/.agent/skills/html-renderer/references/earshot-audio.md +0 -201
  973. package/.agent/skills/html-renderer/references/engine-api.md +0 -480
  974. package/.agent/skills/html-renderer/references/film-styles.md +0 -95
  975. package/.agent/skills/html-renderer/references/hyperframes-motion.md +0 -208
  976. package/.agent/skills/html-renderer/references/motion-craft.md +0 -92
  977. package/.agent/skills/html-renderer/references/onetake-camera.md +0 -59
  978. package/.agent/skills/html-renderer/references/procedural-sfx.md +0 -147
  979. package/.agent/skills/html-renderer/references/remotion-patterns.md +0 -159
  980. package/.agent/skills/html-renderer/references/render-contract.md +0 -66
  981. package/.agent/skills/motion-styles/LIBRARY.md +0 -896
  982. package/.agent/skills/motion-styles/README.md +0 -29
  983. package/.agent/skills/motion-styles/SKILL.md +0 -70
  984. package/.agent/skills/motion-styles/catalog.json +0 -2253
  985. package/.agent/skills/motion-styles/prompts/bio-glow.md +0 -149
  986. package/.agent/skills/motion-styles/prompts/blueprint.md +0 -145
  987. package/.agent/skills/motion-styles/prompts/breaking-desk.md +0 -147
  988. package/.agent/skills/motion-styles/prompts/callout-kit.md +0 -147
  989. package/.agent/skills/motion-styles/prompts/chalk-talk.md +0 -153
  990. package/.agent/skills/motion-styles/prompts/chat-story.md +0 -143
  991. package/.agent/skills/motion-styles/prompts/clay-diorama.md +0 -149
  992. package/.agent/skills/motion-styles/prompts/clean-tech.md +0 -147
  993. package/.agent/skills/motion-styles/prompts/comic-panels.md +0 -145
  994. package/.agent/skills/motion-styles/prompts/cosmic-scale.md +0 -147
  995. package/.agent/skills/motion-styles/prompts/creator-motion.md +0 -149
  996. package/.agent/skills/motion-styles/prompts/data-city.md +0 -149
  997. package/.agent/skills/motion-styles/prompts/data-story.md +0 -147
  998. package/.agent/skills/motion-styles/prompts/deep-layers.md +0 -147
  999. package/.agent/skills/motion-styles/prompts/doc-cinema.md +0 -145
  1000. package/.agent/skills/motion-styles/prompts/evidence-board.md +0 -145
  1001. package/.agent/skills/motion-styles/prompts/exploded-view.md +0 -149
  1002. package/.agent/skills/motion-styles/prompts/glass-icons.md +0 -147
  1003. package/.agent/skills/motion-styles/prompts/glitch-pack.md +0 -145
  1004. package/.agent/skills/motion-styles/prompts/globe-arcs.md +0 -147
  1005. package/.agent/skills/motion-styles/prompts/headline-backdrop.md +0 -145
  1006. package/.agent/skills/motion-styles/prompts/iso-cutaway.md +0 -147
  1007. package/.agent/skills/motion-styles/prompts/isotype.md +0 -145
  1008. package/.agent/skills/motion-styles/prompts/karaoke-subs.md +0 -145
  1009. package/.agent/skills/motion-styles/prompts/low-poly-atlas.md +0 -149
  1010. package/.agent/skills/motion-styles/prompts/lower-thirds.md +0 -147
  1011. package/.agent/skills/motion-styles/prompts/marker-board.md +0 -145
  1012. package/.agent/skills/motion-styles/prompts/market-tape.md +0 -145
  1013. package/.agent/skills/motion-styles/prompts/math-canvas.md +0 -145
  1014. package/.agent/skills/motion-styles/prompts/myth-or-fact.md +0 -145
  1015. package/.agent/skills/motion-styles/prompts/neon-line.md +0 -145
  1016. package/.agent/skills/motion-styles/prompts/paper-craft.md +0 -145
  1017. package/.agent/skills/motion-styles/prompts/parchment-map.md +0 -147
  1018. package/.agent/skills/motion-styles/prompts/physics-toybox.md +0 -147
  1019. package/.agent/skills/motion-styles/prompts/pixel-quest.md +0 -145
  1020. package/.agent/skills/motion-styles/prompts/podcast-clip.md +0 -147
  1021. package/.agent/skills/motion-styles/prompts/pop-info.md +0 -145
  1022. package/.agent/skills/motion-styles/prompts/product-turntable.md +0 -149
  1023. package/.agent/skills/motion-styles/prompts/punch-captions.md +0 -145
  1024. package/.agent/skills/motion-styles/prompts/quiz-countdown.md +0 -145
  1025. package/.agent/skills/motion-styles/prompts/quote-cinema.md +0 -145
  1026. package/.agent/skills/motion-styles/prompts/recipe-steps.md +0 -145
  1027. package/.agent/skills/motion-styles/prompts/rubber-hose-reel.md +0 -145
  1028. package/.agent/skills/motion-styles/prompts/shadow-puppets.md +0 -145
  1029. package/.agent/skills/motion-styles/prompts/size-lineup.md +0 -149
  1030. package/.agent/skills/motion-styles/prompts/software-walkthrough.md +0 -147
  1031. package/.agent/skills/motion-styles/prompts/stack-split.md +0 -145
  1032. package/.agent/skills/motion-styles/prompts/sticker-pop.md +0 -145
  1033. package/.agent/skills/motion-styles/prompts/storybook.md +0 -145
  1034. package/.agent/skills/motion-styles/prompts/swiss-grid.md +0 -145
  1035. package/.agent/skills/motion-styles/prompts/synth-grid.md +0 -147
  1036. package/.agent/skills/motion-styles/prompts/tactics-board.md +0 -145
  1037. package/.agent/skills/motion-styles/prompts/terminal.md +0 -145
  1038. package/.agent/skills/motion-styles/prompts/tier-list.md +0 -143
  1039. package/.agent/skills/motion-styles/prompts/timeline-rail.md +0 -145
  1040. package/.agent/skills/motion-styles/prompts/top-5-countdown.md +0 -145
  1041. package/.agent/skills/motion-styles/prompts/versus-split.md +0 -145
  1042. package/.agent/skills/motion-styles/prompts/vhs-camcorder.md +0 -145
  1043. package/.agent/skills/motion-styles/prompts/visual-novel.md +0 -145
  1044. package/.agent/skills/motion-styles/prompts/warm-canvas.md +0 -145
  1045. package/.agent/skills/motion-styles/prompts/woodcut-tale.md +0 -145
  1046. package/.agent/skills/motion-styles/prompts/zine-collage.md +0 -145
  1047. package/.agent/skills/motion-styles/sources/bio-glow.html +0 -639
  1048. package/.agent/skills/motion-styles/sources/blueprint.html +0 -617
  1049. package/.agent/skills/motion-styles/sources/breaking-desk.html +0 -585
  1050. package/.agent/skills/motion-styles/sources/callout-kit.html +0 -586
  1051. package/.agent/skills/motion-styles/sources/chalk-talk.html +0 -597
  1052. package/.agent/skills/motion-styles/sources/chat-story.html +0 -597
  1053. package/.agent/skills/motion-styles/sources/clay-diorama.html +0 -681
  1054. package/.agent/skills/motion-styles/sources/clean-tech.html +0 -554
  1055. package/.agent/skills/motion-styles/sources/comic-panels.html +0 -608
  1056. package/.agent/skills/motion-styles/sources/cosmic-scale.html +0 -628
  1057. package/.agent/skills/motion-styles/sources/creator-motion.html +0 -805
  1058. package/.agent/skills/motion-styles/sources/data-city.html +0 -613
  1059. package/.agent/skills/motion-styles/sources/data-story.html +0 -539
  1060. package/.agent/skills/motion-styles/sources/deep-layers.html +0 -624
  1061. package/.agent/skills/motion-styles/sources/doc-cinema.html +0 -590
  1062. package/.agent/skills/motion-styles/sources/evidence-board.html +0 -610
  1063. package/.agent/skills/motion-styles/sources/exploded-view.html +0 -618
  1064. package/.agent/skills/motion-styles/sources/glass-icons.html +0 -612
  1065. package/.agent/skills/motion-styles/sources/glitch-pack.html +0 -583
  1066. package/.agent/skills/motion-styles/sources/globe-arcs.html +0 -621
  1067. package/.agent/skills/motion-styles/sources/headline-backdrop.html +0 -584
  1068. package/.agent/skills/motion-styles/sources/iso-cutaway.html +0 -621
  1069. package/.agent/skills/motion-styles/sources/isotype.html +0 -588
  1070. package/.agent/skills/motion-styles/sources/karaoke-subs.html +0 -577
  1071. package/.agent/skills/motion-styles/sources/low-poly-atlas.html +0 -621
  1072. package/.agent/skills/motion-styles/sources/lower-thirds.html +0 -582
  1073. package/.agent/skills/motion-styles/sources/marker-board.html +0 -598
  1074. package/.agent/skills/motion-styles/sources/market-tape.html +0 -598
  1075. package/.agent/skills/motion-styles/sources/math-canvas.html +0 -592
  1076. package/.agent/skills/motion-styles/sources/myth-or-fact.html +0 -587
  1077. package/.agent/skills/motion-styles/sources/neon-line.html +0 -597
  1078. package/.agent/skills/motion-styles/sources/paper-craft.html +0 -612
  1079. package/.agent/skills/motion-styles/sources/parchment-map.html +0 -624
  1080. package/.agent/skills/motion-styles/sources/physics-toybox.html +0 -617
  1081. package/.agent/skills/motion-styles/sources/pixel-quest.html +0 -609
  1082. package/.agent/skills/motion-styles/sources/podcast-clip.html +0 -585
  1083. package/.agent/skills/motion-styles/sources/pop-info.html +0 -677
  1084. package/.agent/skills/motion-styles/sources/product-turntable.html +0 -611
  1085. package/.agent/skills/motion-styles/sources/punch-captions.html +0 -583
  1086. package/.agent/skills/motion-styles/sources/quiz-countdown.html +0 -593
  1087. package/.agent/skills/motion-styles/sources/quote-cinema.html +0 -581
  1088. package/.agent/skills/motion-styles/sources/recipe-steps.html +0 -599
  1089. package/.agent/skills/motion-styles/sources/rubber-hose-reel.html +0 -627
  1090. package/.agent/skills/motion-styles/sources/shadow-puppets.html +0 -609
  1091. package/.agent/skills/motion-styles/sources/size-lineup.html +0 -620
  1092. package/.agent/skills/motion-styles/sources/software-walkthrough.html +0 -651
  1093. package/.agent/skills/motion-styles/sources/stack-split.html +0 -588
  1094. package/.agent/skills/motion-styles/sources/sticker-pop.html +0 -587
  1095. package/.agent/skills/motion-styles/sources/storybook.html +0 -600
  1096. package/.agent/skills/motion-styles/sources/swiss-grid.html +0 -590
  1097. package/.agent/skills/motion-styles/sources/synth-grid.html +0 -614
  1098. package/.agent/skills/motion-styles/sources/tactics-board.html +0 -608
  1099. package/.agent/skills/motion-styles/sources/terminal.html +0 -593
  1100. package/.agent/skills/motion-styles/sources/tier-list.html +0 -600
  1101. package/.agent/skills/motion-styles/sources/timeline-rail.html +0 -598
  1102. package/.agent/skills/motion-styles/sources/top-5-countdown.html +0 -589
  1103. package/.agent/skills/motion-styles/sources/versus-split.html +0 -592
  1104. package/.agent/skills/motion-styles/sources/vhs-camcorder.html +0 -587
  1105. package/.agent/skills/motion-styles/sources/visual-novel.html +0 -623
  1106. package/.agent/skills/motion-styles/sources/warm-canvas.html +0 -742
  1107. package/.agent/skills/motion-styles/sources/woodcut-tale.html +0 -598
  1108. package/.agent/skills/motion-styles/sources/zine-collage.html +0 -594
  1109. package/.agent/skills/reverse-engineer-anything/SKILL.md +0 -356
  1110. package/.agent/skills/reverse-engineer-anything/references/android-applications.md +0 -36
  1111. package/.agent/skills/reverse-engineer-anything/references/connection-and-recovery.md +0 -127
  1112. package/.agent/skills/reverse-engineer-anything/references/evidence-workflows.md +0 -71
  1113. package/.agent/skills/reverse-engineer-anything/references/javascript-applications.md +0 -218
  1114. package/.agent/skills/reverse-engineer-anything/references/native-and-artifacts.md +0 -297
  1115. package/.agent/skills/reverse-engineer-anything/references/runtime-observation.md +0 -78
  1116. package/.agent/skills/video-pipeline/SKILL.md +0 -384
  1117. package/.agent/skills/video-pipeline/assets/project/ANIMATION_GUIDE.md +0 -258
  1118. package/.agent/skills/video-pipeline/assets/project/STORYBOARD.md +0 -77
  1119. package/.agent/skills/video-pipeline/assets/project/index.html +0 -80
  1120. package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/diagram-author.md +0 -43
  1121. package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +0 -75
  1122. package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +0 -63
  1123. package/.agent/skills/video-pipeline/assets/project/reel/engine.css +0 -140
  1124. package/.agent/skills/video-pipeline/assets/project/reel/engine.js +0 -2143
  1125. package/.agent/skills/video-pipeline/assets/project/reel/virtual-clock.js +0 -168
  1126. package/.agent/skills/video-pipeline/references/mcp-tools.md +0 -441
  1127. package/.agent/skills/video-pipeline/references/motion-craft.md +0 -91
  1128. package/.agent/skills/video-pipeline/references/providers.md +0 -174
  1129. package/.agent/skills/video-pipeline/references/render-contract.md +0 -65
@@ -1,2779 +0,0 @@
1
- /**
2
- * Integration tests for all browse commands
3
- *
4
- * Tests run against a local test server serving fixture HTML files.
5
- * A real browse server is started and commands are sent via the CLI HTTP interface.
6
- */
7
-
8
- import * as os from 'os';
9
- import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
10
- import { startTestServer } from './test-server';
11
- import { BrowserManager } from '../src/browser-manager';
12
- import { resolveServerScript } from '../src/cli';
13
- import { handleReadCommand as _handleReadCommand, parseOutArgs, hasOutArg, resultToString } from '../src/read-commands';
14
- import { handleWriteCommand as _handleWriteCommand } from '../src/write-commands';
15
- import { handleMetaCommand } from '../src/meta-commands';
16
- import { WRITE_COMMANDS, READ_COMMANDS, META_COMMANDS, PAGE_CONTENT_COMMANDS, wrapUntrustedContent } from '../src/commands';
17
- import { consoleBuffer, networkBuffer, dialogBuffer, addConsoleEntry, addNetworkEntry, addDialogEntry, CircularBuffer } from '../src/buffers';
18
- import * as fs from 'fs';
19
- import { spawn } from 'child_process';
20
- import { isProcessAlive } from '../src/error-handling';
21
- import * as path from 'path';
22
- import * as os from 'os';
23
-
24
- // Temp files live under os.tmpdir(), never a hardcoded /tmp: macOS points
25
- // tmpdir at a per-user private dir, and syscall-supervised sandboxes
26
- // (Vercel) blanket-deny access(2) under /tmp for busy processes while
27
- // honoring TMPDIR overrides. Hardcoded /tmp is a portability smell.
28
- const tmpp = (name: string) => path.join(os.tmpdir(), name);
29
-
30
-
31
- // Per-FILE Chromium profile: this file launches an in-process persistent
32
- // context (BrowserManager.launch()), and sharing a profile dir with the
33
- // long-lived browse daemon a sibling file may have spawned kills one side's
34
- // Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
35
- // module scope (see test/gstack-home-module-scope.test.ts's rationale).
36
- const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
37
- let CHROMIUM_PROFILE_DIR: string | undefined;
38
- beforeAll(() => {
39
- CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
40
- process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
41
- });
42
- afterAll(() => {
43
- if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
44
- else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
45
- if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
46
- });
47
-
48
-
49
- // Thin wrappers that bridge old test calls (bm as 3rd arg) to new signatures (session + bm)
50
- const handleReadCommand = (cmd: string, args: string[], b: BrowserManager) =>
51
- _handleReadCommand(cmd, args, b.getActiveSession(), b);
52
- const handleWriteCommand = (cmd: string, args: string[], b: BrowserManager) =>
53
- _handleWriteCommand(cmd, args, b.getActiveSession(), b);
54
-
55
- // Chain routes every subcommand through the server's executeCommand pipeline in
56
- // production (the direct-dispatch fallback was deleted — it skipped the security
57
- // gates). Tests mirror the pipeline minimally: real handlers + trust-wrapping,
58
- // server-shaped {status, result} envelope.
59
- function makeChainExecute(b: BrowserManager) {
60
- return async (body: { command: string; args?: string[] }) => {
61
- const name = body.command;
62
- const args = body.args ?? [];
63
- try {
64
- let result: string;
65
- if (WRITE_COMMANDS.has(name)) {
66
- result = await _handleWriteCommand(name, args, b.getActiveSession(), b);
67
- } else if (READ_COMMANDS.has(name)) {
68
- result = await _handleReadCommand(name, args, b.getActiveSession(), b);
69
- if (PAGE_CONTENT_COMMANDS.has(name)) {
70
- result = wrapUntrustedContent(result, b.getCurrentUrl());
71
- }
72
- } else if (META_COMMANDS.has(name)) {
73
- result = await handleMetaCommand(name, args, b, async () => {});
74
- } else {
75
- return { status: 404, result: JSON.stringify({ error: `Unknown command: ${name}` }) };
76
- }
77
- return { status: 200, result };
78
- } catch (err: any) {
79
- return { status: 500, result: JSON.stringify({ error: err.message }) };
80
- }
81
- };
82
- }
83
- const chainMeta = (b: BrowserManager, args: string[]) =>
84
- handleMetaCommand('chain', args, b, async () => {}, null, { executeCommand: makeChainExecute(b) });
85
-
86
- // ─── Pure arg-parser + result-conversion unit tests (no browser) ───
87
- describe('parseOutArgs / hasOutArg', () => {
88
- test('--out <path> splits the flag from the positional', () => {
89
- expect(parseOutArgs(['expr', '--out', '/tmp/x'])).toEqual({ outPath: '/tmp/x', raw: false, rest: ['expr'] });
90
- });
91
-
92
- test('--out=<path> form is equivalent', () => {
93
- expect(parseOutArgs(['expr', '--out=/tmp/x'])).toEqual({ outPath: '/tmp/x', raw: false, rest: ['expr'] });
94
- });
95
-
96
- test('flag ordering does not matter', () => {
97
- expect(parseOutArgs(['--out', '/tmp/x', 'expr'])).toEqual({ outPath: '/tmp/x', raw: false, rest: ['expr'] });
98
- });
99
-
100
- test('--raw and --raw=true|false', () => {
101
- expect(parseOutArgs(['e', '--out', '/tmp/x', '--raw']).raw).toBe(true);
102
- expect(parseOutArgs(['e', '--out', '/tmp/x', '--raw=true']).raw).toBe(true);
103
- expect(parseOutArgs(['e', '--out', '/tmp/x', '--raw=false']).raw).toBe(false);
104
- });
105
-
106
- test('repeated --out throws', () => {
107
- expect(() => parseOutArgs(['e', '--out', '/a', '--out', '/b'])).toThrow(/more than once/);
108
- });
109
-
110
- test('--out with a missing value throws', () => {
111
- expect(() => parseOutArgs(['e', '--out'])).toThrow(/requires a file path/);
112
- expect(() => parseOutArgs(['e', '--out', '--raw'])).toThrow(/requires a file path/);
113
- expect(() => parseOutArgs(['e', '--out='])).toThrow(/requires a file path/);
114
- });
115
-
116
- test('bad --raw value throws', () => {
117
- expect(() => parseOutArgs(['e', '--out', '/a', '--raw=maybe'])).toThrow(/--raw must be true or false/);
118
- });
119
-
120
- test('hasOutArg matches --out and --out= exactly, not lookalikes', () => {
121
- expect(hasOutArg(['a', '--out', 'b'])).toBe(true);
122
- expect(hasOutArg(['a', '--out=b'])).toBe(true);
123
- expect(hasOutArg(['a'])).toBe(false);
124
- expect(hasOutArg(['a', '--output', 'b'])).toBe(false);
125
- expect(hasOutArg(['a', '--outx'])).toBe(false);
126
- });
127
- });
128
-
129
- describe('resultToString — byte-for-byte with pre-refactor behavior', () => {
130
- test('null becomes "null" (typeof null === object → JSON.stringify)', () => {
131
- expect(resultToString(null)).toBe('null');
132
- });
133
- test('undefined becomes empty string', () => {
134
- expect(resultToString(undefined)).toBe('');
135
- });
136
- test('objects are pretty-printed JSON', () => {
137
- expect(resultToString({ a: 1 })).toBe(JSON.stringify({ a: 1 }, null, 2));
138
- });
139
- test('primitives use String()', () => {
140
- expect(resultToString(42)).toBe('42');
141
- expect(resultToString(true)).toBe('true');
142
- });
143
- });
144
-
145
- let testServer: ReturnType<typeof startTestServer>;
146
- let bm: BrowserManager;
147
- let baseUrl: string;
148
-
149
- beforeAll(async () => {
150
- testServer = startTestServer(0);
151
- baseUrl = testServer.url;
152
-
153
- bm = new BrowserManager();
154
- await bm.launch();
155
- });
156
-
157
- afterAll(async () => {
158
- try { testServer.server.stop(true); } catch {} // force-close keep-alives — a lingering Chromium connection otherwise blocks stop() forever
159
- // Close only this file's own browser — never process.exit(): bun test runs
160
- // all files in one process, so a delayed exit kills the whole suite
161
- // (see test/no-suicide-exit.test.ts). close() can hang when the browser
162
- // already died, and its internal 5s timeout ties bun's 5s hook timeout —
163
- // so race it at 3s and abandon; the child is reaped at process exit.
164
- try { await Promise.race([bm?.close(), new Promise((resolve) => setTimeout(resolve, 3000))]); } catch {}
165
- });
166
-
167
- // ─── Navigation ─────────────────────────────────────────────────
168
-
169
- describe('Navigation', () => {
170
- test('goto navigates to URL', async () => {
171
- const result = await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
172
- expect(result).toContain('Navigated to');
173
- expect(result).toContain('200');
174
- });
175
-
176
- test('url returns current URL', async () => {
177
- const result = await handleMetaCommand('url', [], bm, async () => {});
178
- expect(result).toContain('/basic.html');
179
- });
180
-
181
- test('back goes back', async () => {
182
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
183
- const result = await handleWriteCommand('back', [], bm);
184
- expect(result).toContain('Back');
185
- });
186
-
187
- test('forward goes forward', async () => {
188
- const result = await handleWriteCommand('forward', [], bm);
189
- expect(result).toContain('Forward');
190
- });
191
-
192
- test('reload reloads page', async () => {
193
- const result = await handleWriteCommand('reload', [], bm);
194
- expect(result).toContain('Reloaded');
195
- });
196
- });
197
-
198
- // ─── Content Extraction ─────────────────────────────────────────
199
-
200
- describe('Content extraction', () => {
201
- beforeAll(async () => {
202
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
203
- });
204
-
205
- test('text returns cleaned page text', async () => {
206
- const result = await handleReadCommand('text', [], bm);
207
- expect(result).toContain('Hello World');
208
- expect(result).toContain('Item one');
209
- expect(result).not.toContain('<h1>');
210
- });
211
-
212
- test('html returns full page HTML', async () => {
213
- const result = await handleReadCommand('html', [], bm);
214
- expect(result).toContain('<!DOCTYPE html>');
215
- expect(result).toContain('<h1 id="title">Hello World</h1>');
216
- });
217
-
218
- test('html with selector returns element innerHTML', async () => {
219
- const result = await handleReadCommand('html', ['#content'], bm);
220
- expect(result).toContain('Some body text here.');
221
- expect(result).toContain('<li>Item one</li>');
222
- });
223
-
224
- test('links returns all links', async () => {
225
- const result = await handleReadCommand('links', [], bm);
226
- expect(result).toContain('Page 1');
227
- expect(result).toContain('Page 2');
228
- expect(result).toContain('External');
229
- expect(result).toContain('→');
230
- });
231
-
232
- test('forms discovers form fields', async () => {
233
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
234
- const result = await handleReadCommand('forms', [], bm);
235
- const forms = JSON.parse(result);
236
- expect(forms.length).toBe(2);
237
- expect(forms[0].id).toBe('login-form');
238
- expect(forms[0].method).toBe('post');
239
- expect(forms[0].fields.length).toBeGreaterThanOrEqual(2);
240
- expect(forms[1].id).toBe('profile-form');
241
-
242
- // Check field discovery
243
- const emailField = forms[0].fields.find((f: any) => f.name === 'email');
244
- expect(emailField).toBeDefined();
245
- expect(emailField.type).toBe('email');
246
- expect(emailField.required).toBe(true);
247
- });
248
-
249
- test('accessibility returns ARIA tree', async () => {
250
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
251
- const result = await handleReadCommand('accessibility', [], bm);
252
- expect(result).toContain('Hello World');
253
- });
254
- });
255
-
256
- // ─── JavaScript / CSS / Attrs ───────────────────────────────────
257
-
258
- describe('Inspection', () => {
259
- beforeAll(async () => {
260
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
261
- });
262
-
263
- test('js evaluates expression', async () => {
264
- const result = await handleReadCommand('js', ['document.title'], bm);
265
- expect(result).toBe('Test Page - Basic');
266
- });
267
-
268
- test('js returns objects as JSON', async () => {
269
- const result = await handleReadCommand('js', ['({a: 1, b: 2})'], bm);
270
- const obj = JSON.parse(result);
271
- expect(obj.a).toBe(1);
272
- expect(obj.b).toBe(2);
273
- });
274
-
275
- test('js supports await expressions', async () => {
276
- const result = await handleReadCommand('js', ['await Promise.resolve(42)'], bm);
277
- expect(result).toBe('42');
278
- });
279
-
280
- test('js does not false-positive on await substring', async () => {
281
- const result = await handleReadCommand('js', ['(() => { const awaitable = 5; return awaitable })()'], bm);
282
- expect(result).toBe('5');
283
- });
284
-
285
- test('eval supports await in single-line file', async () => {
286
- const tmp = tmpp('eval-await-test.js');
287
- fs.writeFileSync(tmp, 'await Promise.resolve("hello from eval")');
288
- try {
289
- const result = await handleReadCommand('eval', [tmp], bm);
290
- expect(result).toBe('hello from eval');
291
- } finally {
292
- fs.unlinkSync(tmp);
293
- }
294
- });
295
-
296
- test('eval does not wrap when await is only in a comment', async () => {
297
- const tmp = tmpp('eval-comment-test.js');
298
- fs.writeFileSync(tmp, '// no need to await this\ndocument.title');
299
- try {
300
- const result = await handleReadCommand('eval', [tmp], bm);
301
- expect(result).toBe('Test Page - Basic');
302
- } finally {
303
- fs.unlinkSync(tmp);
304
- }
305
- });
306
-
307
- test('eval multi-line with await and explicit return', async () => {
308
- const tmp = tmpp('eval-multiline-await.js');
309
- fs.writeFileSync(tmp, 'const data = await Promise.resolve("multi");\nreturn data;');
310
- try {
311
- const result = await handleReadCommand('eval', [tmp], bm);
312
- expect(result).toBe('multi');
313
- } finally {
314
- fs.unlinkSync(tmp);
315
- }
316
- });
317
-
318
- test('eval multi-line with await but no return gives empty string', async () => {
319
- const tmp = tmpp('eval-multiline-no-return.js');
320
- fs.writeFileSync(tmp, 'const data = await Promise.resolve("lost");\ndata;');
321
- try {
322
- const result = await handleReadCommand('eval', [tmp], bm);
323
- expect(result).toBe('');
324
- } finally {
325
- fs.unlinkSync(tmp);
326
- }
327
- });
328
-
329
- test('js handles multi-line with await', async () => {
330
- const code = 'const x = await Promise.resolve(42);\nreturn x;';
331
- const result = await handleReadCommand('js', [code], bm);
332
- expect(result).toBe('42');
333
- });
334
-
335
- test('js handles await with semicolons', async () => {
336
- const result = await handleReadCommand('js', ['const x = await Promise.resolve(5); return x + 1;'], bm);
337
- expect(result).toBe('6');
338
- });
339
-
340
- test('js handles await with statement keywords', async () => {
341
- const result = await handleReadCommand('js', ['const res = await Promise.resolve("ok"); return res;'], bm);
342
- expect(result).toBe('ok');
343
- });
344
-
345
- test('js still works for simple expressions', async () => {
346
- const result = await handleReadCommand('js', ['1 + 2'], bm);
347
- expect(result).toBe('3');
348
- });
349
-
350
- // ─── js/eval --out (render-to-file) ───────────────────────────
351
-
352
- test('js (no --out) returns a multi-MB string without truncation', async () => {
353
- // Handler-level guarantee: the result is not sliced/capped before return.
354
- // (Full HTTP egress path is exercised elsewhere; this pins the handler.)
355
- const result = await handleReadCommand('js', ["'x'.repeat(3 * 1024 * 1024)"], bm);
356
- expect(result.length).toBe(3 * 1024 * 1024);
357
- });
358
-
359
- test('js --out writes the result to disk and returns a short status, not the payload', async () => {
360
- const out = tmpp(`browse-out-large-${Date.now()}.txt`);
361
- try {
362
- const result = await handleReadCommand('js', ["'y'.repeat(2 * 1024 * 1024)", '--out', out], bm);
363
- expect(result).toContain('JS result written:');
364
- expect(result).toContain(out);
365
- expect(result).toContain(`(${2 * 1024 * 1024} bytes)`);
366
- expect(result.length).toBeLessThan(200); // status, not the 2MB payload
367
- expect(fs.statSync(out).size).toBe(2 * 1024 * 1024);
368
- } finally {
369
- fs.rmSync(out, { force: true });
370
- }
371
- });
372
-
373
- test('js --out decodes a base64 PNG data URL to real bytes', async () => {
374
- // 1x1 transparent PNG.
375
- const b64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
376
- const out = tmpp(`browse-out-png-${Date.now()}.png`);
377
- try {
378
- const result = await handleReadCommand('js', [`'data:image/png;base64,' + '${b64}'`, '--out', out], bm);
379
- const buf = fs.readFileSync(out);
380
- // PNG magic bytes: 89 50 4E 47
381
- expect([buf[0], buf[1], buf[2], buf[3]]).toEqual([0x89, 0x50, 0x4e, 0x47]);
382
- const expectedLen = Buffer.from(b64, 'base64').length;
383
- expect(buf.length).toBe(expectedLen);
384
- expect(result).toContain(`(${expectedLen} bytes)`);
385
- } finally {
386
- fs.rmSync(out, { force: true });
387
- }
388
- });
389
-
390
- test('js --out --raw writes the literal data-URL string (no decode)', async () => {
391
- const dataUrl = 'data:text/plain;base64,aGVsbG8=';
392
- const out = tmpp(`browse-out-raw-${Date.now()}.txt`);
393
- try {
394
- await handleReadCommand('js', [`'${dataUrl}'`, '--out', out, '--raw'], bm);
395
- expect(fs.readFileSync(out, 'utf-8')).toBe(dataUrl);
396
- } finally {
397
- fs.rmSync(out, { force: true });
398
- }
399
- });
400
-
401
- test('js --out throws on a malformed base64 data URL instead of writing corrupt bytes', async () => {
402
- const out = tmpp(`browse-out-bad-${Date.now()}.png`);
403
- try {
404
- await expect(
405
- handleReadCommand('js', ["'data:image/png;base64,!!!not-base64!!!'", '--out', out], bm)
406
- ).rejects.toThrow(/malformed base64/);
407
- expect(fs.existsSync(out)).toBe(false);
408
- } finally {
409
- fs.rmSync(out, { force: true });
410
- }
411
- });
412
-
413
- test('js --out rejects a path outside the safe directories', async () => {
414
- await expect(
415
- handleReadCommand('js', ['1 + 1', '--out', '/etc/browse-should-not-write.txt'], bm)
416
- ).rejects.toThrow();
417
- });
418
-
419
- test('js --out creates a missing parent directory', async () => {
420
- // validateOutputPath resolves the parent's realpath, so it permits one level
421
- // of missing dir under a safe root (/tmp). mkdir then materializes it.
422
- const root = tmpp(`browse-out-nested-${Date.now()}`);
423
- const out = `${root}/result.txt`;
424
- try {
425
- await handleReadCommand('js', ["'nested'", '--out', out], bm);
426
- expect(fs.readFileSync(out, 'utf-8')).toBe('nested');
427
- } finally {
428
- fs.rmSync(root, { recursive: true, force: true });
429
- }
430
- });
431
-
432
- test('eval --out writes the file result to disk (parity with js)', async () => {
433
- const script = tmpp(`browse-eval-out-src-${Date.now()}.js`);
434
- const out = tmpp(`browse-eval-out-${Date.now()}.txt`);
435
- fs.writeFileSync(script, "'from eval'");
436
- try {
437
- const result = await handleReadCommand('eval', [script, '--out', out], bm);
438
- expect(result).toContain('Eval result written:');
439
- expect(fs.readFileSync(out, 'utf-8')).toBe('from eval');
440
- } finally {
441
- fs.rmSync(script, { force: true });
442
- fs.rmSync(out, { force: true });
443
- }
444
- });
445
-
446
- test('css returns computed property', async () => {
447
- const result = await handleReadCommand('css', ['h1', 'color'], bm);
448
- // Navy color
449
- expect(result).toContain('0, 0, 128');
450
- });
451
-
452
- test('css returns font-family', async () => {
453
- const result = await handleReadCommand('css', ['body', 'font-family'], bm);
454
- expect(result).toContain('Helvetica');
455
- });
456
-
457
- test('attrs returns element attributes', async () => {
458
- const result = await handleReadCommand('attrs', ['#content'], bm);
459
- const attrs = JSON.parse(result);
460
- expect(attrs.id).toBe('content');
461
- expect(attrs['data-testid']).toBe('main-content');
462
- expect(attrs['data-version']).toBe('1.0');
463
- });
464
- });
465
-
466
- // ─── Interaction ────────────────────────────────────────────────
467
-
468
- describe('Interaction', () => {
469
- test('fill + click works on form', async () => {
470
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
471
-
472
- let result = await handleWriteCommand('fill', ['#email', 'test@example.com'], bm);
473
- expect(result).toContain('Filled');
474
-
475
- result = await handleWriteCommand('fill', ['#password', 'secret123'], bm);
476
- expect(result).toContain('Filled');
477
-
478
- // Verify values were set
479
- const emailVal = await handleReadCommand('js', ['document.querySelector("#email").value'], bm);
480
- expect(emailVal).toBe('test@example.com');
481
-
482
- result = await handleWriteCommand('click', ['#login-btn'], bm);
483
- expect(result).toContain('Clicked');
484
- });
485
-
486
- test('select works on dropdown', async () => {
487
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
488
- const result = await handleWriteCommand('select', ['#role', 'admin'], bm);
489
- expect(result).toContain('Selected');
490
-
491
- const val = await handleReadCommand('js', ['document.querySelector("#role").value'], bm);
492
- expect(val).toBe('admin');
493
- });
494
-
495
- test('click on option ref auto-routes to selectOption', async () => {
496
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
497
- // Reset select to default
498
- await handleReadCommand('js', ['document.querySelector("#role").value = ""'], bm);
499
- const snap = await handleMetaCommand('snapshot', [], bm, async () => {});
500
- // Find an option ref (e.g., "Admin" option)
501
- const optionLine = snap.split('\n').find((l: string) => l.includes('[option]') && l.includes('"Admin"'));
502
- expect(optionLine).toBeDefined();
503
- const refMatch = optionLine!.match(/@(e\d+)/);
504
- expect(refMatch).toBeDefined();
505
- const ref = `@${refMatch![1]}`;
506
- const result = await handleWriteCommand('click', [ref], bm);
507
- expect(result).toContain('auto-routed');
508
- expect(result).toContain('Selected');
509
- // Verify the select value actually changed
510
- const val = await handleReadCommand('js', ['document.querySelector("#role").value'], bm);
511
- expect(val).toBe('admin');
512
- });
513
-
514
- test('click CSS selector on option gives helpful error', async () => {
515
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
516
- try {
517
- await handleWriteCommand('click', ['option[value="admin"]'], bm);
518
- expect(true).toBe(false); // Should not reach here
519
- } catch (err: any) {
520
- expect(err.message).toContain('select');
521
- expect(err.message).toContain('option');
522
- }
523
- }, 15000);
524
-
525
- test('hover works', async () => {
526
- const result = await handleWriteCommand('hover', ['h1'], bm);
527
- expect(result).toContain('Hovered');
528
- });
529
-
530
- test('wait finds existing element', async () => {
531
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
532
- const result = await handleWriteCommand('wait', ['#title'], bm);
533
- expect(result).toContain('appeared');
534
- });
535
-
536
- test('scroll works', async () => {
537
- const result = await handleWriteCommand('scroll', ['footer'], bm);
538
- expect(result).toContain('Scrolled');
539
- });
540
-
541
- test('viewport changes size', async () => {
542
- const result = await handleWriteCommand('viewport', ['375x812'], bm);
543
- expect(result).toContain('Viewport set');
544
-
545
- const size = await handleReadCommand('js', ['`${window.innerWidth}x${window.innerHeight}`'], bm);
546
- expect(size).toBe('375x812');
547
-
548
- // Reset
549
- await handleWriteCommand('viewport', ['1280x720'], bm);
550
- });
551
-
552
- test('type and press work', async () => {
553
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
554
- await handleWriteCommand('click', ['#name'], bm);
555
-
556
- const result = await handleWriteCommand('type', ['John Doe'], bm);
557
- expect(result).toContain('Typed');
558
-
559
- const val = await handleReadCommand('js', ['document.querySelector("#name").value'], bm);
560
- expect(val).toBe('John Doe');
561
- });
562
-
563
- test('type --selector types into that element, not the focused one (#2936)', async () => {
564
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
565
- await handleWriteCommand('click', ['#email'], bm);
566
- const result = await handleWriteCommand('type', ['--selector', '#name', 'Jane', 'Roe'], bm);
567
- expect(result).toBe('Typed 8 characters into #name');
568
- expect(await handleReadCommand('js', ['document.querySelector("#name").value'], bm)).toBe('Jane Roe');
569
- expect(await handleReadCommand('js', ['document.querySelector("#email").value'], bm)).toBe('');
570
- });
571
-
572
- test('bare type keeps typing selector-looking text literally, with a hint and no refusal', async () => {
573
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
574
- await handleWriteCommand('click', ['#bio'], bm);
575
- const result = await handleWriteCommand('type', ['#name', 'secret-value'], bm);
576
- expect(result).toContain('Typed 18 characters into the focused element');
577
- expect(result).toContain("hint: \"#name\" looks like a CSS selector. To type into that element: browse type --selector '#name' <text>");
578
- expect(result).not.toContain('secret-value');
579
- expect(await handleReadCommand('js', ['document.querySelector("#bio").value'], bm)).toBe('#name secret-value');
580
- expect(await handleReadCommand('js', ['document.querySelector("#name").value'], bm)).toBe('');
581
- });
582
-
583
- test('prettyscreenshot --hide keeps a trailing image path as the output path (#1419)', async () => {
584
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
585
- const out = path.join(os.tmpdir(), `pretty-hide-${process.pid}.png`);
586
- fs.rmSync(out, { force: true });
587
- try {
588
- const result = await handleWriteCommand('prettyscreenshot', ['--hide', '#title', '#content', out], bm);
589
- expect(result).toContain(out);
590
- expect(fs.existsSync(out)).toBe(true);
591
- expect(await handleReadCommand('js', ['getComputedStyle(document.querySelector("#content")).display'], bm)).toBe('none');
592
- } finally {
593
- fs.rmSync(out, { force: true });
594
- }
595
- });
596
-
597
- test('type -- types text that starts with -- literally; plain text gets no hint', async () => {
598
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
599
- await handleWriteCommand('click', ['#bio'], bm);
600
- expect(await handleWriteCommand('type', ['--', '--selector', 'x'], bm)).toBe('Typed 12 characters');
601
- expect(await handleReadCommand('js', ['document.querySelector("#bio").value'], bm)).toBe('--selector x');
602
- await expect(handleWriteCommand('type', ['--selector'], bm)).rejects.toThrow('Usage: browse type [--selector <sel>] [--] <text>');
603
- await expect(handleWriteCommand('type', ['--selector', '#name'], bm)).rejects.toThrow('Usage');
604
- });
605
- });
606
-
607
- // ─── SPA / Console / Network ───────────────────────────────────
608
-
609
- describe('SPA and buffers', () => {
610
- test('wait handles delayed rendering', async () => {
611
- await handleWriteCommand('goto', [baseUrl + '/spa.html'], bm);
612
- const result = await handleWriteCommand('wait', ['.loaded'], bm);
613
- expect(result).toContain('appeared');
614
-
615
- const text = await handleReadCommand('text', [], bm);
616
- expect(text).toContain('SPA Content Loaded');
617
- });
618
-
619
- test('console captures messages', async () => {
620
- const result = await handleReadCommand('console', [], bm);
621
- expect(result).toContain('[SPA] Starting render');
622
- expect(result).toContain('[SPA] Render complete');
623
- });
624
-
625
- test('console --clear clears buffer', async () => {
626
- const result = await handleReadCommand('console', ['--clear'], bm);
627
- expect(result).toContain('cleared');
628
-
629
- const after = await handleReadCommand('console', [], bm);
630
- expect(after).toContain('no console messages');
631
- });
632
-
633
- test('network captures requests', async () => {
634
- const result = await handleReadCommand('network', [], bm);
635
- expect(result).toContain('GET');
636
- expect(result).toContain('/spa.html');
637
- });
638
-
639
- test('network --clear clears buffer', async () => {
640
- const result = await handleReadCommand('network', ['--clear'], bm);
641
- expect(result).toContain('cleared');
642
- });
643
- });
644
-
645
- // ─── Cookies / Storage ──────────────────────────────────────────
646
-
647
- describe('Cookies and storage', () => {
648
- test('cookies returns array', async () => {
649
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
650
- const result = await handleReadCommand('cookies', [], bm);
651
- // Test server doesn't set cookies, so empty array
652
- expect(result).toBe('[]');
653
- });
654
-
655
- test('storage set and get works', async () => {
656
- await handleReadCommand('storage', ['set', 'testData', 'testValue'], bm);
657
- const result = await handleReadCommand('storage', [], bm);
658
- const storage = JSON.parse(result);
659
- expect(storage.localStorage.testData).toBe('testValue');
660
- });
661
-
662
- test('storage read redacts sensitive keys', async () => {
663
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
664
- await handleReadCommand('storage', ['set', 'auth_token', 'my-secret-token'], bm);
665
- await handleReadCommand('storage', ['set', 'api_key', 'key-12345'], bm);
666
- await handleReadCommand('storage', ['set', 'displayName', 'normalValue'], bm);
667
- const result = await handleReadCommand('storage', [], bm);
668
- const storage = JSON.parse(result);
669
- expect(storage.localStorage.auth_token).toMatch(/REDACTED/);
670
- expect(storage.localStorage.api_key).toMatch(/REDACTED/);
671
- expect(storage.localStorage.displayName).toBe('normalValue');
672
- });
673
-
674
- test('storage read redacts sensitive values by prefix', async () => {
675
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
676
- // JWT value under innocuous key name
677
- await handleReadCommand('storage', ['set', 'userData', 'eyJhbGciOiJIUzI1NiJ9.payload.sig'], bm);
678
- // GitHub PAT under innocuous key name
679
- await handleReadCommand('storage', ['set', 'repoAccess', 'ghp_abc123def456'], bm);
680
- const result = await handleReadCommand('storage', [], bm);
681
- const storage = JSON.parse(result);
682
- expect(storage.localStorage.userData).toMatch(/REDACTED/);
683
- expect(storage.localStorage.repoAccess).toMatch(/REDACTED/);
684
- });
685
-
686
- test('storage redaction includes value length', async () => {
687
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
688
- await handleReadCommand('storage', ['set', 'session_token', 'abc123'], bm);
689
- const result = await handleReadCommand('storage', [], bm);
690
- const storage = JSON.parse(result);
691
- expect(storage.localStorage.session_token).toBe('[REDACTED — 6 chars]');
692
- });
693
- });
694
-
695
- // ─── Performance ────────────────────────────────────────────────
696
-
697
- describe('Performance', () => {
698
- test('perf returns timing data', async () => {
699
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
700
- const result = await handleReadCommand('perf', [], bm);
701
- expect(result).toContain('dns');
702
- expect(result).toContain('ttfb');
703
- expect(result).toContain('load');
704
- expect(result).toContain('ms');
705
- });
706
- });
707
-
708
- // ─── Visual ─────────────────────────────────────────────────────
709
-
710
- describe('Visual', () => {
711
- test('screenshot saves file', async () => {
712
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
713
- const screenshotPath = tmpp('browse-test-screenshot.png');
714
- const result = await handleMetaCommand('screenshot', [screenshotPath], bm, async () => {});
715
- expect(result).toContain('Screenshot saved');
716
- expect(fs.existsSync(screenshotPath)).toBe(true);
717
- const stat = fs.statSync(screenshotPath);
718
- expect(stat.size).toBeGreaterThan(1000);
719
- fs.unlinkSync(screenshotPath);
720
- });
721
-
722
- test('screenshot --viewport saves viewport-only', async () => {
723
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
724
- const p = tmpp('browse-test-viewport.png');
725
- const result = await handleMetaCommand('screenshot', ['--viewport', p], bm, async () => {});
726
- expect(result).toContain('Screenshot saved (viewport)');
727
- expect(fs.existsSync(p)).toBe(true);
728
- expect(fs.statSync(p).size).toBeGreaterThan(1000);
729
- fs.unlinkSync(p);
730
- });
731
-
732
- test('screenshot with CSS selector crops to element', async () => {
733
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
734
- const p = tmpp('browse-test-element-css.png');
735
- const result = await handleMetaCommand('screenshot', ['#title', p], bm, async () => {});
736
- expect(result).toContain('Screenshot saved (element)');
737
- expect(fs.existsSync(p)).toBe(true);
738
- expect(fs.statSync(p).size).toBeGreaterThan(100);
739
- fs.unlinkSync(p);
740
- });
741
-
742
- test('screenshot with @ref crops to element', async () => {
743
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
744
- await handleMetaCommand('snapshot', [], bm, async () => {});
745
- const p = tmpp('browse-test-element-ref.png');
746
- const result = await handleMetaCommand('screenshot', ['@e1', p], bm, async () => {});
747
- expect(result).toContain('Screenshot saved (element)');
748
- expect(fs.existsSync(p)).toBe(true);
749
- expect(fs.statSync(p).size).toBeGreaterThan(100);
750
- fs.unlinkSync(p);
751
- });
752
-
753
- test('screenshot --clip crops to region', async () => {
754
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
755
- const p = tmpp('browse-test-clip.png');
756
- const result = await handleMetaCommand('screenshot', ['--clip', '0,0,100,100', p], bm, async () => {});
757
- expect(result).toContain('Screenshot saved (clip 0,0,100,100)');
758
- expect(fs.existsSync(p)).toBe(true);
759
- expect(fs.statSync(p).size).toBeGreaterThan(100);
760
- fs.unlinkSync(p);
761
- });
762
-
763
- test('screenshot --clip + selector throws', async () => {
764
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
765
- try {
766
- await handleMetaCommand('screenshot', ['--clip', '0,0,100,100', '#title'], bm, async () => {});
767
- expect(true).toBe(false);
768
- } catch (err: any) {
769
- expect(err.message).toContain('Cannot use --clip with a selector/ref');
770
- }
771
- });
772
-
773
- test('screenshot --viewport + --clip throws', async () => {
774
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
775
- try {
776
- await handleMetaCommand('screenshot', ['--viewport', '--clip', '0,0,100,100'], bm, async () => {});
777
- expect(true).toBe(false);
778
- } catch (err: any) {
779
- expect(err.message).toContain('Cannot use --viewport with --clip');
780
- }
781
- });
782
-
783
- test('screenshot --clip with invalid coords throws', async () => {
784
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
785
- try {
786
- await handleMetaCommand('screenshot', ['--clip', 'abc'], bm, async () => {});
787
- expect(true).toBe(false);
788
- } catch (err: any) {
789
- expect(err.message).toContain('all must be numbers');
790
- }
791
- });
792
-
793
- test('screenshot accepts --clip=x,y,w,h and --selector=<css>', async () => {
794
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
795
- const p = tmpp(`browse-test-clip-eq-${Date.now()}.png`);
796
- try {
797
- expect(await handleMetaCommand('screenshot', ['--clip=0,0,10,10', p], bm, async () => {})).toContain('Screenshot saved (clip 0,0,10,10)');
798
- const png = fs.readFileSync(p);
799
- expect([png.readUInt32BE(16), png.readUInt32BE(20)]).toEqual([10, 10]);
800
- expect(await handleMetaCommand('screenshot', ['--selector=#title', p], bm, async () => {})).toContain('Screenshot saved (element)');
801
- } finally {
802
- fs.rmSync(p, { force: true });
803
- }
804
- });
805
-
806
- test('screenshot --clip= keeps the usage errors and the selector conflict', async () => {
807
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
808
- const run = (args: string[]) => handleMetaCommand('screenshot', args, bm, async () => {});
809
- await expect(run(['--clip='])).rejects.toThrow('Usage: screenshot --clip x,y,w,h');
810
- await expect(run(['--clip=1,2,3'])).rejects.toThrow('all must be numbers');
811
- await expect(run(['--selector='])).rejects.toThrow('Usage: screenshot --selector');
812
- await expect(run(['--clip=0,0,10,10', '#title'])).rejects.toThrow('Cannot use --clip with a selector/ref');
813
- await expect(run(['--selector=#title', '--clip=0,0,10,10'])).rejects.toThrow('Cannot use --clip with a selector/ref');
814
- await expect(run(['--viewport=1'])).rejects.toThrow('Unknown screenshot flag');
815
- });
816
-
817
- test('screenshot unknown flag throws', async () => {
818
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
819
- try {
820
- await handleMetaCommand('screenshot', ['--bogus', tmpp('foo.png')], bm, async () => {});
821
- expect(true).toBe(false);
822
- } catch (err: any) {
823
- expect(err.message).toContain('Unknown screenshot flag');
824
- }
825
- });
826
-
827
- test('screenshot --viewport still validates path', async () => {
828
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
829
- try {
830
- await handleMetaCommand('screenshot', ['--viewport', '/etc/evil.png'], bm, async () => {});
831
- expect(true).toBe(false);
832
- } catch (err: any) {
833
- expect(err.message).toContain('Path must be within');
834
- }
835
- });
836
-
837
- test('screenshot treats relative dot-slash path as file path, not CSS selector', async () => {
838
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
839
- // ./path/to/file.png must be treated as output path, not a CSS class selector (#495)
840
- const relPath = './browse-test-dotpath.png';
841
- const absPath = path.resolve(relPath);
842
- const result = await handleMetaCommand('screenshot', [relPath], bm, async () => {});
843
- expect(result).toContain('Screenshot saved');
844
- expect(fs.existsSync(absPath)).toBe(true);
845
- fs.unlinkSync(absPath);
846
- });
847
-
848
- test('screenshot with nonexistent selector throws timeout', async () => {
849
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
850
- try {
851
- await handleMetaCommand('screenshot', ['.nonexistent-element-xyz'], bm, async () => {});
852
- expect(true).toBe(false);
853
- } catch (err: any) {
854
- expect(err.message).toBeDefined();
855
- }
856
- }, 10000);
857
-
858
- test('responsive saves 3 screenshots', async () => {
859
- await handleWriteCommand('goto', [baseUrl + '/responsive.html'], bm);
860
- const prefix = tmpp('browse-test-resp');
861
- const result = await handleMetaCommand('responsive', [prefix], bm, async () => {});
862
- expect(result).toContain('mobile');
863
- expect(result).toContain('tablet');
864
- expect(result).toContain('desktop');
865
-
866
- expect(fs.existsSync(`${prefix}-mobile.png`)).toBe(true);
867
- expect(fs.existsSync(`${prefix}-tablet.png`)).toBe(true);
868
- expect(fs.existsSync(`${prefix}-desktop.png`)).toBe(true);
869
-
870
- // Cleanup
871
- fs.unlinkSync(`${prefix}-mobile.png`);
872
- fs.unlinkSync(`${prefix}-tablet.png`);
873
- fs.unlinkSync(`${prefix}-desktop.png`);
874
- });
875
- });
876
-
877
- // ─── Tabs ───────────────────────────────────────────────────────
878
-
879
- describe('Tabs', () => {
880
- test('tabs lists all tabs', async () => {
881
- const result = await handleMetaCommand('tabs', [], bm, async () => {});
882
- expect(result).toContain('[');
883
- expect(result).toContain(']');
884
- });
885
-
886
- test('newtab opens new tab', async () => {
887
- const result = await handleMetaCommand('newtab', [baseUrl + '/forms.html'], bm, async () => {});
888
- expect(result).toContain('Opened tab');
889
-
890
- const tabCount = bm.getTabCount();
891
- expect(tabCount).toBeGreaterThanOrEqual(2);
892
- });
893
-
894
- test('tab switches to specific tab', async () => {
895
- const result = await handleMetaCommand('tab', ['1'], bm, async () => {});
896
- expect(result).toContain('Switched to tab 1');
897
- });
898
-
899
- test('closetab closes a tab', async () => {
900
- const before = bm.getTabCount();
901
- // Close the last opened tab
902
- const tabs = await bm.getTabListWithTitles();
903
- const lastTab = tabs[tabs.length - 1];
904
- const result = await handleMetaCommand('closetab', [String(lastTab.id)], bm, async () => {});
905
- expect(result).toContain('Closed tab');
906
- expect(bm.getTabCount()).toBe(before - 1);
907
- });
908
- });
909
-
910
- // ─── Diff ───────────────────────────────────────────────────────
911
-
912
- describe('Diff', () => {
913
- test('diff shows differences between pages', async () => {
914
- const result = await handleMetaCommand(
915
- 'diff',
916
- [baseUrl + '/basic.html', baseUrl + '/forms.html'],
917
- bm,
918
- async () => {}
919
- );
920
- expect(result).toContain('---');
921
- expect(result).toContain('+++');
922
- // basic.html has "Hello World", forms.html has "Form Test Page"
923
- expect(result).toContain('Hello World');
924
- expect(result).toContain('Form Test Page');
925
- });
926
- });
927
-
928
- // ─── Chain ──────────────────────────────────────────────────────
929
-
930
- describe('Chain', () => {
931
- test('chain executes sequence of commands', async () => {
932
- const commands = JSON.stringify([
933
- ['goto', baseUrl + '/basic.html'],
934
- ['js', 'document.title'],
935
- ['css', 'h1', 'color'],
936
- ]);
937
- const result = await chainMeta(bm, [commands]);
938
- expect(result).toContain('[goto]');
939
- expect(result).toContain('Test Page - Basic');
940
- expect(result).toContain('[css]');
941
- });
942
-
943
- test('chain wraps page-content sub-commands with trust markers', async () => {
944
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
945
- const result = await chainMeta(bm, ['text']);
946
- expect(result).toContain('BEGIN UNTRUSTED EXTERNAL CONTENT');
947
- expect(result).toContain('END UNTRUSTED EXTERNAL CONTENT');
948
- });
949
-
950
- test('chain reports real error when write command fails', async () => {
951
- const commands = JSON.stringify([
952
- ['goto', 'http://localhost:1/unreachable'],
953
- ]);
954
- const result = await chainMeta(bm, [commands]);
955
- expect(result).toContain('[goto] ERROR:');
956
- expect(result).not.toContain('Unknown meta command');
957
- expect(result).not.toContain('Unknown read command');
958
- });
959
- });
960
-
961
- // ─── Status ─────────────────────────────────────────────────────
962
-
963
- describe('Status', () => {
964
- test('status reports health', async () => {
965
- const result = await handleMetaCommand('status', [], bm, async () => {});
966
- expect(result).toContain('Status: healthy');
967
- expect(result).toContain('Tabs:');
968
- });
969
- });
970
-
971
- // ─── CLI server script resolution ───────────────────────────────
972
-
973
- describe('CLI server script resolution', () => {
974
- test('prefers adjacent browse/src/server.ts for compiled project installs', () => {
975
- const root = fs.mkdtempSync(tmpp('gstack-cli-'));
976
- const execPath = path.join(root, '.claude/skills/gstack/browse/dist/browse');
977
- const serverPath = path.join(root, '.claude/skills/gstack/browse/src/server.ts');
978
-
979
- fs.mkdirSync(path.dirname(execPath), { recursive: true });
980
- fs.mkdirSync(path.dirname(serverPath), { recursive: true });
981
- fs.writeFileSync(serverPath, '// test server\n');
982
-
983
- const resolved = resolveServerScript(
984
- { HOME: path.join(root, 'empty-home') },
985
- '$bunfs/root',
986
- execPath
987
- );
988
-
989
- expect(resolved).toBe(serverPath);
990
-
991
- fs.rmSync(root, { recursive: true, force: true });
992
- });
993
- });
994
-
995
- // ─── CLI lifecycle ──────────────────────────────────────────────
996
-
997
- describe('CLI lifecycle', () => {
998
- test('dead state file triggers a clean restart', async () => {
999
- const stateFile = tmpp(`browse-test-state-${Date.now()}.json`);
1000
- fs.writeFileSync(stateFile, JSON.stringify({
1001
- port: 1,
1002
- token: 'fake',
1003
- pid: 999999,
1004
- }));
1005
-
1006
- const cliPath = path.resolve(__dirname, '../src/cli.ts');
1007
- const cliEnv: Record<string, string> = {};
1008
- for (const [k, v] of Object.entries(process.env)) {
1009
- if (v !== undefined) cliEnv[k] = v;
1010
- }
1011
- cliEnv.BROWSE_STATE_FILE = stateFile;
1012
- const result = await new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => {
1013
- const proc = spawn('bun', ['run', cliPath, 'status'], {
1014
- // Must exceed the CLI's startup budget (resolveStartTimeout, 15s
1015
- // non-CI POSIX) or a slow cold boot under full-suite load gets the
1016
- // child killed at the exact moment the CLI would have succeeded.
1017
- timeout: 18000,
1018
- env: cliEnv,
1019
- });
1020
- let stdout = '';
1021
- let stderr = '';
1022
- proc.stdout.on('data', (d) => stdout += d.toString());
1023
- proc.stderr.on('data', (d) => stderr += d.toString());
1024
- proc.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
1025
- });
1026
-
1027
- let restartedPid: number | null = null;
1028
- if (fs.existsSync(stateFile)) {
1029
- restartedPid = JSON.parse(fs.readFileSync(stateFile, 'utf-8')).pid;
1030
- fs.unlinkSync(stateFile);
1031
- }
1032
- let daemonSurvivedStop = false;
1033
- if (restartedPid) {
1034
- try { process.kill(restartedPid, 'SIGINT'); } catch {}
1035
- const deadline = Date.now() + 5000;
1036
- while (Date.now() < deadline && isProcessAlive(restartedPid)) await Bun.sleep(50);
1037
- daemonSurvivedStop = isProcessAlive(restartedPid);
1038
- if (daemonSurvivedStop) try { process.kill(restartedPid, 'SIGKILL'); } catch {}
1039
- }
1040
-
1041
- expect(result.code).toBe(0);
1042
- expect(result.stdout).toContain('Status: healthy');
1043
- expect(result.stderr).toContain('Starting server');
1044
- expect(restartedPid).toBeGreaterThan(0);
1045
- expect(daemonSurvivedStop).toBe(false);
1046
- }, 20000);
1047
- });
1048
-
1049
- // ─── Buffer bounds ──────────────────────────────────────────────
1050
-
1051
- describe('Buffer bounds', () => {
1052
- test('console buffer caps at 50000 entries', () => {
1053
- consoleBuffer.clear();
1054
- for (let i = 0; i < 50_010; i++) {
1055
- addConsoleEntry({ timestamp: i, level: 'log', text: `msg-${i}` });
1056
- }
1057
- expect(consoleBuffer.length).toBe(50_000);
1058
- const entries = consoleBuffer.toArray();
1059
- expect(entries[0].text).toBe('msg-10');
1060
- expect(entries[entries.length - 1].text).toBe('msg-50009');
1061
- consoleBuffer.clear();
1062
- });
1063
-
1064
- test('network buffer caps at 50000 entries', () => {
1065
- networkBuffer.clear();
1066
- for (let i = 0; i < 50_010; i++) {
1067
- addNetworkEntry({ timestamp: i, method: 'GET', url: `http://x/${i}` });
1068
- }
1069
- expect(networkBuffer.length).toBe(50_000);
1070
- const entries = networkBuffer.toArray();
1071
- expect(entries[0].url).toBe('http://x/10');
1072
- expect(entries[entries.length - 1].url).toBe('http://x/50009');
1073
- networkBuffer.clear();
1074
- });
1075
-
1076
- test('totalAdded counters keep incrementing past buffer cap', () => {
1077
- const startConsole = consoleBuffer.totalAdded;
1078
- const startNetwork = networkBuffer.totalAdded;
1079
- for (let i = 0; i < 100; i++) {
1080
- addConsoleEntry({ timestamp: i, level: 'log', text: `t-${i}` });
1081
- addNetworkEntry({ timestamp: i, method: 'GET', url: `http://t/${i}` });
1082
- }
1083
- expect(consoleBuffer.totalAdded).toBe(startConsole + 100);
1084
- expect(networkBuffer.totalAdded).toBe(startNetwork + 100);
1085
- consoleBuffer.clear();
1086
- networkBuffer.clear();
1087
- });
1088
- });
1089
-
1090
- // ─── CircularBuffer Unit Tests ─────────────────────────────────
1091
-
1092
- describe('CircularBuffer', () => {
1093
- test('push and toArray return items in insertion order', () => {
1094
- const buf = new CircularBuffer<number>(5);
1095
- buf.push(1); buf.push(2); buf.push(3);
1096
- expect(buf.toArray()).toEqual([1, 2, 3]);
1097
- expect(buf.length).toBe(3);
1098
- });
1099
-
1100
- test('overwrites oldest when full', () => {
1101
- const buf = new CircularBuffer<number>(3);
1102
- buf.push(1); buf.push(2); buf.push(3); buf.push(4);
1103
- expect(buf.toArray()).toEqual([2, 3, 4]);
1104
- expect(buf.length).toBe(3);
1105
- });
1106
-
1107
- test('totalAdded increments past capacity', () => {
1108
- const buf = new CircularBuffer<number>(2);
1109
- buf.push(1); buf.push(2); buf.push(3); buf.push(4); buf.push(5);
1110
- expect(buf.totalAdded).toBe(5);
1111
- expect(buf.length).toBe(2);
1112
- expect(buf.toArray()).toEqual([4, 5]);
1113
- });
1114
-
1115
- test('last(n) returns most recent entries', () => {
1116
- const buf = new CircularBuffer<number>(5);
1117
- for (let i = 1; i <= 5; i++) buf.push(i);
1118
- expect(buf.last(3)).toEqual([3, 4, 5]);
1119
- expect(buf.last(10)).toEqual([1, 2, 3, 4, 5]); // clamped
1120
- expect(buf.last(1)).toEqual([5]);
1121
- });
1122
-
1123
- test('get and set work by index', () => {
1124
- const buf = new CircularBuffer<string>(3);
1125
- buf.push('a'); buf.push('b'); buf.push('c');
1126
- expect(buf.get(0)).toBe('a');
1127
- expect(buf.get(2)).toBe('c');
1128
- buf.set(1, 'B');
1129
- expect(buf.get(1)).toBe('B');
1130
- expect(buf.get(-1)).toBeUndefined();
1131
- expect(buf.get(5)).toBeUndefined();
1132
- });
1133
-
1134
- test('clear resets size but not totalAdded', () => {
1135
- const buf = new CircularBuffer<number>(5);
1136
- buf.push(1); buf.push(2); buf.push(3);
1137
- buf.clear();
1138
- expect(buf.length).toBe(0);
1139
- expect(buf.totalAdded).toBe(3);
1140
- expect(buf.toArray()).toEqual([]);
1141
- });
1142
-
1143
- test('works with capacity=1', () => {
1144
- const buf = new CircularBuffer<number>(1);
1145
- buf.push(10);
1146
- expect(buf.toArray()).toEqual([10]);
1147
- buf.push(20);
1148
- expect(buf.toArray()).toEqual([20]);
1149
- expect(buf.totalAdded).toBe(2);
1150
- });
1151
- });
1152
-
1153
- // ─── Dialog Handling ─────────────────────────────────────────
1154
-
1155
- describe('Dialog handling', () => {
1156
- test('alert does not hang — auto-accepted', async () => {
1157
- await handleWriteCommand('goto', [baseUrl + '/dialog.html'], bm);
1158
- await handleWriteCommand('click', ['#alert-btn'], bm);
1159
- // If we get here, dialog was handled (no hang)
1160
- const result = await handleReadCommand('dialog', [], bm);
1161
- expect(result).toContain('alert');
1162
- expect(result).toContain('Hello from alert');
1163
- expect(result).toContain('accepted');
1164
- });
1165
-
1166
- test('confirm is auto-accepted by default', async () => {
1167
- await handleWriteCommand('goto', [baseUrl + '/dialog.html'], bm);
1168
- await handleWriteCommand('click', ['#confirm-btn'], bm);
1169
- // Wait for DOM update
1170
- await new Promise(r => setTimeout(r, 100));
1171
- const result = await handleReadCommand('js', ['document.querySelector("#confirm-result").textContent'], bm);
1172
- expect(result).toBe('confirmed');
1173
- });
1174
-
1175
- test('dialog-dismiss changes behavior', async () => {
1176
- const setResult = await handleWriteCommand('dialog-dismiss', [], bm);
1177
- expect(setResult).toContain('dismissed');
1178
-
1179
- await handleWriteCommand('goto', [baseUrl + '/dialog.html'], bm);
1180
- await handleWriteCommand('click', ['#confirm-btn'], bm);
1181
- await new Promise(r => setTimeout(r, 100));
1182
- const result = await handleReadCommand('js', ['document.querySelector("#confirm-result").textContent'], bm);
1183
- expect(result).toBe('cancelled');
1184
-
1185
- // Reset to accept
1186
- await handleWriteCommand('dialog-accept', [], bm);
1187
- });
1188
-
1189
- test('dialog-accept with text provides prompt response', async () => {
1190
- const setResult = await handleWriteCommand('dialog-accept', ['TestUser'], bm);
1191
- expect(setResult).toContain('TestUser');
1192
-
1193
- await handleWriteCommand('goto', [baseUrl + '/dialog.html'], bm);
1194
- await handleWriteCommand('click', ['#prompt-btn'], bm);
1195
- await new Promise(r => setTimeout(r, 100));
1196
- const result = await handleReadCommand('js', ['document.querySelector("#prompt-result").textContent'], bm);
1197
- expect(result).toBe('TestUser');
1198
-
1199
- // Reset
1200
- await handleWriteCommand('dialog-accept', [], bm);
1201
- });
1202
-
1203
- test('dialog --clear clears buffer', async () => {
1204
- const cleared = await handleReadCommand('dialog', ['--clear'], bm);
1205
- expect(cleared).toContain('cleared');
1206
- const after = await handleReadCommand('dialog', [], bm);
1207
- expect(after).toContain('no dialogs');
1208
- });
1209
- });
1210
-
1211
- // ─── Element State Checks (is) ─────────────────────────────────
1212
-
1213
- describe('Element state checks', () => {
1214
- beforeAll(async () => {
1215
- await handleWriteCommand('goto', [baseUrl + '/states.html'], bm);
1216
- });
1217
-
1218
- test('is visible returns true for visible element', async () => {
1219
- const result = await handleReadCommand('is', ['visible', '#visible-div'], bm);
1220
- expect(result).toBe('true');
1221
- });
1222
-
1223
- test('is hidden returns true for hidden element', async () => {
1224
- const result = await handleReadCommand('is', ['hidden', '#hidden-div'], bm);
1225
- expect(result).toBe('true');
1226
- });
1227
-
1228
- test('is visible returns false for hidden element', async () => {
1229
- const result = await handleReadCommand('is', ['visible', '#hidden-div'], bm);
1230
- expect(result).toBe('false');
1231
- });
1232
-
1233
- test('is enabled returns true for enabled input', async () => {
1234
- const result = await handleReadCommand('is', ['enabled', '#enabled-input'], bm);
1235
- expect(result).toBe('true');
1236
- });
1237
-
1238
- test('is disabled returns true for disabled input', async () => {
1239
- const result = await handleReadCommand('is', ['disabled', '#disabled-input'], bm);
1240
- expect(result).toBe('true');
1241
- });
1242
-
1243
- test('is checked returns true for checked checkbox', async () => {
1244
- const result = await handleReadCommand('is', ['checked', '#checked-box'], bm);
1245
- expect(result).toBe('true');
1246
- });
1247
-
1248
- test('is checked returns false for unchecked checkbox', async () => {
1249
- const result = await handleReadCommand('is', ['checked', '#unchecked-box'], bm);
1250
- expect(result).toBe('false');
1251
- });
1252
-
1253
- test('is editable returns true for normal input', async () => {
1254
- const result = await handleReadCommand('is', ['editable', '#enabled-input'], bm);
1255
- expect(result).toBe('true');
1256
- });
1257
-
1258
- test('is editable returns false for readonly input', async () => {
1259
- const result = await handleReadCommand('is', ['editable', '#readonly-input'], bm);
1260
- expect(result).toBe('false');
1261
- });
1262
-
1263
- test('is focused after click', async () => {
1264
- await handleWriteCommand('click', ['#enabled-input'], bm);
1265
- const result = await handleReadCommand('is', ['focused', '#enabled-input'], bm);
1266
- expect(result).toBe('true');
1267
- });
1268
-
1269
- test('is with @ref works', async () => {
1270
- await handleMetaCommand('snapshot', ['-i'], bm, async () => {});
1271
- // Find a ref for the enabled input
1272
- const snap = await handleMetaCommand('snapshot', ['-i'], bm, async () => {});
1273
- const textboxLine = snap.split('\n').find(l => l.includes('[textbox]'));
1274
- if (textboxLine) {
1275
- const refMatch = textboxLine.match(/@(e\d+)/);
1276
- if (refMatch) {
1277
- const ref = `@${refMatch[1]}`;
1278
- const result = await handleReadCommand('is', ['visible', ref], bm);
1279
- expect(result).toBe('true');
1280
- }
1281
- }
1282
- });
1283
-
1284
- test('is with unknown property throws', async () => {
1285
- try {
1286
- await handleReadCommand('is', ['bogus', '#enabled-input'], bm);
1287
- expect(true).toBe(false);
1288
- } catch (err: any) {
1289
- expect(err.message).toContain('Unknown property');
1290
- }
1291
- });
1292
-
1293
- test('is with missing args throws', async () => {
1294
- try {
1295
- await handleReadCommand('is', ['visible'], bm);
1296
- expect(true).toBe(false);
1297
- } catch (err: any) {
1298
- expect(err.message).toContain('Usage');
1299
- }
1300
- });
1301
- });
1302
-
1303
- // ─── File Upload ─────────────────────────────────────────────────
1304
-
1305
- describe('File upload', () => {
1306
- test('upload single file', async () => {
1307
- await handleWriteCommand('goto', [baseUrl + '/upload.html'], bm);
1308
- // Create a temp file to upload
1309
- const tempFile = tmpp('browse-test-upload.txt');
1310
- fs.writeFileSync(tempFile, 'test content');
1311
- const result = await handleWriteCommand('upload', ['#file-input', tempFile], bm);
1312
- expect(result).toContain('Uploaded');
1313
- expect(result).toContain('browse-test-upload.txt');
1314
-
1315
- // Verify upload handler fired
1316
- await new Promise(r => setTimeout(r, 100));
1317
- const text = await handleReadCommand('js', ['document.querySelector("#upload-result").textContent'], bm);
1318
- expect(text).toContain('browse-test-upload.txt');
1319
- fs.unlinkSync(tempFile);
1320
- });
1321
-
1322
- test('upload with @ref works', async () => {
1323
- await handleWriteCommand('goto', [baseUrl + '/upload.html'], bm);
1324
- const tempFile = tmpp('browse-test-upload2.txt');
1325
- fs.writeFileSync(tempFile, 'ref upload test');
1326
- const snap = await handleMetaCommand('snapshot', ['-i'], bm, async () => {});
1327
- // Find the file input ref (it won't appear as "file input" in aria — use CSS selector instead)
1328
- const result = await handleWriteCommand('upload', ['#file-input', tempFile], bm);
1329
- expect(result).toContain('Uploaded');
1330
- fs.unlinkSync(tempFile);
1331
- });
1332
-
1333
- test('upload nonexistent file throws', async () => {
1334
- await handleWriteCommand('goto', [baseUrl + '/upload.html'], bm);
1335
- try {
1336
- await handleWriteCommand('upload', ['#file-input', tmpp('nonexistent-file-12345.txt')], bm);
1337
- expect(true).toBe(false);
1338
- } catch (err: any) {
1339
- expect(err.message).toContain('File not found');
1340
- }
1341
- });
1342
-
1343
- test('upload missing args throws', async () => {
1344
- try {
1345
- await handleWriteCommand('upload', ['#file-input'], bm);
1346
- expect(true).toBe(false);
1347
- } catch (err: any) {
1348
- expect(err.message).toContain('Usage');
1349
- }
1350
- });
1351
- });
1352
-
1353
- // ─── Eval command ───────────────────────────────────────────────
1354
-
1355
- describe('Eval', () => {
1356
- test('eval runs JS file', async () => {
1357
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1358
- const tempFile = tmpp('browse-test-eval.js');
1359
- fs.writeFileSync(tempFile, 'document.title + " — evaluated"');
1360
- const result = await handleReadCommand('eval', [tempFile], bm);
1361
- expect(result).toBe('Test Page - Basic — evaluated');
1362
- fs.unlinkSync(tempFile);
1363
- });
1364
-
1365
- test('eval returns object as JSON', async () => {
1366
- const tempFile = tmpp('browse-test-eval-obj.js');
1367
- fs.writeFileSync(tempFile, '({title: document.title, keys: Object.keys(document.body.dataset)})');
1368
- const result = await handleReadCommand('eval', [tempFile], bm);
1369
- const obj = JSON.parse(result);
1370
- expect(obj.title).toBe('Test Page - Basic');
1371
- expect(Array.isArray(obj.keys)).toBe(true);
1372
- fs.unlinkSync(tempFile);
1373
- });
1374
-
1375
- test('eval file not found throws', async () => {
1376
- try {
1377
- await handleReadCommand('eval', [tmpp('nonexistent-eval.js')], bm);
1378
- expect(true).toBe(false);
1379
- } catch (err: any) {
1380
- expect(err.message).toContain('File not found');
1381
- }
1382
- });
1383
-
1384
- test('eval no arg throws', async () => {
1385
- try {
1386
- await handleReadCommand('eval', [], bm);
1387
- expect(true).toBe(false);
1388
- } catch (err: any) {
1389
- expect(err.message).toContain('Usage');
1390
- }
1391
- });
1392
- });
1393
-
1394
- // ─── Press command ──────────────────────────────────────────────
1395
-
1396
- describe('Press', () => {
1397
- test('press Tab moves focus', async () => {
1398
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
1399
- await handleWriteCommand('click', ['#email'], bm);
1400
- const result = await handleWriteCommand('press', ['Tab'], bm);
1401
- expect(result).toContain('Pressed Tab');
1402
- });
1403
-
1404
- test('press no arg throws', async () => {
1405
- try {
1406
- await handleWriteCommand('press', [], bm);
1407
- expect(true).toBe(false);
1408
- } catch (err: any) {
1409
- expect(err.message).toContain('Usage');
1410
- }
1411
- });
1412
- });
1413
-
1414
- // ─── Cookie command ─────────────────────────────────────────────
1415
-
1416
- describe('Cookie command', () => {
1417
- test('cookie sets value', async () => {
1418
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1419
- const result = await handleWriteCommand('cookie', ['testcookie=testvalue'], bm);
1420
- expect(result).toContain('Cookie set');
1421
-
1422
- const cookies = await handleReadCommand('cookies', [], bm);
1423
- expect(cookies).toContain('testcookie');
1424
- expect(cookies).toContain('testvalue');
1425
- });
1426
-
1427
- test('cookie no arg throws', async () => {
1428
- try {
1429
- await handleWriteCommand('cookie', [], bm);
1430
- expect(true).toBe(false);
1431
- } catch (err: any) {
1432
- expect(err.message).toContain('Usage');
1433
- }
1434
- });
1435
-
1436
- test('cookie no = throws', async () => {
1437
- try {
1438
- await handleWriteCommand('cookie', ['invalid'], bm);
1439
- expect(true).toBe(false);
1440
- } catch (err: any) {
1441
- expect(err.message).toContain('Usage');
1442
- }
1443
- });
1444
- });
1445
-
1446
- // ─── Header command ─────────────────────────────────────────────
1447
-
1448
- describe('Header command', () => {
1449
- test('header sets value and is sent', async () => {
1450
- const result = await handleWriteCommand('header', ['X-Test:test-value'], bm);
1451
- expect(result).toContain('Header set');
1452
-
1453
- await handleWriteCommand('goto', [baseUrl + '/echo'], bm);
1454
- const echoText = await handleReadCommand('text', [], bm);
1455
- expect(echoText).toContain('x-test');
1456
- expect(echoText).toContain('test-value');
1457
- });
1458
-
1459
- test('header no arg throws', async () => {
1460
- try {
1461
- await handleWriteCommand('header', [], bm);
1462
- expect(true).toBe(false);
1463
- } catch (err: any) {
1464
- expect(err.message).toContain('Usage');
1465
- }
1466
- });
1467
-
1468
- test('header no colon throws', async () => {
1469
- try {
1470
- await handleWriteCommand('header', ['invalid'], bm);
1471
- expect(true).toBe(false);
1472
- } catch (err: any) {
1473
- expect(err.message).toContain('Usage');
1474
- }
1475
- });
1476
- });
1477
-
1478
- // ─── PDF command ────────────────────────────────────────────────
1479
-
1480
- describe('PDF', () => {
1481
- test('pdf saves file with size', async () => {
1482
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1483
- const pdfPath = tmpp('browse-test.pdf');
1484
- const result = await handleMetaCommand('pdf', [pdfPath], bm, async () => {});
1485
- expect(result).toContain('PDF saved');
1486
- expect(fs.existsSync(pdfPath)).toBe(true);
1487
- const stat = fs.statSync(pdfPath);
1488
- expect(stat.size).toBeGreaterThan(100);
1489
- fs.unlinkSync(pdfPath);
1490
- });
1491
- });
1492
-
1493
- // ─── Empty page edge cases ──────────────────────────────────────
1494
-
1495
- describe('Empty page', () => {
1496
- test('text returns empty on empty page', async () => {
1497
- await handleWriteCommand('goto', [baseUrl + '/empty.html'], bm);
1498
- const result = await handleReadCommand('text', [], bm);
1499
- expect(result).toBe('');
1500
- });
1501
-
1502
- test('links returns empty on empty page', async () => {
1503
- const result = await handleReadCommand('links', [], bm);
1504
- expect(result).toBe('');
1505
- });
1506
-
1507
- test('forms returns empty array on empty page', async () => {
1508
- const result = await handleReadCommand('forms', [], bm);
1509
- expect(JSON.parse(result)).toEqual([]);
1510
- });
1511
- });
1512
-
1513
- // ─── Error paths ────────────────────────────────────────────────
1514
-
1515
- describe('Errors', () => {
1516
- // Write command errors
1517
- test('goto with no arg throws', async () => {
1518
- try {
1519
- await handleWriteCommand('goto', [], bm);
1520
- expect(true).toBe(false);
1521
- } catch (err: any) {
1522
- expect(err.message).toContain('Usage');
1523
- }
1524
- });
1525
-
1526
- test('click with no arg throws', async () => {
1527
- try {
1528
- await handleWriteCommand('click', [], bm);
1529
- expect(true).toBe(false);
1530
- } catch (err: any) {
1531
- expect(err.message).toContain('Usage');
1532
- }
1533
- });
1534
-
1535
- test('fill with no value throws', async () => {
1536
- try {
1537
- await handleWriteCommand('fill', ['#input'], bm);
1538
- expect(true).toBe(false);
1539
- } catch (err: any) {
1540
- expect(err.message).toContain('Usage');
1541
- }
1542
- });
1543
-
1544
- test('select with no value throws', async () => {
1545
- try {
1546
- await handleWriteCommand('select', ['#sel'], bm);
1547
- expect(true).toBe(false);
1548
- } catch (err: any) {
1549
- expect(err.message).toContain('Usage');
1550
- }
1551
- });
1552
-
1553
- test('hover with no arg throws', async () => {
1554
- try {
1555
- await handleWriteCommand('hover', [], bm);
1556
- expect(true).toBe(false);
1557
- } catch (err: any) {
1558
- expect(err.message).toContain('Usage');
1559
- }
1560
- });
1561
-
1562
- test('type with no arg throws', async () => {
1563
- try {
1564
- await handleWriteCommand('type', [], bm);
1565
- expect(true).toBe(false);
1566
- } catch (err: any) {
1567
- expect(err.message).toContain('Usage');
1568
- }
1569
- });
1570
-
1571
- test('wait with no arg throws', async () => {
1572
- try {
1573
- await handleWriteCommand('wait', [], bm);
1574
- expect(true).toBe(false);
1575
- } catch (err: any) {
1576
- expect(err.message).toContain('Usage');
1577
- }
1578
- });
1579
-
1580
- test('viewport with bad format throws', async () => {
1581
- try {
1582
- await handleWriteCommand('viewport', ['badformat'], bm);
1583
- expect(true).toBe(false);
1584
- } catch (err: any) {
1585
- expect(err.message).toContain('Usage');
1586
- }
1587
- });
1588
-
1589
- test('useragent with no arg throws', async () => {
1590
- try {
1591
- await handleWriteCommand('useragent', [], bm);
1592
- expect(true).toBe(false);
1593
- } catch (err: any) {
1594
- expect(err.message).toContain('Usage');
1595
- }
1596
- });
1597
-
1598
- // Read command errors
1599
- test('js with no expression throws', async () => {
1600
- try {
1601
- await handleReadCommand('js', [], bm);
1602
- expect(true).toBe(false);
1603
- } catch (err: any) {
1604
- expect(err.message).toContain('Usage');
1605
- }
1606
- });
1607
-
1608
- test('css with missing property throws', async () => {
1609
- try {
1610
- await handleReadCommand('css', ['h1'], bm);
1611
- expect(true).toBe(false);
1612
- } catch (err: any) {
1613
- expect(err.message).toContain('Usage');
1614
- }
1615
- });
1616
-
1617
- test('attrs with no selector throws', async () => {
1618
- try {
1619
- await handleReadCommand('attrs', [], bm);
1620
- expect(true).toBe(false);
1621
- } catch (err: any) {
1622
- expect(err.message).toContain('Usage');
1623
- }
1624
- });
1625
-
1626
- // Meta command errors
1627
- test('tab with non-numeric id throws', async () => {
1628
- try {
1629
- await handleMetaCommand('tab', ['abc'], bm, async () => {});
1630
- expect(true).toBe(false);
1631
- } catch (err: any) {
1632
- expect(err.message).toContain('Usage');
1633
- }
1634
- });
1635
-
1636
- test('diff with missing urls throws', async () => {
1637
- try {
1638
- await handleMetaCommand('diff', [baseUrl + '/basic.html'], bm, async () => {});
1639
- expect(true).toBe(false);
1640
- } catch (err: any) {
1641
- expect(err.message).toContain('Usage');
1642
- }
1643
- });
1644
-
1645
- test('chain with invalid JSON falls back to pipe format', async () => {
1646
- // Non-JSON input is now treated as pipe-delimited format
1647
- // 'not json' → [["not", "json"]] → "not" is unknown command → error in result
1648
- const result = await chainMeta(bm, ['not json']);
1649
- expect(result).toContain('ERROR');
1650
- expect(result).toContain('Unknown command: not');
1651
- });
1652
-
1653
- test('chain with no arg throws', async () => {
1654
- try {
1655
- await chainMeta(bm, []);
1656
- expect(true).toBe(false);
1657
- } catch (err: any) {
1658
- expect(err.message).toContain('Usage');
1659
- }
1660
- });
1661
-
1662
- test('unknown read command throws', async () => {
1663
- try {
1664
- await handleReadCommand('bogus' as any, [], bm);
1665
- expect(true).toBe(false);
1666
- } catch (err: any) {
1667
- expect(err.message).toContain('Unknown');
1668
- }
1669
- });
1670
-
1671
- test('unknown write command throws', async () => {
1672
- try {
1673
- await handleWriteCommand('bogus' as any, [], bm);
1674
- expect(true).toBe(false);
1675
- } catch (err: any) {
1676
- expect(err.message).toContain('Unknown');
1677
- }
1678
- });
1679
-
1680
- test('unknown meta command throws', async () => {
1681
- try {
1682
- await handleMetaCommand('bogus' as any, [], bm, async () => {});
1683
- expect(true).toBe(false);
1684
- } catch (err: any) {
1685
- expect(err.message).toContain('Unknown');
1686
- }
1687
- });
1688
- });
1689
-
1690
- // ─── Workflow: Navigation + Snapshot + Interaction ───────────────
1691
-
1692
- describe('Workflows', () => {
1693
- test('navigation → snapshot → click @ref → verify URL', async () => {
1694
- await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
1695
- const snap = await handleMetaCommand('snapshot', ['-i'], bm, async () => {});
1696
- // Find a link ref
1697
- const linkLine = snap.split('\n').find(l => l.includes('[link]'));
1698
- expect(linkLine).toBeDefined();
1699
- const refMatch = linkLine!.match(/@(e\d+)/);
1700
- expect(refMatch).toBeDefined();
1701
- // Click the link
1702
- await handleWriteCommand('click', [`@${refMatch![1]}`], bm);
1703
- // URL should have changed
1704
- const url = await handleMetaCommand('url', [], bm, async () => {});
1705
- expect(url).toBeTruthy();
1706
- });
1707
-
1708
- test('form: goto → snapshot → fill @ref → click @ref', async () => {
1709
- await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
1710
- const snap = await handleMetaCommand('snapshot', ['-i'], bm, async () => {});
1711
- // Find textbox and button
1712
- const textboxLine = snap.split('\n').find(l => l.includes('[textbox]'));
1713
- const buttonLine = snap.split('\n').find(l => l.includes('[button]') && l.includes('"Submit"'));
1714
- if (textboxLine && buttonLine) {
1715
- const textRef = textboxLine.match(/@(e\d+)/)![1];
1716
- const btnRef = buttonLine.match(/@(e\d+)/)![1];
1717
- await handleWriteCommand('fill', [`@${textRef}`, 'testuser'], bm);
1718
- await handleWriteCommand('click', [`@${btnRef}`], bm);
1719
- }
1720
- });
1721
-
1722
- test('tabs: newtab → goto → switch → verify isolation', async () => {
1723
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1724
- const tabsBefore = bm.getTabCount();
1725
- await handleMetaCommand('newtab', [baseUrl + '/forms.html'], bm, async () => {});
1726
- expect(bm.getTabCount()).toBe(tabsBefore + 1);
1727
-
1728
- const url = await handleMetaCommand('url', [], bm, async () => {});
1729
- expect(url).toContain('/forms.html');
1730
-
1731
- // Switch back to previous tab
1732
- const tabs = await bm.getTabListWithTitles();
1733
- const prevTab = tabs.find(t => t.url.includes('/basic.html'));
1734
- if (prevTab) {
1735
- bm.switchTab(prevTab.id);
1736
- const url2 = await handleMetaCommand('url', [], bm, async () => {});
1737
- expect(url2).toContain('/basic.html');
1738
- }
1739
-
1740
- // Clean up extra tab
1741
- const allTabs = await bm.getTabListWithTitles();
1742
- const formTab = allTabs.find(t => t.url.includes('/forms.html'));
1743
- if (formTab) await bm.closeTab(formTab.id);
1744
- });
1745
-
1746
- test('cookies: set → read → reload → verify persistence', async () => {
1747
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1748
- await handleWriteCommand('cookie', ['workflow-test=persisted'], bm);
1749
- await handleWriteCommand('reload', [], bm);
1750
- const cookies = await handleReadCommand('cookies', [], bm);
1751
- expect(cookies).toContain('workflow-test');
1752
- expect(cookies).toContain('persisted');
1753
- });
1754
- });
1755
-
1756
- // ─── Wait load states ──────────────────────────────────────────
1757
-
1758
- describe('Wait load states', () => {
1759
- test('wait --networkidle succeeds after page load', async () => {
1760
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1761
- const result = await handleWriteCommand('wait', ['--networkidle'], bm);
1762
- expect(result).toBe('Network idle');
1763
- });
1764
-
1765
- test('wait --load succeeds', async () => {
1766
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1767
- const result = await handleWriteCommand('wait', ['--load'], bm);
1768
- expect(result).toBe('Page loaded');
1769
- });
1770
-
1771
- test('wait --domcontentloaded succeeds', async () => {
1772
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1773
- const result = await handleWriteCommand('wait', ['--domcontentloaded'], bm);
1774
- expect(result).toBe('DOM content loaded');
1775
- });
1776
-
1777
- test('wait --networkidle with custom timeout', async () => {
1778
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1779
- const result = await handleWriteCommand('wait', ['--networkidle', '5000'], bm);
1780
- expect(result).toBe('Network idle');
1781
- });
1782
-
1783
- test('wait with selector still works', async () => {
1784
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1785
- const result = await handleWriteCommand('wait', ['#title'], bm);
1786
- expect(result).toContain('appeared');
1787
- });
1788
- });
1789
-
1790
- // ─── Console --errors ──────────────────────────────────────────
1791
-
1792
- describe('Console --errors', () => {
1793
- test('console --errors filters to error and warning only', async () => {
1794
- // Clear existing entries
1795
- await handleReadCommand('console', ['--clear'], bm);
1796
-
1797
- // Add mixed entries
1798
- addConsoleEntry({ timestamp: Date.now(), level: 'log', text: 'info message' });
1799
- addConsoleEntry({ timestamp: Date.now(), level: 'warning', text: 'warn message' });
1800
- addConsoleEntry({ timestamp: Date.now(), level: 'error', text: 'error message' });
1801
-
1802
- const result = await handleReadCommand('console', ['--errors'], bm);
1803
- expect(result).toContain('warn message');
1804
- expect(result).toContain('error message');
1805
- expect(result).not.toContain('info message');
1806
-
1807
- // Cleanup
1808
- consoleBuffer.clear();
1809
- });
1810
-
1811
- test('console --errors returns empty message when no errors', async () => {
1812
- consoleBuffer.clear();
1813
- addConsoleEntry({ timestamp: Date.now(), level: 'log', text: 'just a log' });
1814
-
1815
- const result = await handleReadCommand('console', ['--errors'], bm);
1816
- expect(result).toBe('(no console errors)');
1817
-
1818
- consoleBuffer.clear();
1819
- });
1820
-
1821
- test('console --errors on empty buffer', async () => {
1822
- consoleBuffer.clear();
1823
- const result = await handleReadCommand('console', ['--errors'], bm);
1824
- expect(result).toBe('(no console errors)');
1825
- });
1826
-
1827
- test('console without flag still returns all messages', async () => {
1828
- consoleBuffer.clear();
1829
- addConsoleEntry({ timestamp: Date.now(), level: 'log', text: 'all messages test' });
1830
-
1831
- const result = await handleReadCommand('console', [], bm);
1832
- expect(result).toContain('all messages test');
1833
-
1834
- consoleBuffer.clear();
1835
- });
1836
- });
1837
-
1838
- // ─── Cookie Import ─────────────────────────────────────────────
1839
-
1840
- describe('Cookie import', () => {
1841
- test('cookie-import loads valid JSON cookies', async () => {
1842
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1843
- const tempFile = tmpp('browse-test-cookies.json');
1844
- const cookies = [
1845
- { name: 'test-cookie', value: 'test-value' },
1846
- { name: 'another', value: '123' },
1847
- ];
1848
- fs.writeFileSync(tempFile, JSON.stringify(cookies));
1849
-
1850
- const result = await handleWriteCommand('cookie-import', [tempFile], bm);
1851
- expect(result).toBe(`Loaded 2 cookies from ${tempFile}`);
1852
-
1853
- // Verify cookies were set
1854
- const cookieList = await handleReadCommand('cookies', [], bm);
1855
- expect(cookieList).toContain('test-cookie');
1856
- expect(cookieList).toContain('test-value');
1857
- expect(cookieList).toContain('another');
1858
-
1859
- fs.unlinkSync(tempFile);
1860
- });
1861
-
1862
- test('cookie-import auto-fills domain from page URL', async () => {
1863
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1864
- const tempFile = tmpp('browse-test-cookies-nodomain.json');
1865
- // Cookies without domain — should auto-fill from page URL
1866
- const cookies = [{ name: 'autofill-test', value: 'works' }];
1867
- fs.writeFileSync(tempFile, JSON.stringify(cookies));
1868
-
1869
- const result = await handleWriteCommand('cookie-import', [tempFile], bm);
1870
- expect(result).toContain('Loaded 1');
1871
-
1872
- const cookieList = await handleReadCommand('cookies', [], bm);
1873
- expect(cookieList).toContain('autofill-test');
1874
-
1875
- fs.unlinkSync(tempFile);
1876
- });
1877
-
1878
- test('cookie-import preserves explicit domain', async () => {
1879
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1880
- const tempFile = tmpp('browse-test-cookies-domain.json');
1881
- // Domain must match page hostname (127.0.0.1) — cross-domain cookies are now rejected
1882
- const cookies = [{ name: 'explicit', value: 'domain', domain: '127.0.0.1', path: '/foo' }];
1883
- fs.writeFileSync(tempFile, JSON.stringify(cookies));
1884
-
1885
- const result = await handleWriteCommand('cookie-import', [tempFile], bm);
1886
- expect(result).toContain('Loaded 1');
1887
-
1888
- fs.unlinkSync(tempFile);
1889
- });
1890
-
1891
- test('cookie-import with empty array succeeds', async () => {
1892
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1893
- const tempFile = tmpp('browse-test-cookies-empty.json');
1894
- fs.writeFileSync(tempFile, '[]');
1895
-
1896
- const result = await handleWriteCommand('cookie-import', [tempFile], bm);
1897
- expect(result).toBe(`Loaded 0 cookies from ${tempFile}`);
1898
-
1899
- fs.unlinkSync(tempFile);
1900
- });
1901
-
1902
- test('cookie-import throws on file not found', async () => {
1903
- try {
1904
- await handleWriteCommand('cookie-import', [tmpp('nonexistent-cookies.json')], bm);
1905
- expect(true).toBe(false);
1906
- } catch (err: any) {
1907
- expect(err.message).toContain('File not found');
1908
- }
1909
- });
1910
-
1911
- test('cookie-import throws on invalid JSON', async () => {
1912
- const tempFile = tmpp('browse-test-cookies-bad.json');
1913
- fs.writeFileSync(tempFile, 'not json {{{');
1914
-
1915
- try {
1916
- await handleWriteCommand('cookie-import', [tempFile], bm);
1917
- expect(true).toBe(false);
1918
- } catch (err: any) {
1919
- expect(err.message).toContain('Invalid JSON');
1920
- }
1921
-
1922
- fs.unlinkSync(tempFile);
1923
- });
1924
-
1925
- test('cookie-import throws on non-array JSON', async () => {
1926
- const tempFile = tmpp('browse-test-cookies-obj.json');
1927
- fs.writeFileSync(tempFile, '{"name": "not-an-array"}');
1928
-
1929
- try {
1930
- await handleWriteCommand('cookie-import', [tempFile], bm);
1931
- expect(true).toBe(false);
1932
- } catch (err: any) {
1933
- expect(err.message).toContain('JSON array');
1934
- }
1935
-
1936
- fs.unlinkSync(tempFile);
1937
- });
1938
-
1939
- test('cookie-import throws on cookie missing name', async () => {
1940
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1941
- const tempFile = tmpp('browse-test-cookies-noname.json');
1942
- fs.writeFileSync(tempFile, JSON.stringify([{ value: 'no-name' }]));
1943
-
1944
- try {
1945
- await handleWriteCommand('cookie-import', [tempFile], bm);
1946
- expect(true).toBe(false);
1947
- } catch (err: any) {
1948
- expect(err.message).toContain('name');
1949
- }
1950
-
1951
- fs.unlinkSync(tempFile);
1952
- });
1953
-
1954
- test('cookie-import no arg throws', async () => {
1955
- try {
1956
- await handleWriteCommand('cookie-import', [], bm);
1957
- expect(true).toBe(false);
1958
- } catch (err: any) {
1959
- expect(err.message).toContain('Usage');
1960
- }
1961
- });
1962
- });
1963
-
1964
- // ─── Security: Redact sensitive values (PR #21) ─────────────────
1965
-
1966
- describe('Sensitive value redaction', () => {
1967
- test('type command does not echo typed text', async () => {
1968
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1969
- const result = await handleWriteCommand('type', ['my-secret-password'], bm);
1970
- expect(result).not.toContain('my-secret-password');
1971
- expect(result).toContain('18 characters');
1972
- });
1973
-
1974
- test('cookie command redacts value', async () => {
1975
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
1976
- const result = await handleWriteCommand('cookie', ['session=secret123'], bm);
1977
- expect(result).toContain('session');
1978
- expect(result).toContain('****');
1979
- expect(result).not.toContain('secret123');
1980
- });
1981
-
1982
- test('header command redacts Authorization value', async () => {
1983
- const result = await handleWriteCommand('header', ['Authorization:Bearer token-xyz'], bm);
1984
- expect(result).toContain('Authorization');
1985
- expect(result).toContain('****');
1986
- expect(result).not.toContain('token-xyz');
1987
- });
1988
-
1989
- test('header command shows non-sensitive values', async () => {
1990
- const result = await handleWriteCommand('header', ['Content-Type:application/json'], bm);
1991
- expect(result).toContain('Content-Type');
1992
- expect(result).toContain('application/json');
1993
- expect(result).not.toContain('****');
1994
- });
1995
-
1996
- test('header command redacts X-API-Key', async () => {
1997
- const result = await handleWriteCommand('header', ['X-API-Key:sk-12345'], bm);
1998
- expect(result).toContain('X-API-Key');
1999
- expect(result).toContain('****');
2000
- expect(result).not.toContain('sk-12345');
2001
- });
2002
-
2003
- test('storage set does not echo value', async () => {
2004
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2005
- const result = await handleReadCommand('storage', ['set', 'apiKey', 'secret-api-key-value'], bm);
2006
- expect(result).toContain('apiKey');
2007
- expect(result).not.toContain('secret-api-key-value');
2008
- });
2009
-
2010
- test('forms redacts password field values', async () => {
2011
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
2012
- const formsResult = await handleReadCommand('forms', [], bm);
2013
- const forms = JSON.parse(formsResult);
2014
- // Find password fields and verify they're redacted
2015
- for (const form of forms) {
2016
- for (const field of form.fields) {
2017
- if (field.type === 'password') {
2018
- expect(field.value === undefined || field.value === '[redacted]').toBe(true);
2019
- }
2020
- }
2021
- }
2022
- });
2023
- });
2024
-
2025
- // ─── Security: Path traversal prevention (PR #26) ───────────────
2026
-
2027
- describe('Path traversal prevention', () => {
2028
- test('screenshot rejects path outside safe dirs', async () => {
2029
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2030
- try {
2031
- await handleMetaCommand('screenshot', ['/etc/evil.png'], bm, () => {});
2032
- expect(true).toBe(false);
2033
- } catch (err: any) {
2034
- expect(err.message).toContain('Path must be within');
2035
- }
2036
- });
2037
-
2038
- test('screenshot allows /tmp path', async () => {
2039
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2040
- const result = await handleMetaCommand('screenshot', [tmpp('test-safe.png')], bm, () => {});
2041
- expect(result).toContain('Screenshot saved');
2042
- try { fs.unlinkSync(tmpp('test-safe.png')); } catch {}
2043
- });
2044
-
2045
- test('pdf rejects path outside safe dirs', async () => {
2046
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2047
- try {
2048
- await handleMetaCommand('pdf', ['/home/evil.pdf'], bm, () => {});
2049
- expect(true).toBe(false);
2050
- } catch (err: any) {
2051
- expect(err.message).toContain('Path must be within');
2052
- }
2053
- });
2054
-
2055
- test('responsive rejects path outside safe dirs', async () => {
2056
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2057
- try {
2058
- await handleMetaCommand('responsive', ['/var/evil'], bm, () => {});
2059
- expect(true).toBe(false);
2060
- } catch (err: any) {
2061
- expect(err.message).toContain('Path must be within');
2062
- }
2063
- });
2064
-
2065
- test('eval rejects path traversal with ..', async () => {
2066
- try {
2067
- await handleReadCommand('eval', ['../../etc/passwd'], bm);
2068
- expect(true).toBe(false);
2069
- } catch (err: any) {
2070
- expect(err.message).toContain('Path must be within');
2071
- }
2072
- });
2073
-
2074
- test('eval rejects absolute path outside safe dirs', async () => {
2075
- try {
2076
- await handleReadCommand('eval', ['/etc/passwd'], bm);
2077
- expect(true).toBe(false);
2078
- } catch (err: any) {
2079
- expect(err.message).toContain('Path must be within');
2080
- }
2081
- });
2082
-
2083
- test('eval allows /tmp path', async () => {
2084
- const tmpFile = tmpp('test-eval-safe.js');
2085
- fs.writeFileSync(tmpFile, 'document.title');
2086
- try {
2087
- const result = await handleReadCommand('eval', [tmpFile], bm);
2088
- expect(typeof result).toBe('string');
2089
- } finally {
2090
- try { fs.unlinkSync(tmpFile); } catch {}
2091
- }
2092
- });
2093
-
2094
- test('screenshot rejects /tmpevil prefix collision', async () => {
2095
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2096
- try {
2097
- await handleMetaCommand('screenshot', ['/tmpevil/steal.png'], bm, () => {});
2098
- expect(true).toBe(false);
2099
- } catch (err: any) {
2100
- expect(err.message).toContain('Path must be within');
2101
- }
2102
- });
2103
-
2104
- test('cookie-import rejects path traversal', async () => {
2105
- try {
2106
- await handleWriteCommand('cookie-import', ['../../etc/shadow'], bm);
2107
- expect(true).toBe(false);
2108
- } catch (err: any) {
2109
- // Traversal blocked by safe-directory check (#707) or explicit .. check
2110
- expect(err.message).toMatch(/Path must be within|Path traversal/);
2111
- }
2112
- });
2113
-
2114
- test('cookie-import rejects absolute path outside safe dirs', async () => {
2115
- try {
2116
- await handleWriteCommand('cookie-import', ['/etc/passwd'], bm);
2117
- expect(true).toBe(false);
2118
- } catch (err: any) {
2119
- expect(err.message).toContain('Path must be within');
2120
- }
2121
- });
2122
-
2123
- test('snapshot -a -o rejects path outside safe dirs', async () => {
2124
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2125
- // First get a snapshot so refs exist
2126
- await handleMetaCommand('snapshot', ['-i'], bm, () => {});
2127
- try {
2128
- await handleMetaCommand('snapshot', ['-a', '-o', '/etc/evil.png'], bm, () => {});
2129
- expect(true).toBe(false);
2130
- } catch (err: any) {
2131
- expect(err.message).toContain('Path must be within');
2132
- }
2133
- });
2134
- });
2135
-
2136
- // ─── Chain command: cookie-import in chain ──────────────────────
2137
-
2138
- describe('Chain with cookie-import', () => {
2139
- test('cookie-import works inside chain', async () => {
2140
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2141
- const tmpCookies = tmpp('test-chain-cookies.json');
2142
- fs.writeFileSync(tmpCookies, JSON.stringify([
2143
- { name: 'chain_test', value: 'chain_value', domain: '127.0.0.1', path: '/' }
2144
- ]));
2145
- try {
2146
- const commands = JSON.stringify([
2147
- ['cookie-import', tmpCookies],
2148
- ]);
2149
- const result = await chainMeta(bm, [commands]);
2150
- expect(result).toContain('[cookie-import]');
2151
- expect(result).toContain('Loaded 1 cookie');
2152
- } finally {
2153
- try { fs.unlinkSync(tmpCookies); } catch {}
2154
- }
2155
- });
2156
- });
2157
-
2158
- // ─── Network Idle Detection ─────────────────────────────────────
2159
-
2160
- describe('Network idle', () => {
2161
- test('click on fetch button waits for XHR to complete', async () => {
2162
- await handleWriteCommand('goto', [baseUrl + '/network-idle.html'], bm);
2163
- // Click the button that triggers a fetch → networkidle waits for it
2164
- await handleWriteCommand('click', ['#fetch-btn'], bm);
2165
- // The DOM should be updated by the time click returns
2166
- const result = await handleReadCommand('js', ['document.getElementById("result").textContent'], bm);
2167
- expect(result).toContain('Data loaded');
2168
- });
2169
-
2170
- test('click on static button has no latency penalty', async () => {
2171
- await handleWriteCommand('goto', [baseUrl + '/network-idle.html'], bm);
2172
- const start = Date.now();
2173
- await handleWriteCommand('click', ['#static-btn'], bm);
2174
- const elapsed = Date.now() - start;
2175
- // Static click should complete well under 2s (the networkidle timeout)
2176
- // networkidle resolves immediately when no requests are in flight
2177
- expect(elapsed).toBeLessThan(1500);
2178
- const result = await handleReadCommand('js', ['document.getElementById("static-result").textContent'], bm);
2179
- expect(result).toBe('Static action done');
2180
- });
2181
-
2182
- test('fill triggers networkidle wait', async () => {
2183
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
2184
- // fill should complete without error (networkidle resolves immediately on static page)
2185
- const result = await handleWriteCommand('fill', ['#email', 'idle@test.com'], bm);
2186
- expect(result).toContain('Filled');
2187
- });
2188
- });
2189
-
2190
- // ─── Chain Pipe Format ──────────────────────────────────────────
2191
-
2192
- describe('Chain pipe format', () => {
2193
- test('pipe-delimited commands work', async () => {
2194
- const result = await chainMeta(bm, [`goto ${baseUrl}/basic.html | js document.title`]);
2195
- expect(result).toContain('[goto]');
2196
- expect(result).toContain('[js]');
2197
- expect(result).toContain('Test Page - Basic');
2198
- });
2199
-
2200
- test('pipe format with quoted args', async () => {
2201
- const result = await chainMeta(bm, [`goto ${baseUrl}/forms.html | fill #email "pipe@test.com"`]);
2202
- expect(result).toContain('[fill]');
2203
- expect(result).toContain('Filled');
2204
- // Verify the fill actually worked
2205
- const val = await handleReadCommand('js', ['document.querySelector("#email").value'], bm);
2206
- expect(val).toBe('pipe@test.com');
2207
- });
2208
-
2209
- test('JSON format still works', async () => {
2210
- const commands = JSON.stringify([
2211
- ['goto', baseUrl + '/basic.html'],
2212
- ['js', 'document.title'],
2213
- ]);
2214
- const result = await chainMeta(bm, [commands]);
2215
- expect(result).toContain('[goto]');
2216
- expect(result).toContain('Test Page - Basic');
2217
- });
2218
-
2219
- test('pipe format with unknown command includes error', async () => {
2220
- const result = await chainMeta(bm, ['bogus command']);
2221
- expect(result).toContain('ERROR');
2222
- expect(result).toContain('Unknown command: bogus');
2223
- });
2224
- });
2225
-
2226
- // ─── State Persistence ──────────────────────────────────────────
2227
-
2228
- describe('State persistence', () => {
2229
- test('state save and load round-trip', async () => {
2230
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2231
- // Set a cookie so we can verify it persists
2232
- await handleWriteCommand('cookie', ['state_test=hello'], bm);
2233
-
2234
- // Save state
2235
- const saveResult = await handleMetaCommand('state', ['save', 'test-roundtrip'], bm, async () => {});
2236
- expect(saveResult).toContain('State saved');
2237
- expect(saveResult).toContain('Cookies stored in plaintext');
2238
-
2239
- // Navigate away
2240
- await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
2241
-
2242
- // Load state — should restore to basic.html with cookie
2243
- const loadResult = await handleMetaCommand('state', ['load', 'test-roundtrip'], bm, async () => {});
2244
- expect(loadResult).toContain('State loaded');
2245
-
2246
- // Verify we're back on basic.html
2247
- const url = await handleReadCommand('js', ['location.pathname'], bm);
2248
- expect(url).toContain('basic.html');
2249
-
2250
- // Clean up
2251
- try {
2252
- const { resolveConfig } = await import('../src/config');
2253
- const config = resolveConfig();
2254
- fs.unlinkSync(`${config.stateDir}/browse-states/test-roundtrip.json`);
2255
- } catch {}
2256
- });
2257
-
2258
- test('state save rejects invalid names', async () => {
2259
- try {
2260
- await handleMetaCommand('state', ['save', '../../evil'], bm, async () => {});
2261
- expect(true).toBe(false);
2262
- } catch (err: any) {
2263
- expect(err.message).toContain('alphanumeric');
2264
- }
2265
- });
2266
-
2267
- test('state save accepts valid names', async () => {
2268
- const result = await handleMetaCommand('state', ['save', 'my-state_1'], bm, async () => {});
2269
- expect(result).toContain('State saved');
2270
- // Clean up
2271
- try {
2272
- const { resolveConfig } = await import('../src/config');
2273
- const config = resolveConfig();
2274
- fs.unlinkSync(`${config.stateDir}/browse-states/my-state_1.json`);
2275
- } catch {}
2276
- });
2277
-
2278
- test('state load rejects missing state', async () => {
2279
- try {
2280
- await handleMetaCommand('state', ['load', 'nonexistent-state-xyz'], bm, async () => {});
2281
- expect(true).toBe(false);
2282
- } catch (err: any) {
2283
- expect(err.message).toContain('State not found');
2284
- }
2285
- });
2286
-
2287
- test('state requires action and name', async () => {
2288
- try {
2289
- await handleMetaCommand('state', [], bm, async () => {});
2290
- expect(true).toBe(false);
2291
- } catch (err: any) {
2292
- expect(err.message).toContain('Usage');
2293
- }
2294
- });
2295
- });
2296
-
2297
- // ─── Frame (Iframe Support) ─────────────────────────────────────
2298
-
2299
- describe('Frame', () => {
2300
- test('frame switch to iframe and back', async () => {
2301
- await handleWriteCommand('goto', [baseUrl + '/iframe.html'], bm);
2302
-
2303
- // Verify we're on the main page
2304
- const mainTitle = await handleReadCommand('js', ['document.getElementById("main-title").textContent'], bm);
2305
- expect(mainTitle).toBe('Main Page');
2306
-
2307
- // Switch to iframe by CSS selector
2308
- const switchResult = await handleMetaCommand('frame', ['#test-frame'], bm, async () => {});
2309
- expect(switchResult).toContain('Switched to frame');
2310
-
2311
- // Verify we can read iframe content
2312
- const frameTitle = await handleReadCommand('js', ['document.getElementById("frame-title").textContent'], bm);
2313
- expect(frameTitle).toBe('Inside Frame');
2314
-
2315
- // Switch back to main
2316
- const mainResult = await handleMetaCommand('frame', ['main'], bm, async () => {});
2317
- expect(mainResult).toBe('Switched to main frame');
2318
-
2319
- // Verify we're back on the main page
2320
- const mainTitleAgain = await handleReadCommand('js', ['document.getElementById("main-title").textContent'], bm);
2321
- expect(mainTitleAgain).toBe('Main Page');
2322
- });
2323
-
2324
- test('snapshot shows frame context header', async () => {
2325
- await handleWriteCommand('goto', [baseUrl + '/iframe.html'], bm);
2326
- await handleMetaCommand('frame', ['#test-frame'], bm, async () => {});
2327
-
2328
- const snap = await handleMetaCommand('snapshot', ['-i'], bm, async () => {});
2329
- expect(snap).toContain('[Context: iframe');
2330
-
2331
- // Clean up — return to main
2332
- await handleMetaCommand('frame', ['main'], bm, async () => {});
2333
- });
2334
-
2335
- test('goto throws error when in frame context', async () => {
2336
- await handleWriteCommand('goto', [baseUrl + '/iframe.html'], bm);
2337
- await handleMetaCommand('frame', ['#test-frame'], bm, async () => {});
2338
-
2339
- try {
2340
- await handleWriteCommand('goto', ['https://example.com'], bm);
2341
- expect(true).toBe(false);
2342
- } catch (err: any) {
2343
- expect(err.message).toContain('Cannot use goto inside a frame');
2344
- }
2345
-
2346
- await handleMetaCommand('frame', ['main'], bm, async () => {});
2347
- });
2348
-
2349
- test('frame requires argument', async () => {
2350
- try {
2351
- await handleMetaCommand('frame', [], bm, async () => {});
2352
- expect(true).toBe(false);
2353
- } catch (err: any) {
2354
- expect(err.message).toContain('Usage');
2355
- }
2356
- });
2357
-
2358
- test('fill works inside iframe', async () => {
2359
- await handleWriteCommand('goto', [baseUrl + '/iframe.html'], bm);
2360
- await handleMetaCommand('frame', ['#test-frame'], bm, async () => {});
2361
-
2362
- const result = await handleWriteCommand('fill', ['#frame-input', 'hello from frame'], bm);
2363
- expect(result).toContain('Filled');
2364
-
2365
- const value = await handleReadCommand('js', ['document.getElementById("frame-input").value'], bm);
2366
- expect(value).toBe('hello from frame');
2367
-
2368
- await handleMetaCommand('frame', ['main'], bm, async () => {});
2369
- });
2370
- });
2371
-
2372
- // ─── load-html ─────────────────────────────────────────────────
2373
-
2374
- describe('load-html', () => {
2375
- const tmpDir = '/tmp';
2376
- const fixturePath = path.join(tmpDir, `browse-test-loadhtml-${Date.now()}.html`);
2377
- const fragmentPath = path.join(tmpDir, `browse-test-fragment-${Date.now()}.html`);
2378
-
2379
- beforeAll(() => {
2380
- fs.writeFileSync(fixturePath, '<html><body><h1 id="loaded">loaded by load-html</h1></body></html>');
2381
- fs.writeFileSync(fragmentPath, '<div class="fragment" style="width:100px;height:50px">fragment</div>');
2382
- });
2383
-
2384
- afterAll(() => {
2385
- try { fs.unlinkSync(fixturePath); } catch {}
2386
- try { fs.unlinkSync(fragmentPath); } catch {}
2387
- });
2388
-
2389
- test('load-html loads HTML file into page', async () => {
2390
- const result = await handleWriteCommand('load-html', [fixturePath], bm);
2391
- expect(result).toContain('Loaded HTML:');
2392
- expect(result).toContain(fixturePath);
2393
- const text = await handleReadCommand('text', [], bm);
2394
- expect(text).toContain('loaded by load-html');
2395
- });
2396
-
2397
- test('load-html accepts bare HTML fragments (no doctype)', async () => {
2398
- const result = await handleWriteCommand('load-html', [fragmentPath], bm);
2399
- expect(result).toContain('Loaded HTML:');
2400
- const html = await handleReadCommand('html', [], bm);
2401
- expect(html).toContain('fragment');
2402
- });
2403
-
2404
- test('load-html rejects missing file arg', async () => {
2405
- try {
2406
- await handleWriteCommand('load-html', [], bm);
2407
- expect(true).toBe(false);
2408
- } catch (err: any) {
2409
- expect(err.message).toMatch(/Usage: browse load-html/);
2410
- }
2411
- });
2412
-
2413
- test('load-html rejects non-.html extension', async () => {
2414
- const txtPath = path.join(tmpDir, `load-html-test-${Date.now()}.txt`);
2415
- fs.writeFileSync(txtPath, '<html></html>');
2416
- try {
2417
- await handleWriteCommand('load-html', [txtPath], bm);
2418
- expect(true).toBe(false);
2419
- } catch (err: any) {
2420
- expect(err.message).toMatch(/does not appear to be HTML/);
2421
- } finally {
2422
- try { fs.unlinkSync(txtPath); } catch {}
2423
- }
2424
- });
2425
-
2426
- test('load-html rejects .svg files', async () => {
2427
- const svgPath = path.join(tmpDir, `load-html-test-${Date.now()}.svg`);
2428
- fs.writeFileSync(svgPath, '<svg xmlns="http://www.w3.org/2000/svg"><text>hi</text></svg>');
2429
- try {
2430
- await handleWriteCommand('load-html', [svgPath], bm);
2431
- expect(true).toBe(false);
2432
- } catch (err: any) {
2433
- expect(err.message).toMatch(/does not appear to be HTML/);
2434
- } finally {
2435
- try { fs.unlinkSync(svgPath); } catch {}
2436
- }
2437
- });
2438
-
2439
- test('load-html rejects file outside safe dirs', async () => {
2440
- try {
2441
- await handleWriteCommand('load-html', ['/etc/passwd.html'], bm);
2442
- expect(true).toBe(false);
2443
- } catch (err: any) {
2444
- expect(err.message).toMatch(/must be under|not found|security policy/);
2445
- }
2446
- });
2447
-
2448
- test('load-html rejects missing file with actionable error', async () => {
2449
- try {
2450
- await handleWriteCommand('load-html', [path.join(tmpDir, 'does-not-exist.html')], bm);
2451
- expect(true).toBe(false);
2452
- } catch (err: any) {
2453
- expect(err.message).toMatch(/not found|security policy/);
2454
- }
2455
- });
2456
-
2457
- test('load-html rejects directory target', async () => {
2458
- try {
2459
- await handleWriteCommand('load-html', [path.join(tmpDir, 'browse-test-notafile.html') + '/'], bm);
2460
- expect(true).toBe(false);
2461
- } catch (err: any) {
2462
- // Either "not found" or "is a directory" — both valid rejections
2463
- expect(err.message).toMatch(/not found|directory|not a regular file|security policy/);
2464
- }
2465
- });
2466
-
2467
- test('load-html rejects binary content disguised as .html', async () => {
2468
- const binPath = path.join(tmpDir, `load-html-binary-${Date.now()}.html`);
2469
- // PNG magic bytes: 0x89 0x50 0x4E 0x47
2470
- fs.writeFileSync(binPath, Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]));
2471
- try {
2472
- await handleWriteCommand('load-html', [binPath], bm);
2473
- expect(true).toBe(false);
2474
- } catch (err: any) {
2475
- expect(err.message).toMatch(/does not look like HTML/);
2476
- } finally {
2477
- try { fs.unlinkSync(binPath); } catch {}
2478
- }
2479
- });
2480
-
2481
- test('load-html strips UTF-8 BOM before magic-byte check', async () => {
2482
- const bomPath = path.join(tmpDir, `load-html-bom-${Date.now()}.html`);
2483
- const bomBytes = Buffer.from([0xEF, 0xBB, 0xBF]);
2484
- fs.writeFileSync(bomPath, Buffer.concat([bomBytes, Buffer.from('<html><body>bom ok</body></html>')]));
2485
- try {
2486
- const result = await handleWriteCommand('load-html', [bomPath], bm);
2487
- expect(result).toContain('Loaded HTML:');
2488
- } finally {
2489
- try { fs.unlinkSync(bomPath); } catch {}
2490
- }
2491
- });
2492
-
2493
- test('load-html --wait-until networkidle exercises non-default branch', async () => {
2494
- const result = await handleWriteCommand('load-html', [fixturePath, '--wait-until', 'networkidle'], bm);
2495
- expect(result).toContain('Loaded HTML:');
2496
- });
2497
-
2498
- test('load-html rejects invalid --wait-until value', async () => {
2499
- try {
2500
- await handleWriteCommand('load-html', [fixturePath, '--wait-until', 'bogus'], bm);
2501
- expect(true).toBe(false);
2502
- } catch (err: any) {
2503
- expect(err.message).toMatch(/Invalid --wait-until/);
2504
- }
2505
- });
2506
-
2507
- test('load-html rejects unknown flag', async () => {
2508
- try {
2509
- await handleWriteCommand('load-html', [fixturePath, '--bogus'], bm);
2510
- expect(true).toBe(false);
2511
- } catch (err: any) {
2512
- expect(err.message).toMatch(/Unknown flag/);
2513
- }
2514
- });
2515
- });
2516
-
2517
- // ─── screenshot --selector ─────────────────────────────────────
2518
-
2519
- describe('screenshot --selector', () => {
2520
- test('--selector flag with output path captures element', async () => {
2521
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2522
- const p = tmpp(`browse-test-selector-${Date.now()}.png`);
2523
- const result = await handleMetaCommand('screenshot', ['--selector', '#title', p], bm, async () => {});
2524
- expect(result).toContain('Screenshot saved (element)');
2525
- expect(fs.existsSync(p)).toBe(true);
2526
- fs.unlinkSync(p);
2527
- });
2528
-
2529
- test('--selector conflicts with positional selector', async () => {
2530
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2531
- try {
2532
- await handleMetaCommand('screenshot', ['--selector', '#title', '.other'], bm, async () => {});
2533
- expect(true).toBe(false);
2534
- } catch (err: any) {
2535
- expect(err.message).toMatch(/conflicts with positional selector/);
2536
- }
2537
- });
2538
-
2539
- test('--selector conflicts with --clip', async () => {
2540
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2541
- try {
2542
- await handleMetaCommand('screenshot', ['--selector', '#title', '--clip', '0,0,100,100'], bm, async () => {});
2543
- expect(true).toBe(false);
2544
- } catch (err: any) {
2545
- expect(err.message).toMatch(/Cannot use --clip with a selector/);
2546
- }
2547
- });
2548
-
2549
- test('--selector with --base64 returns element base64', async () => {
2550
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2551
- const result = await handleMetaCommand('screenshot', ['--selector', '#title', '--base64'], bm, async () => {});
2552
- expect(result).toMatch(/^data:image\/png;base64,/);
2553
- });
2554
-
2555
- test('--selector missing value throws', async () => {
2556
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2557
- try {
2558
- await handleMetaCommand('screenshot', ['--selector'], bm, async () => {});
2559
- expect(true).toBe(false);
2560
- } catch (err: any) {
2561
- expect(err.message).toMatch(/Usage: screenshot --selector/);
2562
- }
2563
- });
2564
- });
2565
-
2566
- // ─── viewport --scale ───────────────────────────────────────────
2567
-
2568
- describe('viewport --scale', () => {
2569
- test('viewport WxH --scale 2 produces 2x dimension screenshot', async () => {
2570
- const tmpFix = path.join('/tmp', `scale-${Date.now()}.html`);
2571
- fs.writeFileSync(tmpFix, '<div id="box" style="width:100px;height:50px;background:#f00"></div>');
2572
- try {
2573
- await handleWriteCommand('viewport', ['200x200', '--scale', '2'], bm);
2574
- await handleWriteCommand('load-html', [tmpFix], bm);
2575
- const p = tmpp(`scale-${Date.now()}.png`);
2576
- await handleMetaCommand('screenshot', ['--selector', '#box', p], bm, async () => {});
2577
- // Parse PNG IHDR (bytes 16-23 are width/height big-endian u32)
2578
- const buf = fs.readFileSync(p);
2579
- const w = buf.readUInt32BE(16);
2580
- const h = buf.readUInt32BE(20);
2581
- // Box is 100x50 at 2x = 200x100
2582
- expect(w).toBe(200);
2583
- expect(h).toBe(100);
2584
- fs.unlinkSync(p);
2585
- // Reset scale for other tests
2586
- await handleWriteCommand('viewport', ['1280x720', '--scale', '1'], bm);
2587
- } finally {
2588
- try { fs.unlinkSync(tmpFix); } catch {}
2589
- }
2590
- });
2591
-
2592
- test('viewport --scale without WxH keeps current size', async () => {
2593
- await handleWriteCommand('viewport', ['800x600'], bm);
2594
- const result = await handleWriteCommand('viewport', ['--scale', '2'], bm);
2595
- expect(result).toContain('800x600');
2596
- expect(result).toContain('2x');
2597
- expect(bm.getDeviceScaleFactor()).toBe(2);
2598
- await handleWriteCommand('viewport', ['1280x720', '--scale', '1'], bm);
2599
- });
2600
-
2601
- test('--scale non-finite (NaN) throws', async () => {
2602
- try {
2603
- await handleWriteCommand('viewport', ['100x100', '--scale', 'abc'], bm);
2604
- expect(true).toBe(false);
2605
- } catch (err: any) {
2606
- expect(err.message).toMatch(/not a finite number/);
2607
- }
2608
- });
2609
-
2610
- test('--scale out of range throws', async () => {
2611
- try {
2612
- await handleWriteCommand('viewport', ['100x100', '--scale', '4'], bm);
2613
- expect(true).toBe(false);
2614
- } catch (err: any) {
2615
- expect(err.message).toMatch(/between 1 and 3/);
2616
- }
2617
- try {
2618
- await handleWriteCommand('viewport', ['100x100', '--scale', '0.5'], bm);
2619
- expect(true).toBe(false);
2620
- } catch (err: any) {
2621
- expect(err.message).toMatch(/between 1 and 3/);
2622
- }
2623
- });
2624
-
2625
- test('--scale missing value throws', async () => {
2626
- try {
2627
- await handleWriteCommand('viewport', ['--scale'], bm);
2628
- expect(true).toBe(false);
2629
- } catch (err: any) {
2630
- expect(err.message).toMatch(/missing value/);
2631
- }
2632
- });
2633
-
2634
- test('viewport with neither arg nor flag throws usage', async () => {
2635
- try {
2636
- await handleWriteCommand('viewport', [], bm);
2637
- expect(true).toBe(false);
2638
- } catch (err: any) {
2639
- expect(err.message).toMatch(/Usage: browse viewport/);
2640
- }
2641
- });
2642
- });
2643
-
2644
- describe('viewport auto (unpin a pinned size)', () => {
2645
- test('headless: viewport 375x812 then auto returns to the 1280x720 default', async () => {
2646
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2647
- await handleWriteCommand('viewport', ['375x812'], bm);
2648
- expect(await handleWriteCommand('viewport', ['auto'], bm)).toBe('Viewport reset to default 1280x720');
2649
- expect(bm.getPage().viewportSize()).toEqual({ width: 1280, height: 720 });
2650
- expect(await bm.getPage().evaluate(() => window.innerWidth)).toBe(1280);
2651
- expect(bm.getCurrentViewport()).toEqual({ width: 1280, height: 720 });
2652
- });
2653
-
2654
- test('reset and unpin are aliases', async () => {
2655
- for (const alias of ['reset', 'unpin']) {
2656
- await handleWriteCommand('viewport', ['400x300'], bm);
2657
- await handleWriteCommand('viewport', [alias], bm);
2658
- expect(bm.getPage().viewportSize()).toEqual({ width: 1280, height: 720 });
2659
- }
2660
- });
2661
-
2662
- test('auto rejects --scale', async () => {
2663
- await expect(handleWriteCommand('viewport', ['auto', '--scale', '2'], bm)).rejects.toThrow(/cannot be combined/);
2664
- });
2665
-
2666
- test('handler reports which reset the manager performed', async () => {
2667
- for (const [mode, expected] of [['window', /follows the browser window/], ['default', /default 1280x720/]] as const) {
2668
- const fakeBm = { resetViewport: async () => mode } as unknown as BrowserManager;
2669
- expect(await _handleWriteCommand('viewport', ['auto'], bm.getActiveSession(), fakeBm)).toMatch(expected);
2670
- }
2671
- });
2672
-
2673
- test('headed: auto replaces the pinned page with a window-following one in the same tab', async () => {
2674
- const context = await (bm as any).browser.newContext({ viewport: null });
2675
- const headed = new BrowserManager();
2676
- Object.assign(headed as any, { connectionMode: 'headed', context });
2677
- try {
2678
- const tabId = await headed.newTab(baseUrl + '/basic.html');
2679
- await handleWriteCommand('viewport', ['600x400'], headed);
2680
- const pinned = headed.getPage();
2681
- expect(pinned.viewportSize()).toEqual({ width: 600, height: 400 });
2682
-
2683
- expect(await handleWriteCommand('viewport', ['auto'], headed)).toMatch(/follows the browser window/);
2684
- const fresh = headed.getPage();
2685
- expect(fresh).not.toBe(pinned);
2686
- expect(pinned.isClosed()).toBe(true);
2687
- expect(fresh.viewportSize()).toBeNull();
2688
- expect(fresh.url()).toBe(baseUrl + '/basic.html');
2689
- expect(headed.getActiveTabId()).toBe(tabId);
2690
- expect(headed.getTabCount()).toBe(1);
2691
-
2692
- await headed.getActiveSession().setTabContent('<p id="kept">kept</p>');
2693
- await handleWriteCommand('viewport', ['500x300'], headed);
2694
- await handleWriteCommand('viewport', ['auto'], headed);
2695
- expect(await headed.getPage().textContent('#kept')).toBe('kept');
2696
- expect(headed.getPage().viewportSize()).toBeNull();
2697
- } finally {
2698
- await context.close();
2699
- }
2700
- });
2701
- });
2702
-
2703
- // ─── setContent replay across context recreation ────────────────
2704
-
2705
- describe('setContent replay (load-html survives viewport --scale)', () => {
2706
- const tmpDir = '/tmp';
2707
-
2708
- test('load-html → viewport --scale 2 → content survives', async () => {
2709
- const fix = path.join(tmpDir, `replay-${Date.now()}.html`);
2710
- fs.writeFileSync(fix, '<h1 id="marker">replay-test-marker</h1>');
2711
- try {
2712
- await handleWriteCommand('load-html', [fix], bm);
2713
- await handleWriteCommand('viewport', ['400x300', '--scale', '2'], bm);
2714
- const text = await handleReadCommand('text', [], bm);
2715
- expect(text).toContain('replay-test-marker');
2716
- await handleWriteCommand('viewport', ['1280x720', '--scale', '1'], bm);
2717
- } finally {
2718
- try { fs.unlinkSync(fix); } catch {}
2719
- }
2720
- });
2721
-
2722
- test('double scale cycle: 2x → 1.5x, content still survives', async () => {
2723
- const fix = path.join(tmpDir, `replay2-${Date.now()}.html`);
2724
- fs.writeFileSync(fix, '<h2 id="m">double-cycle-marker</h2>');
2725
- try {
2726
- await handleWriteCommand('load-html', [fix], bm);
2727
- await handleWriteCommand('viewport', ['400x300', '--scale', '2'], bm);
2728
- await handleWriteCommand('viewport', ['400x300', '--scale', '1.5'], bm);
2729
- const text = await handleReadCommand('text', [], bm);
2730
- expect(text).toContain('double-cycle-marker');
2731
- await handleWriteCommand('viewport', ['1280x720', '--scale', '1'], bm);
2732
- } finally {
2733
- try { fs.unlinkSync(fix); } catch {}
2734
- }
2735
- });
2736
-
2737
- test('goto clears loadedHtml — subsequent viewport --scale does NOT resurrect old HTML', async () => {
2738
- const fix = path.join(tmpDir, `clear-${Date.now()}.html`);
2739
- fs.writeFileSync(fix, '<div id="stale">stale-content</div>');
2740
- try {
2741
- await handleWriteCommand('load-html', [fix], bm);
2742
- await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
2743
- await handleWriteCommand('viewport', ['400x300', '--scale', '2'], bm);
2744
- const text = await handleReadCommand('text', [], bm);
2745
- // Should see basic.html content, NOT the stale load-html content
2746
- expect(text).not.toContain('stale-content');
2747
- await handleWriteCommand('viewport', ['1280x720', '--scale', '1'], bm);
2748
- } finally {
2749
- try { fs.unlinkSync(fix); } catch {}
2750
- }
2751
- });
2752
- });
2753
-
2754
- // ─── Alias routing ─────────────────────────────────────────────
2755
-
2756
- describe('Command aliases', () => {
2757
- const tmpDir = '/tmp';
2758
- const aliasFix = path.join(tmpDir, `alias-${Date.now()}.html`);
2759
-
2760
- beforeAll(() => {
2761
- fs.writeFileSync(aliasFix, '<p id="alias">alias routing ok</p>');
2762
- });
2763
- afterAll(() => {
2764
- try { fs.unlinkSync(aliasFix); } catch {}
2765
- });
2766
-
2767
- test('setcontent alias routes to load-html via chain', async () => {
2768
- // Chain canonicalizes aliases end-to-end; verifies the dispatch path
2769
- const result = await chainMeta(bm, [JSON.stringify([['setcontent', aliasFix]])]);
2770
- expect(result).toContain('Loaded HTML:');
2771
- const text = await handleReadCommand('text', [], bm);
2772
- expect(text).toContain('alias routing ok');
2773
- });
2774
-
2775
- test('set-content (hyphenated) alias also routes', async () => {
2776
- const result = await chainMeta(bm, [JSON.stringify([['set-content', aliasFix]])]);
2777
- expect(result).toContain('Loaded HTML:');
2778
- });
2779
- });