@amg555/frameforge-mcp 2.7.6 → 2.7.8
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/plugins/video-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/video-pipeline/skills/.upstream-skills.json +1 -1
- package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +1 -1
- package/scripts/deep-validate.js +2 -2
- package/src/mcp-server/server.js +5 -5
- package/src/skills/sync-upstream.js +3 -3
- package/.agent/skills/ai-video-editor/README.md +0 -32
- package/.agent/skills/ai-video-editor/SKILL.md +0 -152
- package/.agent/skills/ai-video-editor/assets/fonts/fonts.css +0 -2409
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/anton-400.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/archivo-800.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/cabin-sketch-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/dm-sans-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-600.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/fredoka-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-800.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-tight-800.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/kalam-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/noto-sans-devanagari-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-400-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-500-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-600-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-700-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-800-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/engine/hidpi.js +0 -117
- package/.agent/skills/ai-video-editor/engine/page.html +0 -43
- package/.agent/skills/ai-video-editor/engine/player.js +0 -145
- package/.agent/skills/ai-video-editor/references/example-story-warm-canvas.js +0 -95
- package/.agent/skills/ai-video-editor/references/story-guide.md +0 -112
- package/.agent/skills/ai-video-editor/requirements.txt +0 -4
- package/.agent/skills/ai-video-editor/styles/chalk-talk/base.js +0 -490
- package/.agent/skills/ai-video-editor/styles/chalk-talk/style.json +0 -14
- package/.agent/skills/ai-video-editor/styles/chalk-talk/style.md +0 -41
- package/.agent/skills/ai-video-editor/styles/clean-tech/base.js +0 -447
- package/.agent/skills/ai-video-editor/styles/clean-tech/style.json +0 -15
- package/.agent/skills/ai-video-editor/styles/clean-tech/style.md +0 -38
- package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/base.js +0 -698
- package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.json +0 -16
- package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.md +0 -39
- package/.agent/skills/ai-video-editor/styles/data-story/base.js +0 -432
- package/.agent/skills/ai-video-editor/styles/data-story/style.json +0 -14
- package/.agent/skills/ai-video-editor/styles/data-story/style.md +0 -38
- package/.agent/skills/ai-video-editor/styles/index.json +0 -103
- package/.agent/skills/ai-video-editor/styles/pop-info/base.js +0 -570
- package/.agent/skills/ai-video-editor/styles/pop-info/style.json +0 -13
- package/.agent/skills/ai-video-editor/styles/pop-info/style.md +0 -37
- package/.agent/skills/ai-video-editor/styles/software-walkthrough/base.js +0 -544
- package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.json +0 -15
- package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.md +0 -38
- package/.agent/skills/ai-video-editor/styles/warm-canvas/base.js +0 -610
- package/.agent/skills/ai-video-editor/styles/warm-canvas/style.json +0 -14
- package/.agent/skills/ai-video-editor/styles/warm-canvas/style.md +0 -37
- package/.agent/skills/faceless-shorts/README.md +0 -34
- package/.agent/skills/faceless-shorts/SKILL.md +0 -124
- package/.agent/skills/faceless-shorts/styles/deep-dive/STYLE.md +0 -58
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/cues.json +0 -140
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/lines.json +0 -67
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.json +0 -614
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.txt +0 -102
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-ExtraBold.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/index.html +0 -16
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/script.json +0 -27
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/sheet.html +0 -10
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/captions.js +0 -68
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/engine.js +0 -65
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/human.js +0 -128
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/look2d.js +0 -33
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sdf.js +0 -79
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sets.js +0 -107
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/main.js +0 -56
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/sheet.js +0 -79
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/shots.js +0 -136
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/LICENSE-three.txt +0 -21
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/environments/RoomEnvironment.js +0 -144
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/BokehPass.js +0 -141
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/MaskPass.js +0 -104
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/OutputPass.js +0 -97
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/Pass.js +0 -95
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/RenderPass.js +0 -99
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/BokehShader.js +0 -145
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/CopyShader.js +0 -45
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/OutputShader.js +0 -85
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/three.module.js +0 -6
- package/.agent/skills/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/inside-out/STYLE.md +0 -97
- package/.agent/skills/faceless-shorts/styles/inside-out/example/data/cues.json +0 -178
- package/.agent/skills/faceless-shorts/styles/inside-out/example/data/lines.json +0 -73
- package/.agent/skills/faceless-shorts/styles/inside-out/example/data/words.json +0 -662
- package/.agent/skills/faceless-shorts/styles/inside-out/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/inside-out/example/index.html +0 -16
- package/.agent/skills/faceless-shorts/styles/inside-out/example/script.json +0 -26
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/captions.js +0 -68
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/kit.js +0 -345
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/main.js +0 -62
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/shots.js +0 -195
- package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/night-file/STYLE.md +0 -36
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/cues.json +0 -146
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/lines.json +0 -73
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.json +0 -572
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.txt +0 -95
- package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/FreeMonoBold.ttf +0 -0
- package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/night-file/example/index.html +0 -16
- package/.agent/skills/faceless-shorts/styles/night-file/example/script.json +0 -26
- package/.agent/skills/faceless-shorts/styles/night-file/example/sheet.html +0 -11
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/captions.js +0 -49
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/engine.js +0 -65
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/human.js +0 -128
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/kit.js +0 -151
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sdf.js +0 -79
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sets3d.js +0 -106
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/main.js +0 -56
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/sheet.js +0 -86
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/shots.js +0 -130
- package/.agent/skills/faceless-shorts/styles/night-file/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/night-file/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/LICENSE-three.txt +0 -21
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/environments/RoomEnvironment.js +0 -144
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/BokehPass.js +0 -141
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/MaskPass.js +0 -104
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/OutputPass.js +0 -97
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/Pass.js +0 -95
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/RenderPass.js +0 -99
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/BokehShader.js +0 -145
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/CopyShader.js +0 -45
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/OutputShader.js +0 -85
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/three.module.js +0 -6
- package/.agent/skills/faceless-shorts/styles/night-file/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/paper-atlas/STYLE.md +0 -30
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/cues.json +0 -167
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/lines.json +0 -67
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.json +0 -614
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.txt +0 -102
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Caladea-Bold.ttf +0 -0
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/index.html +0 -13
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/script.json +0 -26
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/sheet.html +0 -10
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/captions.js +0 -47
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/kit.js +0 -152
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/main.js +0 -62
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/sheet.js +0 -61
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/shots.js +0 -139
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/stick-story/STYLE.md +0 -32
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/cues.json +0 -182
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/lines.json +0 -79
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.json +0 -662
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.txt +0 -110
- package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-ExtraBold.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/stick-story/example/index.html +0 -12
- package/.agent/skills/faceless-shorts/styles/stick-story/example/script.json +0 -27
- package/.agent/skills/faceless-shorts/styles/stick-story/example/sheet.html +0 -9
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/captions.js +0 -47
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/kit.js +0 -166
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/main.js +0 -62
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/sheet.js +0 -64
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/shots.js +0 -148
- package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/STYLE.md +0 -51
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/cues.json +0 -271
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/lines.json +0 -127
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.json +0 -908
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.txt +0 -151
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Caladea-Bold.ttf +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-ExtraBold.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/index.html +0 -17
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/script.json +0 -37
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/sheet.html +0 -11
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/captions.js +0 -56
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/engine.js +0 -65
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/props.js +0 -166
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/toon.js +0 -352
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/world.js +0 -340
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/main.js +0 -56
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/sheet.js +0 -99
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/shots.js +0 -377
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/mix.py +0 -204
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/LICENSE-three.txt +0 -21
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/environments/RoomEnvironment.js +0 -144
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/BokehPass.js +0 -141
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/MaskPass.js +0 -104
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/OutputPass.js +0 -97
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/Pass.js +0 -95
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/RenderPass.js +0 -99
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/BokehShader.js +0 -145
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/CopyShader.js +0 -45
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/OutputShader.js +0 -85
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/three.module.js +0 -6
- package/.agent/skills/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
- package/.agent/skills/gstack-autoplan/SKILL.md +0 -972
- package/.agent/skills/gstack-autoplan/SKILL.md.tmpl +0 -468
- package/.agent/skills/gstack-autoplan/bin/guard-journal.ts +0 -115
- package/.agent/skills/gstack-autoplan/bin/guard-log.ts +0 -40
- package/.agent/skills/gstack-autoplan/bin/guard-reasons.ts +0 -92
- package/.agent/skills/gstack-autoplan/bin/owned-read.ts +0 -72
- package/.agent/skills/gstack-autoplan/bin/phase-publication-hook +0 -15
- package/.agent/skills/gstack-autoplan/bin/phase-publication-hook.ts +0 -769
- package/.agent/skills/gstack-autoplan/sections/ceo-phase.md +0 -195
- package/.agent/skills/gstack-autoplan/sections/ceo-phase.md.tmpl +0 -147
- package/.agent/skills/gstack-autoplan/sections/design-phase.md +0 -128
- package/.agent/skills/gstack-autoplan/sections/design-phase.md.tmpl +0 -82
- package/.agent/skills/gstack-autoplan/sections/dx-phase.md +0 -155
- package/.agent/skills/gstack-autoplan/sections/dx-phase.md.tmpl +0 -109
- package/.agent/skills/gstack-autoplan/sections/eng-phase.md +0 -164
- package/.agent/skills/gstack-autoplan/sections/eng-phase.md.tmpl +0 -116
- package/.agent/skills/gstack-autoplan/sections/manifest.json +0 -50
- package/.agent/skills/gstack-autoplan/sections/phase-close.md +0 -70
- package/.agent/skills/gstack-autoplan/sections/phase-close.md.tmpl +0 -68
- package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md +0 -71
- package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md.tmpl +0 -1
- package/.agent/skills/gstack-autoplan/sections/unattended.md +0 -11
- package/.agent/skills/gstack-autoplan/sections/unattended.md.tmpl +0 -9
- package/.agent/skills/gstack-benchmark/SKILL.md +0 -456
- package/.agent/skills/gstack-benchmark/SKILL.md.tmpl +0 -249
- package/.agent/skills/gstack-benchmark-models/SKILL.md +0 -285
- package/.agent/skills/gstack-benchmark-models/SKILL.md.tmpl +0 -155
- package/.agent/skills/gstack-browse/PLAN-snapshot-dropdown-interactive.md +0 -102
- package/.agent/skills/gstack-browse/SKILL.md +0 -470
- package/.agent/skills/gstack-browse/SKILL.md.tmpl +0 -129
- package/.agent/skills/gstack-browse/bin/find-browse +0 -21
- package/.agent/skills/gstack-browse/bin/remote-slug +0 -18
- package/.agent/skills/gstack-browse/sections/command-list.md +0 -176
- package/.agent/skills/gstack-browse/sections/command-list.md.tmpl +0 -7
- package/.agent/skills/gstack-browse/sections/manifest.json +0 -14
- package/.agent/skills/gstack-browse/src/activity.ts +0 -209
- package/.agent/skills/gstack-browse/src/audit.ts +0 -69
- package/.agent/skills/gstack-browse/src/browse-client.ts +0 -264
- package/.agent/skills/gstack-browse/src/browser-manager.ts +0 -2334
- package/.agent/skills/gstack-browse/src/browser-skill-commands.ts +0 -466
- package/.agent/skills/gstack-browse/src/browser-skill-write.ts +0 -216
- package/.agent/skills/gstack-browse/src/browser-skills.ts +0 -420
- package/.agent/skills/gstack-browse/src/buffers.ts +0 -137
- package/.agent/skills/gstack-browse/src/bun-child-config.ts +0 -35
- package/.agent/skills/gstack-browse/src/bun-polyfill.cjs +0 -276
- package/.agent/skills/gstack-browse/src/cdp-allowlist.ts +0 -235
- package/.agent/skills/gstack-browse/src/cdp-bridge.ts +0 -180
- package/.agent/skills/gstack-browse/src/cdp-commands.ts +0 -64
- package/.agent/skills/gstack-browse/src/cdp-inspector.ts +0 -824
- package/.agent/skills/gstack-browse/src/chromium-profiles.ts +0 -212
- package/.agent/skills/gstack-browse/src/claude-bin.ts +0 -2
- package/.agent/skills/gstack-browse/src/cli.ts +0 -2254
- package/.agent/skills/gstack-browse/src/commands.ts +0 -296
- package/.agent/skills/gstack-browse/src/config.ts +0 -307
- package/.agent/skills/gstack-browse/src/content-security.ts +0 -373
- package/.agent/skills/gstack-browse/src/cookie-auth-verification.ts +0 -225
- package/.agent/skills/gstack-browse/src/cookie-database.ts +0 -73
- package/.agent/skills/gstack-browse/src/cookie-import-browser.ts +0 -929
- package/.agent/skills/gstack-browse/src/cookie-import-native-integrity.ts +0 -95
- package/.agent/skills/gstack-browse/src/cookie-import-native-job.ts +0 -161
- package/.agent/skills/gstack-browse/src/cookie-import-native-qualification.json +0 -1
- package/.agent/skills/gstack-browse/src/cookie-import-native-worker.ts +0 -268
- package/.agent/skills/gstack-browse/src/cookie-import-native.ts +0 -166
- package/.agent/skills/gstack-browse/src/cookie-import-operation.ts +0 -232
- package/.agent/skills/gstack-browse/src/cookie-picker-routes.ts +0 -343
- package/.agent/skills/gstack-browse/src/cookie-picker-ui.ts +0 -717
- package/.agent/skills/gstack-browse/src/domain-skill-commands.ts +0 -301
- package/.agent/skills/gstack-browse/src/domain-skills.ts +0 -438
- package/.agent/skills/gstack-browse/src/error-handling.ts +0 -2
- package/.agent/skills/gstack-browse/src/extension-id.ts +0 -25
- package/.agent/skills/gstack-browse/src/file-permissions.ts +0 -370
- package/.agent/skills/gstack-browse/src/find-browse.ts +0 -118
- package/.agent/skills/gstack-browse/src/find-security-sidecar.ts +0 -78
- package/.agent/skills/gstack-browse/src/media-extract.ts +0 -177
- package/.agent/skills/gstack-browse/src/memory-command.ts +0 -115
- package/.agent/skills/gstack-browse/src/memory-snapshot.ts +0 -73
- package/.agent/skills/gstack-browse/src/meta-commands.ts +0 -1150
- package/.agent/skills/gstack-browse/src/network-capture.ts +0 -179
- package/.agent/skills/gstack-browse/src/path-security.ts +0 -132
- package/.agent/skills/gstack-browse/src/platform.ts +0 -38
- package/.agent/skills/gstack-browse/src/port-allocator.ts +0 -140
- package/.agent/skills/gstack-browse/src/project-slug.ts +0 -36
- package/.agent/skills/gstack-browse/src/proxy-config.ts +0 -283
- package/.agent/skills/gstack-browse/src/proxy-redact.ts +0 -46
- package/.agent/skills/gstack-browse/src/pty-session-cookie.ts +0 -64
- package/.agent/skills/gstack-browse/src/pty-session-lease.ts +0 -137
- package/.agent/skills/gstack-browse/src/read-commands.ts +0 -756
- package/.agent/skills/gstack-browse/src/routes/activity.ts +0 -63
- package/.agent/skills/gstack-browse/src/routes/commands.ts +0 -154
- package/.agent/skills/gstack-browse/src/routes/core.ts +0 -123
- package/.agent/skills/gstack-browse/src/routes/files.ts +0 -47
- package/.agent/skills/gstack-browse/src/routes/index.ts +0 -27
- package/.agent/skills/gstack-browse/src/routes/inspector.ts +0 -132
- package/.agent/skills/gstack-browse/src/routes/pairing.ts +0 -158
- package/.agent/skills/gstack-browse/src/routes/pty.ts +0 -254
- package/.agent/skills/gstack-browse/src/routes/table.ts +0 -182
- package/.agent/skills/gstack-browse/src/routes/tokens.ts +0 -86
- package/.agent/skills/gstack-browse/src/routes/tunnel.ts +0 -62
- package/.agent/skills/gstack-browse/src/sanitize.ts +0 -49
- package/.agent/skills/gstack-browse/src/screenshot-size-guard.ts +0 -106
- package/.agent/skills/gstack-browse/src/security-classifier.ts +0 -258
- package/.agent/skills/gstack-browse/src/security-sidecar-client.ts +0 -234
- package/.agent/skills/gstack-browse/src/security-sidecar-entry.ts +0 -120
- package/.agent/skills/gstack-browse/src/security.ts +0 -328
- package/.agent/skills/gstack-browse/src/server.ts +0 -2220
- package/.agent/skills/gstack-browse/src/session-cookie-store.ts +0 -124
- package/.agent/skills/gstack-browse/src/session-persist.ts +0 -178
- package/.agent/skills/gstack-browse/src/skill-token.ts +0 -91
- package/.agent/skills/gstack-browse/src/snapshot-flags.ts +0 -30
- package/.agent/skills/gstack-browse/src/snapshot.ts +0 -656
- package/.agent/skills/gstack-browse/src/socks-bridge.ts +0 -314
- package/.agent/skills/gstack-browse/src/sse-helpers.ts +0 -147
- package/.agent/skills/gstack-browse/src/sse-session-cookie.ts +0 -65
- package/.agent/skills/gstack-browse/src/stealth.ts +0 -574
- package/.agent/skills/gstack-browse/src/tab-session.ts +0 -210
- package/.agent/skills/gstack-browse/src/telemetry.ts +0 -115
- package/.agent/skills/gstack-browse/src/terminal-agent-control.ts +0 -379
- package/.agent/skills/gstack-browse/src/terminal-agent.ts +0 -1124
- package/.agent/skills/gstack-browse/src/terminal-pty-lifecycle.ts +0 -144
- package/.agent/skills/gstack-browse/src/token-registry.ts +0 -721
- package/.agent/skills/gstack-browse/src/tunnel-denial-log.ts +0 -97
- package/.agent/skills/gstack-browse/src/url-validation.ts +0 -299
- package/.agent/skills/gstack-browse/src/welcome.html +0 -237
- package/.agent/skills/gstack-browse/src/write-commands.ts +0 -1438
- package/.agent/skills/gstack-browse/src/xprotect-heal.ts +0 -439
- package/.agent/skills/gstack-browse/src/xvfb.ts +0 -313
- package/.agent/skills/gstack-browse/test/activity.test.ts +0 -120
- package/.agent/skills/gstack-browse/test/adversarial-security.test.ts +0 -32
- package/.agent/skills/gstack-browse/test/batch.test.ts +0 -267
- package/.agent/skills/gstack-browse/test/bridge-chromium-e2e.test.ts +0 -205
- package/.agent/skills/gstack-browse/test/browse-client.test.ts +0 -300
- package/.agent/skills/gstack-browse/test/browse-eval-wrapping.test.ts +0 -86
- package/.agent/skills/gstack-browse/test/browser-manager-custom-chromium.test.ts +0 -67
- package/.agent/skills/gstack-browse/test/browser-manager-unit.test.ts +0 -335
- package/.agent/skills/gstack-browse/test/browser-skill-commands.test.ts +0 -433
- package/.agent/skills/gstack-browse/test/browser-skill-write.test.ts +0 -350
- package/.agent/skills/gstack-browse/test/browser-skills-e2e.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/browser-skills-storage.test.ts +0 -283
- package/.agent/skills/gstack-browse/test/build-command-response.test.ts +0 -68
- package/.agent/skills/gstack-browse/test/build.test.ts +0 -52
- package/.agent/skills/gstack-browse/test/bun-child-config.test.ts +0 -83
- package/.agent/skills/gstack-browse/test/bun-polyfill.test.ts +0 -402
- package/.agent/skills/gstack-browse/test/busy-daemon-iron-rule.test.ts +0 -270
- package/.agent/skills/gstack-browse/test/busy-daemon-recovery.test.ts +0 -134
- package/.agent/skills/gstack-browse/test/cdp-allowlist.test.ts +0 -109
- package/.agent/skills/gstack-browse/test/cdp-e2e.test.ts +0 -117
- package/.agent/skills/gstack-browse/test/cdp-inspector-history-cap.test.ts +0 -95
- package/.agent/skills/gstack-browse/test/cdp-mutex.test.ts +0 -113
- package/.agent/skills/gstack-browse/test/cdp-session-cleanup.test.ts +0 -171
- package/.agent/skills/gstack-browse/test/chromium-profile-isolation.test.ts +0 -418
- package/.agent/skills/gstack-browse/test/claude-bin.test.ts +0 -95
- package/.agent/skills/gstack-browse/test/cli-chain-stdin.test.ts +0 -122
- package/.agent/skills/gstack-browse/test/cli-config-mismatch-message.test.ts +0 -72
- package/.agent/skills/gstack-browse/test/cli-lock.test.ts +0 -120
- package/.agent/skills/gstack-browse/test/cli-loopback-no-proxy.test.ts +0 -77
- package/.agent/skills/gstack-browse/test/cli-setsid-daemonize.test.ts +0 -75
- package/.agent/skills/gstack-browse/test/cli-start-final-healthcheck.test.ts +0 -77
- package/.agent/skills/gstack-browse/test/cli-supervisor.test.ts +0 -194
- package/.agent/skills/gstack-browse/test/commands.test.ts +0 -2779
- package/.agent/skills/gstack-browse/test/compare-board.test.ts +0 -382
- package/.agent/skills/gstack-browse/test/config.test.ts +0 -519
- package/.agent/skills/gstack-browse/test/content-security.test.ts +0 -681
- package/.agent/skills/gstack-browse/test/cookie-auth-verification.test.ts +0 -760
- package/.agent/skills/gstack-browse/test/cookie-credential-deadline.test.ts +0 -225
- package/.agent/skills/gstack-browse/test/cookie-database.test.ts +0 -277
- package/.agent/skills/gstack-browse/test/cookie-fixture-delete-lease.test.ts +0 -256
- package/.agent/skills/gstack-browse/test/cookie-import-browser.test.ts +0 -523
- package/.agent/skills/gstack-browse/test/cookie-import-native-job.test.ts +0 -1645
- package/.agent/skills/gstack-browse/test/cookie-import-native-qualification.ts +0 -128
- package/.agent/skills/gstack-browse/test/cookie-import-native.test.ts +0 -436
- package/.agent/skills/gstack-browse/test/cookie-import-node.test.ts +0 -166
- package/.agent/skills/gstack-browse/test/cookie-import-operation.test.ts +0 -452
- package/.agent/skills/gstack-browse/test/cookie-import-reliability.test.ts +0 -659
- package/.agent/skills/gstack-browse/test/cookie-import-transport.test.ts +0 -38
- package/.agent/skills/gstack-browse/test/cookie-picker-binding-browser.test.ts +0 -96
- package/.agent/skills/gstack-browse/test/cookie-picker-binding.test.ts +0 -155
- package/.agent/skills/gstack-browse/test/cookie-picker-csrf-browser.test.ts +0 -50
- package/.agent/skills/gstack-browse/test/cookie-picker-routes.test.ts +0 -460
- package/.agent/skills/gstack-browse/test/cookie-picker-ui.test.ts +0 -535
- package/.agent/skills/gstack-browse/test/daemon-log-hygiene.test.ts +0 -135
- package/.agent/skills/gstack-browse/test/daemon-mismatch-refuse.test.ts +0 -178
- package/.agent/skills/gstack-browse/test/data-platform.test.ts +0 -176
- package/.agent/skills/gstack-browse/test/dia-gui-readiness.test.ts +0 -174
- package/.agent/skills/gstack-browse/test/dia-launch-comparison.test.ts +0 -180
- package/.agent/skills/gstack-browse/test/dia-macos-qualification.test.ts +0 -1695
- package/.agent/skills/gstack-browse/test/domain-skills-e2e.test.ts +0 -142
- package/.agent/skills/gstack-browse/test/domain-skills-storage.test.ts +0 -263
- package/.agent/skills/gstack-browse/test/dotenv-isolation.test.ts +0 -121
- package/.agent/skills/gstack-browse/test/dual-listener.test.ts +0 -411
- package/.agent/skills/gstack-browse/test/dx-polish.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/error-handling.test.ts +0 -47
- package/.agent/skills/gstack-browse/test/extension-sender-auth.test.ts +0 -285
- package/.agent/skills/gstack-browse/test/extension-token.test.ts +0 -232
- package/.agent/skills/gstack-browse/test/file-drop.test.ts +0 -271
- package/.agent/skills/gstack-browse/test/file-permissions.test.ts +0 -346
- package/.agent/skills/gstack-browse/test/fill-change-event.test.ts +0 -78
- package/.agent/skills/gstack-browse/test/find-browse.test.ts +0 -61
- package/.agent/skills/gstack-browse/test/findport.test.ts +0 -232
- package/.agent/skills/gstack-browse/test/fixtures/basic.html +0 -33
- package/.agent/skills/gstack-browse/test/fixtures/change-only-validator.html +0 -31
- package/.agent/skills/gstack-browse/test/fixtures/cursor-interactive.html +0 -22
- package/.agent/skills/gstack-browse/test/fixtures/dialog.html +0 -15
- package/.agent/skills/gstack-browse/test/fixtures/dropdown.html +0 -61
- package/.agent/skills/gstack-browse/test/fixtures/empty.html +0 -2
- package/.agent/skills/gstack-browse/test/fixtures/forms.html +0 -55
- package/.agent/skills/gstack-browse/test/fixtures/iframe.html +0 -30
- package/.agent/skills/gstack-browse/test/fixtures/injection-combined.html +0 -58
- package/.agent/skills/gstack-browse/test/fixtures/injection-hidden.html +0 -62
- package/.agent/skills/gstack-browse/test/fixtures/injection-social.html +0 -25
- package/.agent/skills/gstack-browse/test/fixtures/injection-visible.html +0 -26
- package/.agent/skills/gstack-browse/test/fixtures/media-page.html +0 -67
- package/.agent/skills/gstack-browse/test/fixtures/mock-claude/claude +0 -185
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-delete-lease.ts +0 -125
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-file-owners.ts +0 -132
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-launch.cjs +0 -139
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process-observer.ts +0 -150
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process.cjs +0 -13
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-remove-fixture.cjs +0 -42
- package/.agent/skills/gstack-browse/test/fixtures/network-idle.html +0 -30
- package/.agent/skills/gstack-browse/test/fixtures/qa-eval-checkout.html +0 -108
- package/.agent/skills/gstack-browse/test/fixtures/qa-eval-spa.html +0 -98
- package/.agent/skills/gstack-browse/test/fixtures/qa-eval.html +0 -51
- package/.agent/skills/gstack-browse/test/fixtures/qa-only.html +0 -13
- package/.agent/skills/gstack-browse/test/fixtures/responsive.html +0 -49
- package/.agent/skills/gstack-browse/test/fixtures/route-dispatch-allowlist.json +0 -22
- package/.agent/skills/gstack-browse/test/fixtures/snapshot-ambiguous.html +0 -24
- package/.agent/skills/gstack-browse/test/fixtures/snapshot.html +0 -55
- package/.agent/skills/gstack-browse/test/fixtures/spa.html +0 -24
- package/.agent/skills/gstack-browse/test/fixtures/states.html +0 -17
- package/.agent/skills/gstack-browse/test/fixtures/upload-path-validation.ts +0 -91
- package/.agent/skills/gstack-browse/test/fixtures/upload.html +0 -25
- package/.agent/skills/gstack-browse/test/from-file-path-validation.test.ts +0 -68
- package/.agent/skills/gstack-browse/test/gstack-config.test.ts +0 -252
- package/.agent/skills/gstack-browse/test/gstack-update-check.test.ts +0 -614
- package/.agent/skills/gstack-browse/test/handoff.test.ts +0 -508
- package/.agent/skills/gstack-browse/test/headless-gpu-and-reap.test.ts +0 -286
- package/.agent/skills/gstack-browse/test/headless-launch-options.test.ts +0 -93
- package/.agent/skills/gstack-browse/test/launch-signal-flags.test.ts +0 -75
- package/.agent/skills/gstack-browse/test/learnings-injection.test.ts +0 -33
- package/.agent/skills/gstack-browse/test/media-extract-unit.test.ts +0 -294
- package/.agent/skills/gstack-browse/test/memory-command.test.ts +0 -219
- package/.agent/skills/gstack-browse/test/memory-leak-reproducer.test.ts +0 -133
- package/.agent/skills/gstack-browse/test/navigation-guard.test.ts +0 -106
- package/.agent/skills/gstack-browse/test/output-path-symlinks.test.ts +0 -76
- package/.agent/skills/gstack-browse/test/pair-agent-e2e.test.ts +0 -793
- package/.agent/skills/gstack-browse/test/pair-agent-optin-gate.test.ts +0 -171
- package/.agent/skills/gstack-browse/test/pair-agent-tunnel-eval.test.ts +0 -501
- package/.agent/skills/gstack-browse/test/path-security-realpath.test.ts +0 -89
- package/.agent/skills/gstack-browse/test/path-validation.test.ts +0 -268
- package/.agent/skills/gstack-browse/test/pdf-flags.test.ts +0 -86
- package/.agent/skills/gstack-browse/test/platform.test.ts +0 -37
- package/.agent/skills/gstack-browse/test/playwright-core-patch.test.ts +0 -62
- package/.agent/skills/gstack-browse/test/poisoned-bundle-probe.test.ts +0 -148
- package/.agent/skills/gstack-browse/test/process-liveness-windows.test.ts +0 -176
- package/.agent/skills/gstack-browse/test/proxy-config.test.ts +0 -189
- package/.agent/skills/gstack-browse/test/proxy-env-daemon.test.ts +0 -137
- package/.agent/skills/gstack-browse/test/proxy-env.test.ts +0 -189
- package/.agent/skills/gstack-browse/test/proxy-redact.test.ts +0 -64
- package/.agent/skills/gstack-browse/test/pty-inject-scan.test.ts +0 -153
- package/.agent/skills/gstack-browse/test/pty-session-lease.test.ts +0 -98
- package/.agent/skills/gstack-browse/test/rebrand-signed-bundle.test.ts +0 -53
- package/.agent/skills/gstack-browse/test/regression-pr1169-pdf-from-file-invalid-json.test.ts +0 -84
- package/.agent/skills/gstack-browse/test/restart-env.test.ts +0 -39
- package/.agent/skills/gstack-browse/test/route-test-harness.ts +0 -121
- package/.agent/skills/gstack-browse/test/runtime-root-server-bundle.test.ts +0 -100
- package/.agent/skills/gstack-browse/test/sanitize.test.ts +0 -112
- package/.agent/skills/gstack-browse/test/screenshot-size-guard.test.ts +0 -118
- package/.agent/skills/gstack-browse/test/security-adversarial-fixes.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/security-adversarial.test.ts +0 -342
- package/.agent/skills/gstack-browse/test/security-audit-r2.test.ts +0 -432
- package/.agent/skills/gstack-browse/test/security-bench.test.ts +0 -159
- package/.agent/skills/gstack-browse/test/security-classifier-download-cleanup.test.ts +0 -138
- package/.agent/skills/gstack-browse/test/security-classifier.test.ts +0 -29
- package/.agent/skills/gstack-browse/test/security-integration.test.ts +0 -184
- package/.agent/skills/gstack-browse/test/security-live-playwright.test.ts +0 -195
- package/.agent/skills/gstack-browse/test/security-sidecar-client.test.ts +0 -66
- package/.agent/skills/gstack-browse/test/security.test.ts +0 -268
- package/.agent/skills/gstack-browse/test/server-auth.test.ts +0 -600
- package/.agent/skills/gstack-browse/test/server-embedder-terminal-port.test.ts +0 -301
- package/.agent/skills/gstack-browse/test/server-factory.test.ts +0 -640
- package/.agent/skills/gstack-browse/test/server-flush-trackers.test.ts +0 -73
- package/.agent/skills/gstack-browse/test/server-launch-resolution.test.ts +0 -49
- package/.agent/skills/gstack-browse/test/server-lock-errors.test.ts +0 -124
- package/.agent/skills/gstack-browse/test/server-no-import-side-effects.test.ts +0 -110
- package/.agent/skills/gstack-browse/test/server-proxy-fail-fast.test.ts +0 -98
- package/.agent/skills/gstack-browse/test/server-pty-lease-routes.test.ts +0 -156
- package/.agent/skills/gstack-browse/test/server-route-auth-blackbox.test.ts +0 -270
- package/.agent/skills/gstack-browse/test/server-route-dispatch-ratchet.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/server-route-table.test.ts +0 -238
- package/.agent/skills/gstack-browse/test/server-sanitize-surrogates.test.ts +0 -182
- package/.agent/skills/gstack-browse/test/server-tmp-state-path.test.ts +0 -110
- package/.agent/skills/gstack-browse/test/session-cookie-store.test.ts +0 -159
- package/.agent/skills/gstack-browse/test/session-persist.test.ts +0 -287
- package/.agent/skills/gstack-browse/test/sidebar-tabs.test.ts +0 -268
- package/.agent/skills/gstack-browse/test/sidebar-ux.test.ts +0 -1006
- package/.agent/skills/gstack-browse/test/sidepanel-font-size.test.ts +0 -147
- package/.agent/skills/gstack-browse/test/sidepanel-patient-autoconnect.test.ts +0 -70
- package/.agent/skills/gstack-browse/test/sidepanel-reattach.test.ts +0 -93
- package/.agent/skills/gstack-browse/test/sidepanel-restart-dispose.test.ts +0 -106
- package/.agent/skills/gstack-browse/test/skill-token.test.ts +0 -167
- package/.agent/skills/gstack-browse/test/snapshot.test.ts +0 -593
- package/.agent/skills/gstack-browse/test/socks-bridge.test.ts +0 -461
- package/.agent/skills/gstack-browse/test/sse-helpers.test.ts +0 -194
- package/.agent/skills/gstack-browse/test/sse-session-cookie.test.ts +0 -155
- package/.agent/skills/gstack-browse/test/state-ttl.test.ts +0 -35
- package/.agent/skills/gstack-browse/test/stealth-extended.test.ts +0 -127
- package/.agent/skills/gstack-browse/test/stealth-layer-c.test.ts +0 -284
- package/.agent/skills/gstack-browse/test/stealth-webdriver.test.ts +0 -394
- package/.agent/skills/gstack-browse/test/stop-ack-before-shutdown.test.ts +0 -23
- package/.agent/skills/gstack-browse/test/stop-dead-daemon.test.ts +0 -153
- package/.agent/skills/gstack-browse/test/tab-each.test.ts +0 -196
- package/.agent/skills/gstack-browse/test/tab-guardrail.test.ts +0 -118
- package/.agent/skills/gstack-browse/test/tab-isolation.test.ts +0 -309
- package/.agent/skills/gstack-browse/test/tab-session-frame-detach.test.ts +0 -154
- package/.agent/skills/gstack-browse/test/telemetry-optout.test.ts +0 -171
- package/.agent/skills/gstack-browse/test/telemetry.test.ts +0 -77
- package/.agent/skills/gstack-browse/test/temp-dirs.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/terminal-agent-detach-reattach.test.ts +0 -84
- package/.agent/skills/gstack-browse/test/terminal-agent-import.test.ts +0 -42
- package/.agent/skills/gstack-browse/test/terminal-agent-integration.test.ts +0 -565
- package/.agent/skills/gstack-browse/test/terminal-agent-keepalive.test.ts +0 -88
- package/.agent/skills/gstack-browse/test/terminal-agent-lifecycle.test.ts +0 -371
- package/.agent/skills/gstack-browse/test/terminal-agent-native-observation.test.ts +0 -66
- package/.agent/skills/gstack-browse/test/terminal-agent-owner-watchdog.test.ts +0 -81
- package/.agent/skills/gstack-browse/test/terminal-agent-pid-identity.test.ts +0 -161
- package/.agent/skills/gstack-browse/test/terminal-agent-port-range.test.ts +0 -80
- package/.agent/skills/gstack-browse/test/terminal-agent-publication-lock.test.ts +0 -289
- package/.agent/skills/gstack-browse/test/terminal-agent-ring-buffer-runtime.test.ts +0 -157
- package/.agent/skills/gstack-browse/test/terminal-agent-session-routing.test.ts +0 -96
- package/.agent/skills/gstack-browse/test/terminal-agent-watchdog.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/terminal-agent.test.ts +0 -259
- package/.agent/skills/gstack-browse/test/terminal-pty-lifecycle.test.ts +0 -212
- package/.agent/skills/gstack-browse/test/test-server.ts +0 -57
- package/.agent/skills/gstack-browse/test/token-registry.test.ts +0 -615
- package/.agent/skills/gstack-browse/test/tunnel-gate-unit.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/tunnel-revoke-cli.test.ts +0 -455
- package/.agent/skills/gstack-browse/test/url-validation.test.ts +0 -384
- package/.agent/skills/gstack-browse/test/watch.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/watchdog.test.ts +0 -352
- package/.agent/skills/gstack-browse/test/welcome-page.test.ts +0 -143
- package/.agent/skills/gstack-browse/test/windows-spawn-hide.test.ts +0 -167
- package/.agent/skills/gstack-browse/test/xprotect-heal.test.ts +0 -457
- package/.agent/skills/gstack-browse/test/xvfb.test.ts +0 -603
- package/.agent/skills/gstack-canary/SKILL.md +0 -699
- package/.agent/skills/gstack-canary/SKILL.md.tmpl +0 -254
- package/.agent/skills/gstack-careful/SKILL.md +0 -96
- package/.agent/skills/gstack-careful/SKILL.md.tmpl +0 -107
- package/.agent/skills/gstack-careful/bin/check-careful.sh +0 -407
- package/.agent/skills/gstack-careful/bin/hook-extract.sh +0 -198
- package/.agent/skills/gstack-codex/SKILL.md +0 -889
- package/.agent/skills/gstack-codex/SKILL.md.tmpl +0 -358
- package/.agent/skills/gstack-codex/sections/challenge-mode.md +0 -150
- package/.agent/skills/gstack-codex/sections/challenge-mode.md.tmpl +0 -136
- package/.agent/skills/gstack-codex/sections/consult-mode.md +0 -221
- package/.agent/skills/gstack-codex/sections/consult-mode.md.tmpl +0 -207
- package/.agent/skills/gstack-codex/sections/manifest.json +0 -26
- package/.agent/skills/gstack-codex/sections/review-mode.md +0 -265
- package/.agent/skills/gstack-codex/sections/review-mode.md.tmpl +0 -246
- package/.agent/skills/gstack-context-restore/SKILL.md +0 -612
- package/.agent/skills/gstack-context-restore/SKILL.md.tmpl +0 -289
- package/.agent/skills/gstack-context-save/SKILL.md +0 -631
- package/.agent/skills/gstack-context-save/SKILL.md.tmpl +0 -307
- package/.agent/skills/gstack-cso/ACKNOWLEDGEMENTS.md +0 -17
- package/.agent/skills/gstack-cso/SKILL.md +0 -166
- package/.agent/skills/gstack-cso/SKILL.md.tmpl +0 -162
- package/.agent/skills/gstack-cso/sections/audit-phases.md +0 -116
- package/.agent/skills/gstack-cso/sections/audit-phases.md.tmpl +0 -114
- package/.agent/skills/gstack-cso/sections/manifest.json +0 -14
- package/.agent/skills/gstack-design-consultation/SKILL.md +0 -795
- package/.agent/skills/gstack-design-consultation/SKILL.md.tmpl +0 -234
- package/.agent/skills/gstack-design-consultation/sections/manifest.json +0 -14
- package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md +0 -595
- package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md.tmpl +0 -350
- package/.agent/skills/gstack-design-html/SKILL.md +0 -866
- package/.agent/skills/gstack-design-html/SKILL.md.tmpl +0 -476
- package/.agent/skills/gstack-design-html/sections/detector-install-offer.md +0 -40
- package/.agent/skills/gstack-design-html/sections/detector-install-offer.md.tmpl +0 -6
- package/.agent/skills/gstack-design-html/sections/doctrine.md +0 -86
- package/.agent/skills/gstack-design-html/sections/doctrine.md.tmpl +0 -1
- package/.agent/skills/gstack-design-html/sections/manifest.json +0 -26
- package/.agent/skills/gstack-design-html/sections/pretext-patterns.md +0 -145
- package/.agent/skills/gstack-design-html/sections/pretext-patterns.md.tmpl +0 -143
- package/.agent/skills/gstack-design-html/vendor/pretext.js +0 -5
- package/.agent/skills/gstack-design-review/SKILL.md +0 -1957
- package/.agent/skills/gstack-design-review/SKILL.md.tmpl +0 -344
- package/.agent/skills/gstack-design-shotgun/SKILL.md +0 -885
- package/.agent/skills/gstack-design-shotgun/SKILL.md.tmpl +0 -310
- package/.agent/skills/gstack-design-shotgun/sections/doctrine.md +0 -86
- package/.agent/skills/gstack-design-shotgun/sections/doctrine.md.tmpl +0 -1
- package/.agent/skills/gstack-design-shotgun/sections/manifest.json +0 -14
- package/.agent/skills/gstack-deslop-shared-libs/SKILL.md +0 -209
- package/.agent/skills/gstack-deslop-shared-libs/SKILL.md.tmpl +0 -181
- package/.agent/skills/gstack-devex-review/SKILL.md +0 -1107
- package/.agent/skills/gstack-devex-review/SKILL.md.tmpl +0 -241
- package/.agent/skills/gstack-diagram/SKILL.md +0 -295
- package/.agent/skills/gstack-diagram/SKILL.md.tmpl +0 -175
- package/.agent/skills/gstack-document-generate/SKILL.md +0 -814
- package/.agent/skills/gstack-document-generate/SKILL.md.tmpl +0 -460
- package/.agent/skills/gstack-document-release/SKILL.md +0 -557
- package/.agent/skills/gstack-document-release/SKILL.md.tmpl +0 -188
- package/.agent/skills/gstack-document-release/sections/audit-scope.md +0 -51
- package/.agent/skills/gstack-document-release/sections/audit-scope.md.tmpl +0 -49
- package/.agent/skills/gstack-document-release/sections/manifest.json +0 -20
- package/.agent/skills/gstack-document-release/sections/release-body.md +0 -640
- package/.agent/skills/gstack-document-release/sections/release-body.md.tmpl +0 -428
- package/.agent/skills/gstack-eval-plan/SKILL.md +0 -472
- package/.agent/skills/gstack-eval-plan/SKILL.md.tmpl +0 -157
- package/.agent/skills/gstack-freeze/SKILL.md +0 -79
- package/.agent/skills/gstack-freeze/SKILL.md.tmpl +0 -96
- package/.agent/skills/gstack-freeze/bin/check-freeze.sh +0 -210
- package/.agent/skills/gstack-freeze/bin/freeze-state.sh +0 -68
- package/.agent/skills/gstack-gstack-upgrade/SKILL.md +0 -410
- package/.agent/skills/gstack-gstack-upgrade/SKILL.md.tmpl +0 -413
- package/.agent/skills/gstack-gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
- package/.agent/skills/gstack-gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
- package/.agent/skills/gstack-guard/SKILL.md +0 -60
- package/.agent/skills/gstack-guard/SKILL.md.tmpl +0 -88
- package/.agent/skills/gstack-health/SKILL.md +0 -710
- package/.agent/skills/gstack-health/SKILL.md.tmpl +0 -386
- package/.agent/skills/gstack-investigate/SKILL.md +0 -706
- package/.agent/skills/gstack-investigate/SKILL.md.tmpl +0 -305
- package/.agent/skills/gstack-investigate/sections/backlog.md +0 -31
- package/.agent/skills/gstack-investigate/sections/backlog.md.tmpl +0 -29
- package/.agent/skills/gstack-investigate/sections/eval-bisect.md +0 -25
- package/.agent/skills/gstack-investigate/sections/eval-bisect.md.tmpl +0 -23
- package/.agent/skills/gstack-investigate/sections/flake.md +0 -50
- package/.agent/skills/gstack-investigate/sections/flake.md.tmpl +0 -43
- package/.agent/skills/gstack-investigate/sections/manifest.json +0 -38
- package/.agent/skills/gstack-investigate/sections/polluter.md +0 -33
- package/.agent/skills/gstack-investigate/sections/polluter.md.tmpl +0 -31
- package/.agent/skills/gstack-investigate/sections/race.md +0 -27
- package/.agent/skills/gstack-investigate/sections/race.md.tmpl +0 -25
- package/.agent/skills/gstack-ios-clean/SKILL.md +0 -427
- package/.agent/skills/gstack-ios-clean/SKILL.md.tmpl +0 -107
- package/.agent/skills/gstack-ios-design-review/SKILL.md +0 -434
- package/.agent/skills/gstack-ios-design-review/SKILL.md.tmpl +0 -111
- package/.agent/skills/gstack-ios-fix/SKILL.md +0 -423
- package/.agent/skills/gstack-ios-fix/SKILL.md.tmpl +0 -103
- package/.agent/skills/gstack-ios-qa/SKILL.md +0 -643
- package/.agent/skills/gstack-ios-qa/SKILL.md.tmpl +0 -297
- package/.agent/skills/gstack-ios-qa/daemon/src/allowlist.ts +0 -114
- package/.agent/skills/gstack-ios-qa/daemon/src/audit.ts +0 -97
- package/.agent/skills/gstack-ios-qa/daemon/src/auth-mint.ts +0 -89
- package/.agent/skills/gstack-ios-qa/daemon/src/cli-mint.ts +0 -149
- package/.agent/skills/gstack-ios-qa/daemon/src/devicectl.ts +0 -335
- package/.agent/skills/gstack-ios-qa/daemon/src/index.ts +0 -697
- package/.agent/skills/gstack-ios-qa/daemon/src/proxy.ts +0 -160
- package/.agent/skills/gstack-ios-qa/daemon/src/session-tokens.ts +0 -126
- package/.agent/skills/gstack-ios-qa/daemon/src/single-instance.ts +0 -171
- package/.agent/skills/gstack-ios-qa/daemon/src/tailscale-localapi.ts +0 -120
- package/.agent/skills/gstack-ios-qa/daemon/src/tunnel-bootstrap.ts +0 -459
- package/.agent/skills/gstack-ios-qa/daemon/src/types.ts +0 -91
- package/.agent/skills/gstack-ios-qa/daemon/test/allowlist.test.ts +0 -146
- package/.agent/skills/gstack-ios-qa/daemon/test/audit.test.ts +0 -111
- package/.agent/skills/gstack-ios-qa/daemon/test/auth-mint.test.ts +0 -106
- package/.agent/skills/gstack-ios-qa/daemon/test/cli-mint.test.ts +0 -120
- package/.agent/skills/gstack-ios-qa/daemon/test/daemon-integration.test.ts +0 -855
- package/.agent/skills/gstack-ios-qa/daemon/test/fake-device.ts +0 -238
- package/.agent/skills/gstack-ios-qa/daemon/test/proxy-classify.test.ts +0 -47
- package/.agent/skills/gstack-ios-qa/daemon/test/proxy-transport.test.ts +0 -499
- package/.agent/skills/gstack-ios-qa/daemon/test/session-tokens.test.ts +0 -156
- package/.agent/skills/gstack-ios-qa/daemon/test/single-instance.test.ts +0 -96
- package/.agent/skills/gstack-ios-qa/daemon/test/tailscale-localapi.test.ts +0 -55
- package/.agent/skills/gstack-ios-qa/daemon/test/tunnel-bootstrap.test.ts +0 -1030
- package/.agent/skills/gstack-ios-qa/templates/Bridges.swift.template +0 -474
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeManager.swift.template +0 -38
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.h.template +0 -38
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.m.template +0 -365
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeWiring.swift.template +0 -59
- package/.agent/skills/gstack-ios-qa/templates/DebugOverlay.swift.template +0 -137
- package/.agent/skills/gstack-ios-qa/templates/Package.swift.template +0 -82
- package/.agent/skills/gstack-ios-qa/templates/StateAccessor.swift.template +0 -41
- package/.agent/skills/gstack-ios-qa/templates/StateServer.swift.template +0 -625
- package/.agent/skills/gstack-ios-sync/SKILL.md +0 -418
- package/.agent/skills/gstack-ios-sync/SKILL.md.tmpl +0 -99
- package/.agent/skills/gstack-land-and-deploy/SKILL.md +0 -1028
- package/.agent/skills/gstack-land-and-deploy/SKILL.md.tmpl +0 -509
- package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md +0 -188
- package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md.tmpl +0 -150
- package/.agent/skills/gstack-land-and-deploy/sections/manifest.json +0 -26
- package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md +0 -250
- package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md.tmpl +0 -248
- package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md +0 -308
- package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md.tmpl +0 -306
- package/.agent/skills/gstack-landing-report/SKILL.md +0 -488
- package/.agent/skills/gstack-landing-report/SKILL.md.tmpl +0 -164
- package/.agent/skills/gstack-learn/SKILL.md +0 -519
- package/.agent/skills/gstack-learn/SKILL.md.tmpl +0 -197
- package/.agent/skills/gstack-make-pdf/SKILL.md +0 -409
- package/.agent/skills/gstack-make-pdf/SKILL.md.tmpl +0 -253
- package/.agent/skills/gstack-make-pdf/src/asideClient.ts +0 -114
- package/.agent/skills/gstack-make-pdf/src/cli.ts +0 -294
- package/.agent/skills/gstack-make-pdf/src/commands.ts +0 -62
- package/.agent/skills/gstack-make-pdf/src/diagram-prepass.ts +0 -834
- package/.agent/skills/gstack-make-pdf/src/html-to-docx.d.ts +0 -7
- package/.agent/skills/gstack-make-pdf/src/image-policy.ts +0 -236
- package/.agent/skills/gstack-make-pdf/src/image-size.ts +0 -117
- package/.agent/skills/gstack-make-pdf/src/orchestrator.ts +0 -339
- package/.agent/skills/gstack-make-pdf/src/pdftotext.ts +0 -318
- package/.agent/skills/gstack-make-pdf/src/print-css.ts +0 -452
- package/.agent/skills/gstack-make-pdf/src/render.ts +0 -636
- package/.agent/skills/gstack-make-pdf/src/setup.ts +0 -113
- package/.agent/skills/gstack-make-pdf/src/smartypants.ts +0 -105
- package/.agent/skills/gstack-make-pdf/src/toc-pages.ts +0 -197
- package/.agent/skills/gstack-make-pdf/src/types.ts +0 -109
- package/.agent/skills/gstack-make-pdf/test/asideClient.test.ts +0 -153
- package/.agent/skills/gstack-make-pdf/test/cli-args.test.ts +0 -94
- package/.agent/skills/gstack-make-pdf/test/cli-exit-codes.test.ts +0 -329
- package/.agent/skills/gstack-make-pdf/test/diagram-prepass.test.ts +0 -743
- package/.agent/skills/gstack-make-pdf/test/e2e/ci-prereqs.test.ts +0 -48
- package/.agent/skills/gstack-make-pdf/test/e2e/combined-gate.test.ts +0 -74
- package/.agent/skills/gstack-make-pdf/test/e2e/diagram-gate.test.ts +0 -173
- package/.agent/skills/gstack-make-pdf/test/e2e/emoji-gate.test.ts +0 -191
- package/.agent/skills/gstack-make-pdf/test/e2e/format-gate.test.ts +0 -130
- package/.agent/skills/gstack-make-pdf/test/e2e/landscape-gate.test.ts +0 -150
- package/.agent/skills/gstack-make-pdf/test/e2e/toc-gate.test.ts +0 -143
- package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.expected.txt +0 -20
- package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.md +0 -30
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/huge-noise.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/red-box.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-arch.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-screenshot.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-gate.md +0 -48
- package/.agent/skills/gstack-make-pdf/test/fixtures/emoji-gate.md +0 -12
- package/.agent/skills/gstack-make-pdf/test/fixtures/landscape-gate.md +0 -52
- package/.agent/skills/gstack-make-pdf/test/image-policy.test.ts +0 -215
- package/.agent/skills/gstack-make-pdf/test/pdftotext.test.ts +0 -327
- package/.agent/skills/gstack-make-pdf/test/render-offline-sanitize.test.ts +0 -268
- package/.agent/skills/gstack-make-pdf/test/render.test.ts +0 -603
- package/.agent/skills/gstack-make-pdf/test/setup-smoke.test.ts +0 -259
- package/.agent/skills/gstack-make-pdf/test/toc-anchors.test.ts +0 -95
- package/.agent/skills/gstack-make-pdf/test/toc-pages.test.ts +0 -148
- package/.agent/skills/gstack-office-hours/SKILL.md +0 -1324
- package/.agent/skills/gstack-office-hours/SKILL.md.tmpl +0 -372
- package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md +0 -706
- package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md.tmpl +0 -547
- package/.agent/skills/gstack-office-hours/sections/manifest.json +0 -26
- package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md +0 -163
- package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md.tmpl +0 -161
- package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md +0 -43
- package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md.tmpl +0 -41
- package/.agent/skills/gstack-open-gstack-browser/SKILL.md +0 -414
- package/.agent/skills/gstack-open-gstack-browser/SKILL.md.tmpl +0 -248
- package/.agent/skills/gstack-pair-agent/SKILL.md +0 -730
- package/.agent/skills/gstack-pair-agent/SKILL.md.tmpl +0 -366
- package/.agent/skills/gstack-plan-ceo-review/SKILL.md +0 -1245
- package/.agent/skills/gstack-plan-ceo-review/SKILL.md.tmpl +0 -596
- package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook +0 -5
- package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook.ts +0 -34
- package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md +0 -37
- package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md.tmpl +0 -35
- package/.agent/skills/gstack-plan-ceo-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md +0 -1207
- package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md.tmpl +0 -605
- package/.agent/skills/gstack-plan-design-review/SKILL.md +0 -1230
- package/.agent/skills/gstack-plan-design-review/SKILL.md.tmpl +0 -344
- package/.agent/skills/gstack-plan-design-review/sections/checklist.md +0 -27
- package/.agent/skills/gstack-plan-design-review/sections/checklist.md.tmpl +0 -25
- package/.agent/skills/gstack-plan-design-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-design-review/sections/review-sections.md +0 -727
- package/.agent/skills/gstack-plan-design-review/sections/review-sections.md.tmpl +0 -281
- package/.agent/skills/gstack-plan-devex-review/SKILL.md +0 -1072
- package/.agent/skills/gstack-plan-devex-review/SKILL.md.tmpl +0 -459
- package/.agent/skills/gstack-plan-devex-review/dx-hall-of-fame.md +0 -127
- package/.agent/skills/gstack-plan-devex-review/sections/checklist.md +0 -33
- package/.agent/skills/gstack-plan-devex-review/sections/checklist.md.tmpl +0 -31
- package/.agent/skills/gstack-plan-devex-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md +0 -1037
- package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md.tmpl +0 -446
- package/.agent/skills/gstack-plan-eng-review/SKILL.md +0 -702
- package/.agent/skills/gstack-plan-eng-review/SKILL.md.tmpl +0 -191
- package/.agent/skills/gstack-plan-eng-review/sections/checklist.md +0 -34
- package/.agent/skills/gstack-plan-eng-review/sections/checklist.md.tmpl +0 -32
- package/.agent/skills/gstack-plan-eng-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md +0 -1545
- package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md.tmpl +0 -614
- package/.agent/skills/gstack-plan-tune/SKILL.md +0 -986
- package/.agent/skills/gstack-plan-tune/SKILL.md.tmpl +0 -669
- package/.agent/skills/gstack-qa/SKILL.md +0 -781
- package/.agent/skills/gstack-qa/SKILL.md.tmpl +0 -305
- package/.agent/skills/gstack-qa/references/issue-taxonomy.md +0 -85
- package/.agent/skills/gstack-qa/sections/browser-setup.md +0 -114
- package/.agent/skills/gstack-qa/sections/browser-setup.md.tmpl +0 -28
- package/.agent/skills/gstack-qa/sections/browser-verify.md +0 -19
- package/.agent/skills/gstack-qa/sections/browser-verify.md.tmpl +0 -17
- package/.agent/skills/gstack-qa/sections/exploratory.md +0 -92
- package/.agent/skills/gstack-qa/sections/exploratory.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/manifest.json +0 -50
- package/.agent/skills/gstack-qa/sections/qa-patterns.md +0 -226
- package/.agent/skills/gstack-qa/sections/qa-patterns.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/scope.md +0 -23
- package/.agent/skills/gstack-qa/sections/scope.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/system-functional.md +0 -61
- package/.agent/skills/gstack-qa/sections/system-functional.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/test-bootstrap.md +0 -86
- package/.agent/skills/gstack-qa/sections/test-bootstrap.md.tmpl +0 -74
- package/.agent/skills/gstack-qa/templates/functional-report-template.md +0 -55
- package/.agent/skills/gstack-qa/templates/qa-report-template.md +0 -124
- package/.agent/skills/gstack-qa-only/SKILL.md +0 -595
- package/.agent/skills/gstack-qa-only/SKILL.md.tmpl +0 -214
- package/.agent/skills/gstack-qa-only/sections/exploratory.md +0 -107
- package/.agent/skills/gstack-qa-only/sections/exploratory.md.tmpl +0 -1
- package/.agent/skills/gstack-qa-only/sections/manifest.json +0 -19
- package/.agent/skills/gstack-qa-only/sections/reporting.md +0 -78
- package/.agent/skills/gstack-qa-only/sections/reporting.md.tmpl +0 -76
- package/.agent/skills/gstack-retro/SKILL.md +0 -1178
- package/.agent/skills/gstack-retro/SKILL.md.tmpl +0 -769
- package/.agent/skills/gstack-retro/sections/manifest.json +0 -14
- package/.agent/skills/gstack-retro/sections/report-format.md +0 -127
- package/.agent/skills/gstack-retro/sections/report-format.md.tmpl +0 -125
- package/.agent/skills/gstack-review/SKILL.md +0 -1199
- package/.agent/skills/gstack-review/SKILL.md.tmpl +0 -482
- package/.agent/skills/gstack-review/TODOS-format.md +0 -61
- package/.agent/skills/gstack-review/checklist.md +0 -222
- package/.agent/skills/gstack-review/design-checklist.md +0 -193
- package/.agent/skills/gstack-review/gate-integrity.md +0 -171
- package/.agent/skills/gstack-review/greptile-triage.md +0 -261
- package/.agent/skills/gstack-review/sections/adversarial.md +0 -296
- package/.agent/skills/gstack-review/sections/adversarial.md.tmpl +0 -1
- package/.agent/skills/gstack-review/sections/manifest.json +0 -32
- package/.agent/skills/gstack-review/sections/plan-completion.md +0 -232
- package/.agent/skills/gstack-review/sections/plan-completion.md.tmpl +0 -3
- package/.agent/skills/gstack-review/sections/review-army.md +0 -263
- package/.agent/skills/gstack-review/sections/review-army.md.tmpl +0 -1
- package/.agent/skills/gstack-review/sections/shared-code-reuse.md +0 -34
- package/.agent/skills/gstack-review/sections/shared-code-reuse.md.tmpl +0 -1
- package/.agent/skills/gstack-review/specialists/api-contract.md +0 -49
- package/.agent/skills/gstack-review/specialists/data-migration.md +0 -48
- package/.agent/skills/gstack-review/specialists/maintainability.md +0 -46
- package/.agent/skills/gstack-review/specialists/performance.md +0 -52
- package/.agent/skills/gstack-review/specialists/red-team.md +0 -45
- package/.agent/skills/gstack-review/specialists/security.md +0 -65
- package/.agent/skills/gstack-review/specialists/simplification.md +0 -49
- package/.agent/skills/gstack-review/specialists/testing.md +0 -160
- package/.agent/skills/gstack-scrape/SKILL.md +0 -431
- package/.agent/skills/gstack-scrape/SKILL.md.tmpl +0 -187
- package/.agent/skills/gstack-setup-browser-cookies/SKILL.md +0 -231
- package/.agent/skills/gstack-setup-browser-cookies/SKILL.md.tmpl +0 -64
- package/.agent/skills/gstack-setup-deploy/SKILL.md +0 -605
- package/.agent/skills/gstack-setup-deploy/SKILL.md.tmpl +0 -250
- package/.agent/skills/gstack-setup-gbrain/SKILL.md +0 -1074
- package/.agent/skills/gstack-setup-gbrain/SKILL.md.tmpl +0 -742
- package/.agent/skills/gstack-setup-gbrain/memory.md +0 -426
- package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md +0 -269
- package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md.tmpl +0 -267
- package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md +0 -40
- package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md.tmpl +0 -38
- package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md +0 -71
- package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md.tmpl +0 -69
- package/.agent/skills/gstack-setup-gbrain/sections/manifest.json +0 -32
- package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md +0 -105
- package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md.tmpl +0 -103
- package/.agent/skills/gstack-ship/SKILL.md +0 -1220
- package/.agent/skills/gstack-ship/SKILL.md.tmpl +0 -694
- package/.agent/skills/gstack-ship/sections/adversarial.md +0 -362
- package/.agent/skills/gstack-ship/sections/adversarial.md.tmpl +0 -22
- package/.agent/skills/gstack-ship/sections/apple-release.md +0 -98
- package/.agent/skills/gstack-ship/sections/apple-release.md.tmpl +0 -96
- package/.agent/skills/gstack-ship/sections/changelog.md +0 -47
- package/.agent/skills/gstack-ship/sections/changelog.md.tmpl +0 -5
- package/.agent/skills/gstack-ship/sections/documentation.md +0 -118
- package/.agent/skills/gstack-ship/sections/documentation.md.tmpl +0 -116
- package/.agent/skills/gstack-ship/sections/greptile-early.md +0 -57
- package/.agent/skills/gstack-ship/sections/greptile-early.md.tmpl +0 -48
- package/.agent/skills/gstack-ship/sections/greptile.md +0 -95
- package/.agent/skills/gstack-ship/sections/greptile.md.tmpl +0 -93
- package/.agent/skills/gstack-ship/sections/handoff.md +0 -22
- package/.agent/skills/gstack-ship/sections/handoff.md.tmpl +0 -20
- package/.agent/skills/gstack-ship/sections/manifest.json +0 -92
- package/.agent/skills/gstack-ship/sections/measure.md +0 -66
- package/.agent/skills/gstack-ship/sections/measure.md.tmpl +0 -64
- package/.agent/skills/gstack-ship/sections/plan-completion.md +0 -387
- package/.agent/skills/gstack-ship/sections/plan-completion.md.tmpl +0 -67
- package/.agent/skills/gstack-ship/sections/pr-body.md +0 -258
- package/.agent/skills/gstack-ship/sections/pr-body.md.tmpl +0 -241
- package/.agent/skills/gstack-ship/sections/review-army.md +0 -728
- package/.agent/skills/gstack-ship/sections/review-army.md.tmpl +0 -171
- package/.agent/skills/gstack-ship/sections/shared-code-reuse.md +0 -34
- package/.agent/skills/gstack-ship/sections/shared-code-reuse.md.tmpl +0 -1
- package/.agent/skills/gstack-ship/sections/test-coverage.md +0 -478
- package/.agent/skills/gstack-ship/sections/test-coverage.md.tmpl +0 -118
- package/.agent/skills/gstack-ship/sections/tests.md +0 -453
- package/.agent/skills/gstack-ship/sections/tests.md.tmpl +0 -144
- package/.agent/skills/gstack-skillify/SKILL.md +0 -790
- package/.agent/skills/gstack-skillify/SKILL.md.tmpl +0 -437
- package/.agent/skills/gstack-spec/SKILL.md +0 -893
- package/.agent/skills/gstack-spec/SKILL.md.tmpl +0 -498
- package/.agent/skills/gstack-spec/sections/gate-and-file.md +0 -470
- package/.agent/skills/gstack-spec/sections/gate-and-file.md.tmpl +0 -319
- package/.agent/skills/gstack-spec/sections/manifest.json +0 -14
- package/.agent/skills/gstack-sync-gbrain/SKILL.md +0 -935
- package/.agent/skills/gstack-sync-gbrain/SKILL.md.tmpl +0 -615
- package/.agent/skills/gstack-test-audit/SKILL.md +0 -500
- package/.agent/skills/gstack-test-audit/SKILL.md.tmpl +0 -149
- package/.agent/skills/gstack-unfreeze/SKILL.md +0 -39
- package/.agent/skills/gstack-unfreeze/SKILL.md.tmpl +0 -39
- package/.agent/skills/gstack-upgrade/SKILL.md +0 -410
- package/.agent/skills/gstack-upgrade/SKILL.md.tmpl +0 -413
- package/.agent/skills/gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
- package/.agent/skills/gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
- package/.agent/skills/gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
- package/.agent/skills/gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
- package/.agent/skills/gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
- package/.agent/skills/gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
- package/.agent/skills/gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
- package/.agent/skills/gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
- package/.agent/skills/gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
- package/.agent/skills/gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
- package/.agent/skills/gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
- package/.agent/skills/gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
- package/.agent/skills/gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
- package/.agent/skills/gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
- package/.agent/skills/gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
- package/.agent/skills/gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
- package/.agent/skills/html-renderer/SKILL.md +0 -336
- package/.agent/skills/html-renderer/references/animation-patterns.md +0 -609
- package/.agent/skills/html-renderer/references/character-animation.md +0 -388
- package/.agent/skills/html-renderer/references/collage-broll.md +0 -41
- package/.agent/skills/html-renderer/references/design.md +0 -217
- package/.agent/skills/html-renderer/references/directing.md +0 -165
- package/.agent/skills/html-renderer/references/earshot-audio.md +0 -201
- package/.agent/skills/html-renderer/references/engine-api.md +0 -480
- package/.agent/skills/html-renderer/references/film-styles.md +0 -95
- package/.agent/skills/html-renderer/references/hyperframes-motion.md +0 -208
- package/.agent/skills/html-renderer/references/motion-craft.md +0 -92
- package/.agent/skills/html-renderer/references/onetake-camera.md +0 -59
- package/.agent/skills/html-renderer/references/procedural-sfx.md +0 -147
- package/.agent/skills/html-renderer/references/remotion-patterns.md +0 -159
- package/.agent/skills/html-renderer/references/render-contract.md +0 -66
- package/.agent/skills/motion-styles/LIBRARY.md +0 -896
- package/.agent/skills/motion-styles/README.md +0 -29
- package/.agent/skills/motion-styles/SKILL.md +0 -70
- package/.agent/skills/motion-styles/catalog.json +0 -2253
- package/.agent/skills/motion-styles/prompts/bio-glow.md +0 -149
- package/.agent/skills/motion-styles/prompts/blueprint.md +0 -145
- package/.agent/skills/motion-styles/prompts/breaking-desk.md +0 -147
- package/.agent/skills/motion-styles/prompts/callout-kit.md +0 -147
- package/.agent/skills/motion-styles/prompts/chalk-talk.md +0 -153
- package/.agent/skills/motion-styles/prompts/chat-story.md +0 -143
- package/.agent/skills/motion-styles/prompts/clay-diorama.md +0 -149
- package/.agent/skills/motion-styles/prompts/clean-tech.md +0 -147
- package/.agent/skills/motion-styles/prompts/comic-panels.md +0 -145
- package/.agent/skills/motion-styles/prompts/cosmic-scale.md +0 -147
- package/.agent/skills/motion-styles/prompts/creator-motion.md +0 -149
- package/.agent/skills/motion-styles/prompts/data-city.md +0 -149
- package/.agent/skills/motion-styles/prompts/data-story.md +0 -147
- package/.agent/skills/motion-styles/prompts/deep-layers.md +0 -147
- package/.agent/skills/motion-styles/prompts/doc-cinema.md +0 -145
- package/.agent/skills/motion-styles/prompts/evidence-board.md +0 -145
- package/.agent/skills/motion-styles/prompts/exploded-view.md +0 -149
- package/.agent/skills/motion-styles/prompts/glass-icons.md +0 -147
- package/.agent/skills/motion-styles/prompts/glitch-pack.md +0 -145
- package/.agent/skills/motion-styles/prompts/globe-arcs.md +0 -147
- package/.agent/skills/motion-styles/prompts/headline-backdrop.md +0 -145
- package/.agent/skills/motion-styles/prompts/iso-cutaway.md +0 -147
- package/.agent/skills/motion-styles/prompts/isotype.md +0 -145
- package/.agent/skills/motion-styles/prompts/karaoke-subs.md +0 -145
- package/.agent/skills/motion-styles/prompts/low-poly-atlas.md +0 -149
- package/.agent/skills/motion-styles/prompts/lower-thirds.md +0 -147
- package/.agent/skills/motion-styles/prompts/marker-board.md +0 -145
- package/.agent/skills/motion-styles/prompts/market-tape.md +0 -145
- package/.agent/skills/motion-styles/prompts/math-canvas.md +0 -145
- package/.agent/skills/motion-styles/prompts/myth-or-fact.md +0 -145
- package/.agent/skills/motion-styles/prompts/neon-line.md +0 -145
- package/.agent/skills/motion-styles/prompts/paper-craft.md +0 -145
- package/.agent/skills/motion-styles/prompts/parchment-map.md +0 -147
- package/.agent/skills/motion-styles/prompts/physics-toybox.md +0 -147
- package/.agent/skills/motion-styles/prompts/pixel-quest.md +0 -145
- package/.agent/skills/motion-styles/prompts/podcast-clip.md +0 -147
- package/.agent/skills/motion-styles/prompts/pop-info.md +0 -145
- package/.agent/skills/motion-styles/prompts/product-turntable.md +0 -149
- package/.agent/skills/motion-styles/prompts/punch-captions.md +0 -145
- package/.agent/skills/motion-styles/prompts/quiz-countdown.md +0 -145
- package/.agent/skills/motion-styles/prompts/quote-cinema.md +0 -145
- package/.agent/skills/motion-styles/prompts/recipe-steps.md +0 -145
- package/.agent/skills/motion-styles/prompts/rubber-hose-reel.md +0 -145
- package/.agent/skills/motion-styles/prompts/shadow-puppets.md +0 -145
- package/.agent/skills/motion-styles/prompts/size-lineup.md +0 -149
- package/.agent/skills/motion-styles/prompts/software-walkthrough.md +0 -147
- package/.agent/skills/motion-styles/prompts/stack-split.md +0 -145
- package/.agent/skills/motion-styles/prompts/sticker-pop.md +0 -145
- package/.agent/skills/motion-styles/prompts/storybook.md +0 -145
- package/.agent/skills/motion-styles/prompts/swiss-grid.md +0 -145
- package/.agent/skills/motion-styles/prompts/synth-grid.md +0 -147
- package/.agent/skills/motion-styles/prompts/tactics-board.md +0 -145
- package/.agent/skills/motion-styles/prompts/terminal.md +0 -145
- package/.agent/skills/motion-styles/prompts/tier-list.md +0 -143
- package/.agent/skills/motion-styles/prompts/timeline-rail.md +0 -145
- package/.agent/skills/motion-styles/prompts/top-5-countdown.md +0 -145
- package/.agent/skills/motion-styles/prompts/versus-split.md +0 -145
- package/.agent/skills/motion-styles/prompts/vhs-camcorder.md +0 -145
- package/.agent/skills/motion-styles/prompts/visual-novel.md +0 -145
- package/.agent/skills/motion-styles/prompts/warm-canvas.md +0 -145
- package/.agent/skills/motion-styles/prompts/woodcut-tale.md +0 -145
- package/.agent/skills/motion-styles/prompts/zine-collage.md +0 -145
- package/.agent/skills/motion-styles/sources/bio-glow.html +0 -639
- package/.agent/skills/motion-styles/sources/blueprint.html +0 -617
- package/.agent/skills/motion-styles/sources/breaking-desk.html +0 -585
- package/.agent/skills/motion-styles/sources/callout-kit.html +0 -586
- package/.agent/skills/motion-styles/sources/chalk-talk.html +0 -597
- package/.agent/skills/motion-styles/sources/chat-story.html +0 -597
- package/.agent/skills/motion-styles/sources/clay-diorama.html +0 -681
- package/.agent/skills/motion-styles/sources/clean-tech.html +0 -554
- package/.agent/skills/motion-styles/sources/comic-panels.html +0 -608
- package/.agent/skills/motion-styles/sources/cosmic-scale.html +0 -628
- package/.agent/skills/motion-styles/sources/creator-motion.html +0 -805
- package/.agent/skills/motion-styles/sources/data-city.html +0 -613
- package/.agent/skills/motion-styles/sources/data-story.html +0 -539
- package/.agent/skills/motion-styles/sources/deep-layers.html +0 -624
- package/.agent/skills/motion-styles/sources/doc-cinema.html +0 -590
- package/.agent/skills/motion-styles/sources/evidence-board.html +0 -610
- package/.agent/skills/motion-styles/sources/exploded-view.html +0 -618
- package/.agent/skills/motion-styles/sources/glass-icons.html +0 -612
- package/.agent/skills/motion-styles/sources/glitch-pack.html +0 -583
- package/.agent/skills/motion-styles/sources/globe-arcs.html +0 -621
- package/.agent/skills/motion-styles/sources/headline-backdrop.html +0 -584
- package/.agent/skills/motion-styles/sources/iso-cutaway.html +0 -621
- package/.agent/skills/motion-styles/sources/isotype.html +0 -588
- package/.agent/skills/motion-styles/sources/karaoke-subs.html +0 -577
- package/.agent/skills/motion-styles/sources/low-poly-atlas.html +0 -621
- package/.agent/skills/motion-styles/sources/lower-thirds.html +0 -582
- package/.agent/skills/motion-styles/sources/marker-board.html +0 -598
- package/.agent/skills/motion-styles/sources/market-tape.html +0 -598
- package/.agent/skills/motion-styles/sources/math-canvas.html +0 -592
- package/.agent/skills/motion-styles/sources/myth-or-fact.html +0 -587
- package/.agent/skills/motion-styles/sources/neon-line.html +0 -597
- package/.agent/skills/motion-styles/sources/paper-craft.html +0 -612
- package/.agent/skills/motion-styles/sources/parchment-map.html +0 -624
- package/.agent/skills/motion-styles/sources/physics-toybox.html +0 -617
- package/.agent/skills/motion-styles/sources/pixel-quest.html +0 -609
- package/.agent/skills/motion-styles/sources/podcast-clip.html +0 -585
- package/.agent/skills/motion-styles/sources/pop-info.html +0 -677
- package/.agent/skills/motion-styles/sources/product-turntable.html +0 -611
- package/.agent/skills/motion-styles/sources/punch-captions.html +0 -583
- package/.agent/skills/motion-styles/sources/quiz-countdown.html +0 -593
- package/.agent/skills/motion-styles/sources/quote-cinema.html +0 -581
- package/.agent/skills/motion-styles/sources/recipe-steps.html +0 -599
- package/.agent/skills/motion-styles/sources/rubber-hose-reel.html +0 -627
- package/.agent/skills/motion-styles/sources/shadow-puppets.html +0 -609
- package/.agent/skills/motion-styles/sources/size-lineup.html +0 -620
- package/.agent/skills/motion-styles/sources/software-walkthrough.html +0 -651
- package/.agent/skills/motion-styles/sources/stack-split.html +0 -588
- package/.agent/skills/motion-styles/sources/sticker-pop.html +0 -587
- package/.agent/skills/motion-styles/sources/storybook.html +0 -600
- package/.agent/skills/motion-styles/sources/swiss-grid.html +0 -590
- package/.agent/skills/motion-styles/sources/synth-grid.html +0 -614
- package/.agent/skills/motion-styles/sources/tactics-board.html +0 -608
- package/.agent/skills/motion-styles/sources/terminal.html +0 -593
- package/.agent/skills/motion-styles/sources/tier-list.html +0 -600
- package/.agent/skills/motion-styles/sources/timeline-rail.html +0 -598
- package/.agent/skills/motion-styles/sources/top-5-countdown.html +0 -589
- package/.agent/skills/motion-styles/sources/versus-split.html +0 -592
- package/.agent/skills/motion-styles/sources/vhs-camcorder.html +0 -587
- package/.agent/skills/motion-styles/sources/visual-novel.html +0 -623
- package/.agent/skills/motion-styles/sources/warm-canvas.html +0 -742
- package/.agent/skills/motion-styles/sources/woodcut-tale.html +0 -598
- package/.agent/skills/motion-styles/sources/zine-collage.html +0 -594
- package/.agent/skills/reverse-engineer-anything/SKILL.md +0 -356
- package/.agent/skills/reverse-engineer-anything/references/android-applications.md +0 -36
- package/.agent/skills/reverse-engineer-anything/references/connection-and-recovery.md +0 -127
- package/.agent/skills/reverse-engineer-anything/references/evidence-workflows.md +0 -71
- package/.agent/skills/reverse-engineer-anything/references/javascript-applications.md +0 -218
- package/.agent/skills/reverse-engineer-anything/references/native-and-artifacts.md +0 -297
- package/.agent/skills/reverse-engineer-anything/references/runtime-observation.md +0 -78
- package/.agent/skills/video-pipeline/SKILL.md +0 -384
- package/.agent/skills/video-pipeline/assets/project/ANIMATION_GUIDE.md +0 -258
- package/.agent/skills/video-pipeline/assets/project/STORYBOARD.md +0 -77
- package/.agent/skills/video-pipeline/assets/project/index.html +0 -80
- package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/diagram-author.md +0 -43
- package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +0 -75
- package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +0 -63
- package/.agent/skills/video-pipeline/assets/project/reel/engine.css +0 -140
- package/.agent/skills/video-pipeline/assets/project/reel/engine.js +0 -2143
- package/.agent/skills/video-pipeline/assets/project/reel/virtual-clock.js +0 -168
- package/.agent/skills/video-pipeline/references/mcp-tools.md +0 -441
- package/.agent/skills/video-pipeline/references/motion-craft.md +0 -91
- package/.agent/skills/video-pipeline/references/providers.md +0 -174
- package/.agent/skills/video-pipeline/references/render-contract.md +0 -65
- package/plugins/aiinsight-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/inside-out/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/night-file/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/stick-story/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/night-file/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
|
@@ -1,1438 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Write commands — navigate and interact with pages (side effects)
|
|
3
|
-
*
|
|
4
|
-
* goto, back, forward, reload, click, fill, select, hover, type,
|
|
5
|
-
* press, scroll, wait, viewport, cookie, header, useragent
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { TabSession } from './tab-session';
|
|
9
|
-
import type { BrowserManager } from './browser-manager';
|
|
10
|
-
import { CookieImportError, cookieDomainMatches, findInstalledBrowsers, listSupportedBrowserNames } from './cookie-import-browser';
|
|
11
|
-
import { generatePickerCode } from './cookie-picker-routes';
|
|
12
|
-
import { formatCookieImportResult, parseCookieImportArgs, runCookieImport, validateCookieTarget } from './cookie-import-operation';
|
|
13
|
-
import { validateCookieAuthOptions, validateCookieStorageSupport } from './cookie-auth-verification';
|
|
14
|
-
import { validateNavigationUrl } from './url-validation';
|
|
15
|
-
import { validateOutputPath, validateReadPath } from './path-security';
|
|
16
|
-
import { guardScreenshotPath } from './screenshot-size-guard';
|
|
17
|
-
import * as fs from 'fs';
|
|
18
|
-
import * as path from 'path';
|
|
19
|
-
import type { SetContentWaitUntil } from './tab-session';
|
|
20
|
-
import { TEMP_DIR } from './platform';
|
|
21
|
-
import { SAFE_DIRECTORIES } from './path-security';
|
|
22
|
-
import { modifyStyle, undoModification, resetModifications, getModificationHistory } from './cdp-inspector';
|
|
23
|
-
import { withCdpSession } from './cdp-bridge';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Aggressive page cleanup selectors and heuristics.
|
|
27
|
-
* Goal: make the page readable and clean while keeping it recognizable.
|
|
28
|
-
* Inspired by uBlock Origin filter lists, Readability.js, and reader mode heuristics.
|
|
29
|
-
*/
|
|
30
|
-
const CLEANUP_SELECTORS = {
|
|
31
|
-
ads: [
|
|
32
|
-
// Google Ads
|
|
33
|
-
'ins.adsbygoogle', '[id^="google_ads"]', '[id^="div-gpt-ad"]',
|
|
34
|
-
'iframe[src*="doubleclick"]', 'iframe[src*="googlesyndication"]',
|
|
35
|
-
'[data-google-query-id]', '.google-auto-placed',
|
|
36
|
-
// Generic ad patterns (uBlock Origin common filters)
|
|
37
|
-
'[class*="ad-banner"]', '[class*="ad-wrapper"]', '[class*="ad-container"]',
|
|
38
|
-
'[class*="ad-slot"]', '[class*="ad-unit"]', '[class*="ad-zone"]',
|
|
39
|
-
'[class*="ad-placement"]', '[class*="ad-holder"]', '[class*="ad-block"]',
|
|
40
|
-
'[class*="adbox"]', '[class*="adunit"]', '[class*="adwrap"]',
|
|
41
|
-
'[id*="ad-banner"]', '[id*="ad-wrapper"]', '[id*="ad-container"]',
|
|
42
|
-
'[id*="ad-slot"]', '[id*="ad_banner"]', '[id*="ad_container"]',
|
|
43
|
-
'[data-ad]', '[data-ad-slot]', '[data-ad-unit]', '[data-adunit]',
|
|
44
|
-
'[class*="sponsored"]', '[class*="Sponsored"]',
|
|
45
|
-
'.ad', '.ads', '.advert', '.advertisement',
|
|
46
|
-
'#ad', '#ads', '#advert', '#advertisement',
|
|
47
|
-
// Common ad network iframes
|
|
48
|
-
'iframe[src*="amazon-adsystem"]', 'iframe[src*="outbrain"]',
|
|
49
|
-
'iframe[src*="taboola"]', 'iframe[src*="criteo"]',
|
|
50
|
-
'iframe[src*="adsafeprotected"]', 'iframe[src*="moatads"]',
|
|
51
|
-
// Promoted/sponsored content
|
|
52
|
-
'[class*="promoted"]', '[class*="Promoted"]',
|
|
53
|
-
'[data-testid*="promo"]', '[class*="native-ad"]',
|
|
54
|
-
// Empty ad placeholders (divs with only ad classes, no real content)
|
|
55
|
-
'aside[class*="ad"]', 'section[class*="ad-"]',
|
|
56
|
-
],
|
|
57
|
-
cookies: [
|
|
58
|
-
// Cookie consent frameworks
|
|
59
|
-
'[class*="cookie-consent"]', '[class*="cookie-banner"]', '[class*="cookie-notice"]',
|
|
60
|
-
'[id*="cookie-consent"]', '[id*="cookie-banner"]', '[id*="cookie-notice"]',
|
|
61
|
-
'[class*="consent-banner"]', '[class*="consent-modal"]', '[class*="consent-wall"]',
|
|
62
|
-
'[class*="gdpr"]', '[id*="gdpr"]', '[class*="GDPR"]',
|
|
63
|
-
'[class*="CookieConsent"]', '[id*="CookieConsent"]',
|
|
64
|
-
// OneTrust (very common)
|
|
65
|
-
'#onetrust-consent-sdk', '.onetrust-pc-dark-filter', '#onetrust-banner-sdk',
|
|
66
|
-
// Cookiebot
|
|
67
|
-
'#CybotCookiebotDialog', '#CybotCookiebotDialogBodyUnderlay',
|
|
68
|
-
// TrustArc / TRUSTe
|
|
69
|
-
'#truste-consent-track', '.truste_overlay', '.truste_box_overlay',
|
|
70
|
-
// Quantcast
|
|
71
|
-
'.qc-cmp2-container', '#qc-cmp2-main',
|
|
72
|
-
// Generic patterns
|
|
73
|
-
'[class*="cc-banner"]', '[class*="cc-window"]', '[class*="cc-overlay"]',
|
|
74
|
-
'[class*="privacy-banner"]', '[class*="privacy-notice"]',
|
|
75
|
-
'[id*="privacy-banner"]', '[id*="privacy-notice"]',
|
|
76
|
-
'[class*="accept-cookies"]', '[id*="accept-cookies"]',
|
|
77
|
-
],
|
|
78
|
-
overlays: [
|
|
79
|
-
// Paywall / subscription overlays
|
|
80
|
-
'[class*="paywall"]', '[class*="Paywall"]', '[id*="paywall"]',
|
|
81
|
-
'[class*="subscribe-wall"]', '[class*="subscription-wall"]',
|
|
82
|
-
'[class*="meter-wall"]', '[class*="regwall"]', '[class*="reg-wall"]',
|
|
83
|
-
// Newsletter / signup popups
|
|
84
|
-
'[class*="newsletter-popup"]', '[class*="newsletter-modal"]',
|
|
85
|
-
'[class*="signup-modal"]', '[class*="signup-popup"]',
|
|
86
|
-
'[class*="email-capture"]', '[class*="lead-capture"]',
|
|
87
|
-
'[class*="popup-modal"]', '[class*="modal-overlay"]',
|
|
88
|
-
// Interstitials
|
|
89
|
-
'[class*="interstitial"]', '[id*="interstitial"]',
|
|
90
|
-
// Push notification prompts
|
|
91
|
-
'[class*="push-notification"]', '[class*="notification-prompt"]',
|
|
92
|
-
'[class*="web-push"]',
|
|
93
|
-
// Survey / feedback popups
|
|
94
|
-
'[class*="survey-"]', '[class*="feedback-modal"]',
|
|
95
|
-
'[id*="survey-"]', '[class*="nps-"]',
|
|
96
|
-
// App download banners
|
|
97
|
-
'[class*="app-banner"]', '[class*="smart-banner"]', '[class*="app-download"]',
|
|
98
|
-
'[id*="branch-banner"]', '.smartbanner',
|
|
99
|
-
// Cross-promotion / "follow us" / "preferred source" widgets
|
|
100
|
-
'[class*="promo-banner"]', '[class*="cross-promo"]', '[class*="partner-promo"]',
|
|
101
|
-
'[class*="preferred-source"]', '[class*="google-promo"]',
|
|
102
|
-
],
|
|
103
|
-
clutter: [
|
|
104
|
-
// Audio/podcast player widgets (not part of the article text)
|
|
105
|
-
'[class*="audio-player"]', '[class*="podcast-player"]', '[class*="listen-widget"]',
|
|
106
|
-
'[class*="everlit"]', '[class*="Everlit"]',
|
|
107
|
-
'audio', // bare audio elements
|
|
108
|
-
// Sidebar games/puzzles widgets
|
|
109
|
-
'[class*="puzzle"]', '[class*="daily-game"]', '[class*="games-widget"]',
|
|
110
|
-
'[class*="crossword-promo"]', '[class*="mini-game"]',
|
|
111
|
-
// "Most Popular" / "Trending" sidebar recirculation (not the top nav trending bar)
|
|
112
|
-
'aside [class*="most-popular"]', 'aside [class*="trending"]',
|
|
113
|
-
'aside [class*="most-read"]', 'aside [class*="recommended"]',
|
|
114
|
-
// Related articles / recirculation at bottom
|
|
115
|
-
'[class*="related-articles"]', '[class*="more-stories"]',
|
|
116
|
-
'[class*="recirculation"]', '[class*="taboola"]', '[class*="outbrain"]',
|
|
117
|
-
// Hearst-specific (SF Chronicle, etc.)
|
|
118
|
-
'[class*="nativo"]', '[data-tb-region]',
|
|
119
|
-
],
|
|
120
|
-
sticky: [
|
|
121
|
-
// Handled via JavaScript evaluation, not pure selectors
|
|
122
|
-
],
|
|
123
|
-
social: [
|
|
124
|
-
'[class*="social-share"]', '[class*="share-buttons"]', '[class*="share-bar"]',
|
|
125
|
-
'[class*="social-widget"]', '[class*="social-icons"]', '[class*="share-tools"]',
|
|
126
|
-
'iframe[src*="facebook.com/plugins"]', 'iframe[src*="platform.twitter"]',
|
|
127
|
-
'[class*="fb-like"]', '[class*="tweet-button"]',
|
|
128
|
-
'[class*="addthis"]', '[class*="sharethis"]',
|
|
129
|
-
// Follow prompts
|
|
130
|
-
'[class*="follow-us"]', '[class*="social-follow"]',
|
|
131
|
-
],
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export async function handleWriteCommand(
|
|
135
|
-
command: string,
|
|
136
|
-
args: string[],
|
|
137
|
-
session: TabSession,
|
|
138
|
-
bm: BrowserManager
|
|
139
|
-
): Promise<string> {
|
|
140
|
-
const page = session.getPage();
|
|
141
|
-
// Frame-aware target for locator-based operations (click, fill, etc.)
|
|
142
|
-
const target = session.getActiveFrameOrPage();
|
|
143
|
-
const inFrame = session.getFrame() !== null;
|
|
144
|
-
|
|
145
|
-
switch (command) {
|
|
146
|
-
case 'goto': {
|
|
147
|
-
if (inFrame) throw new Error('Cannot use goto inside a frame. Run \'frame main\' first.');
|
|
148
|
-
const url = args[0];
|
|
149
|
-
if (!url) throw new Error('Usage: browse goto <url>');
|
|
150
|
-
// Clear loadedHtml BEFORE navigation — a timeout after the main-frame commit
|
|
151
|
-
// must not leave stale content that could resurrect on a later context recreation.
|
|
152
|
-
session.clearLoadedHtml();
|
|
153
|
-
const normalizedUrl = await validateNavigationUrl(url);
|
|
154
|
-
const response = await page.goto(normalizedUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
155
|
-
const status = response?.status() || 'unknown';
|
|
156
|
-
return `Navigated to ${normalizedUrl} (${status})`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
case 'back': {
|
|
160
|
-
if (inFrame) throw new Error('Cannot use back inside a frame. Run \'frame main\' first.');
|
|
161
|
-
session.clearLoadedHtml();
|
|
162
|
-
await page.goBack({ waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
163
|
-
return `Back → ${page.url()}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
case 'forward': {
|
|
167
|
-
if (inFrame) throw new Error('Cannot use forward inside a frame. Run \'frame main\' first.');
|
|
168
|
-
session.clearLoadedHtml();
|
|
169
|
-
await page.goForward({ waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
170
|
-
return `Forward → ${page.url()}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
case 'reload': {
|
|
174
|
-
if (inFrame) throw new Error('Cannot use reload inside a frame. Run \'frame main\' first.');
|
|
175
|
-
session.clearLoadedHtml();
|
|
176
|
-
await page.reload({ waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
177
|
-
return `Reloaded ${page.url()}`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
case 'load-html': {
|
|
181
|
-
if (inFrame) throw new Error('Cannot use load-html inside a frame. Run \'frame main\' first.');
|
|
182
|
-
|
|
183
|
-
// --from-file <path.json>: read inline HTML from a JSON payload. Used by
|
|
184
|
-
// make-pdf to dodge Windows argv size limits on large rendered HTML.
|
|
185
|
-
// The JSON shape is { html: string, waitUntil?: "load"|"domcontentloaded"|"networkidle" }.
|
|
186
|
-
// The safe-dirs + magic-byte + size-cap checks below still apply to the
|
|
187
|
-
// INLINE HTML content, not to the payload file path itself.
|
|
188
|
-
let fromFilePayload: { html: string; waitUntil?: SetContentWaitUntil } | null = null;
|
|
189
|
-
let filePath: string | undefined;
|
|
190
|
-
let waitUntil: SetContentWaitUntil = 'domcontentloaded';
|
|
191
|
-
for (let i = 0; i < args.length; i++) {
|
|
192
|
-
if (args[i] === '--from-file') {
|
|
193
|
-
const payloadPath = args[++i];
|
|
194
|
-
if (!payloadPath) throw new Error('load-html: --from-file requires a path');
|
|
195
|
-
// Parity with the sibling `load-html <file>` path below (line 249):
|
|
196
|
-
// that branch runs every `file://` target through validateReadPath
|
|
197
|
-
// so the safe-dirs policy can't be side-stepped. Same policy must
|
|
198
|
-
// apply here — otherwise --from-file becomes a read-anywhere escape
|
|
199
|
-
// hatch for any caller that can pick the payload path (e.g., an
|
|
200
|
-
// MCP caller issuing load-html with an attacker-influenced path).
|
|
201
|
-
try {
|
|
202
|
-
validateReadPath(path.resolve(payloadPath));
|
|
203
|
-
} catch {
|
|
204
|
-
throw new Error(
|
|
205
|
-
`load-html: --from-file ${payloadPath} must be under ${SAFE_DIRECTORIES.join(' or ')} (security policy). Copy the payload into the project tree or /tmp first.`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
const raw = fs.readFileSync(payloadPath, 'utf8');
|
|
209
|
-
let json: any;
|
|
210
|
-
try { json = JSON.parse(raw); }
|
|
211
|
-
catch (e: any) { throw new Error(`load-html: --from-file JSON parse failed: ${e.message}`); }
|
|
212
|
-
if (typeof json.html !== 'string') {
|
|
213
|
-
throw new Error('load-html: --from-file JSON must have a "html" string field');
|
|
214
|
-
}
|
|
215
|
-
if (json.waitUntil && json.waitUntil !== 'load'
|
|
216
|
-
&& json.waitUntil !== 'domcontentloaded' && json.waitUntil !== 'networkidle') {
|
|
217
|
-
throw new Error(`load-html: --from-file waitUntil '${json.waitUntil}' invalid`);
|
|
218
|
-
}
|
|
219
|
-
fromFilePayload = { html: json.html, waitUntil: json.waitUntil };
|
|
220
|
-
} else if (args[i] === '--wait-until') {
|
|
221
|
-
const val = args[++i];
|
|
222
|
-
if (val !== 'load' && val !== 'domcontentloaded' && val !== 'networkidle') {
|
|
223
|
-
throw new Error(`Invalid --wait-until '${val}'. Must be one of: load, domcontentloaded, networkidle.`);
|
|
224
|
-
}
|
|
225
|
-
waitUntil = val;
|
|
226
|
-
} else if (args[i].startsWith('--')) {
|
|
227
|
-
throw new Error(`Unknown flag: ${args[i]}`);
|
|
228
|
-
} else if (!filePath) {
|
|
229
|
-
filePath = args[i];
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Inline HTML path: validate size + magic byte, then setContent directly.
|
|
234
|
-
if (fromFilePayload) {
|
|
235
|
-
const MAX_BYTES = parseInt(process.env.GSTACK_BROWSE_MAX_HTML_BYTES || '', 10) || (50 * 1024 * 1024);
|
|
236
|
-
if (Buffer.byteLength(fromFilePayload.html, 'utf8') > MAX_BYTES) {
|
|
237
|
-
throw new Error(
|
|
238
|
-
`load-html: --from-file html too large (> ${MAX_BYTES} bytes). ` +
|
|
239
|
-
'Raise with GSTACK_BROWSE_MAX_HTML_BYTES=<N>.'
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
const peek = fromFilePayload.html.trimStart();
|
|
243
|
-
if (!/^<[a-zA-Z!?]/.test(peek)) {
|
|
244
|
-
throw new Error('load-html: --from-file html does not start with a valid markup opener');
|
|
245
|
-
}
|
|
246
|
-
const finalWaitUntil = fromFilePayload.waitUntil ?? waitUntil;
|
|
247
|
-
await session.setTabContent(fromFilePayload.html, { waitUntil: finalWaitUntil });
|
|
248
|
-
return `Loaded HTML: (inline from --from-file, ${fromFilePayload.html.length} chars)`;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (!filePath) throw new Error('Usage: browse load-html <file> [--wait-until load|domcontentloaded|networkidle] [--tab-id <N>] | load-html --from-file <payload.json> [--tab-id <N>]');
|
|
252
|
-
|
|
253
|
-
// Extension allowlist
|
|
254
|
-
const ALLOWED_EXT = ['.html', '.htm', '.xhtml'];
|
|
255
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
256
|
-
if (!ALLOWED_EXT.includes(ext)) {
|
|
257
|
-
throw new Error(
|
|
258
|
-
`load-html: file does not appear to be HTML. Expected .html/.htm/.xhtml, got ${ext || '(no extension)'}. Rename the file if it's really HTML.`
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const absolutePath = path.resolve(filePath);
|
|
263
|
-
|
|
264
|
-
// Safe-dirs check (reuses canonical read-side policy)
|
|
265
|
-
try {
|
|
266
|
-
validateReadPath(absolutePath);
|
|
267
|
-
} catch (e: any) {
|
|
268
|
-
throw new Error(
|
|
269
|
-
`load-html: ${absolutePath} must be under ${SAFE_DIRECTORIES.join(' or ')} (security policy). Copy the file into the project tree or /tmp first.`
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// stat check — reject non-file targets with actionable error
|
|
274
|
-
let stat: fs.Stats;
|
|
275
|
-
try {
|
|
276
|
-
stat = await fs.promises.stat(absolutePath);
|
|
277
|
-
} catch (e: any) {
|
|
278
|
-
if (e.code === 'ENOENT') {
|
|
279
|
-
throw new Error(
|
|
280
|
-
`load-html: file not found at ${absolutePath}. Check spelling or copy the file under ${process.cwd()} or ${TEMP_DIR}.`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
throw e;
|
|
284
|
-
}
|
|
285
|
-
if (stat.isDirectory()) {
|
|
286
|
-
throw new Error(`load-html: ${absolutePath} is a directory, not a file. Pass a .html file.`);
|
|
287
|
-
}
|
|
288
|
-
if (!stat.isFile()) {
|
|
289
|
-
throw new Error(`load-html: ${absolutePath} is not a regular file.`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Size cap
|
|
293
|
-
const MAX_BYTES = parseInt(process.env.GSTACK_BROWSE_MAX_HTML_BYTES || '', 10) || (50 * 1024 * 1024);
|
|
294
|
-
if (stat.size > MAX_BYTES) {
|
|
295
|
-
throw new Error(
|
|
296
|
-
`load-html: file too large (${stat.size} bytes > ${MAX_BYTES} cap). Raise with GSTACK_BROWSE_MAX_HTML_BYTES=<N> or split the HTML.`
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Single read: Buffer → magic-byte peek → utf-8 string
|
|
301
|
-
const buf = await fs.promises.readFile(absolutePath);
|
|
302
|
-
|
|
303
|
-
// Magic-byte check: strip UTF-8 BOM + leading whitespace, then verify the first
|
|
304
|
-
// non-whitespace byte starts a markup construct. Accepts any <tag, <!doctype,
|
|
305
|
-
// <!-- comment, <?xml prolog — including bare HTML fragments like `<div>...</div>`
|
|
306
|
-
// which setContent wraps in a full document. Rejects binary files mis-renamed .html
|
|
307
|
-
// (first byte won't be `<`).
|
|
308
|
-
let peek = buf.slice(0, 200);
|
|
309
|
-
if (peek[0] === 0xEF && peek[1] === 0xBB && peek[2] === 0xBF) {
|
|
310
|
-
peek = peek.slice(3);
|
|
311
|
-
}
|
|
312
|
-
const peekStr = peek.toString('utf8').trimStart();
|
|
313
|
-
// Valid markup opener: '<' followed by alpha (tag), '!' (doctype/comment), or '?' (xml prolog)
|
|
314
|
-
const looksLikeMarkup = /^<[a-zA-Z!?]/.test(peekStr);
|
|
315
|
-
if (!looksLikeMarkup) {
|
|
316
|
-
const hexDump = Array.from(buf.slice(0, 16)).map(b => b.toString(16).padStart(2, '0')).join(' ');
|
|
317
|
-
throw new Error(
|
|
318
|
-
`load-html: ${absolutePath} has ${ext} extension but content does not look like HTML. First bytes: ${hexDump}`
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const html = buf.toString('utf8');
|
|
323
|
-
await session.setTabContent(html, { waitUntil });
|
|
324
|
-
return `Loaded HTML: ${absolutePath} (${stat.size} bytes)`;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
case 'click': {
|
|
328
|
-
const selector = args[0];
|
|
329
|
-
if (!selector) throw new Error('Usage: browse click <selector>');
|
|
330
|
-
|
|
331
|
-
// Auto-route: if ref points to a real <option> inside a <select>, use selectOption
|
|
332
|
-
const role = session.getRefRole(selector);
|
|
333
|
-
if (role === 'option') {
|
|
334
|
-
const resolved = await session.resolveRef(selector);
|
|
335
|
-
if ('locator' in resolved) {
|
|
336
|
-
const optionInfo = await resolved.locator.evaluate(el => {
|
|
337
|
-
if (el.tagName !== 'OPTION') return null; // custom [role=option], not real <option>
|
|
338
|
-
const option = el as HTMLOptionElement;
|
|
339
|
-
const select = option.closest('select');
|
|
340
|
-
if (!select) return null;
|
|
341
|
-
return { value: option.value, text: option.text };
|
|
342
|
-
});
|
|
343
|
-
if (optionInfo) {
|
|
344
|
-
await resolved.locator.locator('xpath=ancestor::select').selectOption(optionInfo.value, { timeout: 5000 });
|
|
345
|
-
return `Selected "${optionInfo.text}" (auto-routed from click on <option>) → now at ${page.url()}`;
|
|
346
|
-
}
|
|
347
|
-
// Real <option> with no parent <select> or custom [role=option] — fall through to normal click
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const resolved = await session.resolveRef(selector);
|
|
352
|
-
try {
|
|
353
|
-
if ('locator' in resolved) {
|
|
354
|
-
await resolved.locator.click({ timeout: 5000 });
|
|
355
|
-
} else {
|
|
356
|
-
await target.locator(resolved.selector).click({ timeout: 5000 });
|
|
357
|
-
}
|
|
358
|
-
} catch (err: any) {
|
|
359
|
-
// Enhanced error guidance: clicking <option> elements always fails (not visible / timeout)
|
|
360
|
-
const isOption = 'locator' in resolved
|
|
361
|
-
? await resolved.locator.evaluate(el => el.tagName === 'OPTION').catch(() => false)
|
|
362
|
-
: await target.locator(resolved.selector).evaluate(
|
|
363
|
-
el => el.tagName === 'OPTION'
|
|
364
|
-
).catch(() => false);
|
|
365
|
-
if (isOption) {
|
|
366
|
-
throw new Error(
|
|
367
|
-
`Cannot click <option> elements. Use 'browse select <parent-select> <value>' instead of 'click' for dropdown options.`
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
throw err;
|
|
371
|
-
}
|
|
372
|
-
// Wait for network to settle (catches XHR/fetch triggered by clicks)
|
|
373
|
-
await page.waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
|
|
374
|
-
return `Clicked ${selector} → now at ${page.url()}`;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
case 'fill': {
|
|
378
|
-
const [selector, ...valueParts] = args;
|
|
379
|
-
const value = valueParts.join(' ');
|
|
380
|
-
if (!selector || !value) throw new Error('Usage: browse fill <selector> <value>');
|
|
381
|
-
const resolved = await session.resolveRef(selector);
|
|
382
|
-
const locator = 'locator' in resolved ? resolved.locator : target.locator(resolved.selector);
|
|
383
|
-
await locator.fill(value, { timeout: 5000 });
|
|
384
|
-
// Playwright's fill() only dispatches an `input` event. Frameworks that
|
|
385
|
-
// validate on `change` (AngularJS ng-change, debounced strength/match
|
|
386
|
-
// checks — e.g. cPanel's Jupiter theme) never see the update, so a value
|
|
387
|
-
// that's correct in the DOM can still fail the framework's own
|
|
388
|
-
// validation. Dispatch `change` too so those listeners fire.
|
|
389
|
-
await locator.dispatchEvent('change');
|
|
390
|
-
// Wait for network to settle (form validation XHRs)
|
|
391
|
-
await page.waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
|
|
392
|
-
return `Filled ${selector}`;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
case 'select': {
|
|
396
|
-
const [selector, ...valueParts] = args;
|
|
397
|
-
const value = valueParts.join(' ');
|
|
398
|
-
if (!selector || !value) throw new Error('Usage: browse select <selector> <value>');
|
|
399
|
-
const resolved = await session.resolveRef(selector);
|
|
400
|
-
if ('locator' in resolved) {
|
|
401
|
-
await resolved.locator.selectOption(value, { timeout: 5000 });
|
|
402
|
-
} else {
|
|
403
|
-
await target.locator(resolved.selector).selectOption(value, { timeout: 5000 });
|
|
404
|
-
}
|
|
405
|
-
// Wait for network to settle (dropdown-triggered requests)
|
|
406
|
-
await page.waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
|
|
407
|
-
return `Selected "${value}" in ${selector}`;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
case 'hover': {
|
|
411
|
-
const selector = args[0];
|
|
412
|
-
if (!selector) throw new Error('Usage: browse hover <selector>');
|
|
413
|
-
const resolved = await session.resolveRef(selector);
|
|
414
|
-
if ('locator' in resolved) {
|
|
415
|
-
await resolved.locator.hover({ timeout: 5000 });
|
|
416
|
-
} else {
|
|
417
|
-
await target.locator(resolved.selector).hover({ timeout: 5000 });
|
|
418
|
-
}
|
|
419
|
-
return `Hovered ${selector}`;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
case 'type': {
|
|
423
|
-
// #2936: bare `type` sends keystrokes to whatever has focus, so a selector
|
|
424
|
-
// given as the first word was typed as text. --selector targets an
|
|
425
|
-
// element; `--` ends flags so literal text may start with "--".
|
|
426
|
-
const usage = 'Usage: browse type [--selector <sel>] [--] <text>';
|
|
427
|
-
let rest = args;
|
|
428
|
-
let selector: string | undefined;
|
|
429
|
-
if (rest[0] === '--selector') {
|
|
430
|
-
selector = rest[1];
|
|
431
|
-
if (!selector) throw new Error(usage);
|
|
432
|
-
rest = rest.slice(2);
|
|
433
|
-
}
|
|
434
|
-
if (rest[0] === '--') rest = rest.slice(1);
|
|
435
|
-
const text = rest.join(' ');
|
|
436
|
-
if (!text) throw new Error(usage);
|
|
437
|
-
if (selector) {
|
|
438
|
-
const resolved = await session.resolveRef(selector);
|
|
439
|
-
const locator = 'locator' in resolved ? resolved.locator : target.locator(resolved.selector);
|
|
440
|
-
await locator.pressSequentially(text, { timeout: 5000 });
|
|
441
|
-
return `Typed ${text.length} characters into ${selector}`;
|
|
442
|
-
}
|
|
443
|
-
await page.keyboard.type(text);
|
|
444
|
-
const first = rest[0];
|
|
445
|
-
const selectorLike = /^([#.][A-Za-z_-]|\[[^\]]+\]$|[A-Za-z][\w-]*[#.[][A-Za-z_-])/.test(first) || rest.includes('>');
|
|
446
|
-
return selectorLike
|
|
447
|
-
? `Typed ${text.length} characters into the focused element\nhint: "${first}" looks like a CSS selector. To type into that element: browse type --selector '${first}' <text>`
|
|
448
|
-
: `Typed ${text.length} characters`;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
case 'press': {
|
|
452
|
-
const key = args[0];
|
|
453
|
-
if (!key) throw new Error('Usage: browse press <key> (e.g., Enter, Tab, Escape)');
|
|
454
|
-
await page.keyboard.press(key);
|
|
455
|
-
return `Pressed ${key}`;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
case 'scroll': {
|
|
459
|
-
// Parse --times N and --wait ms flags
|
|
460
|
-
const timesIdx = args.indexOf('--times');
|
|
461
|
-
const times = timesIdx >= 0 ? parseInt(args[timesIdx + 1], 10) || 1 : 0;
|
|
462
|
-
const waitIdx = args.indexOf('--wait');
|
|
463
|
-
const waitMs = waitIdx >= 0 ? parseInt(args[waitIdx + 1], 10) || 1000 : 1000;
|
|
464
|
-
const selector = args.find(a => !a.startsWith('--') && args.indexOf(a) !== timesIdx + 1 && args.indexOf(a) !== waitIdx + 1);
|
|
465
|
-
|
|
466
|
-
if (times > 0) {
|
|
467
|
-
// Repeated scroll mode
|
|
468
|
-
for (let i = 0; i < times; i++) {
|
|
469
|
-
if (selector) {
|
|
470
|
-
const resolved = await bm.resolveRef(selector);
|
|
471
|
-
if ('locator' in resolved) {
|
|
472
|
-
await resolved.locator.scrollIntoViewIfNeeded({ timeout: 5000 });
|
|
473
|
-
} else {
|
|
474
|
-
await target.locator(resolved.selector).scrollIntoViewIfNeeded({ timeout: 5000 });
|
|
475
|
-
}
|
|
476
|
-
} else {
|
|
477
|
-
await target.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
478
|
-
}
|
|
479
|
-
if (i < times - 1) await new Promise(r => setTimeout(r, waitMs));
|
|
480
|
-
}
|
|
481
|
-
return `Scrolled ${times} times${selector ? ` (${selector})` : ''} with ${waitMs}ms delay`;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Single scroll (original behavior)
|
|
485
|
-
if (selector) {
|
|
486
|
-
const resolved = await session.resolveRef(selector);
|
|
487
|
-
if ('locator' in resolved) {
|
|
488
|
-
await resolved.locator.scrollIntoViewIfNeeded({ timeout: 5000 });
|
|
489
|
-
} else {
|
|
490
|
-
await target.locator(resolved.selector).scrollIntoViewIfNeeded({ timeout: 5000 });
|
|
491
|
-
}
|
|
492
|
-
return `Scrolled ${selector} into view`;
|
|
493
|
-
}
|
|
494
|
-
await target.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
495
|
-
return 'Scrolled to bottom';
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
case 'wait': {
|
|
499
|
-
const selector = args[0];
|
|
500
|
-
if (!selector) throw new Error('Usage: browse wait <selector|--networkidle|--load|--domcontentloaded>');
|
|
501
|
-
if (selector === '--networkidle') {
|
|
502
|
-
const MAX_WAIT_MS = 300_000;
|
|
503
|
-
const MIN_WAIT_MS = 1_000;
|
|
504
|
-
const timeout = Math.min(Math.max(args[1] ? parseInt(args[1], 10) || MIN_WAIT_MS : 15000, MIN_WAIT_MS), MAX_WAIT_MS);
|
|
505
|
-
await page.waitForLoadState('networkidle', { timeout });
|
|
506
|
-
return 'Network idle';
|
|
507
|
-
}
|
|
508
|
-
if (selector === '--load') {
|
|
509
|
-
await page.waitForLoadState('load');
|
|
510
|
-
return 'Page loaded';
|
|
511
|
-
}
|
|
512
|
-
if (selector === '--domcontentloaded') {
|
|
513
|
-
await page.waitForLoadState('domcontentloaded');
|
|
514
|
-
return 'DOM content loaded';
|
|
515
|
-
}
|
|
516
|
-
const MAX_WAIT_MS = 300_000;
|
|
517
|
-
const MIN_WAIT_MS = 1_000;
|
|
518
|
-
const timeout = Math.min(Math.max(args[1] ? parseInt(args[1], 10) || MIN_WAIT_MS : 15000, MIN_WAIT_MS), MAX_WAIT_MS);
|
|
519
|
-
const resolved = await session.resolveRef(selector);
|
|
520
|
-
if ('locator' in resolved) {
|
|
521
|
-
await resolved.locator.waitFor({ state: 'visible', timeout });
|
|
522
|
-
} else {
|
|
523
|
-
await target.locator(resolved.selector).waitFor({ state: 'visible', timeout });
|
|
524
|
-
}
|
|
525
|
-
return `Element ${selector} appeared`;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
case 'viewport': {
|
|
529
|
-
// Parse args: [<WxH>] [--scale <n>]. Either may be omitted, but NOT both.
|
|
530
|
-
let sizeArg: string | undefined;
|
|
531
|
-
let scaleArg: number | undefined;
|
|
532
|
-
for (let i = 0; i < args.length; i++) {
|
|
533
|
-
if (args[i] === '--scale') {
|
|
534
|
-
const val = args[++i];
|
|
535
|
-
if (val === undefined || val === '') {
|
|
536
|
-
throw new Error('viewport --scale: missing value. Usage: viewport [WxH] --scale <n>');
|
|
537
|
-
}
|
|
538
|
-
const parsed = Number(val);
|
|
539
|
-
if (!Number.isFinite(parsed)) {
|
|
540
|
-
throw new Error(`viewport --scale: value '${val}' is not a finite number.`);
|
|
541
|
-
}
|
|
542
|
-
scaleArg = parsed;
|
|
543
|
-
} else if (args[i].startsWith('--')) {
|
|
544
|
-
throw new Error(`Unknown viewport flag: ${args[i]}`);
|
|
545
|
-
} else if (sizeArg === undefined) {
|
|
546
|
-
sizeArg = args[i];
|
|
547
|
-
} else {
|
|
548
|
-
throw new Error(`Unexpected positional arg: ${args[i]}. Usage: viewport [WxH|auto] [--scale <n>]`);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (sizeArg === undefined && scaleArg === undefined) {
|
|
553
|
-
throw new Error('Usage: browse viewport [<WxH>|auto] [--scale <n>] (e.g. 375x812, auto to unpin, or --scale 2 to keep current size)');
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (sizeArg !== undefined && ['auto', 'reset', 'unpin'].includes(sizeArg.toLowerCase())) {
|
|
557
|
-
if (scaleArg !== undefined) throw new Error('viewport auto cannot be combined with --scale or a size.');
|
|
558
|
-
return (await bm.resetViewport()) === 'window'
|
|
559
|
-
? 'Viewport unpinned: the page follows the browser window again (refs cleared)'
|
|
560
|
-
: 'Viewport reset to default 1280x720';
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Resolve width/height: either from sizeArg or from current viewport if --scale-only.
|
|
564
|
-
let w: number, h: number;
|
|
565
|
-
if (sizeArg) {
|
|
566
|
-
if (!sizeArg.includes('x')) throw new Error('Usage: browse viewport [<WxH>|auto] [--scale <n>] (e.g., 375x812)');
|
|
567
|
-
const [rawW, rawH] = sizeArg.split('x').map(Number);
|
|
568
|
-
w = Math.min(Math.max(Math.round(rawW) || 1280, 1), 16384);
|
|
569
|
-
h = Math.min(Math.max(Math.round(rawH) || 720, 1), 16384);
|
|
570
|
-
} else {
|
|
571
|
-
// --scale without WxH → use BrowserManager's tracked viewport (source of truth
|
|
572
|
-
// since setViewport + launchContext keep it in sync). Falls back reliably on
|
|
573
|
-
// headed → headless transitions or contexts with viewport:null.
|
|
574
|
-
const current = bm.getCurrentViewport();
|
|
575
|
-
w = current.width;
|
|
576
|
-
h = current.height;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (scaleArg !== undefined) {
|
|
580
|
-
const err = await bm.setDeviceScaleFactor(scaleArg, w, h);
|
|
581
|
-
if (err) return `Viewport partially set: ${err}`;
|
|
582
|
-
return `Viewport set to ${w}x${h} @ ${scaleArg}x (context recreated; refs and load-html content replayed)`;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
await bm.setViewport(w, h);
|
|
586
|
-
return `Viewport set to ${w}x${h}`;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
case 'cookie': {
|
|
590
|
-
const cookieStr = args[0];
|
|
591
|
-
if (!cookieStr || !cookieStr.includes('=')) throw new Error('Usage: browse cookie <name>=<value>');
|
|
592
|
-
const eq = cookieStr.indexOf('=');
|
|
593
|
-
const name = cookieStr.slice(0, eq);
|
|
594
|
-
const value = cookieStr.slice(eq + 1);
|
|
595
|
-
const url = new URL(page.url());
|
|
596
|
-
await page.context().addCookies([{
|
|
597
|
-
name,
|
|
598
|
-
value,
|
|
599
|
-
domain: url.hostname,
|
|
600
|
-
path: '/',
|
|
601
|
-
}]);
|
|
602
|
-
return `Cookie set: ${name}=****`;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
case 'header': {
|
|
606
|
-
const headerStr = args[0];
|
|
607
|
-
if (!headerStr || !headerStr.includes(':')) throw new Error('Usage: browse header <name>:<value>');
|
|
608
|
-
const sep = headerStr.indexOf(':');
|
|
609
|
-
const name = headerStr.slice(0, sep).trim();
|
|
610
|
-
const value = headerStr.slice(sep + 1).trim();
|
|
611
|
-
await bm.setExtraHeader(name, value);
|
|
612
|
-
const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie', 'x-api-key', 'x-auth-token'];
|
|
613
|
-
const redactedValue = sensitiveHeaders.includes(name.toLowerCase()) ? '****' : value;
|
|
614
|
-
return `Header set: ${name}: ${redactedValue}`;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
case 'useragent': {
|
|
618
|
-
const ua = args.join(' ');
|
|
619
|
-
if (!ua) throw new Error('Usage: browse useragent <string>');
|
|
620
|
-
bm.setUserAgent(ua);
|
|
621
|
-
const error = await bm.recreateContext();
|
|
622
|
-
if (error) {
|
|
623
|
-
return `User agent set to "${ua}" but: ${error}`;
|
|
624
|
-
}
|
|
625
|
-
return `User agent set: ${ua}`;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
case 'upload': {
|
|
629
|
-
const [selector, ...filePaths] = args;
|
|
630
|
-
if (!selector || filePaths.length === 0) throw new Error('Usage: browse upload <selector> <file1> [file2...]');
|
|
631
|
-
|
|
632
|
-
const validatedPaths = filePaths.map(fp => {
|
|
633
|
-
if (!fs.existsSync(fp)) throw new Error(`File not found: ${fp}`);
|
|
634
|
-
const realPath = fs.realpathSync(path.resolve(fp));
|
|
635
|
-
validateReadPath(realPath);
|
|
636
|
-
return realPath;
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
const resolved = await session.resolveRef(selector);
|
|
640
|
-
if ('locator' in resolved) {
|
|
641
|
-
await resolved.locator.setInputFiles(validatedPaths);
|
|
642
|
-
} else {
|
|
643
|
-
await target.locator(resolved.selector).setInputFiles(validatedPaths);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
const fileInfo = validatedPaths.map(fp => {
|
|
647
|
-
const stat = fs.statSync(fp);
|
|
648
|
-
return `${path.basename(fp)} (${stat.size}B)`;
|
|
649
|
-
}).join(', ');
|
|
650
|
-
return `Uploaded: ${fileInfo}`;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
case 'dialog-accept': {
|
|
654
|
-
const text = args.length > 0 ? args.join(' ') : null;
|
|
655
|
-
bm.setDialogAutoAccept(true);
|
|
656
|
-
bm.setDialogPromptText(text);
|
|
657
|
-
return text
|
|
658
|
-
? `Dialogs will be accepted with text: "${text}"`
|
|
659
|
-
: 'Dialogs will be accepted';
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
case 'dialog-dismiss': {
|
|
663
|
-
bm.setDialogAutoAccept(false);
|
|
664
|
-
bm.setDialogPromptText(null);
|
|
665
|
-
return 'Dialogs will be dismissed';
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
case 'cookie-import': {
|
|
669
|
-
const filePath = args[0];
|
|
670
|
-
if (!filePath) throw new Error('Usage: browse cookie-import <json-file>');
|
|
671
|
-
// Path validation against safe dirs (#707: relative paths included),
|
|
672
|
-
// through the same realpath as SAFE_DIRECTORIES (#3104).
|
|
673
|
-
validateReadPath(filePath);
|
|
674
|
-
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
675
|
-
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
676
|
-
let cookies: any[];
|
|
677
|
-
try { cookies = JSON.parse(raw); } catch (err: any) { throw new Error(`Invalid JSON in ${filePath}: ${err?.message || err}`); }
|
|
678
|
-
if (!Array.isArray(cookies)) throw new Error('Cookie file must contain a JSON array');
|
|
679
|
-
|
|
680
|
-
// Auto-fill domain from current page URL when missing (consistent with cookie command)
|
|
681
|
-
const pageUrl = new URL(page.url());
|
|
682
|
-
const defaultDomain = pageUrl.hostname;
|
|
683
|
-
|
|
684
|
-
for (const c of cookies) {
|
|
685
|
-
if (!c.name || c.value === undefined) throw new Error('Each cookie must have "name" and "value" fields');
|
|
686
|
-
if (!c.domain) {
|
|
687
|
-
c.domain = defaultDomain;
|
|
688
|
-
} else {
|
|
689
|
-
const cookieDomain = c.domain.startsWith('.') ? c.domain.slice(1) : c.domain;
|
|
690
|
-
if (cookieDomain !== defaultDomain && !defaultDomain.endsWith('.' + cookieDomain)) {
|
|
691
|
-
throw new Error(`Cookie domain "${c.domain}" does not match current page domain "${defaultDomain}". Use the target site first.`);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
if (!c.path) c.path = '/';
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
await page.context().addCookies(cookies);
|
|
698
|
-
const importedDomains = [...new Set(cookies.map((c: any) => c.domain).filter(Boolean))];
|
|
699
|
-
if (importedDomains.length > 0) bm.trackCookieImportDomains(importedDomains);
|
|
700
|
-
return `Loaded ${cookies.length} cookies from ${filePath}`;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
case 'cookie-import-browser': {
|
|
704
|
-
const options = parseCookieImportArgs(args);
|
|
705
|
-
const target = { page, url: page.url() };
|
|
706
|
-
const authOptions = {
|
|
707
|
-
identitySelector: process.env.GSTACK_COOKIE_AUTH_SELECTOR,
|
|
708
|
-
expectedIdentity: process.env.GSTACK_COOKIE_AUTH_EXPECTED_IDENTITY,
|
|
709
|
-
};
|
|
710
|
-
if (options.domains || options.all) {
|
|
711
|
-
if (options.domains) {
|
|
712
|
-
const targetUrl = validateCookieTarget(target);
|
|
713
|
-
if (!options.domains.every(domain => cookieDomainMatches(targetUrl.hostname, '.' + domain))) {
|
|
714
|
-
throw new CookieImportError('The requested cookie domain does not match the current page. Navigate to the target site first.', 'target_mismatch');
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
const result = await runCookieImport(options, target, domains => bm.trackCookieImportDomains(domains), authOptions);
|
|
718
|
-
const message = formatCookieImportResult(result);
|
|
719
|
-
if (result.outcome === 'failed' || options.verifyAuth && !result.verification.verified) {
|
|
720
|
-
throw new CookieImportError(message, 'cookie_import_incomplete');
|
|
721
|
-
}
|
|
722
|
-
return message + (options.all ? ' Used --all: all source-profile cookie domains were selected.' : '');
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
const port = bm.serverPort;
|
|
726
|
-
if (!port) throw new CookieImportError('Server port not available', 'unavailable');
|
|
727
|
-
const browsers = findInstalledBrowsers();
|
|
728
|
-
if (browsers.length === 0) throw new CookieImportError(`No Chromium browsers found. Supported: ${listSupportedBrowserNames().join(', ')}`, 'not_installed');
|
|
729
|
-
if (options.clearStorage || options.verifyAuth) validateCookieTarget(target);
|
|
730
|
-
if (options.clearStorage) validateCookieStorageSupport(page);
|
|
731
|
-
if (options.verifyAuth) validateCookieAuthOptions(authOptions);
|
|
732
|
-
const code = generatePickerCode({
|
|
733
|
-
target,
|
|
734
|
-
browser: options.browser,
|
|
735
|
-
profile: options.profile,
|
|
736
|
-
clearStorage: options.clearStorage,
|
|
737
|
-
verifyAuth: options.verifyAuth,
|
|
738
|
-
});
|
|
739
|
-
const pickerUrl = `http://127.0.0.1:${port}/cookie-picker?code=${code}`;
|
|
740
|
-
const openCommand = process.platform === 'darwin' ? ['open', pickerUrl]
|
|
741
|
-
: process.platform === 'win32' ? ['cmd.exe', '/d', '/c', 'start', '', pickerUrl] : ['xdg-open', pickerUrl];
|
|
742
|
-
try {
|
|
743
|
-
Bun.spawn(openCommand, { stdout: 'ignore', stderr: 'ignore', windowsHide: true });
|
|
744
|
-
} catch {
|
|
745
|
-
throw new CookieImportError('Could not open the cookie picker in a local browser. Retry from a desktop session.', 'picker_open_failed');
|
|
746
|
-
}
|
|
747
|
-
return `Cookie picker opened at http://127.0.0.1:${port}/cookie-picker\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect the source profile and domains. Cookies copied does not mean sign-in verified.`;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
case 'style': {
|
|
751
|
-
// style --undo [N] → revert modification
|
|
752
|
-
if (args[0] === '--undo') {
|
|
753
|
-
const idx = args[1] ? parseInt(args[1], 10) : undefined;
|
|
754
|
-
await undoModification(page, idx);
|
|
755
|
-
return idx !== undefined ? `Reverted modification #${idx}` : 'Reverted last modification';
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// style <selector> <property> <value>
|
|
759
|
-
const [selector, property, ...valueParts] = args;
|
|
760
|
-
const value = valueParts.join(' ');
|
|
761
|
-
if (!selector || !property || !value) {
|
|
762
|
-
throw new Error('Usage: browse style <sel> <prop> <value> | style --undo [N]');
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// Validate CSS property name
|
|
766
|
-
if (!/^[a-zA-Z-]+$/.test(property)) {
|
|
767
|
-
throw new Error(`Invalid CSS property name: ${property}. Only letters and hyphens allowed.`);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Validate CSS value — block data exfiltration patterns
|
|
771
|
-
const DANGEROUS_CSS = /url\s*\(|expression\s*\(|@import|javascript:|data:/i;
|
|
772
|
-
if (DANGEROUS_CSS.test(value)) {
|
|
773
|
-
throw new Error('CSS value rejected: contains potentially dangerous pattern.');
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const mod = await modifyStyle(page, selector, property, value);
|
|
777
|
-
return `Style modified: ${selector} { ${property}: ${mod.oldValue || '(none)'} → ${value} } (${mod.method})`;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
case 'cleanup': {
|
|
781
|
-
// Parse flags
|
|
782
|
-
let doAds = false, doCookies = false, doSticky = false, doSocial = false;
|
|
783
|
-
let doOverlays = false, doClutter = false;
|
|
784
|
-
let doAll = false;
|
|
785
|
-
|
|
786
|
-
// Default to --all if no args (most common use case from sidebar button)
|
|
787
|
-
if (args.length === 0) {
|
|
788
|
-
doAll = true;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
for (const arg of args) {
|
|
792
|
-
switch (arg) {
|
|
793
|
-
case '--ads': doAds = true; break;
|
|
794
|
-
case '--cookies': doCookies = true; break;
|
|
795
|
-
case '--sticky': doSticky = true; break;
|
|
796
|
-
case '--social': doSocial = true; break;
|
|
797
|
-
case '--overlays': doOverlays = true; break;
|
|
798
|
-
case '--clutter': doClutter = true; break;
|
|
799
|
-
case '--all': doAll = true; break;
|
|
800
|
-
default:
|
|
801
|
-
throw new Error(`Unknown cleanup flag: ${arg}. Use: --ads, --cookies, --sticky, --social, --overlays, --clutter, --all`);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
if (doAll) {
|
|
806
|
-
doAds = doCookies = doSticky = doSocial = doOverlays = doClutter = true;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const removed: string[] = [];
|
|
810
|
-
|
|
811
|
-
// Build selector list for categories to clean
|
|
812
|
-
const selectors: string[] = [];
|
|
813
|
-
if (doAds) selectors.push(...CLEANUP_SELECTORS.ads);
|
|
814
|
-
if (doCookies) selectors.push(...CLEANUP_SELECTORS.cookies);
|
|
815
|
-
if (doSocial) selectors.push(...CLEANUP_SELECTORS.social);
|
|
816
|
-
if (doOverlays) selectors.push(...CLEANUP_SELECTORS.overlays);
|
|
817
|
-
if (doClutter) selectors.push(...CLEANUP_SELECTORS.clutter);
|
|
818
|
-
|
|
819
|
-
if (selectors.length > 0) {
|
|
820
|
-
const count = await page.evaluate((sels: string[]) => {
|
|
821
|
-
let removed = 0;
|
|
822
|
-
for (const sel of sels) {
|
|
823
|
-
try {
|
|
824
|
-
const els = document.querySelectorAll(sel);
|
|
825
|
-
els.forEach(el => {
|
|
826
|
-
(el as HTMLElement).style.setProperty('display', 'none', 'important');
|
|
827
|
-
removed++;
|
|
828
|
-
});
|
|
829
|
-
} catch (err: any) {
|
|
830
|
-
// querySelectorAll throws DOMException on invalid CSS selectors — skip those
|
|
831
|
-
if (!(err instanceof DOMException)) throw err;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
return removed;
|
|
835
|
-
}, selectors);
|
|
836
|
-
if (count > 0) {
|
|
837
|
-
if (doAds) removed.push('ads');
|
|
838
|
-
if (doCookies) removed.push('cookie banners');
|
|
839
|
-
if (doSocial) removed.push('social widgets');
|
|
840
|
-
if (doOverlays) removed.push('overlays/popups');
|
|
841
|
-
if (doClutter) removed.push('clutter');
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Sticky/fixed elements — handled separately with computed style check
|
|
846
|
-
if (doSticky) {
|
|
847
|
-
const stickyCount = await page.evaluate(() => {
|
|
848
|
-
let removed = 0;
|
|
849
|
-
// Collect all sticky/fixed elements, sort by vertical position
|
|
850
|
-
const stickyEls: Array<{ el: Element; top: number; width: number; height: number }> = [];
|
|
851
|
-
const allElements = document.querySelectorAll('*');
|
|
852
|
-
const viewportWidth = window.innerWidth;
|
|
853
|
-
for (const el of allElements) {
|
|
854
|
-
const style = getComputedStyle(el);
|
|
855
|
-
if (style.position === 'fixed' || style.position === 'sticky') {
|
|
856
|
-
const rect = el.getBoundingClientRect();
|
|
857
|
-
stickyEls.push({ el, top: rect.top, width: rect.width, height: rect.height });
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
// Sort by vertical position (topmost first)
|
|
861
|
-
stickyEls.sort((a, b) => a.top - b.top);
|
|
862
|
-
let preservedTopNav = false;
|
|
863
|
-
for (const { el, top, width, height } of stickyEls) {
|
|
864
|
-
const tag = el.tagName.toLowerCase();
|
|
865
|
-
// Always skip nav/header semantic elements
|
|
866
|
-
if (tag === 'nav' || tag === 'header') continue;
|
|
867
|
-
if (el.getAttribute('role') === 'navigation') continue;
|
|
868
|
-
// Skip the gstack control indicator
|
|
869
|
-
if ((el as HTMLElement).id === 'gstack-ctrl') continue;
|
|
870
|
-
// Preserve the FIRST full-width element near the top (site's main nav bar)
|
|
871
|
-
// This catches divs that act as navbars but aren't semantic <nav> elements
|
|
872
|
-
if (!preservedTopNav && top <= 50 && width > viewportWidth * 0.8 && height < 120) {
|
|
873
|
-
preservedTopNav = true;
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
(el as HTMLElement).style.setProperty('display', 'none', 'important');
|
|
877
|
-
removed++;
|
|
878
|
-
}
|
|
879
|
-
return removed;
|
|
880
|
-
});
|
|
881
|
-
if (stickyCount > 0) removed.push(`${stickyCount} sticky/fixed elements`);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
// Unlock scrolling (many sites lock body scroll when modals are open)
|
|
885
|
-
const scrollFixed = await page.evaluate(() => {
|
|
886
|
-
let fixed = 0;
|
|
887
|
-
// Unlock body and html scroll
|
|
888
|
-
for (const el of [document.body, document.documentElement]) {
|
|
889
|
-
if (!el) continue;
|
|
890
|
-
const style = getComputedStyle(el);
|
|
891
|
-
if (style.overflow === 'hidden' || style.overflowY === 'hidden') {
|
|
892
|
-
(el as HTMLElement).style.setProperty('overflow', 'auto', 'important');
|
|
893
|
-
(el as HTMLElement).style.setProperty('overflow-y', 'auto', 'important');
|
|
894
|
-
fixed++;
|
|
895
|
-
}
|
|
896
|
-
// Remove height:100% + position:fixed that locks scroll
|
|
897
|
-
if (style.position === 'fixed' && (el === document.body || el === document.documentElement)) {
|
|
898
|
-
(el as HTMLElement).style.setProperty('position', 'static', 'important');
|
|
899
|
-
fixed++;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
// Remove blur/filter effects (paywalls often blur the content)
|
|
903
|
-
const blurred = document.querySelectorAll('[style*="blur"], [style*="filter"]');
|
|
904
|
-
blurred.forEach(el => {
|
|
905
|
-
const s = (el as HTMLElement).style;
|
|
906
|
-
if (s.filter?.includes('blur') || s.webkitFilter?.includes('blur')) {
|
|
907
|
-
s.setProperty('filter', 'none', 'important');
|
|
908
|
-
s.setProperty('-webkit-filter', 'none', 'important');
|
|
909
|
-
fixed++;
|
|
910
|
-
}
|
|
911
|
-
});
|
|
912
|
-
// Remove max-height truncation (article truncation)
|
|
913
|
-
const truncated = document.querySelectorAll('[class*="truncat"], [class*="preview"], [class*="teaser"]');
|
|
914
|
-
truncated.forEach(el => {
|
|
915
|
-
const s = getComputedStyle(el);
|
|
916
|
-
if (s.maxHeight && s.maxHeight !== 'none' && parseInt(s.maxHeight) < 500) {
|
|
917
|
-
(el as HTMLElement).style.setProperty('max-height', 'none', 'important');
|
|
918
|
-
(el as HTMLElement).style.setProperty('overflow', 'visible', 'important');
|
|
919
|
-
fixed++;
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
return fixed;
|
|
923
|
-
});
|
|
924
|
-
if (scrollFixed > 0) removed.push('scroll unlocked');
|
|
925
|
-
|
|
926
|
-
// Remove "ADVERTISEMENT" / "Article continues below" text labels
|
|
927
|
-
const adLabelCount = await page.evaluate(() => {
|
|
928
|
-
let removed = 0;
|
|
929
|
-
const adTextPatterns = [
|
|
930
|
-
/^advertisement$/i, /^sponsored$/i, /^promoted$/i,
|
|
931
|
-
/article continues/i, /continues below/i,
|
|
932
|
-
/^ad$/i, /^paid content$/i, /^partner content$/i,
|
|
933
|
-
];
|
|
934
|
-
// Walk text-heavy small elements looking for ad labels
|
|
935
|
-
const candidates = document.querySelectorAll('div, span, p, figcaption, label');
|
|
936
|
-
for (const el of candidates) {
|
|
937
|
-
const text = (el.textContent || '').trim();
|
|
938
|
-
if (text.length > 50) continue; // Too much text, probably real content
|
|
939
|
-
if (adTextPatterns.some(p => p.test(text))) {
|
|
940
|
-
// Also hide the parent if it's a wrapper with little else
|
|
941
|
-
const parent = el.parentElement;
|
|
942
|
-
if (parent && (parent.textContent || '').trim().length < 80) {
|
|
943
|
-
(parent as HTMLElement).style.setProperty('display', 'none', 'important');
|
|
944
|
-
} else {
|
|
945
|
-
(el as HTMLElement).style.setProperty('display', 'none', 'important');
|
|
946
|
-
}
|
|
947
|
-
removed++;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
return removed;
|
|
951
|
-
});
|
|
952
|
-
if (adLabelCount > 0) removed.push(`${adLabelCount} ad labels`);
|
|
953
|
-
|
|
954
|
-
// Remove empty ad placeholder whitespace (divs that are now empty after ad removal)
|
|
955
|
-
const collapsedCount = await page.evaluate(() => {
|
|
956
|
-
let collapsed = 0;
|
|
957
|
-
const candidates = document.querySelectorAll(
|
|
958
|
-
'div[class*="ad"], div[id*="ad"], aside[class*="ad"], div[class*="sidebar"], ' +
|
|
959
|
-
'div[class*="rail"], div[class*="right-col"], div[class*="widget"]'
|
|
960
|
-
);
|
|
961
|
-
for (const el of candidates) {
|
|
962
|
-
const rect = el.getBoundingClientRect();
|
|
963
|
-
// If the element has significant height but no visible text content, collapse it
|
|
964
|
-
if (rect.height > 50 && rect.width > 0) {
|
|
965
|
-
const text = (el.textContent || '').trim();
|
|
966
|
-
const images = el.querySelectorAll('img:not([src*="logo"]):not([src*="icon"])');
|
|
967
|
-
const links = el.querySelectorAll('a');
|
|
968
|
-
// Empty or mostly empty: collapse
|
|
969
|
-
if (text.length < 20 && images.length === 0 && links.length < 2) {
|
|
970
|
-
(el as HTMLElement).style.setProperty('display', 'none', 'important');
|
|
971
|
-
collapsed++;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
return collapsed;
|
|
976
|
-
});
|
|
977
|
-
if (collapsedCount > 0) removed.push(`${collapsedCount} empty placeholders`);
|
|
978
|
-
|
|
979
|
-
if (removed.length === 0) return 'No clutter elements found to remove.';
|
|
980
|
-
return `Cleaned up: ${removed.join(', ')}`;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
case 'prettyscreenshot': {
|
|
984
|
-
// Parse flags
|
|
985
|
-
let scrollTo: string | undefined;
|
|
986
|
-
let doCleanup = false;
|
|
987
|
-
const hideSelectors: string[] = [];
|
|
988
|
-
let viewportWidth: number | undefined;
|
|
989
|
-
let outputPath: string | undefined;
|
|
990
|
-
|
|
991
|
-
for (let i = 0; i < args.length; i++) {
|
|
992
|
-
if (args[i] === '--scroll-to' && i + 1 < args.length) {
|
|
993
|
-
scrollTo = args[++i];
|
|
994
|
-
} else if (args[i] === '--cleanup') {
|
|
995
|
-
doCleanup = true;
|
|
996
|
-
} else if (args[i] === '--hide' && i + 1 < args.length) {
|
|
997
|
-
// Collect all following non-flag args as selectors to hide. A
|
|
998
|
-
// trailing image path is the output path, not a selector (#1419).
|
|
999
|
-
const values: string[] = [];
|
|
1000
|
-
i++;
|
|
1001
|
-
while (i < args.length && !args[i].startsWith('--')) {
|
|
1002
|
-
values.push(args[i]);
|
|
1003
|
-
i++;
|
|
1004
|
-
}
|
|
1005
|
-
i--; // Back up since the for loop will increment
|
|
1006
|
-
const last = values[values.length - 1];
|
|
1007
|
-
if (!outputPath && values.length > 1 && /^(\/|\.\.?\/|~)|\.(png|jpe?g|webp)$/i.test(last)) outputPath = values.pop();
|
|
1008
|
-
hideSelectors.push(...values);
|
|
1009
|
-
} else if (args[i] === '--width' && i + 1 < args.length) {
|
|
1010
|
-
viewportWidth = parseInt(args[++i], 10);
|
|
1011
|
-
if (isNaN(viewportWidth)) throw new Error('--width must be a number');
|
|
1012
|
-
} else if (!args[i].startsWith('--')) {
|
|
1013
|
-
outputPath = args[i];
|
|
1014
|
-
} else {
|
|
1015
|
-
throw new Error(`Unknown prettyscreenshot flag: ${args[i]}`);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
// Default output path
|
|
1020
|
-
if (!outputPath) {
|
|
1021
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
1022
|
-
outputPath = `${TEMP_DIR}/browse-pretty-${timestamp}.png`;
|
|
1023
|
-
}
|
|
1024
|
-
validateOutputPath(outputPath);
|
|
1025
|
-
|
|
1026
|
-
const originalViewport = page.viewportSize();
|
|
1027
|
-
|
|
1028
|
-
// Set viewport width if specified
|
|
1029
|
-
if (viewportWidth && originalViewport) {
|
|
1030
|
-
await page.setViewportSize({ width: viewportWidth, height: originalViewport.height });
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
// Run cleanup if requested
|
|
1034
|
-
if (doCleanup) {
|
|
1035
|
-
const allSelectors = [
|
|
1036
|
-
...CLEANUP_SELECTORS.ads,
|
|
1037
|
-
...CLEANUP_SELECTORS.cookies,
|
|
1038
|
-
...CLEANUP_SELECTORS.social,
|
|
1039
|
-
];
|
|
1040
|
-
await page.evaluate((sels: string[]) => {
|
|
1041
|
-
for (const sel of sels) {
|
|
1042
|
-
try {
|
|
1043
|
-
document.querySelectorAll(sel).forEach(el => {
|
|
1044
|
-
(el as HTMLElement).style.display = 'none';
|
|
1045
|
-
});
|
|
1046
|
-
} catch (err: any) {
|
|
1047
|
-
if (!(err instanceof DOMException)) throw err;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
// Also hide fixed/sticky (except nav)
|
|
1051
|
-
for (const el of document.querySelectorAll('*')) {
|
|
1052
|
-
const style = getComputedStyle(el);
|
|
1053
|
-
if (style.position === 'fixed' || style.position === 'sticky') {
|
|
1054
|
-
const tag = el.tagName.toLowerCase();
|
|
1055
|
-
if (tag === 'nav' || tag === 'header') continue;
|
|
1056
|
-
if (el.getAttribute('role') === 'navigation') continue;
|
|
1057
|
-
(el as HTMLElement).style.display = 'none';
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
}, allSelectors);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
// Hide specific elements
|
|
1064
|
-
if (hideSelectors.length > 0) {
|
|
1065
|
-
await page.evaluate((sels: string[]) => {
|
|
1066
|
-
for (const sel of sels) {
|
|
1067
|
-
try {
|
|
1068
|
-
document.querySelectorAll(sel).forEach(el => {
|
|
1069
|
-
(el as HTMLElement).style.display = 'none';
|
|
1070
|
-
});
|
|
1071
|
-
} catch (err: any) {
|
|
1072
|
-
if (!(err instanceof DOMException)) throw err;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}, hideSelectors);
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// Scroll to target
|
|
1079
|
-
if (scrollTo) {
|
|
1080
|
-
// Try as CSS selector first, then as text content
|
|
1081
|
-
const scrolled = await page.evaluate((target: string) => {
|
|
1082
|
-
// Try CSS selector
|
|
1083
|
-
let el = document.querySelector(target);
|
|
1084
|
-
if (el) {
|
|
1085
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
1086
|
-
return true;
|
|
1087
|
-
}
|
|
1088
|
-
// Try text match
|
|
1089
|
-
const walker = document.createTreeWalker(
|
|
1090
|
-
document.body,
|
|
1091
|
-
NodeFilter.SHOW_TEXT,
|
|
1092
|
-
null,
|
|
1093
|
-
);
|
|
1094
|
-
let node: Node | null;
|
|
1095
|
-
while ((node = walker.nextNode())) {
|
|
1096
|
-
if (node.textContent?.includes(target)) {
|
|
1097
|
-
const parent = node.parentElement;
|
|
1098
|
-
if (parent) {
|
|
1099
|
-
parent.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
1100
|
-
return true;
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
return false;
|
|
1105
|
-
}, scrollTo);
|
|
1106
|
-
|
|
1107
|
-
if (!scrolled) {
|
|
1108
|
-
// Restore viewport before throwing
|
|
1109
|
-
if (viewportWidth && originalViewport) {
|
|
1110
|
-
await page.setViewportSize(originalViewport);
|
|
1111
|
-
}
|
|
1112
|
-
throw new Error(`Could not find element or text to scroll to: ${scrollTo}`);
|
|
1113
|
-
}
|
|
1114
|
-
// Brief wait for scroll to settle
|
|
1115
|
-
await page.waitForTimeout(300);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// Take screenshot
|
|
1119
|
-
await page.screenshot({ path: outputPath, fullPage: !scrollTo });
|
|
1120
|
-
// Guard against Anthropic vision API >2000px brick (#1214). Only
|
|
1121
|
-
// applies to fullPage captures; scrollTo viewport-bound shots are
|
|
1122
|
-
// already capped by the viewport size.
|
|
1123
|
-
if (!scrollTo) await guardScreenshotPath(outputPath);
|
|
1124
|
-
|
|
1125
|
-
// Restore viewport
|
|
1126
|
-
if (viewportWidth && originalViewport) {
|
|
1127
|
-
await page.setViewportSize(originalViewport);
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
const parts = ['Screenshot saved'];
|
|
1131
|
-
if (doCleanup) parts.push('(cleaned)');
|
|
1132
|
-
if (scrollTo) parts.push(`(scrolled to: ${scrollTo})`);
|
|
1133
|
-
parts.push(`: ${outputPath}`);
|
|
1134
|
-
return parts.join(' ');
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
case 'download': {
|
|
1138
|
-
if (args.length === 0) throw new Error('Usage: download <url|@ref> [path] [--base64] [--navigate]');
|
|
1139
|
-
const isBase64 = args.includes('--base64');
|
|
1140
|
-
const useNavigate = args.includes('--navigate');
|
|
1141
|
-
const filteredArgs = args.filter(a => a !== '--base64' && a !== '--navigate');
|
|
1142
|
-
let url = filteredArgs[0];
|
|
1143
|
-
const outputPath = filteredArgs[1];
|
|
1144
|
-
|
|
1145
|
-
// Resolve @ref to element src
|
|
1146
|
-
if (url.startsWith('@')) {
|
|
1147
|
-
const resolved = await bm.resolveRef(url);
|
|
1148
|
-
if (!('locator' in resolved)) throw new Error(`Expected @ref, got CSS selector: ${url}`);
|
|
1149
|
-
const locator = resolved.locator;
|
|
1150
|
-
const tagName = await locator.evaluate(el => el.tagName.toLowerCase());
|
|
1151
|
-
if (tagName === 'img') {
|
|
1152
|
-
url = await locator.evaluate(el => {
|
|
1153
|
-
const img = el as HTMLImageElement;
|
|
1154
|
-
return img.currentSrc || img.src || img.getAttribute('data-src') || '';
|
|
1155
|
-
});
|
|
1156
|
-
} else if (tagName === 'video') {
|
|
1157
|
-
url = await locator.evaluate(el => (el as HTMLVideoElement).currentSrc || (el as HTMLVideoElement).src || '');
|
|
1158
|
-
} else if (tagName === 'audio') {
|
|
1159
|
-
url = await locator.evaluate(el => (el as HTMLAudioElement).currentSrc || (el as HTMLAudioElement).src || '');
|
|
1160
|
-
} else {
|
|
1161
|
-
// Try src attribute on any element
|
|
1162
|
-
url = await locator.evaluate(el => el.getAttribute('src') || '');
|
|
1163
|
-
}
|
|
1164
|
-
if (!url) throw new Error(`Could not extract URL from ${filteredArgs[0]} (${tagName})`);
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
// Check for HLS/DASH
|
|
1168
|
-
if (url.includes('.m3u8') || url.includes('.mpd')) {
|
|
1169
|
-
throw new Error('This is an HLS/DASH stream. Use yt-dlp or ffmpeg for adaptive stream downloads.');
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
// Determine output path and extension
|
|
1173
|
-
const page = bm.getPage();
|
|
1174
|
-
let contentType = 'application/octet-stream';
|
|
1175
|
-
let buffer: Buffer;
|
|
1176
|
-
|
|
1177
|
-
if (url.startsWith('blob:')) {
|
|
1178
|
-
// Strategy 3: Blob URL -- in-page fetch + base64
|
|
1179
|
-
const dataUrl = await page.evaluate(async (blobUrl) => {
|
|
1180
|
-
try {
|
|
1181
|
-
const resp = await fetch(blobUrl);
|
|
1182
|
-
const blob = await resp.blob();
|
|
1183
|
-
if (blob.size > 100 * 1024 * 1024) return 'ERROR:TOO_LARGE';
|
|
1184
|
-
return new Promise<string>((resolve, reject) => {
|
|
1185
|
-
const reader = new FileReader();
|
|
1186
|
-
reader.onloadend = () => resolve(reader.result as string);
|
|
1187
|
-
reader.onerror = () => reject('Failed to read blob');
|
|
1188
|
-
reader.readAsDataURL(blob);
|
|
1189
|
-
});
|
|
1190
|
-
} catch (err: any) {
|
|
1191
|
-
return `ERROR:EXPIRED:${err?.message || 'unknown'}`;
|
|
1192
|
-
}
|
|
1193
|
-
}, url);
|
|
1194
|
-
|
|
1195
|
-
if (dataUrl === 'ERROR:TOO_LARGE') throw new Error('Blob too large (>100MB). Use a different approach.');
|
|
1196
|
-
if (dataUrl.startsWith('ERROR:EXPIRED')) throw new Error(`Blob URL expired or inaccessible: ${dataUrl.slice('ERROR:EXPIRED:'.length)}`);
|
|
1197
|
-
|
|
1198
|
-
const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
1199
|
-
if (!match) throw new Error('Failed to decode blob data');
|
|
1200
|
-
contentType = match[1];
|
|
1201
|
-
buffer = Buffer.from(match[2], 'base64');
|
|
1202
|
-
} else if (useNavigate) {
|
|
1203
|
-
// Strategy 2: Navigate to URL and capture browser-triggered download.
|
|
1204
|
-
// Handles URLs that trigger file downloads via redirects,
|
|
1205
|
-
// Content-Disposition headers, or anti-bot CDN chains where
|
|
1206
|
-
// page.request.fetch() can't follow the auth/redirect chain.
|
|
1207
|
-
await validateNavigationUrl(url);
|
|
1208
|
-
const downloadPromise = page.waitForEvent('download', { timeout: 60000 });
|
|
1209
|
-
// Use goto with 'commit' wait — the page may redirect to trigger
|
|
1210
|
-
// the download, so 'domcontentloaded' may never fire.
|
|
1211
|
-
page.goto(url, { waitUntil: 'commit', timeout: 30000 }).catch(() => {
|
|
1212
|
-
// Navigation may "fail" because the response is a download,
|
|
1213
|
-
// not a page. The download event handles it.
|
|
1214
|
-
});
|
|
1215
|
-
const download = await downloadPromise;
|
|
1216
|
-
const failure = await download.failure();
|
|
1217
|
-
if (failure) {
|
|
1218
|
-
throw new Error(`Download failed: ${failure}`);
|
|
1219
|
-
}
|
|
1220
|
-
// Save to temp location first, then read into buffer
|
|
1221
|
-
const tempPath = path.join(TEMP_DIR, `browse-nav-download-${Date.now()}`);
|
|
1222
|
-
await download.saveAs(tempPath);
|
|
1223
|
-
buffer = fs.readFileSync(tempPath);
|
|
1224
|
-
// Try to infer content type from suggested filename
|
|
1225
|
-
const suggested = download.suggestedFilename();
|
|
1226
|
-
if (suggested) {
|
|
1227
|
-
const extMatch = suggested.match(/\.([a-z0-9]+)$/i);
|
|
1228
|
-
if (extMatch) {
|
|
1229
|
-
const extLower = extMatch[1].toLowerCase();
|
|
1230
|
-
const mimeMap: Record<string, string> = {
|
|
1231
|
-
epub: 'application/epub+zip', pdf: 'application/pdf',
|
|
1232
|
-
zip: 'application/zip', gz: 'application/gzip',
|
|
1233
|
-
mp3: 'audio/mpeg', mp4: 'video/mp4',
|
|
1234
|
-
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
|
|
1235
|
-
txt: 'text/plain', html: 'text/html', json: 'application/json',
|
|
1236
|
-
};
|
|
1237
|
-
contentType = mimeMap[extLower] || 'application/octet-stream';
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
// Clean up temp file if we're going to write elsewhere
|
|
1241
|
-
if (outputPath || isBase64) {
|
|
1242
|
-
try { fs.unlinkSync(tempPath); } catch { /* ignore */ }
|
|
1243
|
-
} else {
|
|
1244
|
-
// No explicit output path — rename temp file with inferred extension.
|
|
1245
|
-
const ext = contentType.split(';')[0].includes('/')
|
|
1246
|
-
? mimeToExt(contentType.split(';')[0].trim())
|
|
1247
|
-
: '.bin';
|
|
1248
|
-
const finalPath = path.join(TEMP_DIR, `browse-download-${Date.now()}${ext}`);
|
|
1249
|
-
fs.renameSync(tempPath, finalPath);
|
|
1250
|
-
const sizeKB = Math.round(buffer.length / 1024);
|
|
1251
|
-
return `Downloaded: ${finalPath} (${sizeKB}KB, ${contentType.split(';')[0].trim()})${suggested ? ` [${suggested}]` : ''}`;
|
|
1252
|
-
}
|
|
1253
|
-
if (buffer.length > 200 * 1024 * 1024) {
|
|
1254
|
-
throw new Error('File too large (>200MB).');
|
|
1255
|
-
}
|
|
1256
|
-
} else {
|
|
1257
|
-
// Strategy 1: Direct URL via page.request.fetch().
|
|
1258
|
-
// Gate the URL through the same validator `goto` uses. Without
|
|
1259
|
-
// this check, download + scrape bypass the navigation
|
|
1260
|
-
// blocklist and a caller with write scope can read
|
|
1261
|
-
// http://169.254.169.254/latest/meta-data/ (AWS IMDSv1), the
|
|
1262
|
-
// GCP/Azure metadata equivalents, or any internal IPv4/IPv6
|
|
1263
|
-
// the server happens to route to. The response body is then
|
|
1264
|
-
// returned to the caller (base64) or written to disk where
|
|
1265
|
-
// GET /file serves it back.
|
|
1266
|
-
await validateNavigationUrl(url);
|
|
1267
|
-
const response = await page.request.fetch(url, { timeout: 30000 });
|
|
1268
|
-
const status = response.status();
|
|
1269
|
-
if (status >= 400) {
|
|
1270
|
-
throw new Error(`Download failed: HTTP ${status} ${response.statusText()}`);
|
|
1271
|
-
}
|
|
1272
|
-
contentType = response.headers()['content-type'] || 'application/octet-stream';
|
|
1273
|
-
buffer = Buffer.from(await response.body());
|
|
1274
|
-
if (buffer.length > 200 * 1024 * 1024) {
|
|
1275
|
-
throw new Error('File too large (>200MB).');
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// --base64 mode: return inline
|
|
1280
|
-
if (isBase64) {
|
|
1281
|
-
if (buffer.length > 10 * 1024 * 1024) {
|
|
1282
|
-
throw new Error('File too large for --base64 (>10MB). Use disk download + GET /file instead.');
|
|
1283
|
-
}
|
|
1284
|
-
const mimeType = contentType.split(';')[0].trim();
|
|
1285
|
-
return `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
// Write to disk
|
|
1289
|
-
const ext = contentType.split(';')[0].includes('/')
|
|
1290
|
-
? mimeToExt(contentType.split(';')[0].trim())
|
|
1291
|
-
: '.bin';
|
|
1292
|
-
const destPath = outputPath || path.join(TEMP_DIR, `browse-download-${Date.now()}${ext}`);
|
|
1293
|
-
validateOutputPath(destPath);
|
|
1294
|
-
fs.writeFileSync(destPath, buffer);
|
|
1295
|
-
const sizeKB = Math.round(buffer.length / 1024);
|
|
1296
|
-
return `Downloaded: ${destPath} (${sizeKB}KB, ${contentType.split(';')[0].trim()})`;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
case 'scrape': {
|
|
1300
|
-
if (args.length === 0) throw new Error('Usage: scrape <images|videos|media> [--selector sel] [--dir path] [--limit N]');
|
|
1301
|
-
const mediaType = args[0];
|
|
1302
|
-
if (!['images', 'videos', 'media'].includes(mediaType)) {
|
|
1303
|
-
throw new Error(`Invalid type: ${mediaType}. Use: images, videos, or media`);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// Parse flags
|
|
1307
|
-
const selectorIdx = args.indexOf('--selector');
|
|
1308
|
-
const selector = selectorIdx >= 0 ? args[selectorIdx + 1] : undefined;
|
|
1309
|
-
const dirIdx = args.indexOf('--dir');
|
|
1310
|
-
const requestedDir = dirIdx >= 0 ? args[dirIdx + 1] : path.join(TEMP_DIR, `browse-scrape-${Date.now()}`);
|
|
1311
|
-
const limitIdx = args.indexOf('--limit');
|
|
1312
|
-
const limit = Math.min(limitIdx >= 0 ? parseInt(args[limitIdx + 1], 10) || 50 : 50, 200);
|
|
1313
|
-
|
|
1314
|
-
validateOutputPath(requestedDir);
|
|
1315
|
-
const dir = path.resolve(requestedDir);
|
|
1316
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1317
|
-
// Each file is validated too: a symlink planted in an existing --dir
|
|
1318
|
-
// would otherwise redirect the write outside the safe directories.
|
|
1319
|
-
const manifestPath = path.join(dir, 'manifest.json');
|
|
1320
|
-
validateOutputPath(manifestPath);
|
|
1321
|
-
|
|
1322
|
-
const { extractMedia } = await import('./media-extract');
|
|
1323
|
-
const target = bm.getActiveFrameOrPage();
|
|
1324
|
-
const filter = mediaType === 'images' ? 'images' as const
|
|
1325
|
-
: mediaType === 'videos' ? 'videos' as const
|
|
1326
|
-
: undefined;
|
|
1327
|
-
const mediaResult = await extractMedia(target, { selector, filter });
|
|
1328
|
-
|
|
1329
|
-
// Collect URLs to download
|
|
1330
|
-
const urls: Array<{ url: string; type: string }> = [];
|
|
1331
|
-
const seen = new Set<string>();
|
|
1332
|
-
|
|
1333
|
-
for (const img of mediaResult.images) {
|
|
1334
|
-
const url = img.currentSrc || img.src || img.dataSrc;
|
|
1335
|
-
if (url && !seen.has(url) && !url.startsWith('data:')) {
|
|
1336
|
-
seen.add(url);
|
|
1337
|
-
urls.push({ url, type: 'image' });
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
for (const vid of mediaResult.videos) {
|
|
1341
|
-
const url = vid.currentSrc || vid.src;
|
|
1342
|
-
if (url && !seen.has(url) && !url.startsWith('blob:') && !vid.isHLS && !vid.isDASH) {
|
|
1343
|
-
seen.add(url);
|
|
1344
|
-
urls.push({ url, type: 'video' });
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
for (const bg of mediaResult.backgroundImages) {
|
|
1348
|
-
if (bg.url && !seen.has(bg.url)) {
|
|
1349
|
-
seen.add(bg.url);
|
|
1350
|
-
urls.push({ url: bg.url, type: 'image' });
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
const toDownload = urls.slice(0, limit);
|
|
1355
|
-
const page = bm.getPage();
|
|
1356
|
-
const manifest: any = {
|
|
1357
|
-
url: page.url(),
|
|
1358
|
-
scraped_at: new Date().toISOString(),
|
|
1359
|
-
files: [] as any[],
|
|
1360
|
-
total_size: 0,
|
|
1361
|
-
succeeded: 0,
|
|
1362
|
-
failed: 0,
|
|
1363
|
-
};
|
|
1364
|
-
|
|
1365
|
-
const lines: string[] = [];
|
|
1366
|
-
for (let i = 0; i < toDownload.length; i++) {
|
|
1367
|
-
const { url, type } = toDownload[i];
|
|
1368
|
-
try {
|
|
1369
|
-
// Same gate as the download command — page.request.fetch
|
|
1370
|
-
// must not reach cloud metadata, ULA ranges, or the rest of
|
|
1371
|
-
// the blocklist. See url-validation.ts for the full list.
|
|
1372
|
-
await validateNavigationUrl(url);
|
|
1373
|
-
const response = await page.request.fetch(url, { timeout: 30000 });
|
|
1374
|
-
if (response.status() >= 400) throw new Error(`HTTP ${response.status()}`);
|
|
1375
|
-
const ct = response.headers()['content-type'] || 'application/octet-stream';
|
|
1376
|
-
const ext = mimeToExt(ct.split(';')[0].trim());
|
|
1377
|
-
const filename = `${type}-${String(i + 1).padStart(3, '0')}${ext}`;
|
|
1378
|
-
const filePath = path.join(dir, filename);
|
|
1379
|
-
validateOutputPath(filePath);
|
|
1380
|
-
const body = Buffer.from(await response.body());
|
|
1381
|
-
try {
|
|
1382
|
-
fs.writeFileSync(filePath, body);
|
|
1383
|
-
} catch (writeErr: any) {
|
|
1384
|
-
throw new Error(`Disk write failed: ${writeErr.message}`);
|
|
1385
|
-
}
|
|
1386
|
-
manifest.files.push({ path: filename, src: url, size: body.length, type: ct.split(';')[0].trim() });
|
|
1387
|
-
manifest.total_size += body.length;
|
|
1388
|
-
manifest.succeeded++;
|
|
1389
|
-
lines.push(` [${i + 1}/${toDownload.length}] ${filename} (${Math.round(body.length / 1024)}KB)`);
|
|
1390
|
-
} catch (err: any) {
|
|
1391
|
-
manifest.files.push({ path: null, src: url, size: 0, type: '', error: err.message });
|
|
1392
|
-
manifest.failed++;
|
|
1393
|
-
lines.push(` [${i + 1}/${toDownload.length}] FAILED: ${err.message}`);
|
|
1394
|
-
}
|
|
1395
|
-
// 100ms delay between downloads
|
|
1396
|
-
if (i < toDownload.length - 1) await new Promise(r => setTimeout(r, 100));
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
// Write manifest
|
|
1400
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1401
|
-
|
|
1402
|
-
return `Scraped ${toDownload.length} items to ${dir}/\n${lines.join('\n')}\n\nSummary: ${manifest.succeeded} succeeded, ${manifest.failed} failed, ${Math.round(manifest.total_size / 1024)}KB total`;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
case 'archive': {
|
|
1406
|
-
const page = bm.getPage();
|
|
1407
|
-
const outputPath = args[0] || path.join(TEMP_DIR, `browse-archive-${Date.now()}.mhtml`);
|
|
1408
|
-
validateOutputPath(outputPath);
|
|
1409
|
-
|
|
1410
|
-
try {
|
|
1411
|
-
const data = await withCdpSession(page, async (cdp) => {
|
|
1412
|
-
const result = await cdp.send('Page.captureSnapshot', { format: 'mhtml' });
|
|
1413
|
-
return (result as { data: string }).data;
|
|
1414
|
-
});
|
|
1415
|
-
fs.writeFileSync(outputPath, data);
|
|
1416
|
-
return `Archive saved: ${outputPath} (${Math.round(data.length / 1024)}KB, MHTML)`;
|
|
1417
|
-
} catch (err: any) {
|
|
1418
|
-
throw new Error(`MHTML archive requires Chromium CDP. Use 'text' or 'html' for raw page content. (${err.message})`);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
default:
|
|
1423
|
-
throw new Error(`Unknown write command: ${command}`);
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
/** Map MIME type to file extension. */
|
|
1428
|
-
function mimeToExt(mime: string): string {
|
|
1429
|
-
const map: Record<string, string> = {
|
|
1430
|
-
'image/png': '.png', 'image/jpeg': '.jpg', 'image/gif': '.gif',
|
|
1431
|
-
'image/webp': '.webp', 'image/svg+xml': '.svg', 'image/avif': '.avif',
|
|
1432
|
-
'video/mp4': '.mp4', 'video/webm': '.webm', 'video/quicktime': '.mov',
|
|
1433
|
-
'audio/mpeg': '.mp3', 'audio/wav': '.wav', 'audio/ogg': '.ogg',
|
|
1434
|
-
'application/pdf': '.pdf', 'application/json': '.json',
|
|
1435
|
-
'text/html': '.html', 'text/plain': '.txt',
|
|
1436
|
-
};
|
|
1437
|
-
return map[mime] || '.bin';
|
|
1438
|
-
}
|