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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeff Shute
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,88 @@
1
- # Temporary Holding Version
1
+ # SourceMonkey developer tools for userscripts
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Develop, test and debug userscripts in a real browser, running in the
4
+ real [SourceMonkey](https://github.com/jshute96/SourceMonkey) extension,
5
+ using real `GM_*` APIs and other features.
6
+
7
+ Use these tools with coding agents (like Claude Code) to enable fully agentic
8
+ userscript development.
9
+
10
+ This package includes:
11
+
12
+ - **`sm-dev` command-line tool**
13
+ - Run a userscript against a live page and watch what it does.
14
+ - Or run against a saved page snapshot (for example, on pages that require login).
15
+ - Drive and inspect the page, and read userscript state and stored values.
16
+ - Run and manage scripts under Tampermonkey or Violentmonkey too.
17
+ - **A Playwright test fixture**
18
+ - Write tests that load userscripts on a page, and check for expected results.
19
+ - Test against live pages or snapshots.
20
+ - Catch changes on the live page that break your scripts.
21
+ - **Agent skills**
22
+ - Use with coding agents, so they can develop, test and debug
23
+ userscripts using `sm-dev`, against live pages or snapshots.
24
+
25
+ ## Install
26
+
27
+ Needs Node 22 or newer.
28
+
29
+ In a repo of userscripts, install it with Playwright, and the Chromium
30
+ build Playwright drives:
31
+
32
+ ```sh
33
+ pnpm add -D sourcemonkey @playwright/test
34
+ pnpm exec playwright install chromium
35
+ pnpm sm-dev --help
36
+ ```
37
+
38
+ Or run `sm-dev` without installing anything:
39
+
40
+ ```sh
41
+ npx sm-dev --help
42
+ ```
43
+
44
+ Commands that launch a browser require Chromium. They'll report the command
45
+ to install the required version, if it's not already installed.
46
+
47
+ ## Run a script
48
+
49
+ ```sh
50
+ pnpm sm-dev start path/script.user.js [http://example.com]
51
+ ```
52
+
53
+ This launches a browser with SourceMonkey loaded, loads the script,
54
+ and opens the given URL (or the script's `@match` target page, by default).
55
+ This streams the page's console, and lets you interact with the
56
+ page and the loaded userscripts.
57
+
58
+ At the prompt:
59
+
60
+ ```
61
+ > status # Browser info, loaded scripts, and tab status
62
+ > snapshot --visible # What's on screen, with element refs (e12)
63
+ > click e12 # Click one, and wait for the page to settle
64
+ > menu # The script's menu commands
65
+ > values # What the script stored
66
+ > help # See other commands
67
+ ```
68
+
69
+ ## Write a test
70
+
71
+ ```js
72
+ import { test, expect } from 'sourcemonkey/harness';
73
+
74
+ test('adds the button to the toolbar', async ({ page, loadUserscript, gm, reports }) => {
75
+ await loadUserscript('sites/example.com/add-button.user.js');
76
+ await page.goto('https://example.com/items/42');
77
+ await reports.waitForStart();
78
+ await expect(page.locator('#add-button')).toBeVisible();
79
+ expect(await gm.get('lastItem')).toBe('42');
80
+ });
81
+ ```
82
+
83
+ ## More
84
+
85
+ - [The SourceMonkey extension](https://github.com/jshute96/SourceMonkey):
86
+ the default userscript manager these tools use.
87
+ - [Harness guide](https://github.com/jshute96/SourceMonkey/blob/main/docs/harness-guide.md):
88
+ details on setup, browsers, all `sm-dev` commands, and the test fixture.
package/dist/bytes.js ADDED
@@ -0,0 +1,37 @@
1
+ // Small byte / crypto helpers shared across modules so the SHA-256 and
2
+ // binary→base64 logic lives in one place (script content hashes in
3
+ // script-store, @require/@resource byte hashes + transport in
4
+ // external-files, GM_xmlhttpRequest response transport in gm-xhr).
5
+ // SHA-256 → lowercase hex. Accepts a string (UTF-8 encoded first) or
6
+ // bytes. Async because SubtleCrypto has no sync form on the web
7
+ // platform.
8
+ export async function sha256Hex(data) {
9
+ const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
10
+ const digest = await crypto.subtle.digest('SHA-256', bytes);
11
+ let out = '';
12
+ for (const b of new Uint8Array(digest))
13
+ out += b.toString(16).padStart(2, '0');
14
+ return out;
15
+ }
16
+ // base64→binary. The inverse of `bytesToBase64`, used on the receiving
17
+ // end of message hops that can't carry an ArrayBuffer, and to rebuild a
18
+ // stored `@resource` body when a conditional GET answers 304 and the
19
+ // server sends no bytes of its own.
20
+ export function base64ToBytes(base64) {
21
+ const binary = atob(base64);
22
+ const bytes = new Uint8Array(binary.length);
23
+ for (let i = 0; i < binary.length; i++)
24
+ bytes[i] = binary.charCodeAt(i);
25
+ return bytes.buffer;
26
+ }
27
+ // Chunked binary→base64. `String.fromCharCode(...hugeArray)` throws
28
+ // "Maximum call stack size exceeded" past ~125k args in V8, so we walk
29
+ // the buffer in 32 KiB slices and concatenate. Linear and bounded.
30
+ export function bytesToBase64(bytes) {
31
+ let binary = '';
32
+ const CHUNK = 0x8000;
33
+ for (let i = 0; i < bytes.length; i += CHUNK) {
34
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
35
+ }
36
+ return btoa(binary);
37
+ }
@@ -0,0 +1,5 @@
1
+ // The stand-in for the channel key in the static wrapper files
2
+ // (src/extension/channel.ts swaps the real key in when it registers
3
+ // them). Its own module so the harness, which loads a wrapper under
4
+ // Node, can call the registry gate with it.
5
+ export const CHANNEL_KEY_PLACEHOLDER = '__SM_KEY__';
@@ -0,0 +1,17 @@
1
+ // Shared scan primitives used by every collection-kind scanner.
2
+ //
3
+ // Lives in its own module because both `directories.ts` (local
4
+ // directory / manifest scanner) and `github-provider.ts` (Contents-API
5
+ // scanner) need them — importing them directly between the two
6
+ // scanner modules would create a runtime ES-module cycle, since
7
+ // `directories.ts` also imports the github dispatcher from
8
+ // `github-provider.ts`.
9
+ // How many subdirectory levels below the root a scanner walks before
10
+ // stopping. Applies to local directory collections AND GitHub directory
11
+ // collections (the cost model is different but the cap keeps both
12
+ // bounded).
13
+ export const MAX_SCAN_DEPTH = 2;
14
+ // The one and only filename SourceMonkey recognises as a userscript
15
+ // manifest. Lives here (not in `directories.ts`) so `github-provider.ts`
16
+ // can import it without forming a runtime cycle with `directories.ts`.
17
+ export const MANIFEST_FILENAME = 'script_manifest.json';
@@ -0,0 +1,353 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>SourceMonkey | Collections</title>
6
+ <link rel="stylesheet" href="styles.css" />
7
+ <style>
8
+ /* Full-bleed body; the header bar and `.page-body` (styles.css)
9
+ each centre their own contents on the shared `--page-width`
10
+ column. */
11
+ body { font: 14px system-ui, sans-serif; margin: 0; color: #222; --page-width: 720px; }
12
+ /* Grid layout so a long display name that needs to wrap stays
13
+ inside the right-hand column instead of dropping back to the
14
+ row's left edge. Column 1 sizes to the bubble; column 2 takes
15
+ the remaining space and is where the name wraps. */
16
+ h2 {
17
+ font-size: 1em;
18
+ margin: 0;
19
+ word-break: break-all;
20
+ display: grid;
21
+ grid-template-columns: max-content 1fr;
22
+ column-gap: 0.5em;
23
+ align-items: baseline;
24
+ }
25
+ /* `.collection-type-bubble` + colored `.bubble-*` variants +
26
+ `.collection-display-name` typography are in styles.css (the
27
+ cross-page shared stylesheet). */
28
+ /* Height, surface, hover and press are shared across the two
29
+ pages (styles.css); this is just this page's roomier
30
+ horizontal padding on text buttons. */
31
+ button { font: inherit; padding: 0 11px; cursor: pointer; }
32
+ input[type="text"] { font: inherit; padding: 4px 7px; }
33
+ .placeholder { color: #666; }
34
+ .add-form { display: flex; gap: 0.5em; margin-bottom: 1em; align-items: center; }
35
+ .add-form input[type="text"] { flex: 1 1 auto; }
36
+ .collection { border: 1px solid #ddd; border-radius: 6px; padding: 11px 14px; margin-bottom: 11px; }
37
+ .collection-header { display: flex; justify-content: flex-start; align-items: center; gap: 0.6em; margin-bottom: 7px; }
38
+ .collection-header h2 { flex: 1 1 auto; min-width: 0; }
39
+ .collection-toggle { flex: 0 0 auto; }
40
+ .collection-buttons { display: flex; gap: 0.4em; flex: 0 0 auto; margin-left: auto; align-items: center; }
41
+ /* The ⋯ menus (src/extension/more-menu.ts): square, like the
42
+ glyph buttons beside them. */
43
+ button.more-menu { width: var(--icon-btn-size); padding: 0; font-size: 16px; }
44
+ button.more-menu[aria-expanded="true"] { background: var(--btn-bg-hover); }
45
+ /* Up/Down arrow buttons. They wear `.icon-btn` like the rest of
46
+ the header row, so the box and the disabled state come from
47
+ styles.css; the class is what the reorder code and the tests
48
+ address them by. */
49
+ /* Whole-px vertical metrics on this page, and `separate` borders rather
50
+ than `collapse` (a collapsed border is split half into each adjacent
51
+ cell, landing rows on half pixels) — see docs/architecture.md,
52
+ "Extension-page styling". */
53
+ /* A can inside a script row is a row control, not a control in a
54
+ button row: at the shared 26px it makes its row 7px taller than
55
+ the rows around it, and only SOME rows have one (a `missing`
56
+ row, a local copy), so the table comes out ragged. Shrinking
57
+ the pair here puts it back inside the row's own line box —
58
+ this is the escape hatch styles.css describes. */
59
+ .script-table { --icon-btn-size: 18px; --icon-btn-glyph: 14px; }
60
+ .script-table { width: 100%; border-collapse: separate; border-spacing: 0; }
61
+ .script-table th, .script-table td { text-align: left; padding: 4px 8px; border-bottom: 1px solid #eee; vertical-align: top; }
62
+ .script-table th { font-weight: 600; color: #555; font-size: 0.9em; line-height: 16px; }
63
+ .script-table tbody tr:last-child td { border-bottom-color: transparent; }
64
+ .script-table td.size-cell, .script-table td.status-cell, .script-table td.toggle-cell, .script-table td.action-cell, .script-table td.scheme-cell { white-space: nowrap; width: 1%; }
65
+ /* Container-collection action column: the per-row remove button.
66
+ Right-aligned so the trash icon hugs the table edge regardless
67
+ of how narrow / wide the name column wants to be. */
68
+ .script-table td.action-cell { text-align: right; padding-left: 3px; }
69
+ .script-table th:last-child { width: 1%; }
70
+ .script-table td.toggle-cell { padding-right: 3px; }
71
+ /* When a collection is off, its whole section dims; when a script is off, just the row dims.
72
+ The toggle itself stays at full opacity so users can flip it back. */
73
+ .collection.disabled .collection-header h2,
74
+ .script-table tr.disabled td:not(.toggle-cell) { opacity: 0.45; }
75
+ .toggle { display: inline-flex; align-items: center; cursor: pointer; position: relative; }
76
+ .toggle input[type="checkbox"] { cursor: pointer; margin: 0; }
77
+ .toggle-disabled { cursor: not-allowed; opacity: 0.5; }
78
+ .toggle-disabled input[type="checkbox"] { cursor: not-allowed; }
79
+ /* Bulk-toggle button in the script-table header. Sized like the
80
+ per-row checkbox so the column lines up. */
81
+ /* vertical-align keeps the button's place in the header line box
82
+ independent of its text: baseline-aligned, an empty inline-flex
83
+ button sits a fraction lower and resizes the header row, which
84
+ shifts every collection below. */
85
+ .bulk-toggle { vertical-align: middle; width: 14px; height: 14px; padding: 0; font: inherit; font-weight: 700; line-height: 1; border: 1px solid #888; border-radius: 2px; background: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
86
+ .bulk-toggle:hover:not(:disabled) { background: #f0f0f0; }
87
+ .bulk-toggle:disabled { cursor: not-allowed; opacity: 0.5; }
88
+ .script-table th.toggle-cell { white-space: nowrap; width: 1%; padding-right: 3px; }
89
+ /* Column of site favicons to the left of the name link. One
90
+ 16px icon per distinct site the script targets (see
91
+ `MAX_SITE_ICONS`), side by side with a 2px gap. Each icon is
92
+ a clickable <a class="site-icon-inner"> when its @match yields
93
+ a usable origin; a script with no site holds one inert
94
+ <span class="site-icon-inner">. Every inner starts as a globe
95
+ placeholder that is swapped for the real favicon on load.
96
+ The slot is as wide as this table's widest row needs
97
+ (`--icon-cols`, set per table by collections.ts), so the
98
+ names in one collection share a straight left edge. */
99
+ .script-table { --icon-cols: 1; --icon-gap: 2px; }
100
+ .site-icon {
101
+ display: inline-flex;
102
+ gap: var(--icon-gap);
103
+ width: calc(var(--icon-cols) * 16px + (var(--icon-cols) - 1) * var(--icon-gap));
104
+ height: 16px;
105
+ margin-right: 0.4em;
106
+ vertical-align: text-bottom;
107
+ flex: 0 0 auto;
108
+ /* text-indent inherits, and this is a block container: reset it
109
+ so the name cell's negative hanging indent can't shift the
110
+ icon if a future inner is ever inline rather than
111
+ display:block. */
112
+ text-indent: 0;
113
+ }
114
+ /* The icons inside the slot (`.site-icon-inner`, the globe, the
115
+ <img>) are styled once for every page in styles.css. */
116
+ /* Script names come from the file/URL and can be arbitrarily long
117
+ (web-installed scripts often carry a whole marketing title).
118
+ Let them wrap rather than force the table wider than its
119
+ `.collection` card, which pushed Size / Status outside the box.
120
+ `<wbr>` markers after each `/` (appendWithSlashBreaks) give
121
+ preferred break points before a mid-word break is needed.
122
+ The hanging indent keeps continuation lines aligned with the
123
+ first line's text instead of tucking under the favicon. */
124
+ .script-table td.name-cell {
125
+ overflow-wrap: anywhere;
126
+ word-break: normal;
127
+ /* `--icon-w` is the slot's width incl. its right margin; the
128
+ hanging indent must track the icon column so wrapped lines
129
+ still align under the first line's text. */
130
+ --icon-w: calc(var(--icon-cols) * 16px + (var(--icon-cols) - 1) * var(--icon-gap) + 0.4em);
131
+ padding-left: calc(8px + var(--icon-w));
132
+ text-indent: calc(-1 * var(--icon-w));
133
+ }
134
+ .status-ok { color: #166534; }
135
+ .status-invalid, .status-rejected, .status-error { color: #b00; }
136
+ .status-warning, .status-outdated { color: #b67200; }
137
+ .status-refresh-failed { color: #c2410c; }
138
+ /* The Status cell content is a link to that script's Log tab.
139
+ Keep the link looking like status text (not blue underlined). */
140
+ .status-cell a { text-decoration: none; color: inherit; cursor: pointer; }
141
+ .status-cell a:hover { text-decoration: underline; }
142
+ .empty { color: #666; font-style: italic; }
143
+ .error { color: #b00; }
144
+ /* A collection's failure line: a hint can start a line of its own,
145
+ and the address it couldn't read is a link. */
146
+ p.error[data-message-kind] { white-space: pre-line; }
147
+ p.error[data-message-kind] a { color: inherit; }
148
+ /* The banner box itself is in styles.css (shared with the
149
+ viewer). These are the collections-page-only parts: a roomier
150
+ gap before the collection list, and the raw-API-error line in the
151
+ "Allow User Scripts" banner. */
152
+ .banner { margin-bottom: 1em; }
153
+ .banner.banner-error .banner-detail { color: #b71c1c; }
154
+ .banner .banner-detail { color: #92400e; font-size: 0.85em; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; margin-top: 0.5em; }
155
+ /* Inline busy spinner for individual controls (toggles, add form).
156
+ `@keyframes sm-spin` is in styles.css, shared with the dialog
157
+ spinner and the glyph buttons' spin state. */
158
+ .busy-spinner { display: inline-block; width: 12px; height: 12px; margin-left: 6px; border: 2px solid #ccc; border-top-color: #555; border-radius: 50%; animation: sm-spin 0.7s linear infinite; vertical-align: middle; box-sizing: border-box; }
159
+ /* Bulk-toggle button shows its spinner in place of the +/- glyph. */
160
+ .bulk-toggle.busy { position: relative; color: transparent; }
161
+ .bulk-toggle.busy::after { content: ''; position: absolute; left: 50%; top: 50%; margin: -5px 0 0 -5px; width: 10px; height: 10px; border: 2px solid #ccc; border-top-color: #555; border-radius: 50%; animation: sm-spin 0.7s linear infinite; box-sizing: border-box; }
162
+ /* A toggle's spinner is overlaid on its own checkbox rather than
163
+ appended beside it. The script table's toggle cell sizes to its
164
+ content, so an in-flow spinner widened that column and shifted
165
+ every row in the collection a few pixels right (and back) for as
166
+ long as the write was in flight. */
167
+ .toggle .busy-spinner { position: absolute; inset: 0; width: auto; height: auto; margin: 0; }
168
+ .toggle:has(.busy-spinner) input[type="checkbox"] { opacity: 0.3; }
169
+ /* While a collection is being removed, dim the whole section and block further clicks. */
170
+ .collection.removing { opacity: 0.5; pointer-events: none; }
171
+ /* Highlight rows that changed during a refresh. The color
172
+ persists until the next refresh clears it, so the
173
+ user can leave the page and come back without losing the
174
+ what-changed signal.
175
+
176
+ Two independent signals: `row-added` / `row-updated` say what
177
+ happened, `row-failed` says how it came out. `row-failed` is
178
+ listed after both so it wins the wash — a script that arrived
179
+ broken is red, and keeps its "New" chip to say it is also new.
180
+ (`row-removed` sits last and never shares a row with any of
181
+ them.) */
182
+ .script-table tr.row-added td { background: #cdebcf; }
183
+ .script-table tr.row-updated td { background: #d7e7f7; }
184
+ .script-table tr.row-failed td { background: #f5c2c7; }
185
+ .script-table tr.row-removed td { background: #f0f0ef; }
186
+ /* The accent bar rides on the row's first cell as an inset
187
+ shadow, so it costs no column width and can't shift the table. */
188
+ .script-table tr.row-added td:first-child { box-shadow: inset 3px 0 0 #166534; }
189
+ .script-table tr.row-updated td:first-child { box-shadow: inset 3px 0 0 #2b6cb0; }
190
+ .script-table tr.row-failed td:first-child { box-shadow: inset 3px 0 0 #b00; }
191
+ .script-table tr.row-removed td:first-child { box-shadow: inset 3px 0 0 #9ca3af; }
192
+ /* What a refresh did to the row, said in words as well as colour.
193
+ New / Updated are pseudo-elements rather than real nodes: their
194
+ Status cell is rebuilt from stored state on every repaint, which
195
+ would discard a chip element, while the class lives on the `<tr>`
196
+ the repaint preserves. A tombstone's `Removed` chip IS a real
197
+ element (`.change-chip`) — nothing repaints that row, and there
198
+ the chip is the whole status, so it has to be selectable and
199
+ readable by a screen reader. One rule for the three, so they
200
+ can't drift apart. */
201
+ .script-table tr.row-added td.status-cell::after,
202
+ .script-table tr.row-updated td.status-cell::after,
203
+ .script-table .change-chip {
204
+ display: inline-block;
205
+ margin-left: 6px;
206
+ padding: 0 6px;
207
+ border-radius: 8px;
208
+ font-size: 10px;
209
+ font-weight: 700;
210
+ text-transform: uppercase;
211
+ letter-spacing: 0.03em;
212
+ color: #fff;
213
+ vertical-align: 1px;
214
+ }
215
+ .script-table tr.row-added td.status-cell::after { content: 'New'; background: #166534; }
216
+ .script-table tr.row-updated td.status-cell::after { content: 'Updated'; background: #2b6cb0; }
217
+ /* A dropped row has no status of its own, so its chip stands
218
+ alone in the cell rather than beside a status word. */
219
+ .script-table .chip-removed { background: #9ca3af; margin-left: 0; }
220
+ /* Tombstone: the ghost of a script this refresh dropped. Struck
221
+ through and greyed, with nothing on it to click — the checkbox
222
+ is disabled and the favicon is de-linked in `renderTombstoneRow`.
223
+ Lives in the DOM only: the next refresh clears it and a reload
224
+ never rebuilds it. */
225
+ .script-table tr.row-removed td { color: #6b7280; }
226
+ /* The strike is drawn on the name and size themselves, not on the
227
+ cells: a decoration inherited by a cell is painted straight
228
+ across the favicon (an atomic inline the line can't skip), and
229
+ the chip and checkbox have nothing to strike through. */
230
+ .script-table tr.row-removed .tombstone-name,
231
+ .script-table tr.row-removed td.size-cell { text-decoration: line-through; }
232
+ /* Sizes line up on their units. */
233
+ .script-table .size-cell { text-align: right; font-variant-numeric: tabular-nums; }
234
+ /* The favicon stays — it's what the eye finds the row by — but
235
+ muted, so a dead row never draws attention ahead of a live one. */
236
+ .script-table tr.row-removed .site-icon { filter: grayscale(1); opacity: 0.55; }
237
+ .script-table tr.row-removed td.toggle-cell input { opacity: 0.5; }
238
+ /* The live rows' toggle box without the `.toggle` class the bulk
239
+ census counts — see `renderTombstoneRow`. Same box AND the same
240
+ zeroed checkbox margin: the UA's default margin on an
241
+ `<input type="checkbox">` is what `.toggle` above cancels, and
242
+ without cancelling it here too the ghost's box sits a few pixels
243
+ down and right of every other row's. */
244
+ .toggle-ghost { display: inline-flex; align-items: center; }
245
+ .toggle-ghost input[type="checkbox"] { margin: 0; }
246
+ .script-table tr.row-removed .scheme-chip { opacity: 0.55; }
247
+ </style>
248
+ </head>
249
+ <body>
250
+ <!-- Shared glyph sprite: the one copy of every inline SVG glyph in the
251
+ extension. scripts/build.mjs inlines it into each page wherever the
252
+ page writes an `#include glyph-sprite` marker comment. (Spelling
253
+ that marker out here would be nice, but its own closing `--` plus
254
+ `>` would end THIS comment and spill the rest onto the page.)
255
+
256
+ One source of truth: the markup used to be copy-pasted into each
257
+ page's HTML, which is how two pages end up with glyphs that have
258
+ quietly drifted apart.
259
+
260
+ Each symbol is defined once per page and referenced by a `use`
261
+ element, so a hundred rows share one copy of the path data instead
262
+ of inlining it per row.
263
+
264
+ #sm-globe favicon placeholder until a site icon loads, and
265
+ the permanent icon for a script with no resolvable
266
+ origin (src/extension/site-icon.ts; styling on
267
+ `.site-icon-globe`).
268
+ #sm-trash remove SourceMonkey's record of a script.
269
+ #sm-trash-file delete the script FILE from disk — the same can,
270
+ badged with a disk. Both painted by
271
+ src/extension/delete-icon.ts, which explains the pair.
272
+ The can is drawn ~19% smaller than #sm-trash — its
273
+ own inner scale(.9) inside the normalizing
274
+ wrapper's scale(.9) — so the badge has a corner to
275
+ sit in without swallowing the can. Deliberate: the two appear in the same
276
+ column in the "Other versions" panel, and the
277
+ badged one has to stay a can at 15px.
278
+ #sm-copy the viewer's "copy this to the clipboard" button,
279
+ #sm-check and the checkmark it flashes on success
280
+ (src/extension/copy-button.ts).
281
+ #sm-refresh refetch a collection, a script, or one row of the
282
+ "Other versions" table. Its busy state is this
283
+ same arc spinning (`.btn-glyph.spinning`), so the
284
+ button never changes shape mid-refresh.
285
+ #sm-gear open a collection's rename / options dialog.
286
+ #sm-open open a location in a new tab (the collection
287
+ dialog's button beside its location box).
288
+ #sm-swap the compare page's "swap the two sides" button:
289
+ two arrows, one each way, the same width so their
290
+ ends line up.
291
+
292
+ Colour comes from `currentColor`, so a button's own `color` (and
293
+ its hover) drives the glyph.
294
+
295
+ OPTICAL SIZING — read before editing a path or a stroke width.
296
+ Identical 16px boxes do not make icons look the same size; the ink
297
+ inside them has to match too. As drawn, these ranged from 18 to
298
+ 23.3 units of 24, and the gear read ~22% larger than the arrow. So
299
+ a glyph that needed resizing is wrapped in
300
+ <g transform="translate(12 12) scale(k) translate(-12 -12)">
301
+ and its `stroke-width` is DIVIDED by the same k. Consequences:
302
+ - The number in the file is not the number that renders. Every
303
+ glyph here strokes at 1.75 units once its wrapper is applied
304
+ (1.9/... , 1.95/.9, 1.667*1.05, 1.825*.959). Change a stroke
305
+ width and you break that unless you change k with it.
306
+ - #sm-refresh translates by -13, not -12: Feather's rotate-cw is
307
+ drawn a unit right of centre, and this puts it back. It is not
308
+ a typo.
309
+ - #sm-copy and #sm-check are deliberately outside this: they
310
+ stroke at 2.0 in the 14px copy button. That button sits beside
311
+ the 1.75 set only in the collection dialog, next to #sm-open,
312
+ where the smaller box evens out the heavier stroke. -->
313
+ <svg width="0" height="0" style="position:absolute" aria-hidden="true">
314
+ <symbol id="sm-globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><path d="M2 12h20"></path><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></symbol>
315
+ <symbol id="sm-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 6h17"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"></path><path d="M10 10.5v7"></path><path d="M14 10.5v7"></path></symbol>
316
+ <symbol id="sm-trash-file" viewBox="0 0 24 24"><g transform="translate(12 12) scale(.9) translate(-12 -12)"><mask id="sm-trash-file-cut" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24"><rect width="24" height="24" fill="#fff"></rect><circle cx="19.6" cy="19.6" r="5.2" fill="#000"></circle></mask><g mask="url(#sm-trash-file-cut)" transform="translate(-.7 -1.5) scale(.9)" fill="none" stroke="currentColor" stroke-width="2.167" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 6h17"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"></path><path d="M10 10.5v7"></path><path d="M14 10.5v7"></path></g><g transform="translate(14.53 14.36) scale(.422)" fill="none" stroke="currentColor" stroke-width="4.611" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"></path></g></g></symbol>
317
+ <symbol id="sm-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></symbol>
318
+ <symbol id="sm-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></symbol>
319
+ <symbol id="sm-refresh" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.667" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(12 12) scale(1.05) translate(-13 -12)"><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path><polyline points="23 4 23 10 17 10"></polyline></g></symbol>
320
+ <symbol id="sm-swap" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5h18"></path><path d="M16.5 3l4.5 4.5-4.5 4.5"></path><path d="M21 16.5H3"></path><path d="M7.5 12L3 16.5 7.5 21"></path></symbol>
321
+ <symbol id="sm-gear" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.825" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(12 12) scale(.959) translate(-12 -12)"><circle cx="12" cy="12" r="3.1"></circle><path d="M19.4 14.6a1.6 1.6 0 0 0 .32 1.77l.06.06a1.95 1.95 0 1 1-2.76 2.76l-.06-.06a1.6 1.6 0 0 0-1.77-.32 1.6 1.6 0 0 0-.97 1.47V21a1.95 1.95 0 1 1-3.9 0v-.1a1.6 1.6 0 0 0-1.05-1.47 1.6 1.6 0 0 0-1.77.32l-.06.06a1.95 1.95 0 1 1-2.76-2.76l.06-.06a1.6 1.6 0 0 0 .32-1.77 1.6 1.6 0 0 0-1.47-.97H3a1.95 1.95 0 1 1 0-3.9h.1a1.6 1.6 0 0 0 1.47-1.05 1.6 1.6 0 0 0-.32-1.77l-.06-.06a1.95 1.95 0 1 1 2.76-2.76l.06.06a1.6 1.6 0 0 0 1.77.32H9a1.6 1.6 0 0 0 .97-1.47V3a1.95 1.95 0 1 1 3.9 0v.1a1.6 1.6 0 0 0 .97 1.47 1.6 1.6 0 0 0 1.77-.32l.06-.06a1.95 1.95 0 1 1 2.76 2.76l-.06.06a1.6 1.6 0 0 0-.32 1.77V9a1.6 1.6 0 0 0 1.47.97H21a1.95 1.95 0 1 1 0 3.9h-.1a1.6 1.6 0 0 0-1.47.97z"></path></g></symbol>
322
+ <symbol id="sm-open" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><path d="M10 14L21 3"></path></symbol>
323
+ </svg>
324
+ <header class="page-header">
325
+ <div class="page-header-inner">
326
+ <img class="page-icon" src="icons/icon-48.png" alt="" />
327
+ <h1><span class="brand">SourceMonkey |</span> Collections</h1>
328
+ <div class="page-header-actions">
329
+ <button id="open-options" type="button" title="Open the options page">Options</button>
330
+ <!-- The label ships in the markup so the button is never blank:
331
+ collections.js is a deferred module, and `setGlyphButton`
332
+ (src/extension/collections.ts) replaces this text with the same word
333
+ beside a glyph once it runs. -->
334
+ <button id="refresh-all" type="button">Refresh All</button>
335
+ </div>
336
+ </div>
337
+ </header>
338
+
339
+ <div class="page-body">
340
+ <div id="banner-slot"></div>
341
+
342
+ <form id="add-form" class="add-form">
343
+ <input id="add-path" type="text" placeholder="A .user.js file, a directory, a script_manifest.json, or a name for a new collection" autocomplete="off" spellcheck="false" />
344
+ <button type="submit">Add</button>
345
+ </form>
346
+ <p id="add-error" class="error" hidden></p>
347
+
348
+ <div id="collections"></div>
349
+ </div>
350
+
351
+ <script src="extension/collections.js" type="module"></script>
352
+ </body>
353
+ </html>