sourcemonkey 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,868 @@
1
+ ---
2
+ name: SourceMonkey-dev
3
+ description: Develop, debug and test userscripts with SourceMonkey's sm-dev tool and Playwright harness. Run a script in a real browser behind real GM_* APIs, drive and inspect the page, read the script's state, work from saved pages, go beyond sm-dev over raw CDP, and turn the result into specs. Use when writing, debugging or testing a userscript in a repo with the `sourcemonkey` dev dependency.
4
+ ---
5
+
6
+ # Userscript development with SourceMonkey
7
+
8
+ ## What this is
9
+
10
+ - **SourceMonkey** is a Chrome userscript manager (like Tampermonkey).
11
+ It ships as a dev dependency, `sourcemonkey`, with its built
12
+ extension and install checks, so tests and tools use the manager's
13
+ real behavior instead of stubs.
14
+ - **`sm-dev`** (`pnpm sm-dev ...`; `scripts/sm-dev` in a SourceMonkey
15
+ checkout) is the interactive tool. It starts a
16
+ **run**: a background process that connects to a Chromium over CDP
17
+ (Chrome DevTools Protocol), pushes your scripts as they are on disk
18
+ into the SourceMonkey extension there, opens tabs, and streams what
19
+ the scripts do and log. You send the run commands to drive and read
20
+ the page.
21
+ - **The Playwright fixture** (`import { test, expect } from
22
+ 'sourcemonkey/harness'`) runs scripts in specs, also in the real
23
+ SourceMonkey extension.
24
+ - So header rules, `@grant`, `@require`, `@run-at`, worlds, `GM_*`
25
+ storage and `GM_xmlhttpRequest` behave as they will for users (see
26
+ [How a run uses the extension](#how-a-run-uses-the-extension)).
27
+ - **A run can use Tampermonkey or Violentmonkey instead**: to check a
28
+ script is portable across managers, or to debug something that works
29
+ in one but not another. sm-dev downloads either. See
30
+ [Testing under other managers](#testing-under-other-managers).
31
+ - **The browser's CDP port stays open** to you as well.
32
+ - Anything sm-dev has no command for (back button, window size, mouse
33
+ wheel, drag), do over CDP directly, in the same tab, while
34
+ the run goes on.
35
+ - `$R cdp` sends raw calls to a tab. See
36
+ [Beyond sm-dev: raw CDP](#beyond-sm-dev-raw-cdp).
37
+
38
+ Paths below like `references/...` are relative
39
+ to this skill's directory (where this `SKILL.md` is, for example
40
+ `.claude/skills/SourceMonkey-dev/`).
41
+
42
+ Where to look:
43
+
44
+ - [references/commands.md](references/commands.md): the commands,
45
+ grouped by task.
46
+ - [references/cdp.md](references/cdp.md): raw CDP recipes.
47
+ - [references/specs.md](references/specs.md): writing Playwright specs
48
+ with the harness fixture.
49
+ - [references/other-managers.md](references/other-managers.md): testing
50
+ under Violentmonkey.
51
+ - `pnpm sm-dev help`: the authoritative, current reference. Alone it
52
+ lists the topics; `help <topic>`, `help <command>` or
53
+ `help <command> <subcommand>` (`help script save`) shows one,
54
+ `help commands` lists the commands, and `help all` everything.
55
+ - The `SourceMonkey-friction` skill, when installed: log where these
56
+ tools got in the way once the task is done.
57
+ - Full docs, when the above isn't enough:
58
+ [harness-guide.md](https://github.com/jshute96/SourceMonkey/blob/main/docs/harness-guide.md).
59
+ Long; search it rather than reading it through.
60
+
61
+ ## Rules that save the most time
62
+
63
+ - **Never `sleep`.** Commands that act (`start`, `click`, `key`,
64
+ `type`, `reload`, `open`, `newtab`) return once the page has settled:
65
+ loaded, scripts started, no requests or DOM changes for a moment.
66
+ For anything later, wait for the signal itself: `wait log <text>`,
67
+ `wait selector <css>`, `wait selector-gone <css>`.
68
+ - Waits give up after 5 seconds (page loads: 30). Pass
69
+ `--timeout-seconds S` to any command that waits, after the command
70
+ name (`wait selector .x --timeout-seconds 20`), for a slow site, and keep
71
+ S under your shell tool's own time limit.
72
+ - With no signal to wait for (sampling something over time), use
73
+ `wait 2s`, not `sleep`.
74
+ - **Don't use `--stop-after-seconds`.** A run that expires mid-test leaves pages
75
+ with no script, and results that look like bugs. Use `--detach`, and
76
+ `stop` when done.
77
+ - **Name your run**: `pnpm sm-dev --run Dagent start ... --detach`,
78
+ then `--run Dagent` on each command. Run IDs start with `D`.
79
+ - The person may have runs of their own. `start --detach` without
80
+ `--run` stops earlier detached runs, and with several runs active,
81
+ commands without `--run` are refused (`status` and `list` show them all).
82
+ - Find and end runs with `sm-dev list` and `sm-dev stop <ID>`, never
83
+ `pkill`.
84
+ - **Name tabs by `T<n>` or `targetId`, never by URL.** Two tabs on the
85
+ same page are common, and acting on the wrong one looks like the
86
+ script didn't run.
87
+ - **Read the run's output before theorizing.** It says whether the
88
+ script started, was skipped (and which header rule decided), or
89
+ threw, with file and line.
90
+ - **Write scripts as they ship.** No guards for missing `GM_*`, no
91
+ test-only branches: they run in the real manager.
92
+ - **Real sites have real data.** Only do reversible actions on
93
+ account or shopping pages, and never submit or save. Use a saved
94
+ page when that isn't enough.
95
+ - The `--open-browser` browser may be signed in to the person's
96
+ accounts. Stay read-only there: don't change any data or settings,
97
+ or do anything else with side effects.
98
+
99
+ ## Getting a browser
100
+
101
+ A line picks one browser mode:
102
+
103
+ - `pnpm sm-dev start ...` (the default): a temp, throwaway
104
+ browser for that run, on a random port, closed when the run ends,
105
+ with SourceMonkey loaded and ready. Good for pages with no login. Its
106
+ profile is empty. Hidden, unless the line goes to a prompt;
107
+ `--headed` or `--headless` says outright. Like the other browser
108
+ options, they go before the command: `pnpm sm-dev --headed start ...`.
109
+ - `pnpm sm-dev --open-browser`: a visible browser on port 9233 (or
110
+ `--browser PORT`) that stays open, with a kept profile, so sign-ins
111
+ persist. Use it for sites that need a login; ask the person to sign
112
+ in there. A no-op if one is already up. Then pass `--open-browser`
113
+ (or `--existing-browser`) to the `start` that uses it.
114
+ - `--existing-browser`: connect to the browser already on port 9233
115
+ (or `--browser PORT`), such as the person's own. Their tabs are left
116
+ alone, and only the run's own tabs are touched.
117
+
118
+ The run needs SourceMonkey in the browser, with "Allow User Scripts" on.
119
+
120
+ - A temp browser always has it. An `--open-browser` or
121
+ `--existing-browser` browser needs it loaded once:
122
+ `pnpm sm-dev --existing-browser manager install` loads it, with
123
+ "Allow User Scripts" on.
124
+ - The run's first line (`Connected to browser ... (Chrome ..., has
125
+ SourceMonkey)`) and `status` say which: `has SourceMonkey`,
126
+ `SourceMonkey disabled` or `no SourceMonkey`. `status` also lists the
127
+ browser's managers, marking one whose "Allow User Scripts" is off.
128
+ - When it is missing, use a temp browser if no login is needed.
129
+ Otherwise ask the person whether to install it into their browser
130
+ with `manager install`, or `manager enable` if it's disabled.
131
+ - Another manager enabled there (Tampermonkey, Violentmonkey) runs its
132
+ scripts in the run's tabs too. The run's first lines and `status`
133
+ warn of it: ask the person before turning it off with
134
+ `pnpm sm-dev --existing-browser --manager tm manager disable`.
135
+
136
+ A headless temp browser is often blocked by large sites (Reddit,
137
+ Google, Amazon, Vimeo, IMDb, parts of YouTube).
138
+
139
+ - The block page still loads, so `start` reports the script as
140
+ started on it. Check the page (`eval document.title`, `screenshot`)
141
+ before believing a result.
142
+ - Use `--open-browser` for those sites from the start.
143
+
144
+ In the shared `--open-browser` browser:
145
+
146
+ - Its background tabs are throttled, and a hidden tab can make
147
+ `click` fail. `focus` brings the run's tab to the front.
148
+ - `stop` leaves the run's tabs open. `closetab all` first.
149
+
150
+ ## The core loop
151
+
152
+ ```sh
153
+ R="pnpm sm-dev --run Dagent"
154
+
155
+ $R validate sites/x/y.user.js # header, @require, syntax (see below)
156
+ $R match sites/x/y.user.js https://x.com/some/page # would it run there, and why
157
+ $R start sites/x/y.user.js https://x.com/some/page --watch --detach
158
+ $R status # tabs, scripts, what's active
159
+ # ... drive and read the page (recipes below) ...
160
+ # ... edit and save: --watch re-pushes and reloads the affected tabs ...
161
+ $R stop
162
+ ```
163
+
164
+ - `start --detach` returns once the page has settled, printing the
165
+ run's startup output: whether the script started, and what it
166
+ logged.
167
+ - Each later command prints its own output, including what the page
168
+ and scripts logged during it, and exits.
169
+ - After a save, the next command's output shows the reload and the
170
+ script's fresh startup lines. Without `--watch`, run `update`.
171
+ - The run's full stream, with timestamps, is in its log file. `status`
172
+ shows the path; `<ID>.log` beside it links to the run's latest.
173
+
174
+ What the stream looks like:
175
+
176
+ ```
177
+ [sm-dev] Pushed to SourceMonkey: sites/x/y.user.js
178
+ [sm-dev] Opening https://x.com/some/page in T1 [targetId=61023580282B1D684FAF4072C201AB51]
179
+ [event] Y started on https://x.com/some/page
180
+ [script log] [y] toolbar found, button inserted
181
+ [event] GM_setValue lastItem = "42"
182
+ ```
183
+
184
+ - Each line's prefix is its log source, narrowest first:
185
+ - `[sm error]` (or `info`, `warning`): SourceMonkey logs, as the
186
+ script's Log tab shows them;
187
+ - `[event]`: script and tab events: started, threw, page loads (in
188
+ `logs`), tabs opened and closed, dialog boxes, and some `GM_*`
189
+ calls (requests, downloads, menu commands, notifications, values
190
+ set or deleted);
191
+ - `[script log]`: Chrome console logs from the script, by its stack,
192
+ so a page helper it calls counts;
193
+ - `[page log]`, `[page error]`: the rest of Chrome's console, hidden
194
+ by default.
195
+ - A line can be in multiple sources: `--log-source page` shows all
196
+ the page's console, the script's lines too, and `[event] X threw`
197
+ also counts as `sm`, `script` and `page`.
198
+ - `[sm-dev]` lines are sm-dev's own notices.
199
+ - Lines from other tabs are prefixed with `[T2]`.
200
+ - A DOM node logged to the console prints as its opening tag,
201
+ `<div id="x" class="a b">`, cut at 120 characters.
202
+ - The site's own lines are hidden. Add `--log-source all` to `start`
203
+ when the script may be breaking the page, or runs code outside its
204
+ file (an added `<script>`, `eval`). `logs --log-source all` reads
205
+ them back afterwards.
206
+ - For a script that acts on the site's or browser's language, add
207
+ `--lang zh-CN` (or `--lang zh-CN,en`) to `start`: the run's tabs then
208
+ report it in `navigator.language(s)`, `Accept-Language` and `Intl`.
209
+ In a connected browser, cross-site iframes and workers keep the
210
+ browser's languages; use a temp browser for a script in frames.
211
+
212
+ A person at a terminal can use `pnpm sm-dev start ...` with no commands
213
+ after `start` for an interactive prompt. An agent should use `--detach`
214
+ and separate commands.
215
+
216
+ ## Recipes
217
+
218
+ ### Checking the header and script: `validate`
219
+
220
+ Run it after any edit, before loading the script anywhere. It is fast,
221
+ needs no browser or run, and runs the extension's own install checks:
222
+ the metadata block, `@match` / `@include`, `@require` loading (local
223
+ copies included), and a syntax check of the body.
224
+
225
+ ```sh
226
+ $R validate sites/x/y.user.js
227
+ $R validate sites/x/ # all scripts under a directory
228
+ $R validate 'sites/**/*.user.js' # or a glob (quoted: sm-dev expands it)
229
+ ```
230
+
231
+ ```
232
+ INVALID sites/x/bad.user.js
233
+ Syntax error: Unexpected token ';' (in sites/x/bad.user.js, line 5, column 11)
234
+ ok sites/x/y.user.js
235
+ warn: Ignoring unsupported @grant GM_frobnicate (in sites/x/y.user.js, line 6)
236
+
237
+ 1 ok, 1 invalid
238
+ ```
239
+
240
+ - Exit code 1 when any script is invalid, so it works as a gate.
241
+ - The message is the one the extension's viewer would show, with the
242
+ file and line. A script with no `@match` or `@include` is invalid
243
+ (it would never run).
244
+ - `warn:` lines don't make it invalid, but read them. An unsupported
245
+ or misspelled `@grant` is ignored, so that `GM_*` name won't exist
246
+ at runtime.
247
+ - `UNCHECKED` means the syntax check couldn't finish (e.g. very deep
248
+ nesting). The script still loads and the exit code is 0, but its
249
+ syntax wasn't verified.
250
+ - An invalid script is refused by `start` and `loadUserscript` with the
251
+ same message, so fixing it here saves a failed run.
252
+ - It doesn't check that `@grant` covers every `GM_*` the code calls; a
253
+ missing one shows up at runtime as `GM_x is not defined`.
254
+ - In a run, `validate` re-reads the selected script from disk, and
255
+ `validate all` checks all loaded scripts.
256
+ - Over many scripts, list only the ones that need a look:
257
+ `$R --json validate sites/ | jq -c '.results[] | select(.verdict != "ok" or .warnings)'`.
258
+
259
+ ### Does the script run on this page?
260
+
261
+ ```sh
262
+ $R match sites/x/y.user.js https://x.com/a https://x.com/b
263
+ $R start sites/x/y.user.js https://x.com/a --detach
264
+ $R status
265
+ ```
266
+
267
+ - `match` names the rule that matched or excluded each URL. It is
268
+ cheap: run it after any header edit.
269
+ - `status` shows each script as `Active` or `Not active` on the current
270
+ tab, and the other tabs and frames it runs on.
271
+ - On a single-page app the URL can change without a load. `match` in a
272
+ run (no arguments) checks the selected script against the current
273
+ tab's URL now.
274
+ - A script also runs in each iframe whose own URL it matches; see
275
+ [Scripts in iframes](#scripts-in-iframes).
276
+
277
+ ### Finding selectors
278
+
279
+ ```sh
280
+ $R snapshot --visible # what's on screen, one line per element, with refs
281
+ $R snapshot --find 'Liked Songs' --context # only elements containing that, with their wrapper divs
282
+ $R probe --css e14 # a short unique CSS selector for a ref
283
+ $R probe --css --html '.titleline > a' # selector + HTML for the first 10 matches
284
+ $R probe '.title a' '[data-testid=post]' '#missing' # which match, how many
285
+ $R click e14 # refs work anywhere a selector does
286
+ ```
287
+
288
+ - `snapshot` is the accessibility tree: roles, names, and a ref like
289
+ `e14`, or `f2e6` with a frame number. `snapshot <selector>` shows
290
+ each of its matches (the first 5; `--limit 0` for all); `--out file`
291
+ saves a large one.
292
+ - When you know some text on the page (from a screenshot, the person,
293
+ or an earlier look), use `snapshot --find <text>` instead of a full
294
+ snapshot piped to `grep`. It shows the matching elements with refs.
295
+ - With a selector, it looks only within the selector's matches.
296
+ - `--context` adds the wrapper divs and small containers around each
297
+ match (of `--find` or a selector); `--up N` climbs N levels.
298
+ - Refs only last until the page changes: take a new snapshot after a
299
+ reload or navigation. Refs from `snapshot <selector>` and from the
300
+ whole page both work.
301
+ - `snapshot` includes panels a script adds after `<body>` and inside
302
+ open shadow roots.
303
+ - `probe --css` skips ids and classes that look generated
304
+ (`css-1x2y3z`). Elements inside a shadow root or frame get no
305
+ selector; use the ref.
306
+ - Prefer selectors that describe purpose: ids and `data-*` the site
307
+ put there, then meaningful class prefixes and `aria-label`, then
308
+ role plus text. Avoid layout and SVG geometry.
309
+ - Verify the selector you'll put in the script with `probe` (count),
310
+ then `eval` for its content:
311
+ `$R eval "[...document.querySelectorAll('.titleline > a')].map(a => a.href).slice(0, 5)"`.
312
+ - Capture the states the script will walk before writing it: open each
313
+ menu or popup (`click`), then `snapshot` or `probe --html` again.
314
+ Menus often replace each other, and attributes like `aria-controls`
315
+ often exist only while open.
316
+
317
+ ### Driving an interaction and checking the result
318
+
319
+ ```sh
320
+ $R click '[aria-label="Next"]'
321
+ $R click e14 --button right # also middle; --modifiers Shift+Control
322
+ $R click '.word' --count 2 # double-click (3: triple)
323
+ $R key j j Shift+ArrowRight
324
+ $R type 'input[name=q]' 'hello world'
325
+ $R hover '.card:first-child'
326
+ $R wait selector '.result' # when the result comes later than the settle
327
+ $R eval 'document.querySelector(".result")?.textContent'
328
+ $R screenshot shot.png # then look at the PNG
329
+ ```
330
+
331
+ - Clicks, hovers and keys are real input (scrolled into view, trusted
332
+ events), so the page can't tell them from a person's.
333
+ - A `click` or `hover` that fails says why: the element stayed hidden,
334
+ disabled or moving, or another element covered it (named). Fix that
335
+ (hover first, wait, close the overlay) rather than clicking its
336
+ pixels over `cdp`, which skips those checks.
337
+ - `--at x,y` clicks or hovers at a point in the element (pixels from
338
+ its top left corner), for a spot other than its center, e.g. a row's
339
+ empty space rather than the link in its middle. `hover` takes
340
+ `--modifiers` too.
341
+ - A scroll from `eval` (`scrollIntoView`, `scrollBy`) fires no
342
+ `wheel` event, so a script that watches for the user scrolling won't
343
+ see it. Send a real wheel event over `cdp` (see
344
+ [Mouse wheel](references/cdp.md#mouse-wheel)).
345
+ - Keys go to the page, not to Chrome: Chrome's shortcuts (back, reload,
346
+ zoom, Escape leaving fullscreen) don't fire.
347
+ - A command that opens a dialog (`alert`, `confirm`, `prompt`) returns
348
+ once it's up. Answer with `accept [tab] [text]` or `dismiss [tab]`;
349
+ until then the page is frozen, and most commands refuse.
350
+ - A `*PERMISSION PROMPT*` line needs an answer before that request
351
+ goes on: see [Permission prompts](#permission-prompts).
352
+ - `logs` reads the run's log back for the tab: its script's lines and
353
+ events, timestamped. `logs all`, `logs 2` (one script),
354
+ `--log-source all` (the site's own too), `--find <text|/re/>`,
355
+ `--tail 50` or `--tail 30s`.
356
+ - It ends with `Logs up to <log ID>`; pass that to `--since` next
357
+ time to read only what came after.
358
+ - Quoting: quote selectors with spaces or `#` for the shell
359
+ (`click '#submit'`).
360
+
361
+ ### Seeing into the script: debug state and logging
362
+
363
+ `eval` runs inside the selected script (`--script=<script>` names
364
+ another), so it sees what the script's own code sees:
365
+
366
+ ```sh
367
+ $R eval 'GM_info.script.version'
368
+ $R eval 'GM_getValue("prefs")'
369
+ $R eval 'config.mode' # a top-level const of the script
370
+ ```
371
+
372
+ - That covers the script's `GM_*` functions, its `@require`
373
+ libraries, its top-level declarations, and its world's globals.
374
+ - A script that opens with a `(function () { ... })()` wrapper (or an
375
+ arrow or `async` one) gets it inside that wrapper, so its locals
376
+ there are in reach too.
377
+ - Locals inside other functions aren't. Expose them on `window`
378
+ (below).
379
+ - Use `eval --page` for the page's own globals (its `$`, an app's
380
+ state object) as the page sees them. An isolated script can't see
381
+ them, and a sandboxed one (below) may have its own `$` from a
382
+ `@require`.
383
+
384
+ For state inside other functions, expose state and functions on
385
+ `window`. Build this in while developing; it's cheap and can stay:
386
+
387
+ ```js
388
+ const state = { slide: 0, zoom: 1 };
389
+ window.myScript = { state, currentSlide, relayout }; // for eval
390
+ ```
391
+
392
+ ```sh
393
+ $R eval 'myScript.state' # printed as JSON
394
+ $R eval 'myScript.currentSlide()'
395
+ $R eval 'myScript.relayout(); myScript.state'
396
+ ```
397
+
398
+ - Unless the script has `@grant none` (no `@grant` line counts as
399
+ sandboxed too), its `window` is its own sandbox, as in Tampermonkey
400
+ and Violentmonkey, so a `window` property there is private to the
401
+ script.
402
+ - The sandbox shows only the browser's built-ins. Read the page's
403
+ globals by bare name (`$`, `appState`) or as `unsafeWindow.x`;
404
+ `window.x` is `undefined` for them.
405
+ - A `@grant none` script shares the page's `window`: pick a name the
406
+ site won't collide with. `eval --page` reaches it too.
407
+
408
+ Log what happens, and wait for it instead of sleeping:
409
+
410
+ ```js
411
+ const log = (...args) => console.log('[my-script]', ...args);
412
+ log('applied preset', name);
413
+ ```
414
+
415
+ ```sh
416
+ $R click '.apply' -- wait log 'applied preset'
417
+ $R wait log '/saved record \d+/' # a regex
418
+ ```
419
+
420
+ - `wait log` also matches lines printed since the previous command
421
+ began, so a line printed during the click counts.
422
+ - It sees every log source, including the site's own lines the stream
423
+ leaves out. Each is matched as the stream would print it:
424
+ `[page log] KEYUP 143`. `--log-source script,event` narrows it.
425
+ - It ends with `Found at <log ID>`, which `--since` takes.
426
+ - So a loose pattern (`error`, `done`) can match the site's own lines;
427
+ use the script's `[name]` prefix or a regex.
428
+ - A command returns once the page settles, which can come before a
429
+ script's delayed setup (a timer, a retry loop). `wait log` for the
430
+ script's own ready line before driving it.
431
+ - Errors a script throws later, from events and timers, show in the
432
+ output of whatever command was running, or only in `logs` if none
433
+ was. Check `logs` before calling a script clean.
434
+ - With several runs or long sessions, `logs --since <position>` from
435
+ the previous `Logs up to` reads only what's new.
436
+ - `GM_log` goes to the page console and the script's Log tab, so it
437
+ shows twice: `[script log] [SourceMonkey:...] ...` and
438
+ `[sm info] <name>: ...`.
439
+ - Async work fired from an event handler: have it log on completion
440
+ and `wait log` for that, rather than polling the DOM for a state
441
+ that may be intermediate.
442
+
443
+ Gate chatty logging on a stored value, so it can be switched live:
444
+
445
+ ```js
446
+ let debug = GM_getValue('debug', false); // @grant GM_getValue
447
+ GM_addValueChangeListener('debug', (key, old, value) => { debug = value; });
448
+ const trace = (...args) => debug && console.log('[my-script]', ...args);
449
+ ```
450
+
451
+ ```sh
452
+ $R start sites/x/y.user.js --values '{"debug": true}' --detach # on from the start
453
+ $R set debug true # or flip it live
454
+ ```
455
+
456
+ - `set` writes as another tab would, so the change listener fires.
457
+ - For state a spec should read too, mirror it into the DOM:
458
+ `document.documentElement.dataset.myScriptZoom = zoom`. Then
459
+ `eval document.documentElement.dataset` or a Playwright locator reads
460
+ it.
461
+
462
+ ### Menu commands and stored values
463
+
464
+ ```sh
465
+ $R menu # the tab's commands: M1, M2 ...
466
+ $R menu 'Toggle dark' # fire by label (or: menu M1)
467
+ $R menu 'Run this script' # run a @run-at context-menu script (again)
468
+ $R values # what the scripts stored
469
+ $R get lastItem
470
+ $R set units '"metric"' # JSON; running scripts' change listeners fire
471
+ $R set prefs --file prefs.json # long JSON, from a file
472
+ $R start ... --values '{"units": "metric"}' # seed before the script runs
473
+ $R start ... --values-file values.json # seed from a JSON file
474
+ ```
475
+
476
+ - `set <key> {{` ... `}}` takes several lines of JSON, on stdin or at a
477
+ prompt, as `eval` does. Send it in a heredoc (see
478
+ [Multi-step sequences](#multi-step-sequences)).
479
+ - Reads wait for the script's write batching, so `menu X` then
480
+ `values` shows what X stored.
481
+ - Stored values belong to the script and survive `update` and reloads.
482
+ They live in the extension; `--values` seeds them as the script is
483
+ pushed.
484
+
485
+ ### `@run-at context-menu` scripts
486
+
487
+ - These don't run on page load. The run says the script is waiting to
488
+ be run, and `menu` lists `Run this script` first under it.
489
+ - Fire that to run the script. Each fire runs it again on the same
490
+ page, so you can test what a second run does too.
491
+ - A reload or navigation leaves it unrun again.
492
+ - In a spec: `gm.runScript()`.
493
+
494
+ ### Working from a saved page
495
+
496
+ Use a saved copy when the live page needs a login, keeps changing,
497
+ has state you mustn't touch, or a spec needs to be stable.
498
+
499
+ `--from-saved` opens a saved HTML file at the URL it was saved from,
500
+ with the scripts matching and running as on the live site. The file
501
+ can come from:
502
+
503
+ - sm-dev's `savehtml` (below).
504
+ - Chrome's Save Page As ("HTML only", or "Webpage, Complete" with its
505
+ `<name>_files` folder, which is served from disk);
506
+ - SeeWhatISee, a Chrome extension that captures screenshots and page
507
+ HTML. The person may already given you a snapshot you can use.
508
+
509
+ ```sh
510
+ $R newtab page.html --from-saved # served at its recorded URL
511
+ $R newtab 'pages/*.html' --from-saved --offline # several; no network
512
+ $R open page.html --from-saved --url https://x.com/some/page # one that records no URL
513
+ ```
514
+
515
+ - The tab shows the original URL, so `@match`, origin, cookies and
516
+ relative URLs behave as live.
517
+ - Without `--offline`, the page's requests (images, fetches) go to the
518
+ live site. A file that keeps the site's own JavaScript (Chrome's and
519
+ SeeWhatISee's do) runs it on replay, and it may re-fetch or redirect:
520
+ `--offline` stops that.
521
+ - `reload` re-reads the file, so it can be edited between loads.
522
+ - It sticks to that URL in that tab: other URLs load live, and coming
523
+ back serves the file again.
524
+ - In a spec: `await page.goto(await serveHtml('pages/some-page.html'))`.
525
+
526
+ Saving one with sm-dev:
527
+
528
+ ```sh
529
+ $R newtab --noscript https://x.com/some/page # a tab without the scripts
530
+ $R savehtml pages/some-page.html # the DOM, with the URL recorded
531
+ ```
532
+
533
+ - Save from a `--noscript` tab. A page saved with the script running
534
+ has its changes in it, and gets them twice on replay (sm-dev warns).
535
+ - Capture each state the script cares about (menu open, popup shown)
536
+ as its own file: bring it up in the `--noscript` tab, then `savehtml`.
537
+ - A `savehtml` file has the site's JavaScript disabled, so it can't
538
+ re-fetch or rebuild itself. Interactions that need the site's code
539
+ won't work there; the script's own code runs normally.
540
+ - It can't capture shadow roots, typed form values or canvas pixels.
541
+ Check selectors inside a shadow root on the live page.
542
+
543
+ ### Multiple tabs
544
+
545
+ - Tabs the run's tab opens (links, `window.open`, `GM_openInTab`) get
546
+ the scripts too, as `T2`, `T3`. Tabs the person opens are left
547
+ alone.
548
+ - Such a tab is set up after its first load, so console lines from
549
+ that load aren't shown. Starts, errors and other reports are.
550
+ - `newtab <url>` opens another controlled tab and makes it current;
551
+ `tab T1` switches back. Page commands take a tab first:
552
+ `click T2 '.x'`, `eval T2 location.href`, `open T2 <url>`,
553
+ `wait T2 selector '.x'`, `menu T2.M1`.
554
+ - `closetab` closes the current tab; `closetab T2 T3` or `closetab all`
555
+ close others. A page that asks first ("Leave site?") shows its
556
+ dialog, answered with `accept` or `dismiss`; `--force` skips it.
557
+ - In a temp browser, closing the last tab quits it, which ends the
558
+ run. Use `stop` there instead of `closetab all`.
559
+ - `tabs` lists them with their `targetId`s, for your own CDP clients.
560
+ In a script: `$R --json tabs | jq -r '.tabs[] | select(.id == "T2") | .targetId'`.
561
+ - `reload all` reloads every tab; `wait all` waits for every tab to
562
+ settle.
563
+
564
+ ### Scripts in iframes
565
+
566
+ A script runs in each frame whose own URL it matches (the top page
567
+ needn't match), unless it has `@noframes`. Each frame gets its own copy
568
+ of the script; they share stored values, and can talk to each other with
569
+ `postMessage`.
570
+
571
+ ```sh
572
+ $R frames # List the tab's iframes as a tree, with the scripts running in each
573
+ $R match --frames # Check which iframes the script's rules match
574
+ $R snapshot --find Save # Find elements in all frames; refs in iframes look like f2e4
575
+ $R click f2e4 # Use a ref from any frame directly, with no --frame
576
+ $R probe --css f2e4 # Get a CSS selector for the element, and which frame it's in
577
+ $R eval --frame F2 'state' # Run code in the script instance in F2 (--page: in F2's page)
578
+ $R click --frame F2 '.play' # Look for a selector inside F2 (also hover, type, probe, snapshot, screenshot)
579
+ $R wait selector --frame F2 '.ready' # Wait for an element inside F2
580
+ ```
581
+
582
+ - Iframes are named `F1`, `F2`, ... for the whole run; the top frame
583
+ is its tab (`T1`). An iframe keeps its id when it or the tab reloads,
584
+ so you can keep using `F2` after a reload.
585
+ - Where a script runs: `frames`, or `tabs` / `status`, which list a
586
+ script not only in the top frame as `(T1, F2)` or `(F2)`.
587
+ - `frames` marks iframes that aren't visible `(hidden)`: often ad or
588
+ tracking frames.
589
+ - A frame added after load (a widget opened by a click) shows up
590
+ once it exists. Run `frames` again after the click.
591
+ - A frame with no URL of its own (`about:blank`, `srcdoc`) never
592
+ runs scripts.
593
+ - To get at an element in a frame, a snapshot ref is the quickest: it
594
+ reaches into any frame, cross-origin ones included, with no
595
+ `--frame`. Refs change when the frame reloads, though, so for a
596
+ selector that lasts (in a spec, or across `--watch` reloads), use
597
+ `--frame F2 <selector>`.
598
+ - `wait` and the actions' own settling watch the top frame's page (and
599
+ every frame's requests). After an action that makes a frame load or
600
+ change, `wait selector --frame F2 ...` for what you need there.
601
+ - `eval --frame` reaches cross-origin frames too, and works like `eval`
602
+ in the top frame: the selected script's scope, `--script=<script>`,
603
+ or `--page`.
604
+ - Lines from a frame are prefixed with it: `[T1.F2] [event] ...
605
+ started in an iframe on <frame URL>`, `[T1.F2] [script log] ...`.
606
+ - `menu` shows the script's one menu for the tab, as SourceMonkey's
607
+ popup does, with every frame's commands in it. sm-dev labels a
608
+ command an iframe registered with its frame: `M3: Toggle (F2)`.
609
+ Firing it runs it in that frame. A command registered by the copies
610
+ in several frames is listed once per frame.
611
+ - For raw CDP, `frames` shows each frame's `[frameId=...]`, which CDP
612
+ calls on a frame take, and `--frame` accepts it too. A frame from
613
+ another site (another domain, not just another subdomain) also has a
614
+ `[targetId=...]` of its own to attach to. `cdp --frame F2 ...` sends
615
+ calls to the target the frame is in.
616
+
617
+ ### Multi-step sequences
618
+
619
+ Commands on one line run in order and stop at the first failure (a
620
+ `click` with no element, an `eval` that threw, a `wait` that gives up),
621
+ like `&&`. A `match` or `validate` miss doesn't stop them, nor does an
622
+ `eval` or `cdp` call cut off by a navigation it set off. To check for
623
+ an element that may not be there, use `probe`, which doesn't fail.
624
+
625
+ Keep sm-dev's stderr: that's where a mistyped command says what was
626
+ wrong. To keep output short, filter stdout (`| tail`, `| grep`) or send
627
+ only stdout to `/dev/null`, never `2>/dev/null`.
628
+
629
+ ```sh
630
+ $R start sites/x/y.user.js https://x.com/ --detach -- click '.open' -- wait selector '#modal' -- snapshot '#modal'
631
+ ```
632
+
633
+ A longer script, or any command with quotes in its arguments (JSON
634
+ strings and selectors included), reads better as a heredoc (no shell
635
+ quoting).
636
+ Each line is a command:
637
+
638
+ ```sh
639
+ $R --stdin <<'EOF'
640
+ click '.open-modal'
641
+ wait selector '#modal-dialog'
642
+ type '#input-name' 'Test User'
643
+ click '#modal-submit'
644
+ wait log /Saved record \d+/
645
+ eval myScript.state
646
+ EOF
647
+ ```
648
+
649
+ - Blank lines and `# comments` are skipped.
650
+ - Quotes: most commands take one level of quotes off, as a shell
651
+ would (`menu 'Toggle dark'`). `set` takes off single quotes around
652
+ its JSON (`set k '["a"]'`), since double quotes are JSON's own.
653
+ `eval` takes off none: the rest of its line is JavaScript as
654
+ written, so `eval "abc"` is a string.
655
+ - A `{{` ... `}}` block spans several lines: JavaScript for
656
+ `eval {{`, JSON for `set <key> {{`, text for `type <selector> {{`,
657
+ CDP calls for `cdp {{` (see [references/commands.md](references/commands.md#eval)).
658
+ - `{{` ends its line, and the block's content starts on the next.
659
+ - `--file` reads the same from a file instead: `eval --file probe.js`,
660
+ `set <key> --file value.json`, `type <selector> --file notes.txt`,
661
+ `cdp --file calls.txt`.
662
+ - The whole input is checked before anything runs, so an invalid
663
+ command anywhere in it runs nothing.
664
+
665
+ ### Reading results in a script: `--json`
666
+
667
+ Most commands that report state can print JSON instead of text, with
668
+ `--json`.
669
+
670
+ - **Use it when you'll process the answer** with `jq` or code:
671
+ - picking a few fields out of a long list (many scripts, tabs or
672
+ matches);
673
+ - checking a value in a script or a loop, rather than by reading;
674
+ - comparing state before and after an action.
675
+ - **Don't use it when you're reading the answer yourself.** The text is
676
+ shorter, and says what an empty answer means and where to look next.
677
+ - **Which commands:** `list`, `status`, `scripts`, `tabs`, `values`,
678
+ `get`, `match`, `validate`, `menu`, `logs`, `probe`, and every
679
+ `collection` and `script` verb (`script list`, `script show`, ...).
680
+ The rest print text as usual.
681
+ - **Output:** one JSON object per command on stdout. Other messages
682
+ and failures stay text, on stderr, with the usual exit code.
683
+ - **The fields**, and what each script `state` means:
684
+ [references/commands.md](references/commands.md#shape-of-a-command-line).
685
+
686
+ ```sh
687
+ $R --json values | jq '.scripts[] | {name, units: .values.units}'
688
+ $R --json probe '.result' | jq '.selectors[0].count'
689
+ ```
690
+
691
+ ### Beyond sm-dev: raw CDP
692
+
693
+ For what sm-dev has no command for, `cdp` sends a raw CDP call to a
694
+ tab (the current one, or `T2` first), and prints the result as JSON.
695
+ A `{{` block, or `--file`, holds several calls, one per line, on one
696
+ session:
697
+
698
+ ```sh
699
+ $R cdp Input.dispatchMouseEvent '{"type":"mouseWheel","x":400,"y":300,"deltaX":0,"deltaY":600}'
700
+ $R cdp T2 Page.getNavigationHistory
701
+ ```
702
+
703
+ - [references/cdp.md](references/cdp.md) has details and recipes:
704
+ back and forward, window size, mouse wheel, clicks at a point,
705
+ drag, fullscreen, per-frame tracing.
706
+ - `cdp` doesn't wait for the page to settle. Run `wait` after it.
707
+ - For events, or state that outlasts one command, connect your own CDP
708
+ client alongside the run (see the reference).
709
+ - Things sm-dev can do itself are better done there: it waits for the
710
+ page to settle, and its output lands in the run's stream.
711
+ - Some things need no CDP: `eval history.back()` then `wait`,
712
+ `eval 'scrollBy(0, 800)'`.
713
+
714
+ ### Fullscreen (images, videos, lightboxes)
715
+
716
+ - Enter it as a person would: `key f`, or `click` the site's button.
717
+ - Exit with `eval document.exitFullscreen()`: `key Escape` doesn't
718
+ exit (sm-dev says so).
719
+ - Checking the state, forcing it off, pitfalls, and tracing a
720
+ transition frame by frame:
721
+ [references/cdp.md](references/cdp.md#fullscreen).
722
+
723
+ ### How a run uses the extension
724
+
725
+ ```sh
726
+ $R start sites/x/y.user.js https://x.com/ --detach
727
+ $R stop # also removes the run's test collection
728
+ ```
729
+
730
+ - The script is pushed into a test collection of the run's own
731
+ (`sm-dev test collection (run D2-a3f9)` in the extension, the id
732
+ `status` shows), removed when the run ends.
733
+ - Each run starts with no stored values but those `--values` seeds.
734
+ - `@require`s read from disk (relative ones, and local copies under
735
+ the collection root or `--local` roots) are pushed with it;
736
+ remote ones are fetched live.
737
+ - The run's tabs run only its own scripts: SourceMonkey-managed
738
+ scripts are kept out of them. The run's scripts run only in the
739
+ run's tabs, not in tabs you open yourself.
740
+ - `scripts` lists those others on the tab by their SourceMonkey
741
+ script numbers (`E12`, the same in every run);
742
+ `values E1` reads one's stored values, and `scripts E1` picks it
743
+ for `get`/`set` (`set key --force <json>` outside a temp
744
+ browser, since that's the person's real data).
745
+ - A `document-start` script may run after the page's first inline
746
+ scripts in a tab a run tab opened (a popup, a link to a new tab),
747
+ where SourceMonkey alone would run it first: every script there waits
748
+ for the extension's check before running. In the tabs the run opens
749
+ itself, the run's scripts run as they would alone.
750
+ - **Before building on a new request path, send one real request
751
+ from it.**
752
+ - A request that works from the page can still be refused from the
753
+ extension: different cookies, and headers a WAF (web application
754
+ firewall) rejects.
755
+ - A 403 the page can't reproduce often comes from the site's CDN
756
+ edge, not its origin server.
757
+
758
+ ### Permission prompts
759
+
760
+ A `GM_xmlhttpRequest` to a site the script's `@connect` lines don't
761
+ cover waits on SourceMonkey's permission prompt, as it would for a
762
+ user. Any script that uses `GM_xmlhttpRequest` can hit one, and one
763
+ with no `@connect` lines likely will. Until it's answered, that request
764
+ and whatever the script does with its response wait.
765
+
766
+ - The run prints `*PERMISSION PROMPT*: <script> requests permission to
767
+ connect to <host> ...`. Answer it with `connect`:
768
+ - `connect allow` to test the script as a user who allowed it (the
769
+ usual case), or `connect allow-all` for one that reaches many sites;
770
+ - `connect deny` to test what the script does when refused;
771
+ - `connect once` or `connect cancel` for a one-time answer: the next
772
+ request asks again.
773
+ - `allow`, `deny` and `allow-all` are kept for the run's copy of the
774
+ script, so later requests to that site don't ask again in the run.
775
+ - `connect` answers the selected script's prompt; with several scripts
776
+ loaded, `script N` first.
777
+ - When testing a script's real behavior, note which sites it asked
778
+ about: its `@connect` lines may be missing some.
779
+
780
+ ### Downloads
781
+
782
+ - Find a downloaded file at the path its `Download saved: <path>` line
783
+ names (for an http(s) `GM_download`, the `GM_download` line's
784
+ `-> <path>`).
785
+ - In a temp browser, `status` shows the folder as `Downloads:` once
786
+ something was downloaded.
787
+
788
+ ### Turning it into a spec
789
+
790
+ ```js
791
+ import { test, expect } from 'sourcemonkey/harness';
792
+
793
+ test('adds the button', async ({ page, loadUserscript, gm, reports }) => {
794
+ await loadUserscript('sites/x/y.user.js'); // before the goto
795
+ await page.goto('https://x.com/items/42');
796
+ await reports.waitForStart();
797
+ await expect(page.locator('#add-button')).toBeVisible();
798
+ expect(reports.errors()).toEqual([]);
799
+ });
800
+ ```
801
+
802
+ - Run it with `pnpm exec playwright test sites/x/y.spec.mjs`.
803
+ - [references/specs.md](references/specs.md) has the rest: mapping
804
+ sm-dev commands to the fixture, network requests, and pages with no
805
+ site behind them.
806
+
807
+ ## Testing under other managers
808
+
809
+ A run can load its scripts into **Tampermonkey** (`--manager tm`) or
810
+ **Violentmonkey** (`--manager vm`) instead of SourceMonkey: to check a
811
+ script is portable, or to debug a difference between managers by
812
+ running the same script and page under each.
813
+
814
+ ```sh
815
+ pnpm sm-dev --manager tm manager fetch # once each: download Tampermonkey,
816
+ pnpm sm-dev --manager vm manager fetch # and Violentmonkey
817
+ pnpm sm-dev --manager tm --run Dtm start sites/x/y.user.js https://x.com/ --detach
818
+ pnpm sm-dev --manager vm --run Dvm start sites/x/y.user.js https://x.com/ --detach
819
+ ```
820
+
821
+ It reports much less than SourceMonkey: which scripts a tab runs is
822
+ predicted from their match rules, and `values`, `menu` and the like are
823
+ refused. How it works and what differs:
824
+ [references/other-managers.md](references/other-managers.md).
825
+
826
+ ## Troubleshooting
827
+
828
+ - **`skipped on <url>: ...`**: the header doesn't cover the page; the
829
+ extension would skip it too. `match` shows why.
830
+ - **`GM_x is not defined`**: missing `@grant GM_x`. Same in the
831
+ extension.
832
+ - **A page global is `undefined` in the script**: if it's read as
833
+ `window.x`, read it bare (`x`) or as `unsafeWindow.x`: unless the
834
+ script has `@grant none`, its `window` shows only the browser's
835
+ built-ins. Otherwise it's in the isolated world (`@inject-into
836
+ content` or `@sandbox DOM`); remove that line.
837
+ - **An early `document-start` change gets overwritten by the page
838
+ under sm-dev only**: in a tab a run tab opened, the script may run
839
+ after the page's first inline scripts (see
840
+ [How a run uses the extension](#how-a-run-uses-the-extension)).
841
+ - **`start` reports that Playwright needs a Chromium build that isn't
842
+ installed**: Playwright was updated. Run the install command the
843
+ error gives (`pnpm exec playwright install chromium` in a repo).
844
+ - **The run refuses to start, or says `no SourceMonkey` / `SourceMonkey
845
+ disabled`**: see [Getting a browser](#getting-a-browser).
846
+ - **A command these docs name is unknown**: sm-dev is older than the
847
+ docs. Ask the person whether to update it: `pnpm run build` in the
848
+ SourceMonkey checkout, then `pnpm install` in a userscript repo. A
849
+ run keeps the code it started with, so start it again after.
850
+ - **`start` says sm-dev or SourceMonkey is older than the other**:
851
+ ask the person whether to update it as the message says. Building
852
+ changes the code under other running sessions. If the run still
853
+ started, keep working meanwhile.
854
+ - **The script seems stale**: check the reload lines after the save, or
855
+ `update` (it skips files whose modification time hasn't changed; a
856
+ `touch` forces it).
857
+ - **After changing SourceMonkey itself**: `pnpm run build` there, then
858
+ `pnpm install` in the userscript repo. The dependency is a copy of
859
+ the build, taken at install time.
860
+ - **`start` hangs connecting**: a tab in that browser is showing a
861
+ dialog. The run names it; answer it or close the tab.
862
+ - **Commands refuse with a dialog open**: `accept` or `dismiss` first.
863
+ - **A `GM_xmlhttpRequest` never finishes, or the script stops after
864
+ one**: look for a `*PERMISSION PROMPT*` line; the request is waiting
865
+ for `connect` (see [Permission prompts](#permission-prompts)).
866
+ - **A wait gave up**: its message says what was still busy (requests,
867
+ DOM changes). A page with a slow API or a never-ending resource never
868
+ settles; `wait selector` for the element you need instead.