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,140 @@
1
+ // A run's log file: its stream, each line timestamped, to tail or read
2
+ // back for what happened and when. The run's process writes its output
3
+ // there. Each file is named by its start time and kept after the run
4
+ // ends.
5
+ //
6
+ // Node-only, like runs.ts.
7
+ import { randomBytes } from 'node:crypto';
8
+ import { mkdir, open, rename, rm, symlink } from 'node:fs/promises';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { StringDecoder } from 'node:string_decoder';
12
+ import { checkPrivateDir, owner } from './runs.js';
13
+ /**
14
+ * Where the logs go: `/tmp/sm-dev-logs-<user>` on most machines.
15
+ * `SM_DEV_LOGS_DIR` moves it, for tests.
16
+ */
17
+ export function runLogsDir() {
18
+ return process.env.SM_DEV_LOGS_DIR || path.join(os.tmpdir(), `sm-dev-logs-${owner()}`);
19
+ }
20
+ /**
21
+ * Where a temp browser run's downloads go: in the logs folder, named
22
+ * after the run's log when it has one, and kept after the run ends as
23
+ * the log is (the browser's own profile is deleted).
24
+ */
25
+ export function runDownloadsDir(log) {
26
+ if (!log)
27
+ return path.join(runLogsDir(), `sm-dev-${logTimestamp()}-downloads`);
28
+ const { dir, name } = path.parse(log);
29
+ return path.join(dir, `${name}-downloads`);
30
+ }
31
+ /**
32
+ * Local time as `20260408-203012-345`: SeeWhatISee's file-name
33
+ * timestamp. Names the log files, and starts each line in them.
34
+ */
35
+ export function logTimestamp(ms = Date.now()) {
36
+ const d = new Date(ms);
37
+ const pad = (n, width = 2) => String(n).padStart(width, '0');
38
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}`
39
+ + `-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`
40
+ + `-${pad(d.getMilliseconds(), 3)}`;
41
+ }
42
+ /** A log line's timestamp and the space after it, for taking back off. */
43
+ export const LOG_LINE_PREFIX = /^\d{8}-\d{6}-\d{3} /;
44
+ /**
45
+ * Creates a new log file, `sm-dev-<timestamp>.log`. A name already
46
+ * taken moves the timestamp on a millisecond and tries again; created
47
+ * exclusively, so two runs starting together can't share one.
48
+ */
49
+ export async function createRunLog() {
50
+ const dir = runLogsDir();
51
+ try {
52
+ await mkdir(dir, { recursive: true, mode: 0o700 });
53
+ }
54
+ catch (err) {
55
+ throw new Error(`Could not create the log directory ${dir}: ${err.message}`);
56
+ }
57
+ // In a shared temp directory, another user could have made it first,
58
+ // and then delete or swap the run links.
59
+ await checkPrivateDir(dir, 'log');
60
+ for (let ms = Date.now();; ms++) {
61
+ const file = path.join(dir, `sm-dev-${logTimestamp(ms)}.log`);
62
+ try {
63
+ return { file, handle: await open(file, 'wx', 0o600) };
64
+ }
65
+ catch (err) {
66
+ if (err.code !== 'EEXIST') {
67
+ throw new Error(`Could not create the log file ${file}: ${err.message}`);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * Starts each line this process writes to stdout and stderr with the
74
+ * time it was written. For the detached run, whose output is its log
75
+ * file. Output that bypasses the streams (a crash in Node itself)
76
+ * still lands in the file, unstamped.
77
+ */
78
+ export function timestampOutputLines() {
79
+ // Shared, since both streams go to the one file.
80
+ let atLineStart = true;
81
+ for (const stream of [process.stdout, process.stderr]) {
82
+ const write = stream.write.bind(stream);
83
+ // So a character split across two byte writes survives.
84
+ const decoder = new StringDecoder('utf8');
85
+ stream.write = ((chunk, ...rest) => {
86
+ const text = typeof chunk === 'string' ? chunk : decoder.write(Buffer.from(chunk));
87
+ const stamp = logTimestamp();
88
+ let out = '';
89
+ for (const [i, part] of text.split('\n').entries()) {
90
+ if (i > 0) {
91
+ out += '\n';
92
+ atLineStart = true;
93
+ }
94
+ if (part === '')
95
+ continue;
96
+ if (atLineStart)
97
+ out += `${stamp} `;
98
+ atLineStart = false;
99
+ out += part;
100
+ }
101
+ // A string, whatever came in, so an encoding argument no longer applies.
102
+ const callback = rest.find((r) => typeof r === 'function');
103
+ return callback ? write(out, callback) : write(out);
104
+ });
105
+ }
106
+ }
107
+ /** The link to a run's latest log, beside it: `D1.log`. */
108
+ function runLogLinkName(id) {
109
+ return `${id}.log`;
110
+ }
111
+ /**
112
+ * A log's full path, and its link's name when it has one, for printing:
113
+ * `/tmp/sm-dev-logs-you/sm-dev-20260926-063756-629.log (D1.log)`.
114
+ */
115
+ export function runLogLabel(file, id) {
116
+ return id && process.platform !== 'win32' ? `${file} (${runLogLinkName(id)})` : file;
117
+ }
118
+ /**
119
+ * Points `<run id>.log`, beside the log, at it: the latest log of
120
+ * a run with that id, found by the id alone. Swapped in by a rename, so
121
+ * the link is never missing or half made. A failure only costs the
122
+ * link, so it is said and the run goes on.
123
+ */
124
+ export async function linkRunLog(file, id, out) {
125
+ // A file symlink there takes admin rights or Developer Mode, so most
126
+ // runs would only say it failed.
127
+ if (process.platform === 'win32')
128
+ return;
129
+ const link = path.join(path.dirname(file), runLogLinkName(id));
130
+ const temp = `${link}.${randomBytes(4).toString('hex')}.tmp`;
131
+ try {
132
+ // Relative, so the pair still works if the directory is moved.
133
+ await symlink(path.basename(file), temp);
134
+ await rename(temp, link);
135
+ }
136
+ catch (err) {
137
+ await rm(temp, { force: true }).catch(() => undefined);
138
+ out(`[sm-dev] Could not link ${link} to the log: ${err.message}`);
139
+ }
140
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Where a line came from, from narrowest to broadest:
3
+ * - `sm`: SourceMonkey logs: the run's scripts' Log tab entries.
4
+ * - `event`: script and tab events sm-dev saw: starts, page loads,
5
+ * errors, menu commands, dialog boxes, tabs opened.
6
+ * - `script`: Chrome console logs that came from the run's scripts.
7
+ * - `page`: full Chrome console logs, the scripts' included.
8
+ */
9
+ export type LogSource = 'sm' | 'event' | 'script' | 'page';
10
+ export declare const LOG_SOURCES: readonly LogSource[];
11
+ /** What the stream and `logs` show unless told otherwise: all but `page`. */
12
+ export declare const DEFAULT_LOG_SOURCES: ReadonlySet<LogSource>;
13
+ export declare const ALL_LOG_SOURCES: ReadonlySet<LogSource>;
14
+ /** Whether a line belongs to any of `sources`. */
15
+ export declare function inSources(record: LogFields, sources: ReadonlySet<LogSource>): boolean;
16
+ /** A `--log-source` value: a source, several separated by commas, or `all`. */
17
+ export declare function parseLogSources(text: string): ReadonlySet<LogSource> | {
18
+ error: string;
19
+ };
20
+ /** A line as it's logged, before it has an ID. */
21
+ export interface LogFields {
22
+ /** The source its prefix names. */
23
+ source: LogSource;
24
+ /**
25
+ * Other sources it belongs to, so asking for any one of them shows it:
26
+ * a script's throw is an event, a Log entry, and the script's own.
27
+ */
28
+ also?: LogSource[];
29
+ /** The console type (`log`, `warning`, ...), the Log entry's level, or the event's name. */
30
+ kind: string;
31
+ /** The tab it came from (`T<n>`'s n); none for a Log entry made outside a tab. */
32
+ tab?: number;
33
+ /** The tab's iframe it came from (`F<n>`'s n), when known. */
34
+ frame?: number;
35
+ /** The run's script it's about (`PreparedScript.id`), when known. */
36
+ scriptId?: string;
37
+ /** The line after its prefix. Can run over several lines (a stack). */
38
+ text: string;
39
+ /** False for a line only `logs` and `wait log` see: a page load, which the stream says its own way. */
40
+ streamed?: boolean;
41
+ /** Streamed whatever sources were asked for: a dialog box, which holds up the page until answered. */
42
+ always?: boolean;
43
+ }
44
+ /** A logged line. */
45
+ export interface LogRecord extends LogFields {
46
+ /** Its log ID: when it arrived, in microseconds since the epoch, unique and increasing. */
47
+ id: number;
48
+ }
49
+ /**
50
+ * Now, in microseconds since the epoch. By the wall clock, which
51
+ * `--since` times are read by; `performance`'s drifts from it after a
52
+ * sleep. RunLog keeps IDs increasing if the clock steps back.
53
+ */
54
+ export declare function nowMicros(): number;
55
+ /** A run's log lines, for `logs` and `wait log`, and a way to hear new ones. */
56
+ export declare class RunLog {
57
+ private readonly records;
58
+ private lastId;
59
+ private droppedUpTo;
60
+ private readonly watchers;
61
+ /** Logs a line: its ID given, its text cut past a length. */
62
+ add(fields: LogFields): LogRecord;
63
+ /** The lines kept, oldest first. */
64
+ all(): readonly LogRecord[];
65
+ /** The lines after `id`, oldest first. */
66
+ since(id: number): LogRecord[];
67
+ /**
68
+ * The position reached: the newest line's ID, or now when there's
69
+ * none yet. Any line after it gets a higher ID.
70
+ */
71
+ position(): number;
72
+ /** When lines after `id` were dropped for space: the oldest kept line's ID. */
73
+ lostAfter(id: number): number | undefined;
74
+ /** Calls `listener` with each new line, until the returned function is called. */
75
+ watch(listener: (record: LogRecord) => void): () => void;
76
+ }
77
+ /**
78
+ * A `--since` value as a log ID: a log ID itself, an ISO 8601 time
79
+ * (local without a zone), or a time of day today, local.
80
+ */
81
+ export declare function parsePosition(text: string, now?: Date): number | {
82
+ error: string;
83
+ };
84
+ /** A `--tail` value: a number of lines, or a stretch of time (`30s`, `5m`). */
85
+ export declare function parseTail(text: string): {
86
+ lines: number;
87
+ } | {
88
+ micros: number;
89
+ } | {
90
+ error: string;
91
+ };
92
+ /** A log ID's time of day, local: `14:05:12.678`. */
93
+ export declare function timeOfDay(id: number): string;
94
+ /** A log ID as ISO 8601, for JSON. */
95
+ export declare function isoTime(id: number): string;
96
+ export declare const logsUpTo: (id: number) => string;
97
+ export declare const foundAt: (id: number) => string;
98
+ export declare const searchedUpTo: (id: number) => string;
99
+ export declare const linesLost: (oldest: number) => string;
100
+ /** `tab`: the tab asked for (`T4`), or none for all of them. */
101
+ export declare const noLogLines: (tab?: string) => string;
102
+ /**
103
+ * What `--find` (or `wait log`'s text) matches: a plain substring, or
104
+ * `/regex/flags`. `shown`: how a message names it.
105
+ */
106
+ export declare function textMatcher(text: string, flag?: string): {
107
+ matches: (line: string) => boolean;
108
+ shown: string;
109
+ } | {
110
+ error: string;
111
+ };
112
+ /** `[event]`, `[script log]`, `[sm error]`, `[page warning]`. */
113
+ export declare function sourcePrefix(record: LogFields): string;
114
+ /** A console line as the stream shows it: cut past CONSOLE_SHOWN_CHARS. */
115
+ export declare function shortConsoleText(text: string): string;
116
+ /** Where a line came from: `T2`, or `T1.F3` for an iframe; '' for none. */
117
+ export declare function lineOrigin(record: LogFields): string;
118
+ /**
119
+ * A line as the stream prints it: `[T2] ` for a tab after T1, `[T1.F3] `
120
+ * for an iframe of any tab, its
121
+ * prefix, then its text, a console line cut past a length unless
122
+ * `whole` (`wait log` matches it whole).
123
+ */
124
+ export declare function streamText(record: LogFields, whole?: boolean): string;
125
+ /** `streamText`'s line as the stream shows it: its prefix dimmed, an error or warning in color. */
126
+ export declare function streamLine(record: LogFields): string;
127
+ /**
128
+ * Lines as `logs` prints them: the time and tab, then the prefix and
129
+ * text, each run of identical lines once, marked `(N times)`. In color
130
+ * as the stream shows them (see streamLine).
131
+ */
132
+ export declare function logsLines(records: readonly LogRecord[]): string[];
@@ -0,0 +1,241 @@
1
+ // A run's log: every line the page, the scripts, SourceMonkey and the
2
+ // run's events produce, each tagged with its source, kept run-wide for
3
+ // `logs` and `wait log`, and streamed to the clients that asked for its
4
+ // source. See docs/sm-dev-logs.md.
5
+ //
6
+ // Light, importing only output-style.ts (which imports nothing):
7
+ // runs.ts, which the sm-dev client loads, takes the sources from here.
8
+ import { asError, asWarning, dim, ui } from './output-style.js';
9
+ export const LOG_SOURCES = ['sm', 'event', 'script', 'page'];
10
+ /** What the stream and `logs` show unless told otherwise: all but `page`. */
11
+ export const DEFAULT_LOG_SOURCES = new Set(['sm', 'event', 'script']);
12
+ export const ALL_LOG_SOURCES = new Set(LOG_SOURCES);
13
+ /** Whether a line belongs to any of `sources`. */
14
+ export function inSources(record, sources) {
15
+ return sources.has(record.source) || (record.also ?? []).some((s) => sources.has(s));
16
+ }
17
+ /** A `--log-source` value: a source, several separated by commas, or `all`. */
18
+ export function parseLogSources(text) {
19
+ const words = text.split(',').map((w) => w.trim()).filter(Boolean);
20
+ if (words.length === 1 && words[0] === 'all')
21
+ return ALL_LOG_SOURCES;
22
+ const wrong = words.find((w) => !LOG_SOURCES.includes(w));
23
+ if (words.length === 0 || wrong !== undefined) {
24
+ return { error: `The --log-source flag takes ${LOG_SOURCES.join(', ')} or all, or multiple separated by commas${wrong !== undefined ? `, not ${wrong}` : ''}` };
25
+ }
26
+ return new Set(words);
27
+ }
28
+ // How many lines a run keeps, oldest dropped first, and how long each
29
+ // can be.
30
+ const KEPT_LINES = 5000;
31
+ const LINE_CHARS = 2000;
32
+ /**
33
+ * Now, in microseconds since the epoch. By the wall clock, which
34
+ * `--since` times are read by; `performance`'s drifts from it after a
35
+ * sleep. RunLog keeps IDs increasing if the clock steps back.
36
+ */
37
+ export function nowMicros() {
38
+ return Date.now() * 1000;
39
+ }
40
+ // `text`'s first `chars` UTF-16 units, one fewer rather than half an
41
+ // emoji.
42
+ function cutAt(text, chars) {
43
+ const code = text.charCodeAt(chars - 1);
44
+ return text.slice(0, code >= 0xd800 && code <= 0xdbff ? chars - 1 : chars);
45
+ }
46
+ /** A run's log lines, for `logs` and `wait log`, and a way to hear new ones. */
47
+ export class RunLog {
48
+ records = [];
49
+ lastId = 0;
50
+ // The newest line dropped for space, if any.
51
+ droppedUpTo = 0;
52
+ watchers = new Set();
53
+ /** Logs a line: its ID given, its text cut past a length. */
54
+ add(fields) {
55
+ this.lastId = Math.max(nowMicros(), this.lastId + 1);
56
+ const text = fields.text.length > LINE_CHARS ? `${cutAt(fields.text, LINE_CHARS)}…` : fields.text;
57
+ const record = { ...fields, text, id: this.lastId };
58
+ this.records.push(record);
59
+ if (this.records.length > KEPT_LINES)
60
+ this.droppedUpTo = this.records.shift().id;
61
+ for (const watcher of [...this.watchers])
62
+ watcher(record);
63
+ return record;
64
+ }
65
+ /** The lines kept, oldest first. */
66
+ all() {
67
+ return this.records;
68
+ }
69
+ /** The lines after `id`, oldest first. */
70
+ since(id) {
71
+ // From the end: usually only the last few are wanted.
72
+ let i = this.records.length;
73
+ while (i > 0 && this.records[i - 1].id > id)
74
+ i--;
75
+ return this.records.slice(i);
76
+ }
77
+ /**
78
+ * The position reached: the newest line's ID, or now when there's
79
+ * none yet. Any line after it gets a higher ID.
80
+ */
81
+ position() {
82
+ if (this.lastId === 0)
83
+ this.lastId = nowMicros();
84
+ return this.lastId;
85
+ }
86
+ /** When lines after `id` were dropped for space: the oldest kept line's ID. */
87
+ lostAfter(id) {
88
+ return id < this.droppedUpTo ? this.records[0]?.id : undefined;
89
+ }
90
+ /** Calls `listener` with each new line, until the returned function is called. */
91
+ watch(listener) {
92
+ this.watchers.add(listener);
93
+ return () => { this.watchers.delete(listener); };
94
+ }
95
+ }
96
+ // ---------- Positions and spans ----------
97
+ const SINCE_FORMS = 'a log ID, an ISO 8601 time such as 2026-10-03T14:05:12Z, or a time of day such as 14:05:12';
98
+ /**
99
+ * A `--since` value as a log ID: a log ID itself, an ISO 8601 time
100
+ * (local without a zone), or a time of day today, local.
101
+ */
102
+ export function parsePosition(text, now = new Date()) {
103
+ if (/^\d+$/.test(text))
104
+ return Number(text);
105
+ const time = /^(\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d{1,6}))?)?$/.exec(text);
106
+ if (time) {
107
+ const [, h, m, s = '0', frac = ''] = time;
108
+ const day = new Date(now.getFullYear(), now.getMonth(), now.getDate(), Number(h), Number(m), Number(s));
109
+ if (Number(h) > 23 || Number(m) > 59 || Number(s) > 59)
110
+ return { error: `The --since flag takes ${SINCE_FORMS}, not ${text}` };
111
+ return day.getTime() * 1000 + Number(frac.padEnd(6, '0'));
112
+ }
113
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(text)) {
114
+ const ms = Date.parse(text);
115
+ if (!Number.isNaN(ms))
116
+ return ms * 1000;
117
+ }
118
+ return { error: `The --since flag takes ${SINCE_FORMS}, not ${text}` };
119
+ }
120
+ /** A `--tail` value: a number of lines, or a stretch of time (`30s`, `5m`). */
121
+ export function parseTail(text) {
122
+ if (/^\d+$/.test(text) && Number(text) > 0)
123
+ return { lines: Number(text) };
124
+ const span = /^(\d+(?:\.\d+)?)(ms|s|m|h)$/.exec(text);
125
+ if (span)
126
+ return { micros: Math.round(Number(span[1]) * { ms: 1e3, s: 1e6, m: 6e7, h: 3.6e9 }[span[2]]) };
127
+ return { error: `The --tail flag takes a number of lines, or a time such as 500ms, 30s, 5m or 1h, not ${text}` };
128
+ }
129
+ /** A log ID's time of day, local: `14:05:12.678`. */
130
+ export function timeOfDay(id) {
131
+ const d = new Date(Math.floor(id / 1000));
132
+ const pad = (n, width = 2) => String(n).padStart(width, '0');
133
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
134
+ }
135
+ /** A log ID as ISO 8601, for JSON. */
136
+ export function isoTime(id) {
137
+ return new Date(Math.floor(id / 1000)).toISOString();
138
+ }
139
+ // The end-of-logs lines, which say where to pick up with --since.
140
+ const at = (id) => `${id} (${timeOfDay(id)})`;
141
+ export const logsUpTo = (id) => `[sm-dev] Logs up to ${at(id)}`;
142
+ export const foundAt = (id) => `[sm-dev] Found at ${at(id)}`;
143
+ export const searchedUpTo = (id) => `[sm-dev] Searched up to ${at(id)}`;
144
+ export const linesLost = (oldest) => `[sm-dev] Lines before ${timeOfDay(oldest)} are no longer kept`;
145
+ /** `tab`: the tab asked for (`T4`), or none for all of them. */
146
+ export const noLogLines = (tab) => `[sm-dev] No log lines match ${tab ? `on tab ${tab}` : 'on any tab'}`;
147
+ // ---------- Text to find ----------
148
+ /**
149
+ * What `--find` (or `wait log`'s text) matches: a plain substring, or
150
+ * `/regex/flags`. `shown`: how a message names it.
151
+ */
152
+ export function textMatcher(text, flag = '--find') {
153
+ const regex = /^\/(.+)\/([a-z]*)$/.exec(text);
154
+ if (!regex)
155
+ return { matches: (line) => line.includes(text), shown: JSON.stringify(text) };
156
+ let pattern;
157
+ try {
158
+ pattern = new RegExp(regex[1], regex[2]);
159
+ }
160
+ catch (err) {
161
+ return { error: `${text} is not a valid regular expression${flag ? ` for ${flag}` : ''}: ${err.message}` };
162
+ }
163
+ // Each line from its start: g and y otherwise carry on from where the
164
+ // last match ended, which would skip matches.
165
+ return {
166
+ matches: (line) => {
167
+ pattern.lastIndex = 0;
168
+ return pattern.test(line);
169
+ },
170
+ shown: text,
171
+ };
172
+ }
173
+ // ---------- How lines read ----------
174
+ /** `[event]`, `[script log]`, `[sm error]`, `[page warning]`. */
175
+ export function sourcePrefix(record) {
176
+ return record.source === 'event' ? '[event]' : `[${record.source} ${record.kind}]`;
177
+ }
178
+ // Longer console lines are cut in the stream, so one huge value (a
179
+ // fetched page, a cache) doesn't bury the rest. `logs` shows them whole.
180
+ const CONSOLE_SHOWN_CHARS = 1000;
181
+ /** A console line as the stream shows it: cut past CONSOLE_SHOWN_CHARS. */
182
+ export function shortConsoleText(text) {
183
+ if (text.length <= CONSOLE_SHOWN_CHARS)
184
+ return text;
185
+ // By code point, so an emoji isn't cut in half.
186
+ return `${Array.from(text).slice(0, CONSOLE_SHOWN_CHARS).join('')}... (${text.length} chars; logs shows ${text.length > LINE_CHARS ? `the first ${LINE_CHARS}` : 'it all'})`;
187
+ }
188
+ /** Where a line came from: `T2`, or `T1.F3` for an iframe; '' for none. */
189
+ export function lineOrigin(record) {
190
+ if (record.tab === undefined)
191
+ return '';
192
+ return `T${record.tab}${record.frame !== undefined ? `.F${record.frame}` : ''}`;
193
+ }
194
+ /**
195
+ * A line as the stream prints it: `[T2] ` for a tab after T1, `[T1.F3] `
196
+ * for an iframe of any tab, its
197
+ * prefix, then its text, a console line cut past a length unless
198
+ * `whole` (`wait log` matches it whole).
199
+ */
200
+ export function streamText(record, whole = false) {
201
+ const shown = record.tab !== undefined && (record.tab > 1 || record.frame !== undefined);
202
+ return `${shown ? `[${lineOrigin(record)}] ` : ''}${sourcePrefix(record)} ${streamBody(record, whole)}`;
203
+ }
204
+ // A line's text as the stream shows it: a console line cut past a
205
+ // length unless `whole`.
206
+ function streamBody(record, whole = false) {
207
+ const console = record.source === 'script' || record.source === 'page';
208
+ return console && !whole ? shortConsoleText(record.text) : record.text;
209
+ }
210
+ // A line's text in color by its level: an error's, a warning's, or as
211
+ // is. Taken as it is, since a script or page wrote it.
212
+ function leveledText(record, text) {
213
+ const level = record.source === 'event' ? (record.kind === 'threw' ? 'error' : '') : record.kind;
214
+ return level === 'error' ? asError(text) : level === 'warning' || level === 'warn' ? asWarning(text) : text;
215
+ }
216
+ /** `streamText`'s line as the stream shows it: its prefix dimmed, an error or warning in color. */
217
+ export function streamLine(record) {
218
+ const shown = record.tab !== undefined && (record.tab > 1 || record.frame !== undefined);
219
+ return ui `${dim(`${shown ? `[${lineOrigin(record)}] ` : ''}${sourcePrefix(record)}`)} ${leveledText(record, streamBody(record))}`;
220
+ }
221
+ /**
222
+ * Lines as `logs` prints them: the time and tab, then the prefix and
223
+ * text, each run of identical lines once, marked `(N times)`. In color
224
+ * as the stream shows them (see streamLine).
225
+ */
226
+ export function logsLines(records) {
227
+ const width = Math.max(0, ...records.map((r) => lineOrigin(r).length));
228
+ const head = (r) => `${lineOrigin(r).padEnd(width)}${width > 0 ? ' ' : ''}${sourcePrefix(r)}`;
229
+ const body = (r) => `${head(r)} ${r.text}`;
230
+ const lines = [];
231
+ for (let i = 0; i < records.length;) {
232
+ const r = records[i];
233
+ const text = body(r);
234
+ let end = i + 1;
235
+ while (end < records.length && body(records[end]) === text)
236
+ end++;
237
+ lines.push(ui `${dim(`${timeOfDay(r.id)} ${head(r)}`)} ${leveledText(r, r.text)}${end - i > 1 ? ` (${end - i} times)` : ''}`);
238
+ i = end;
239
+ }
240
+ return lines;
241
+ }