sweetlink 0.2.0 → 0.2.2

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 (233) hide show
  1. package/CHANGELOG.md +21 -2
  2. package/LICENSE +1 -1
  3. package/README.md +6 -6
  4. package/dist/daemon/src/cli.d.ts +3 -0
  5. package/dist/daemon/src/cli.d.ts.map +1 -0
  6. package/dist/daemon/src/cli.js +14 -0
  7. package/dist/daemon/src/cli.js.map +1 -0
  8. package/dist/daemon/src/codename.d.ts.map +1 -1
  9. package/dist/daemon/src/codename.js +11 -11
  10. package/dist/daemon/src/codename.js.map +1 -1
  11. package/dist/daemon/src/index.d.ts +1 -1
  12. package/dist/daemon/src/index.js +143 -128
  13. package/dist/daemon/src/index.js.map +1 -1
  14. package/dist/shared/src/env.d.ts.map +1 -1
  15. package/dist/shared/src/env.js +12 -12
  16. package/dist/shared/src/env.js.map +1 -1
  17. package/dist/shared/src/index.d.ts +30 -30
  18. package/dist/shared/src/index.d.ts.map +1 -1
  19. package/dist/shared/src/index.js +17 -17
  20. package/dist/shared/src/index.js.map +1 -1
  21. package/dist/shared/src/node.d.ts +1 -1
  22. package/dist/shared/src/node.d.ts.map +1 -1
  23. package/dist/shared/src/node.js +18 -18
  24. package/dist/shared/src/node.js.map +1 -1
  25. package/dist/src/codex.d.ts +1 -1
  26. package/dist/src/codex.js +19 -19
  27. package/dist/src/codex.js.map +1 -1
  28. package/dist/src/commands/click.d.ts +1 -1
  29. package/dist/src/commands/click.d.ts.map +1 -1
  30. package/dist/src/commands/click.js +32 -24
  31. package/dist/src/commands/click.js.map +1 -1
  32. package/dist/src/commands/run-js.d.ts +1 -1
  33. package/dist/src/commands/run-js.d.ts.map +1 -1
  34. package/dist/src/commands/run-js.js +10 -10
  35. package/dist/src/commands/run-js.js.map +1 -1
  36. package/dist/src/commands/trust-ca.d.ts +1 -1
  37. package/dist/src/commands/trust-ca.js +15 -15
  38. package/dist/src/core/config-file.d.ts.map +1 -1
  39. package/dist/src/core/config-file.js +49 -41
  40. package/dist/src/core/config-file.js.map +1 -1
  41. package/dist/src/core/config.d.ts +2 -2
  42. package/dist/src/core/config.d.ts.map +1 -1
  43. package/dist/src/core/config.js +22 -18
  44. package/dist/src/core/config.js.map +1 -1
  45. package/dist/src/core/dev-bootstrap.d.ts +1 -1
  46. package/dist/src/core/dev-bootstrap.d.ts.map +1 -1
  47. package/dist/src/core/dev-bootstrap.js +12 -12
  48. package/dist/src/core/dev-bootstrap.js.map +1 -1
  49. package/dist/src/core/env.d.ts +1 -1
  50. package/dist/src/core/env.d.ts.map +1 -1
  51. package/dist/src/core/env.js +3 -3
  52. package/dist/src/core/env.js.map +1 -1
  53. package/dist/src/devtools-registry.d.ts.map +1 -1
  54. package/dist/src/devtools-registry.js +48 -46
  55. package/dist/src/devtools-registry.js.map +1 -1
  56. package/dist/src/env.d.ts +1 -1
  57. package/dist/src/env.js +1 -1
  58. package/dist/src/http.d.ts.map +1 -1
  59. package/dist/src/http.js +7 -6
  60. package/dist/src/http.js.map +1 -1
  61. package/dist/src/index.d.ts +7 -7
  62. package/dist/src/index.d.ts.map +1 -1
  63. package/dist/src/index.js +399 -367
  64. package/dist/src/index.js.map +1 -1
  65. package/dist/src/runtime/browser/client.d.ts +5 -4
  66. package/dist/src/runtime/browser/client.d.ts.map +1 -1
  67. package/dist/src/runtime/browser/client.js +79 -72
  68. package/dist/src/runtime/browser/client.js.map +1 -1
  69. package/dist/src/runtime/browser/commands/index.d.ts +2 -2
  70. package/dist/src/runtime/browser/commands/index.js +17 -17
  71. package/dist/src/runtime/browser/commands/index.js.map +1 -1
  72. package/dist/src/runtime/browser/dom-to-image-loader.d.ts.map +1 -1
  73. package/dist/src/runtime/browser/dom-to-image-loader.js +7 -7
  74. package/dist/src/runtime/browser/dom-to-image-loader.js.map +1 -1
  75. package/dist/src/runtime/browser/index.d.ts +3 -3
  76. package/dist/src/runtime/browser/index.js +2 -2
  77. package/dist/src/runtime/browser/module-loader.js +2 -2
  78. package/dist/src/runtime/browser/screenshot/hooks.d.ts +5 -5
  79. package/dist/src/runtime/browser/screenshot/hooks.d.ts.map +1 -1
  80. package/dist/src/runtime/browser/screenshot/hooks.js +32 -29
  81. package/dist/src/runtime/browser/screenshot/hooks.js.map +1 -1
  82. package/dist/src/runtime/browser/screenshot/index.d.ts +3 -3
  83. package/dist/src/runtime/browser/screenshot/index.d.ts.map +1 -1
  84. package/dist/src/runtime/browser/screenshot/index.js +17 -15
  85. package/dist/src/runtime/browser/screenshot/index.js.map +1 -1
  86. package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.d.ts +1 -1
  87. package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.js +13 -13
  88. package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.js.map +1 -1
  89. package/dist/src/runtime/browser/screenshot/renderers/html2canvas.d.ts +1 -1
  90. package/dist/src/runtime/browser/screenshot/renderers/html2canvas.d.ts.map +1 -1
  91. package/dist/src/runtime/browser/screenshot/renderers/html2canvas.js +24 -24
  92. package/dist/src/runtime/browser/screenshot/renderers/html2canvas.js.map +1 -1
  93. package/dist/src/runtime/browser/screenshot/targets.d.ts +2 -2
  94. package/dist/src/runtime/browser/screenshot/targets.js +9 -9
  95. package/dist/src/runtime/browser/screenshot/utils.d.ts +1 -1
  96. package/dist/src/runtime/browser/screenshot/utils.d.ts.map +1 -1
  97. package/dist/src/runtime/browser/screenshot/utils.js +73 -71
  98. package/dist/src/runtime/browser/screenshot/utils.js.map +1 -1
  99. package/dist/src/runtime/browser/selector-discovery.d.ts +1 -1
  100. package/dist/src/runtime/browser/selector-discovery.d.ts.map +1 -1
  101. package/dist/src/runtime/browser/selector-discovery.js +24 -22
  102. package/dist/src/runtime/browser/selector-discovery.js.map +1 -1
  103. package/dist/src/runtime/browser/storage/session-storage.d.ts +10 -5
  104. package/dist/src/runtime/browser/storage/session-storage.d.ts.map +1 -1
  105. package/dist/src/runtime/browser/storage/session-storage.js +12 -10
  106. package/dist/src/runtime/browser/storage/session-storage.js.map +1 -1
  107. package/dist/src/runtime/browser/types.d.ts +7 -7
  108. package/dist/src/runtime/browser/types.js +1 -1
  109. package/dist/src/runtime/browser/utils/console.d.ts +1 -1
  110. package/dist/src/runtime/browser/utils/console.js +16 -16
  111. package/dist/src/runtime/browser/utils/console.js.map +1 -1
  112. package/dist/src/runtime/browser/utils/environment.js +2 -2
  113. package/dist/src/runtime/browser/utils/errors.d.ts.map +1 -1
  114. package/dist/src/runtime/browser/utils/errors.js +11 -11
  115. package/dist/src/runtime/browser/utils/number.d.ts.map +1 -1
  116. package/dist/src/runtime/browser/utils/number.js.map +1 -1
  117. package/dist/src/runtime/browser/utils/object.d.ts.map +1 -1
  118. package/dist/src/runtime/browser/utils/object.js +2 -2
  119. package/dist/src/runtime/browser/utils/object.js.map +1 -1
  120. package/dist/src/runtime/browser/utils/sanitize.js +2 -2
  121. package/dist/src/runtime/browser/utils/time.d.ts.map +1 -1
  122. package/dist/src/runtime/browser/utils/time.js +1 -1
  123. package/dist/src/runtime/chrome/cookies.d.ts +4 -4
  124. package/dist/src/runtime/chrome/cookies.d.ts.map +1 -1
  125. package/dist/src/runtime/chrome/cookies.js +31 -31
  126. package/dist/src/runtime/chrome/cookies.js.map +1 -1
  127. package/dist/src/runtime/chrome/deep-link.d.ts +1 -1
  128. package/dist/src/runtime/chrome/deep-link.d.ts.map +1 -1
  129. package/dist/src/runtime/chrome/deep-link.js +46 -36
  130. package/dist/src/runtime/chrome/deep-link.js.map +1 -1
  131. package/dist/src/runtime/chrome/diagnostics.d.ts.map +1 -1
  132. package/dist/src/runtime/chrome/diagnostics.js +13 -11
  133. package/dist/src/runtime/chrome/diagnostics.js.map +1 -1
  134. package/dist/src/runtime/chrome/focus.d.ts.map +1 -1
  135. package/dist/src/runtime/chrome/focus.js +5 -5
  136. package/dist/src/runtime/chrome/focus.js.map +1 -1
  137. package/dist/src/runtime/chrome/launch.d.ts.map +1 -1
  138. package/dist/src/runtime/chrome/launch.js +30 -30
  139. package/dist/src/runtime/chrome/launch.js.map +1 -1
  140. package/dist/src/runtime/chrome/puppeteer.d.ts +2 -2
  141. package/dist/src/runtime/chrome/puppeteer.d.ts.map +1 -1
  142. package/dist/src/runtime/chrome/puppeteer.js +16 -11
  143. package/dist/src/runtime/chrome/puppeteer.js.map +1 -1
  144. package/dist/src/runtime/chrome/reuse.d.ts.map +1 -1
  145. package/dist/src/runtime/chrome/reuse.js +38 -35
  146. package/dist/src/runtime/chrome/reuse.js.map +1 -1
  147. package/dist/src/runtime/chrome/session.d.ts +1 -1
  148. package/dist/src/runtime/chrome/session.d.ts.map +1 -1
  149. package/dist/src/runtime/chrome/session.js +12 -10
  150. package/dist/src/runtime/chrome/session.js.map +1 -1
  151. package/dist/src/runtime/chrome.d.ts +8 -8
  152. package/dist/src/runtime/chrome.js +8 -8
  153. package/dist/src/runtime/cookies.d.ts +1 -16
  154. package/dist/src/runtime/cookies.d.ts.map +1 -1
  155. package/dist/src/runtime/cookies.js +175 -286
  156. package/dist/src/runtime/cookies.js.map +1 -1
  157. package/dist/src/runtime/devstack.d.ts +1 -1
  158. package/dist/src/runtime/devstack.d.ts.map +1 -1
  159. package/dist/src/runtime/devstack.js +31 -28
  160. package/dist/src/runtime/devstack.js.map +1 -1
  161. package/dist/src/runtime/devtools/background.js +22 -22
  162. package/dist/src/runtime/devtools/cdp.d.ts +3 -3
  163. package/dist/src/runtime/devtools/cdp.d.ts.map +1 -1
  164. package/dist/src/runtime/devtools/cdp.js +59 -55
  165. package/dist/src/runtime/devtools/cdp.js.map +1 -1
  166. package/dist/src/runtime/devtools/config.d.ts.map +1 -1
  167. package/dist/src/runtime/devtools/config.js +15 -15
  168. package/dist/src/runtime/devtools/config.js.map +1 -1
  169. package/dist/src/runtime/devtools/constants.d.ts.map +1 -1
  170. package/dist/src/runtime/devtools/constants.js +18 -10
  171. package/dist/src/runtime/devtools/constants.js.map +1 -1
  172. package/dist/src/runtime/devtools/diagnostics.d.ts +4 -4
  173. package/dist/src/runtime/devtools/diagnostics.d.ts.map +1 -1
  174. package/dist/src/runtime/devtools/diagnostics.js +40 -38
  175. package/dist/src/runtime/devtools/diagnostics.js.map +1 -1
  176. package/dist/src/runtime/devtools/oauth.d.ts +1 -1
  177. package/dist/src/runtime/devtools/oauth.d.ts.map +1 -1
  178. package/dist/src/runtime/devtools/oauth.js +35 -28
  179. package/dist/src/runtime/devtools/oauth.js.map +1 -1
  180. package/dist/src/runtime/devtools/types.d.ts +3 -3
  181. package/dist/src/runtime/devtools/types.d.ts.map +1 -1
  182. package/dist/src/runtime/devtools.d.ts +7 -7
  183. package/dist/src/runtime/devtools.js +7 -7
  184. package/dist/src/runtime/next-devtools.d.ts.map +1 -1
  185. package/dist/src/runtime/next-devtools.js +12 -12
  186. package/dist/src/runtime/next-devtools.js.map +1 -1
  187. package/dist/src/runtime/screenshot.d.ts +9 -9
  188. package/dist/src/runtime/screenshot.d.ts.map +1 -1
  189. package/dist/src/runtime/screenshot.js +65 -46
  190. package/dist/src/runtime/screenshot.js.map +1 -1
  191. package/dist/src/runtime/scripts.d.ts +1 -1
  192. package/dist/src/runtime/scripts.d.ts.map +1 -1
  193. package/dist/src/runtime/scripts.js +28 -28
  194. package/dist/src/runtime/scripts.js.map +1 -1
  195. package/dist/src/runtime/session.d.ts +6 -6
  196. package/dist/src/runtime/session.d.ts.map +1 -1
  197. package/dist/src/runtime/session.js +35 -35
  198. package/dist/src/runtime/session.js.map +1 -1
  199. package/dist/src/runtime/smoke.d.ts +3 -3
  200. package/dist/src/runtime/smoke.d.ts.map +1 -1
  201. package/dist/src/runtime/smoke.js +85 -79
  202. package/dist/src/runtime/smoke.js.map +1 -1
  203. package/dist/src/runtime/url.d.ts +2 -2
  204. package/dist/src/runtime/url.d.ts.map +1 -1
  205. package/dist/src/runtime/url.js +20 -20
  206. package/dist/src/runtime/url.js.map +1 -1
  207. package/dist/src/screenshot-hooks.d.ts +1 -1
  208. package/dist/src/screenshot-hooks.d.ts.map +1 -1
  209. package/dist/src/screenshot-hooks.js +12 -12
  210. package/dist/src/screenshot-hooks.js.map +1 -1
  211. package/dist/src/shared/env.d.ts.map +1 -1
  212. package/dist/src/shared/env.js +11 -11
  213. package/dist/src/shared/env.js.map +1 -1
  214. package/dist/src/shared/index.d.ts +30 -30
  215. package/dist/src/shared/index.d.ts.map +1 -1
  216. package/dist/src/shared/index.js +17 -17
  217. package/dist/src/shared/index.js.map +1 -1
  218. package/dist/src/shared/node.d.ts +1 -1
  219. package/dist/src/shared/node.d.ts.map +1 -1
  220. package/dist/src/shared/node.js +18 -18
  221. package/dist/src/shared/node.js.map +1 -1
  222. package/dist/src/token.d.ts +1 -1
  223. package/dist/src/token.d.ts.map +1 -1
  224. package/dist/src/token.js +16 -16
  225. package/dist/src/token.js.map +1 -1
  226. package/dist/src/types.d.ts +1 -1
  227. package/dist/src/util/app-label.js +4 -4
  228. package/dist/src/util/errors.d.ts.map +1 -1
  229. package/dist/src/util/errors.js +12 -10
  230. package/dist/src/util/errors.js.map +1 -1
  231. package/dist/src/util/path.js +2 -2
  232. package/dist/src/util/regex.js +4 -4
  233. package/package.json +53 -45
package/dist/src/index.js CHANGED
@@ -1,44 +1,44 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import { mkdir, readFile, rm } from 'node:fs/promises';
4
- import { spawn } from 'node:child_process';
5
- import os from 'node:os';
6
- import path from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
- import { Command, CommanderError, Option } from 'commander';
9
- import { compact, uniq } from 'es-toolkit';
10
- import { createSweetLinkCommandId, } from '../shared/src/index.js';
11
- import { registerClickCommand } from './commands/click.js';
12
- import { registerRunJsCommand } from './commands/run-js.js';
13
- import { registerTrustCaCommand } from './commands/trust-ca.js';
14
- import { readRootProgramOptions, resolveConfig } from './core/config.js';
15
- import { loadSweetLinkFileConfig } from './core/config-file.js';
16
- import { cloneProcessEnv, readCommandOptions } from './core/env.js';
17
- import { cleanupControlledChromeRegistry, registerControlledChromeInstance } from './devtools-registry.js';
18
- import { sweetLinkCliTestMode, sweetLinkDebug, sweetLinkEnv } from './env.js';
19
- import { fetchJson } from './http.js';
20
- import { collectPuppeteerDiagnostics, focusControlledChromePage, launchChrome, launchControlledChrome, prepareChromeLaunch, reuseExistingControlledChrome, signalSweetLinkBootstrap, waitForSweetLinkSession, } from './runtime/chrome.js';
21
- import { ensureDeepLinkAuthFlow } from './runtime/chrome/deep-link.js';
22
- import { buildCookieOrigins, collectChromeCookies, collectChromeCookiesForDomains, normalizePuppeteerCookie, } from './runtime/cookies.js';
23
- import { ensureDevStackRunning as ensureDevStackRunningRuntime, isAppReachable as isAppReachableRuntime, maybeInstallMkcertDispatcher, } from './runtime/devstack.js';
24
- import { attemptTwitterOauthAutoAccept, collectBootstrapDiagnostics, connectToDevTools, createEmptyDevToolsState, createNetworkEntryFromRequest, DEVTOOLS_CONSOLE_LIMIT, DEVTOOLS_NETWORK_LIMIT, DEVTOOLS_STATE_PATH, deriveDevtoolsLinkInfo, diagnosticsContainBlockingIssues, ensureBackgroundDevtoolsListener, fetchDevToolsTabs, formatConsoleArg, loadDevToolsConfig, loadDevToolsState, logBootstrapDiagnostics, saveDevToolsConfig, saveDevToolsState, serializeConsoleMessage, trimBuffer, } from './runtime/devtools.js';
25
- import { fetchNextDevtoolsErrors } from './runtime/next-devtools.js';
26
- import { attemptDevToolsCapture, maybeDescribeScreenshot, persistScreenshotResult, tryDevToolsRecovery, tryHtmlToImageFallback, } from './runtime/screenshot.js';
27
- import { renderCommandResult } from './runtime/scripts.js';
28
- import { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, getSessionSummaryById, isSweetLinkSelectorCandidate, isSweetLinkSelectorDiscoveryResult, resolvePromptOption, resolveSessionIdFromHint, } from './runtime/session.js';
29
- import { buildSmokeRouteUrl, clearSmokeProgress, consoleEventIndicatesAuthIssue, consoleEventIndicatesRuntimeError, DEFAULT_SMOKE_ROUTES, deriveSmokeRoutes, ensureSweetLinkSessionConnected, formatConsoleEventSummary, loadSmokeProgressIndex, navigateSweetLinkSession, saveSmokeProgressIndex, triggerSweetLinkCliAuto, waitForSmokeRouteReady, } from './runtime/smoke.js';
30
- import { buildWaitCandidateUrls, configurePathRedirects, normalizeUrlForMatch, trimTrailingSlash } from './runtime/url.js';
31
- import { buildScreenshotHooks } from './screenshot-hooks.js';
32
- import { fetchCliToken } from './token.js';
33
- import { describeAppForPrompt, formatAppLabel } from './util/app-label.js';
34
- import { extractEventMessage, isErrnoException, logDebugError } from './util/errors.js';
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { mkdir, readFile, rm } from "node:fs/promises";
4
+ import { spawn } from "node:child_process";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { Command, CommanderError, Option } from "commander";
9
+ import { compact, uniq } from "es-toolkit";
10
+ import { createSweetLinkCommandId, } from "../shared/src/index.js";
11
+ import { registerClickCommand } from "./commands/click.js";
12
+ import { registerRunJsCommand } from "./commands/run-js.js";
13
+ import { registerTrustCaCommand } from "./commands/trust-ca.js";
14
+ import { readRootProgramOptions, resolveConfig } from "./core/config.js";
15
+ import { loadSweetLinkFileConfig } from "./core/config-file.js";
16
+ import { cloneProcessEnv, readCommandOptions } from "./core/env.js";
17
+ import { cleanupControlledChromeRegistry, registerControlledChromeInstance, } from "./devtools-registry.js";
18
+ import { sweetLinkCliTestMode, sweetLinkDebug, sweetLinkEnv } from "./env.js";
19
+ import { fetchJson } from "./http.js";
20
+ import { collectPuppeteerDiagnostics, focusControlledChromePage, launchChrome, launchControlledChrome, prepareChromeLaunch, reuseExistingControlledChrome, signalSweetLinkBootstrap, waitForSweetLinkSession, } from "./runtime/chrome.js";
21
+ import { ensureDeepLinkAuthFlow } from "./runtime/chrome/deep-link.js";
22
+ import { buildCookieOrigins, collectChromeCookies, collectChromeCookiesForDomains, normalizePuppeteerCookie, } from "./runtime/cookies.js";
23
+ import { ensureDevStackRunning as ensureDevStackRunningRuntime, isAppReachable as isAppReachableRuntime, maybeInstallMkcertDispatcher, } from "./runtime/devstack.js";
24
+ import { attemptTwitterOauthAutoAccept, collectBootstrapDiagnostics, connectToDevTools, createEmptyDevToolsState, createNetworkEntryFromRequest, DEVTOOLS_CONSOLE_LIMIT, DEVTOOLS_NETWORK_LIMIT, DEVTOOLS_STATE_PATH, deriveDevtoolsLinkInfo, diagnosticsContainBlockingIssues, ensureBackgroundDevtoolsListener, fetchDevToolsTabs, formatConsoleArg, loadDevToolsConfig, loadDevToolsState, logBootstrapDiagnostics, saveDevToolsConfig, saveDevToolsState, serializeConsoleMessage, trimBuffer, } from "./runtime/devtools.js";
25
+ import { fetchNextDevtoolsErrors } from "./runtime/next-devtools.js";
26
+ import { attemptDevToolsCapture, maybeDescribeScreenshot, persistScreenshotResult, tryDevToolsRecovery, tryHtmlToImageFallback, } from "./runtime/screenshot.js";
27
+ import { renderCommandResult } from "./runtime/scripts.js";
28
+ import { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, getSessionSummaryById, isSweetLinkSelectorCandidate, isSweetLinkSelectorDiscoveryResult, resolvePromptOption, resolveSessionIdFromHint, } from "./runtime/session.js";
29
+ import { buildSmokeRouteUrl, clearSmokeProgress, consoleEventIndicatesAuthIssue, consoleEventIndicatesRuntimeError, DEFAULT_SMOKE_ROUTES, deriveSmokeRoutes, ensureSweetLinkSessionConnected, formatConsoleEventSummary, loadSmokeProgressIndex, navigateSweetLinkSession, saveSmokeProgressIndex, triggerSweetLinkCliAuto, waitForSmokeRouteReady, } from "./runtime/smoke.js";
30
+ import { buildWaitCandidateUrls, configurePathRedirects, normalizeUrlForMatch, trimTrailingSlash, } from "./runtime/url.js";
31
+ import { buildScreenshotHooks } from "./screenshot-hooks.js";
32
+ import { fetchCliToken } from "./token.js";
33
+ import { describeAppForPrompt, formatAppLabel } from "./util/app-label.js";
34
+ import { extractEventMessage, isErrnoException, logDebugError } from "./util/errors.js";
35
35
  const __filename = fileURLToPath(import.meta.url);
36
36
  const __dirname = path.dirname(__filename);
37
37
  function resolveRepoRoot(startDir) {
38
38
  let currentDir = startDir;
39
39
  // Walk up until a package.json exists or we reach the filesystem root.
40
40
  // This keeps the CLI working whether we run from `src` (tsx) or `dist/src`.
41
- while (!existsSync(path.join(currentDir, 'package.json'))) {
41
+ while (!existsSync(path.join(currentDir, "package.json"))) {
42
42
  const parent = path.dirname(currentDir);
43
43
  if (parent === currentDir) {
44
44
  return startDir;
@@ -48,11 +48,11 @@ function resolveRepoRoot(startDir) {
48
48
  return currentDir;
49
49
  }
50
50
  const repoRoot = resolveRepoRoot(__dirname);
51
- const packageJsonPath = path.join(repoRoot, 'package.json');
52
- let packageVersion = '0.0.0';
51
+ const packageJsonPath = path.join(repoRoot, "package.json");
52
+ let packageVersion = "0.0.0";
53
53
  try {
54
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
55
- if (typeof packageJson.version === 'string') {
54
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
55
+ if (typeof packageJson.version === "string") {
56
56
  packageVersion = packageJson.version;
57
57
  }
58
58
  }
@@ -65,7 +65,7 @@ const OAUTH_URL_PATTERN = /oauth|authorize/i;
65
65
  const TRAILING_SLASH_PATTERN = /\/?$/;
66
66
  function formatDuration(ms) {
67
67
  if (!Number.isFinite(ms)) {
68
- return 'unknown';
68
+ return "unknown";
69
69
  }
70
70
  const abs = Math.max(0, ms);
71
71
  if (abs < 1000) {
@@ -92,11 +92,11 @@ function formatDuration(ms) {
92
92
  }
93
93
  const program = new Command();
94
94
  program
95
- .name('sweetlink')
96
- .description('Interact with SweetLink daemon sessions')
97
- .version(packageVersion, '-v, --version', 'Show SweetLink CLI version');
95
+ .name("sweetlink")
96
+ .description("Interact with SweetLink daemon sessions")
97
+ .version(packageVersion, "-v, --version", "Show SweetLink CLI version");
98
98
  maybeInstallMkcertDispatcher();
99
- const LOOSE_PATH_SUFFIXES = new Set(['home', 'index', 'overview']);
99
+ const LOOSE_PATH_SUFFIXES = new Set(["home", "index", "overview"]);
100
100
  const { config: fileConfig } = loadSweetLinkFileConfig();
101
101
  configurePathRedirects(fileConfig.redirects);
102
102
  const { appUrl: envAppUrl, daemonUrl: envDaemonUrl, localAdminApiKey, adminApiKey: sharedAdminApiKey, prodAppUrl: envProdAppUrl, } = sweetLinkEnv;
@@ -104,15 +104,15 @@ const defaultAppLabel = formatAppLabel(fileConfig.appLabel ?? sweetLinkEnv.appLa
104
104
  const defaultAppUrl = deriveDefaultAppUrl(envAppUrl, fileConfig);
105
105
  const defaultProdAppUrl = fileConfig.prodUrl ?? envProdAppUrl;
106
106
  const defaultDaemonUrl = fileConfig.daemonUrl ?? envDaemonUrl;
107
- const defaultAdminKey = fileConfig.adminKey ?? localAdminApiKey ?? sharedAdminApiKey ?? '';
107
+ const defaultAdminKey = fileConfig.adminKey ?? localAdminApiKey ?? sharedAdminApiKey ?? "";
108
108
  const defaultHealthPaths = fileConfig.healthChecks?.paths ?? null;
109
- const LOCAL_DEFAULT_APP_URL = 'http://localhost:3000';
109
+ const LOCAL_DEFAULT_APP_URL = "http://localhost:3000";
110
110
  function deriveDefaultAppUrl(envUrl, config) {
111
- const configuredAppUrl = typeof config.appUrl === 'string' ? config.appUrl.trim() : '';
111
+ const configuredAppUrl = typeof config.appUrl === "string" ? config.appUrl.trim() : "";
112
112
  if (configuredAppUrl.length > 0) {
113
113
  return configuredAppUrl;
114
114
  }
115
- if (typeof config.port === 'number' && Number.isFinite(config.port) && config.port > 0) {
115
+ if (typeof config.port === "number" && Number.isFinite(config.port) && config.port > 0) {
116
116
  const baseUrl = envUrl ?? LOCAL_DEFAULT_APP_URL;
117
117
  return applyPortToUrl(baseUrl, config.port);
118
118
  }
@@ -131,22 +131,22 @@ function applyPortToUrl(base, port) {
131
131
  function parseCliPort(value) {
132
132
  const parsed = Number.parseInt(value, 10);
133
133
  if (!Number.isFinite(parsed) || parsed <= 0) {
134
- throw new CommanderError(1, 'InvalidPort', `--port expects a positive integer, received "${value}".`);
134
+ throw new CommanderError(1, "InvalidPort", `--port expects a positive integer, received "${value}".`);
135
135
  }
136
136
  return parsed;
137
137
  }
138
138
  program
139
- .option('-a, --app-url <url>', 'Application base URL for SweetLink commands', defaultAppUrl)
140
- .option('--app-label <label>', 'Friendly name for your application (used in help output)', defaultAppLabel)
141
- .option('--url <url>', 'Alias for --app-url')
142
- .addOption(new Option('--port <number>', 'Override local app port (defaults to config or 3000)').argParser(parseCliPort))
143
- .option('-d, --daemon-url <url>', 'SweetLink daemon base URL', defaultDaemonUrl)
144
- .option('-k, --admin-key <key>', 'Optional admin API key (defaults to SWEETLINK_LOCAL_ADMIN_API_KEY or SWEETLINK_ADMIN_API_KEY env; falls back to legacy SWEETISTICS_* keys)', defaultAdminKey)
145
- .option('--oauth-script <path>', 'Absolute or relative path to an OAuth automation script (ESM module). Overrides config/env defaults.');
139
+ .option("-a, --app-url <url>", "Application base URL for SweetLink commands", defaultAppUrl)
140
+ .option("--app-label <label>", "Friendly name for your application (used in help output)", defaultAppLabel)
141
+ .option("--url <url>", "Alias for --app-url")
142
+ .addOption(new Option("--port <number>", "Override local app port (defaults to config or 3000)").argParser(parseCliPort))
143
+ .option("-d, --daemon-url <url>", "SweetLink daemon base URL", defaultDaemonUrl)
144
+ .option("-k, --admin-key <key>", "Optional admin API key (defaults to SWEETLINK_LOCAL_ADMIN_API_KEY or SWEETLINK_ADMIN_API_KEY env; falls back to legacy SWEETISTICS_* keys)", defaultAdminKey)
145
+ .option("--oauth-script <path>", "Absolute or relative path to an OAuth automation script (ESM module). Overrides config/env defaults.");
146
146
  program
147
- .command('sessions')
148
- .description('List active SweetLink sessions')
149
- .option('--json', 'Output JSON instead of a table', false)
147
+ .command("sessions")
148
+ .description("List active SweetLink sessions")
149
+ .option("--json", "Output JSON instead of a table", false)
150
150
  .action(async (options, command) => {
151
151
  const config = resolveConfig(command);
152
152
  const token = await fetchCliToken(config);
@@ -169,15 +169,17 @@ program
169
169
  return;
170
170
  }
171
171
  if (sessions.length === 0) {
172
- console.log('No active SweetLink sessions.');
173
- console.log('Hint: run `pnpm sweetlink open --controlled --path /` to launch an authenticated tab automatically.');
172
+ console.log("No active SweetLink sessions.");
173
+ console.log("Hint: run `pnpm sweetlink open --controlled --path /` to launch an authenticated tab automatically.");
174
174
  return;
175
175
  }
176
176
  const now = Date.now();
177
- console.log('Active SweetLink sessions:\n');
177
+ console.log("Active SweetLink sessions:\n");
178
178
  for (const session of sessions) {
179
- const heartbeatMsAgo = typeof session.heartbeatMsAgo === 'number' ? session.heartbeatMsAgo : Math.max(0, now - session.lastSeenAt);
180
- const socketState = session.socketState ?? 'unknown';
179
+ const heartbeatMsAgo = typeof session.heartbeatMsAgo === "number"
180
+ ? session.heartbeatMsAgo
181
+ : Math.max(0, now - session.lastSeenAt);
182
+ const socketState = session.socketState ?? "unknown";
181
183
  const consoleEventsBuffered = session.consoleEventsBuffered ?? 0;
182
184
  const consoleErrorsBuffered = session.consoleErrorsBuffered ?? 0;
183
185
  const pendingCommandCount = session.pendingCommandCount ?? 0;
@@ -188,30 +190,30 @@ program
188
190
  const openedLabel = `${formatDuration(openedMsAgo)} ago`;
189
191
  const socketLabel = `socket ${socketState}`;
190
192
  console.log(` - last: ${lastHeartbeatLabel} • opened ${openedLabel} • ${socketLabel}`);
191
- const pendingLabel = pendingCommandCount === 1 ? 'command' : 'commands';
193
+ const pendingLabel = pendingCommandCount === 1 ? "command" : "commands";
192
194
  const consoleLabel = consoleEventsBuffered > 0
193
- ? `${consoleEventsBuffered} event${consoleEventsBuffered === 1 ? '' : 's'}${consoleErrorsBuffered ? ` (${consoleErrorsBuffered} error${consoleErrorsBuffered === 1 ? '' : 's'})` : ''}`
194
- : 'none';
195
+ ? `${consoleEventsBuffered} event${consoleEventsBuffered === 1 ? "" : "s"}${consoleErrorsBuffered ? ` (${consoleErrorsBuffered} error${consoleErrorsBuffered === 1 ? "" : "s"})` : ""}`
196
+ : "none";
195
197
  console.log(` - queues: ${pendingCommandCount} ${pendingLabel} • console ${consoleLabel}`);
196
198
  if (devtoolsSessionIds.has(session.sessionId)) {
197
- console.log(` - devtools: linked${devtoolsEndpoint ? ` (${devtoolsEndpoint})` : ''}`);
199
+ console.log(` - devtools: linked${devtoolsEndpoint ? ` (${devtoolsEndpoint})` : ""}`);
198
200
  }
199
201
  if (session.userAgent) {
200
202
  console.log(` - ua: ${session.userAgent}`);
201
203
  }
202
- console.log('');
204
+ console.log("");
203
205
  }
204
- console.log('Tip: run `pnpm sweetlink console <sessionId> -n 50` to inspect the most recent console events for a session.');
206
+ console.log("Tip: run `pnpm sweetlink console <sessionId> -n 50` to inspect the most recent console events for a session.");
205
207
  });
206
208
  program
207
- .command('cookies')
208
- .description('Dump Chrome cookies for one or more domains or origins')
209
- .argument('<domains...>', 'Domains or fully-qualified origins (e.g. localhost, https://example.com)')
210
- .option('--json', 'Output JSON instead of a human-readable list', false)
209
+ .command("cookies")
210
+ .description("Dump Chrome cookies for one or more domains or origins")
211
+ .argument("<domains...>", "Domains or fully-qualified origins (e.g. localhost, https://example.com)")
212
+ .option("--json", "Output JSON instead of a human-readable list", false)
211
213
  .action(async (domains, options) => {
212
214
  const uniqueDomains = uniq(compact(domains.map((domain) => domain.trim())));
213
215
  if (uniqueDomains.length === 0) {
214
- console.log('No domains provided; nothing to collect.');
216
+ console.log("No domains provided; nothing to collect.");
215
217
  return;
216
218
  }
217
219
  const cookiesByDomain = await collectChromeCookiesForDomains(uniqueDomains);
@@ -222,29 +224,29 @@ program
222
224
  for (const domain of uniqueDomains) {
223
225
  const domainCookies = cookiesByDomain[domain];
224
226
  const cookies = Array.isArray(domainCookies) ? domainCookies : [];
225
- console.log(`\n${domain} — ${cookies.length} cookie${cookies.length === 1 ? '' : 's'}`);
227
+ console.log(`\n${domain} — ${cookies.length} cookie${cookies.length === 1 ? "" : "s"}`);
226
228
  if (cookies.length === 0) {
227
229
  continue;
228
230
  }
229
231
  for (const cookie of cookies) {
230
- const scope = cookie.domain ? `domain=${cookie.domain}` : `url=${cookie.url ?? 'unknown'}`;
231
- const cookiePath = cookie.path ?? '/';
232
- const secureFlag = cookie.secure ? '; Secure' : '';
233
- const httpOnlyFlag = cookie.httpOnly ? '; HttpOnly' : '';
232
+ const scope = cookie.domain ? `domain=${cookie.domain}` : `url=${cookie.url ?? "unknown"}`;
233
+ const cookiePath = cookie.path ?? "/";
234
+ const secureFlag = cookie.secure ? "; Secure" : "";
235
+ const httpOnlyFlag = cookie.httpOnly ? "; HttpOnly" : "";
234
236
  console.log(` • ${cookie.name} (${scope} ${cookiePath}${secureFlag}${httpOnlyFlag})`);
235
237
  console.log(` ${cookie.value}`);
236
238
  }
237
239
  }
238
- console.log('');
240
+ console.log("");
239
241
  });
240
242
  registerRunJsCommand(program);
241
243
  registerTrustCaCommand(program);
242
244
  registerClickCommand(program);
243
245
  program
244
- .command('console <sessionId>')
245
- .description('Fetch buffered console events for a session')
246
- .option('-n, --limit <count>', 'Show only the last <count> console events', Number)
247
- .option('--json', 'Output JSON', false)
246
+ .command("console <sessionId>")
247
+ .description("Fetch buffered console events for a session")
248
+ .option("-n, --limit <count>", "Show only the last <count> console events", Number)
249
+ .option("--json", "Output JSON", false)
248
250
  .action(async (sessionId, options, command) => {
249
251
  const config = resolveConfig(command);
250
252
  const resolvedSessionId = await resolveSessionIdFromHint(sessionId, config);
@@ -257,10 +259,10 @@ program
257
259
  return;
258
260
  }
259
261
  if (response.events.length === 0) {
260
- console.log('No buffered console output.');
262
+ console.log("No buffered console output.");
261
263
  return;
262
264
  }
263
- const limit = typeof options.limit === 'number' && Number.isFinite(options.limit) && options.limit > 0
265
+ const limit = typeof options.limit === "number" && Number.isFinite(options.limit) && options.limit > 0
264
266
  ? Math.floor(options.limit)
265
267
  : null;
266
268
  const events = limit ? response.events.slice(-limit) : response.events;
@@ -272,25 +274,25 @@ program
272
274
  }
273
275
  });
274
276
  program
275
- .command('open')
277
+ .command("open")
276
278
  .description(`Open ${defaultAppLabel} in Chrome with SweetLink auto-enabled`)
277
- .option('-e, --env <env>', 'Environment to open (dev or prod)', 'dev')
278
- .option('-p, --path <path>', 'Optional path to append (default "")', '')
279
- .option('--url <url>', 'Explicit URL to open (overrides --path and --env)')
280
- .option('--controlled', 'Launch Chrome in controlled mode with DevTools enabled', false)
281
- .option('--devtools-port <port>', 'Specify DevTools port to use with --controlled', Number)
282
- .option('--no-cookie-sync', 'Disable copying cookies from your main Chrome profile into the controlled window', false)
283
- .option('--timeout <seconds>', 'Seconds to wait for a SweetLink session (default 15)', Number)
284
- .option('--no-devtools', 'Skip DevTools automation when opening in controlled mode')
285
- .option('--headless', 'Launch the controlled browser headlessly', false)
286
- .option('--foreground', 'Bring the Chrome window to the foreground after opening (macOS only)', false)
279
+ .option("-e, --env <env>", "Environment to open (dev or prod)", "dev")
280
+ .option("-p, --path <path>", 'Optional path to append (default "")', "")
281
+ .option("--url <url>", "Explicit URL to open (overrides --path and --env)")
282
+ .option("--controlled", "Launch Chrome in controlled mode with DevTools enabled", false)
283
+ .option("--devtools-port <port>", "Specify DevTools port to use with --controlled", Number)
284
+ .option("--no-cookie-sync", "Disable copying cookies from your main Chrome profile into the controlled window", false)
285
+ .option("--timeout <seconds>", "Seconds to wait for a SweetLink session (default 15)", Number)
286
+ .option("--no-devtools", "Skip DevTools automation when opening in controlled mode")
287
+ .option("--headless", "Launch the controlled browser headlessly", false)
288
+ .option("--foreground", "Bring the Chrome window to the foreground after opening (macOS only)", false)
287
289
  .action(async (options, command) => {
288
290
  await runOpenCommand(options, command, program);
289
291
  });
290
292
  program
291
- .command('daemon')
292
- .description('Launch the SweetLink daemon process')
293
- .argument('[args...]', 'Arguments forwarded to sweetlinkd')
293
+ .command("daemon")
294
+ .description("Launch the SweetLink daemon process")
295
+ .argument("[args...]", "Arguments forwarded to sweetlinkd")
294
296
  .allowUnknownOption(true)
295
297
  .action((_args, command) => {
296
298
  runDaemonCommand(command);
@@ -299,16 +301,16 @@ async function runOpenCommand(options, command, rootProgram) {
299
301
  const context = buildOpenCommandContext(options, command, rootProgram);
300
302
  if (!context.controlled) {
301
303
  if (options.devtools === false) {
302
- console.log('--no-devtools is ignored when launching an uncontrolled browser window.');
304
+ console.log("--no-devtools is ignored when launching an uncontrolled browser window.");
303
305
  }
304
306
  if (options.headless) {
305
- console.log('--headless requires --controlled; launching a regular Chrome window instead.');
307
+ console.log("--headless requires --controlled; launching a regular Chrome window instead.");
306
308
  }
307
309
  }
308
310
  if (context.headless && context.foreground) {
309
- console.log('--foreground is ignored in headless mode.');
311
+ console.log("--foreground is ignored in headless mode.");
310
312
  }
311
- if (context.env === 'dev') {
313
+ if (context.env === "dev") {
312
314
  await ensureDevStackRunningRuntime(context.targetUrl, {
313
315
  repoRoot,
314
316
  healthPaths: context.healthCheckPaths ?? undefined,
@@ -331,23 +333,23 @@ async function runOpenCommand(options, command, rootProgram) {
331
333
  function runDaemonCommand(command) {
332
334
  const parent = command.parent;
333
335
  const rawArgs = parent?.rawArgs ?? process.argv;
334
- const daemonIndex = rawArgs.findIndex((arg) => arg === 'daemon');
336
+ const daemonIndex = rawArgs.findIndex((arg) => arg === "daemon");
335
337
  const afterDaemon = daemonIndex === -1 ? [] : rawArgs.slice(daemonIndex + 1);
336
- const passthroughIndex = afterDaemon.indexOf('--');
338
+ const passthroughIndex = afterDaemon.indexOf("--");
337
339
  const forwarded = passthroughIndex >= 0
338
340
  ? afterDaemon.slice(passthroughIndex + 1)
339
- : (command.args ?? []).filter((value) => typeof value === 'string');
340
- const child = spawn('sweetlinkd', forwarded, { stdio: 'inherit', env: cloneProcessEnv() });
341
- child.on('error', (error) => {
342
- if (isErrnoException(error) && error.code === 'ENOENT') {
341
+ : (command.args ?? []).filter((value) => typeof value === "string");
342
+ const child = spawn("sweetlinkd", forwarded, { stdio: "inherit", env: cloneProcessEnv() });
343
+ child.on("error", (error) => {
344
+ if (isErrnoException(error) && error.code === "ENOENT") {
343
345
  console.error('Unable to find "sweetlinkd" on PATH. Try `pnpm exec sweetlinkd` instead.');
344
346
  process.exitCode = 1;
345
347
  return;
346
348
  }
347
- console.error('Failed to launch sweetlinkd:', extractEventMessage(error));
349
+ console.error("Failed to launch sweetlinkd:", extractEventMessage(error));
348
350
  process.exitCode = 1;
349
351
  });
350
- child.on('exit', (code) => {
352
+ child.on("exit", (code) => {
351
353
  process.exitCode = code ?? 0;
352
354
  });
353
355
  }
@@ -358,11 +360,11 @@ function buildOpenCommandContext(options, command, rootProgram) {
358
360
  const parentOptions = readRootProgramOptions(parent);
359
361
  const developmentBaseUrl = parentOptions.appUrl;
360
362
  const productionBaseUrl = defaultProdAppUrl;
361
- const baseUrl = env === 'prod' ? productionBaseUrl : developmentBaseUrl;
363
+ const baseUrl = env === "prod" ? productionBaseUrl : developmentBaseUrl;
362
364
  const explicitTarget = resolveExplicitTargetUrl(options.url);
363
365
  const targetUrl = explicitTarget ?? buildOpenCommandTargetUrl(baseUrl, options.path);
364
366
  const serverConfig = config.servers[env] ?? null;
365
- const preferredPort = typeof options.devtoolsPort === 'number' && Number.isFinite(options.devtoolsPort)
367
+ const preferredPort = typeof options.devtoolsPort === "number" && Number.isFinite(options.devtoolsPort)
366
368
  ? options.devtoolsPort
367
369
  : undefined;
368
370
  const controlled = Boolean(options.controlled);
@@ -388,8 +390,8 @@ function buildOpenCommandContext(options, command, rootProgram) {
388
390
  };
389
391
  }
390
392
  function normalizeOpenCommandEnvironment(value) {
391
- const normalized = (value ?? 'dev').trim().toLowerCase();
392
- if (normalized === 'dev' || normalized === 'prod') {
393
+ const normalized = (value ?? "dev").trim().toLowerCase();
394
+ if (normalized === "dev" || normalized === "prod") {
393
395
  return normalized;
394
396
  }
395
397
  throw new Error('Invalid environment. Use "dev" or "prod".');
@@ -403,13 +405,13 @@ function buildOpenCommandTargetUrl(baseUrl, rawPath) {
403
405
  const message = error instanceof Error ? error.message : String(error);
404
406
  throw new Error(`Failed to parse base URL "${baseUrl}": ${message}`);
405
407
  }
406
- targetUrl.searchParams.set('sweetlink', 'auto');
407
- const fallbackPath = '/timeline';
408
+ targetUrl.searchParams.set("sweetlink", "auto");
409
+ const fallbackPath = "/timeline";
408
410
  const trimmedPath = rawPath?.trim();
409
411
  const pathSource = trimmedPath && trimmedPath.length > 0 ? trimmedPath : fallbackPath;
410
- const [pathPartRaw, queryPart] = pathSource.split('?', 2);
412
+ const [pathPartRaw, queryPart] = pathSource.split("?", 2);
411
413
  const pathPart = pathPartRaw && pathPartRaw.length > 0 ? pathPartRaw : fallbackPath;
412
- targetUrl.pathname = pathPart.startsWith('/') ? pathPart : `/${pathPart}`;
414
+ targetUrl.pathname = pathPart.startsWith("/") ? pathPart : `/${pathPart}`;
413
415
  if (queryPart) {
414
416
  const extraSearch = new URLSearchParams(queryPart);
415
417
  for (const [key, value] of extraSearch.entries()) {
@@ -428,7 +430,7 @@ function resolveExplicitTargetUrl(raw) {
428
430
  }
429
431
  try {
430
432
  const target = new URL(trimmed);
431
- target.searchParams.set('sweetlink', target.searchParams.get('sweetlink') ?? 'auto');
433
+ target.searchParams.set("sweetlink", target.searchParams.get("sweetlink") ?? "auto");
432
434
  return target;
433
435
  }
434
436
  catch (error) {
@@ -437,14 +439,14 @@ function resolveExplicitTargetUrl(raw) {
437
439
  }
438
440
  }
439
441
  function resolveCookieSyncPreference(command, cookieSyncOption) {
440
- const source = command.getOptionValueSource?.('cookieSync');
441
- if (source === 'default') {
442
+ const source = command.getOptionValueSource?.("cookieSync");
443
+ if (source === "default") {
442
444
  return true;
443
445
  }
444
446
  return cookieSyncOption !== false;
445
447
  }
446
448
  function resolveOpenCommandTimeoutSeconds(value) {
447
- if (typeof value === 'number' && Number.isFinite(value)) {
449
+ if (typeof value === "number" && Number.isFinite(value)) {
448
450
  return Math.max(0, value);
449
451
  }
450
452
  return 30;
@@ -457,7 +459,7 @@ async function fetchWaitTokenIfNeeded(context) {
457
459
  return await fetchCliToken(context.config);
458
460
  }
459
461
  catch (error) {
460
- console.warn('Unable to fetch CLI token for session wait:', error);
462
+ console.warn("Unable to fetch CLI token for session wait:", error);
461
463
  return null;
462
464
  }
463
465
  }
@@ -479,7 +481,7 @@ async function handleControlledOpen(context, waitToken) {
479
481
  await handleControlledReuse(context, waitToken, reuseResult);
480
482
  return;
481
483
  }
482
- console.warn('No reusable controlled Chrome session matched the target; launching a fresh controlled window.');
484
+ console.warn("No reusable controlled Chrome session matched the target; launching a fresh controlled window.");
483
485
  await handleControlledLaunch(context, waitToken);
484
486
  }
485
487
  async function handleControlledReuse(context, waitToken, reuseResult) {
@@ -496,32 +498,32 @@ async function handleControlledReuse(context, waitToken, reuseResult) {
496
498
  oauthScriptPath: context.oauthScriptPath,
497
499
  });
498
500
  if (deepLinkResult.signInClicked) {
499
- console.log('Triggered Sweetistics sign-in to reach the deep link.');
501
+ console.log("Triggered Sweetistics sign-in to reach the deep link.");
500
502
  }
501
503
  const oauthAttempt = deepLinkResult.oauthAttempt;
502
504
  if (oauthAttempt) {
503
505
  if (oauthAttempt.handled) {
504
- console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ?? 'click'}${oauthAttempt.clickedText ? ` (${oauthAttempt.clickedText})` : ''}.`);
506
+ console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ?? "click"}${oauthAttempt.clickedText ? ` (${oauthAttempt.clickedText})` : ""}.`);
505
507
  }
506
- else if (oauthAttempt.reason && oauthAttempt.reason !== 'button-not-found') {
508
+ else if (oauthAttempt.reason && oauthAttempt.reason !== "button-not-found") {
507
509
  const locationHint = oauthAttempt.url || oauthAttempt.title || oauthAttempt.host
508
- ? ` (at ${oauthAttempt.title ?? oauthAttempt.host ?? 'unknown page'} ${oauthAttempt.url ?? ''})`
509
- : '';
510
+ ? ` (at ${oauthAttempt.title ?? oauthAttempt.host ?? "unknown page"} ${oauthAttempt.url ?? ""})`
511
+ : "";
510
512
  console.log(`OAuth auto-accept skipped: ${oauthAttempt.reason}${locationHint}.`);
511
513
  }
512
514
  }
513
515
  }
514
516
  catch (error) {
515
517
  if (sweetLinkDebug) {
516
- console.warn('Deep-link auth flow failed:', error);
518
+ console.warn("Deep-link auth flow failed:", error);
517
519
  }
518
520
  }
519
521
  }
520
522
  else {
521
- console.log('DevTools automation disabled (--no-devtools); skipping telemetry bootstrap and OAuth auto-click.');
523
+ console.log("DevTools automation disabled (--no-devtools); skipping telemetry bootstrap and OAuth auto-click.");
522
524
  }
523
525
  if (context.enableDevtools && !deepLinkResult?.navigatedToTarget) {
524
- console.warn('Deep link did not resolve in reused controlled Chrome; launching a fresh controlled window.');
526
+ console.warn("Deep link did not resolve in reused controlled Chrome; launching a fresh controlled window.");
525
527
  const freshContext = {
526
528
  ...context,
527
529
  preferredPort: undefined,
@@ -531,24 +533,24 @@ async function handleControlledReuse(context, waitToken, reuseResult) {
531
533
  }
532
534
  console.log(`Reused controlled Chrome at ${reuseResult.devtoolsUrl} (env: ${context.env}).`);
533
535
  if (reuseResult.targetAlreadyOpen) {
534
- console.log('Target tab was already open; activated existing page.');
536
+ console.log("Target tab was already open; activated existing page.");
535
537
  }
536
538
  else {
537
- console.log('Opened a new tab in the existing controlled Chrome window.');
539
+ console.log("Opened a new tab in the existing controlled Chrome window.");
538
540
  }
539
541
  if (context.enableDevtools) {
540
- console.log('The screenshot command will continue to use this DevTools instance.');
542
+ console.log("The screenshot command will continue to use this DevTools instance.");
541
543
  }
542
544
  if (shouldFocus && context.enableDevtools) {
543
545
  const focused = await focusControlledChromePage(reuseResult.devtoolsUrl, context.targetUrlString);
544
546
  if (!focused && sweetLinkDebug) {
545
- console.warn('Unable to focus controlled Chrome window automatically.');
547
+ console.warn("Unable to focus controlled Chrome window automatically.");
546
548
  }
547
549
  }
548
550
  else if (context.foreground && !context.enableDevtools) {
549
- console.log('--foreground requires DevTools automation; skipping automatic focus.');
551
+ console.log("--foreground requires DevTools automation; skipping automatic focus.");
550
552
  }
551
- console.log('Remember this session codename. Run `pnpm sweetlink sessions`, copy the session id or codename, and use that handle for every follow-up command instead of rerunning `pnpm sweetlink open`.');
553
+ console.log("Remember this session codename. Run `pnpm sweetlink sessions`, copy the session id or codename, and use that handle for every follow-up command instead of rerunning `pnpm sweetlink open`.");
552
554
  await waitForSessionAfterOpen(context, waitToken, {
553
555
  devtoolsUrl: context.enableDevtools ? reuseResult.devtoolsUrl : undefined,
554
556
  trigger: context.enableDevtools
@@ -558,10 +560,10 @@ async function handleControlledReuse(context, waitToken, reuseResult) {
558
560
  ? () => triggerSweetLinkCliAuto(reuseResult.devtoolsUrl, context.targetUrlString)
559
561
  : undefined,
560
562
  failureMessage: context.enableDevtools
561
- ? 'Controlled Chrome reused but SweetLink did not register automatically.'
562
- : 'Controlled Chrome reused. DevTools automation disabled; verify the session from the UI if SweetLink does not appear.',
563
+ ? "Controlled Chrome reused but SweetLink did not register automatically."
564
+ : "Controlled Chrome reused. DevTools automation disabled; verify the session from the UI if SweetLink does not appear.",
563
565
  });
564
- await surfaceBlockingDiagnosticsAfterNavigation('SweetLink open', context.enableDevtools ? reuseResult.devtoolsUrl : undefined, context.targetUrlString);
566
+ await surfaceBlockingDiagnosticsAfterNavigation("SweetLink open", context.enableDevtools ? reuseResult.devtoolsUrl : undefined, context.targetUrlString);
565
567
  }
566
568
  async function handleControlledLaunch(context, waitToken) {
567
569
  const info = await launchControlledChrome(context.targetUrlString, {
@@ -570,7 +572,7 @@ async function handleControlledLaunch(context, waitToken) {
570
572
  headless: context.headless,
571
573
  foreground: context.foreground,
572
574
  });
573
- const userDataDirectoryDisplay = info.userDataDir.replace(os.homedir(), '~');
575
+ const userDataDirectoryDisplay = info.userDataDir.replace(os.homedir(), "~");
574
576
  if (context.enableDevtools) {
575
577
  await registerControlledChromeInstance(info.devtoolsUrl, info.userDataDir);
576
578
  await cleanupControlledChromeRegistry(info.devtoolsUrl);
@@ -582,56 +584,56 @@ async function handleControlledLaunch(context, waitToken) {
582
584
  oauthScriptPath: context.oauthScriptPath,
583
585
  });
584
586
  if (deepLinkResult.signInClicked) {
585
- console.log('Triggered Sweetistics sign-in to reach the deep link.');
587
+ console.log("Triggered Sweetistics sign-in to reach the deep link.");
586
588
  }
587
589
  const oauthAttempt = deepLinkResult.oauthAttempt;
588
590
  if (oauthAttempt) {
589
591
  if (oauthAttempt.handled) {
590
- console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ?? 'click'}${oauthAttempt.clickedText ? ` (${oauthAttempt.clickedText})` : ''}.`);
592
+ console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ?? "click"}${oauthAttempt.clickedText ? ` (${oauthAttempt.clickedText})` : ""}.`);
591
593
  }
592
- else if (oauthAttempt.reason && oauthAttempt.reason !== 'button-not-found') {
594
+ else if (oauthAttempt.reason && oauthAttempt.reason !== "button-not-found") {
593
595
  const locationHint = oauthAttempt.url || oauthAttempt.title || oauthAttempt.host
594
- ? ` (at ${oauthAttempt.title ?? oauthAttempt.host ?? 'unknown page'} ${oauthAttempt.url ?? ''})`
595
- : '';
596
+ ? ` (at ${oauthAttempt.title ?? oauthAttempt.host ?? "unknown page"} ${oauthAttempt.url ?? ""})`
597
+ : "";
596
598
  console.log(`OAuth auto-accept skipped: ${oauthAttempt.reason}${locationHint}.`);
597
599
  }
598
600
  }
599
601
  }
600
602
  catch (error) {
601
603
  if (sweetLinkDebug) {
602
- console.warn('Deep-link auth flow failed:', error);
604
+ console.warn("Deep-link auth flow failed:", error);
603
605
  }
604
606
  }
605
607
  }
606
608
  else {
607
- console.log('DevTools automation disabled (--no-devtools); launched without OAuth auto-click.');
609
+ console.log("DevTools automation disabled (--no-devtools); launched without OAuth auto-click.");
608
610
  }
609
611
  console.log(`Opened controlled Chrome window to ${context.targetUrlString} (env: ${context.env}).`);
610
612
  console.log(`DevTools endpoint: ${info.devtoolsUrl}`);
611
613
  console.log(`User data dir : ${userDataDirectoryDisplay}`);
612
614
  if (context.enableDevtools) {
613
- console.log('The screenshot command will auto-detect this DevTools instance.');
615
+ console.log("The screenshot command will auto-detect this DevTools instance.");
614
616
  }
615
617
  if (context.headless) {
616
- console.log('Running in headless mode (--headless).');
618
+ console.log("Running in headless mode (--headless).");
617
619
  }
618
620
  const shouldFocus = context.foreground && !context.headless;
619
621
  if (shouldFocus && context.enableDevtools) {
620
622
  const focused = await focusControlledChromePage(info.devtoolsUrl, context.targetUrlString);
621
623
  if (!focused && sweetLinkDebug) {
622
- console.warn('Unable to focus controlled Chrome window automatically.');
624
+ console.warn("Unable to focus controlled Chrome window automatically.");
623
625
  }
624
626
  }
625
627
  else if (context.foreground && !context.enableDevtools) {
626
- console.log('--foreground requires DevTools automation; skipping automatic focus.');
628
+ console.log("--foreground requires DevTools automation; skipping automatic focus.");
627
629
  }
628
630
  await waitForSessionAfterOpen(context, waitToken, {
629
631
  devtoolsUrl: context.enableDevtools ? info.devtoolsUrl : undefined,
630
632
  failureMessage: context.enableDevtools
631
- ? 'Controlled Chrome launched but SweetLink did not register automatically; keep the window open and retry from the UI if needed.'
632
- : 'Controlled Chrome launched without DevTools automation; complete the login manually if SweetLink does not register.',
633
+ ? "Controlled Chrome launched but SweetLink did not register automatically; keep the window open and retry from the UI if needed."
634
+ : "Controlled Chrome launched without DevTools automation; complete the login manually if SweetLink does not register.",
633
635
  });
634
- await surfaceBlockingDiagnosticsAfterNavigation('SweetLink open', context.enableDevtools ? info.devtoolsUrl : undefined, context.targetUrlString);
636
+ await surfaceBlockingDiagnosticsAfterNavigation("SweetLink open", context.enableDevtools ? info.devtoolsUrl : undefined, context.targetUrlString);
635
637
  }
636
638
  async function waitForSessionAfterOpen(context, waitToken, options) {
637
639
  if (!waitToken || context.timeoutSeconds <= 0) {
@@ -676,14 +678,14 @@ async function waitForSessionAfterOpen(context, waitToken, options) {
676
678
  try {
677
679
  const diagnostics = await collectBootstrapDiagnostics(options.devtoolsUrl, candidates);
678
680
  if (diagnostics) {
679
- console.warn('SweetLink bootstrap diagnostics (DevTools snapshot):');
680
- logBootstrapDiagnostics('SweetLink', diagnostics);
681
+ console.warn("SweetLink bootstrap diagnostics (DevTools snapshot):");
682
+ logBootstrapDiagnostics("SweetLink", diagnostics);
681
683
  }
682
684
  }
683
685
  catch (error) {
684
- console.warn('Failed to collect DevTools diagnostics:', error);
686
+ console.warn("Failed to collect DevTools diagnostics:", error);
685
687
  }
686
- await logPuppeteerPageSnapshot('SweetLink', options.devtoolsUrl, context.targetUrlString);
688
+ await logPuppeteerPageSnapshot("SweetLink", options.devtoolsUrl, context.targetUrlString);
687
689
  }
688
690
  }
689
691
  // Runs after every navigation so CLI users see the same crash text the browser shows.
@@ -699,7 +701,8 @@ async function surfaceBlockingDiagnosticsAfterNavigation(label, devtoolsUrl, tar
699
701
  const diagnostics = await collectBootstrapDiagnostics(devtoolsUrl, candidates);
700
702
  if (diagnostics) {
701
703
  const bootstrapIncomplete = diagnostics.autoFlag !== true ||
702
- (typeof diagnostics.sessionStorageAuto === 'string' && diagnostics.sessionStorageAuto.length > 0);
704
+ (typeof diagnostics.sessionStorageAuto === "string" &&
705
+ diagnostics.sessionStorageAuto.length > 0);
703
706
  const hasBlocking = diagnosticsContainBlockingIssues(diagnostics) || bootstrapIncomplete;
704
707
  if (hasBlocking) {
705
708
  console.warn(`${label}: detected runtime anomalies after navigation.`);
@@ -716,7 +719,7 @@ async function surfaceBlockingDiagnosticsAfterNavigation(label, devtoolsUrl, tar
716
719
  }
717
720
  }
718
721
  catch (error) {
719
- console.warn('Failed to collect DevTools diagnostics after navigation:', error);
722
+ console.warn("Failed to collect DevTools diagnostics after navigation:", error);
720
723
  }
721
724
  if (!loggedBlocking) {
722
725
  const nextErrors = await fetchNextDevtoolsErrors(targetUrl);
@@ -742,7 +745,7 @@ async function logPuppeteerPageSnapshot(label, devtoolsUrl, targetUrl) {
742
745
  }
743
746
  if (snapshot.overlayText) {
744
747
  console.warn(`${label} overlay (via Puppeteer):`);
745
- for (const line of snapshot.overlayText.split('\n')) {
748
+ for (const line of snapshot.overlayText.split("\n")) {
746
749
  const trimmed = line.trim();
747
750
  if (trimmed.length > 0) {
748
751
  console.warn(` ${trimmed}`);
@@ -751,7 +754,7 @@ async function logPuppeteerPageSnapshot(label, devtoolsUrl, targetUrl) {
751
754
  return;
752
755
  }
753
756
  if (snapshot.bodyText && snapshot.bodyText.trim().length > 0) {
754
- const condensed = snapshot.bodyText.replaceAll(/\s+/g, ' ').trim();
757
+ const condensed = snapshot.bodyText.replaceAll(/\s+/g, " ").trim();
755
758
  const snippet = condensed.length > 600 ? `${condensed.slice(0, 600)}…` : condensed;
756
759
  console.warn(`${label} body text (via Puppeteer): ${snippet}`);
757
760
  }
@@ -767,50 +770,52 @@ async function handleUncontrolledOpen(context, waitToken) {
767
770
  await launchChrome(context.targetUrlString, { foreground: context.foreground });
768
771
  console.log(`Opened Chrome to ${context.targetUrlString} (env: ${context.env}).`);
769
772
  await waitForSessionAfterOpen(context, waitToken, {
770
- failureMessage: 'Chrome tab opened but SweetLink did not register within the timeout window. Use `pnpm sweetlink sessions` to inspect manually.',
773
+ failureMessage: "Chrome tab opened but SweetLink did not register within the timeout window. Use `pnpm sweetlink sessions` to inspect manually.",
771
774
  });
772
775
  }
773
776
  program
774
- .command('smoke')
775
- .description('Run the SweetLink authenticated smoke test across non-admin routes')
776
- .option('--session <sessionId>', 'Existing SweetLink session id or codename to reuse')
777
- .option('--routes <routes>', `Comma-separated list of routes (default ${DEFAULT_SMOKE_ROUTES.join(', ')})`)
778
- .option('--timeout <seconds>', 'Per-route timeout in seconds (default 45)', Number)
779
- .option('--resume', 'Resume from the last completed route', false)
777
+ .command("smoke")
778
+ .description("Run the SweetLink authenticated smoke test across non-admin routes")
779
+ .option("--session <sessionId>", "Existing SweetLink session id or codename to reuse")
780
+ .option("--routes <routes>", `Comma-separated list of routes (default ${DEFAULT_SMOKE_ROUTES.join(", ")})`)
781
+ .option("--timeout <seconds>", "Per-route timeout in seconds (default 45)", Number)
782
+ .option("--resume", "Resume from the last completed route", false)
780
783
  .action(async function () {
781
784
  const options = readCommandOptions(this);
782
785
  const routes = deriveSmokeRoutes(options.routes, DEFAULT_SMOKE_ROUTES);
783
786
  if (routes.length === 0) {
784
- console.log('No routes specified for the smoke test. Provide --routes or keep the default set.');
787
+ console.log("No routes specified for the smoke test. Provide --routes or keep the default set.");
785
788
  return;
786
789
  }
787
- const timeoutSeconds = typeof options.timeout === 'number' && Number.isFinite(options.timeout) ? Math.max(5, options.timeout) : 45;
790
+ const timeoutSeconds = typeof options.timeout === "number" && Number.isFinite(options.timeout)
791
+ ? Math.max(5, options.timeout)
792
+ : 45;
788
793
  await runSweetLinkSmoke({ sessionHint: options.session, routes, timeoutSeconds, resume: options.resume === true }, this);
789
794
  });
790
795
  program
791
- .command('screenshot <sessionId>')
792
- .description('Capture a JPEG screenshot from a SweetLink session')
793
- .option('-s, --selector <selector>', 'CSS selector to capture (defaults to full page)')
794
- .option('-q, --quality <0-1>', 'JPEG quality (0-1, default 0.92)', Number)
795
- .option('-o, --output <path>', 'Output path (defaults to /tmp/sweetlink-<timestamp>.jpg)')
796
- .option('-t, --timeout <ms>', 'Command timeout in milliseconds (default 30_000)', Number, 30_000)
797
- .option('--devtools-url <url>', 'DevTools HTTP endpoint (default http://127.0.0.1:9222)')
798
- .option('--method <method>', 'Capture method: auto, puppeteer, html2canvas, html-to-image', 'auto')
799
- .option('--scroll-into-view', 'Scroll the target into view before capturing', false)
800
- .option('--scroll-selector <selector>', 'Selector to scroll into view (defaults to capture selector)')
801
- .option('--wait-for-selector <selector>', 'Wait for a selector to appear before capturing')
802
- .option('--wait-visible', 'Require the wait selector to be visible', false)
803
- .option('--wait-timeout <ms>', 'Timeout for --wait-for-selector (default 10_000)', Number, 10_000)
804
- .option('--delay <ms>', 'Delay in milliseconds after hooks run (default 0)', Number)
805
- .option('--before-script <codeOrPath>', 'Inline JS (or @path) to run before capture')
806
- .option('--preset <name>', 'Hook preset compatibility alias (card-ready is applied automatically and no longer requires this flag)')
807
- .option('--prompt <prompt>', 'Send the saved screenshot to Codex for analysis')
808
- .addOption(program.createOption('--question <prompt>').hideHelp())
796
+ .command("screenshot <sessionId>")
797
+ .description("Capture a JPEG screenshot from a SweetLink session")
798
+ .option("-s, --selector <selector>", "CSS selector to capture (defaults to full page)")
799
+ .option("-q, --quality <0-1>", "JPEG quality (0-1, default 0.92)", Number)
800
+ .option("-o, --output <path>", "Output path (defaults to /tmp/sweetlink-<timestamp>.jpg)")
801
+ .option("-t, --timeout <ms>", "Command timeout in milliseconds (default 30_000)", Number, 30_000)
802
+ .option("--devtools-url <url>", "DevTools HTTP endpoint (default http://127.0.0.1:9222)")
803
+ .option("--method <method>", "Capture method: auto, puppeteer, html2canvas, html-to-image", "auto")
804
+ .option("--scroll-into-view", "Scroll the target into view before capturing", false)
805
+ .option("--scroll-selector <selector>", "Selector to scroll into view (defaults to capture selector)")
806
+ .option("--wait-for-selector <selector>", "Wait for a selector to appear before capturing")
807
+ .option("--wait-visible", "Require the wait selector to be visible", false)
808
+ .option("--wait-timeout <ms>", "Timeout for --wait-for-selector (default 10_000)", Number, 10_000)
809
+ .option("--delay <ms>", "Delay in milliseconds after hooks run (default 0)", Number)
810
+ .option("--before-script <codeOrPath>", "Inline JS (or @path) to run before capture")
811
+ .option("--preset <name>", "Hook preset compatibility alias (card-ready is applied automatically and no longer requires this flag)")
812
+ .option("--prompt <prompt>", "Send the saved screenshot to Codex for analysis")
813
+ .addOption(program.createOption("--question <prompt>").hideHelp())
809
814
  .action(async (sessionId, options, command) => {
810
815
  const config = resolveConfig(command);
811
816
  const resolvedSessionId = await resolveSessionIdFromHint(sessionId, config);
812
817
  const token = await fetchCliToken(config);
813
- const mode = options.selector ? 'element' : 'full';
818
+ const mode = options.selector ? "element" : "full";
814
819
  const prompt = resolvePromptOption(options);
815
820
  const suppressOutput = Boolean(prompt);
816
821
  const logInfo = (...args) => {
@@ -819,15 +824,17 @@ program
819
824
  }
820
825
  };
821
826
  const now = new Date();
822
- const defaultOutput = path.join(os.tmpdir(), `sweetlink-${now.toISOString().replaceAll(':', '-').replaceAll('.', '-')}.jpg`);
827
+ const defaultOutput = path.join(os.tmpdir(), `sweetlink-${now.toISOString().replaceAll(":", "-").replaceAll(".", "-")}.jpg`);
823
828
  const outputPath = options.output ? path.resolve(options.output) : defaultOutput;
824
- const quality = typeof options.quality === 'number' && !Number.isNaN(options.quality) ? options.quality : 0.92;
829
+ const quality = typeof options.quality === "number" && !Number.isNaN(options.quality)
830
+ ? options.quality
831
+ : 0.92;
825
832
  const method = normalizeScreenshotMethod(options.method);
826
833
  await mkdir(path.dirname(outputPath), { recursive: true }).catch(() => {
827
834
  /* ignore directory creation failures; writeFile will surface if it truly fails */
828
835
  });
829
836
  const devtoolsConfig = await loadDevToolsConfig();
830
- const devtoolsUrl = options.devtoolsUrl ?? devtoolsConfig?.devtoolsUrl ?? 'http://127.0.0.1:9222';
837
+ const devtoolsUrl = options.devtoolsUrl ?? devtoolsConfig?.devtoolsUrl ?? "http://127.0.0.1:9222";
831
838
  let sessionSummary;
832
839
  try {
833
840
  sessionSummary = await getSessionSummaryById(config, token, resolvedSessionId);
@@ -835,7 +842,7 @@ program
835
842
  catch {
836
843
  sessionSummary = undefined;
837
844
  }
838
- const wantsPuppeteer = method === 'puppeteer' || method === 'auto';
845
+ const wantsPuppeteer = method === "puppeteer" || method === "auto";
839
846
  if (wantsPuppeteer && sessionSummary?.url) {
840
847
  const devtoolsCaptureResult = await attemptDevToolsCapture({
841
848
  devtoolsUrl,
@@ -847,21 +854,24 @@ program
847
854
  });
848
855
  if (devtoolsCaptureResult) {
849
856
  logInfo(`Saved screenshot to ${outputPath} (${devtoolsCaptureResult.width}x${devtoolsCaptureResult.height}, ${devtoolsCaptureResult.sizeKb.toFixed(1)} KB, method: ${devtoolsCaptureResult.renderer}).`);
850
- await maybeDescribeScreenshot(prompt, outputPath, { silent: suppressOutput, appLabel: config.appLabel });
857
+ await maybeDescribeScreenshot(prompt, outputPath, {
858
+ silent: suppressOutput,
859
+ appLabel: config.appLabel,
860
+ });
851
861
  return;
852
862
  }
853
- if (method === 'puppeteer') {
854
- throw new Error('Failed to capture via Puppeteer. Ensure Chrome is running with a DevTools port (try `pnpm sweetlink open --controlled`).');
863
+ if (method === "puppeteer") {
864
+ throw new Error("Failed to capture via Puppeteer. Ensure Chrome is running with a DevTools port (try `pnpm sweetlink open --controlled`).");
855
865
  }
856
866
  }
857
- const rendererOverride = method === 'html2canvas' || method === 'html-to-image' ? method : undefined;
867
+ const rendererOverride = method === "html2canvas" || method === "html-to-image" ? method : undefined;
858
868
  const beforeScriptCode = await resolveHookSnippet(options.beforeScript);
859
869
  const commandOptions = readCommandOptions(command);
860
870
  const presetCandidate = commandOptions.preset ?? options.preset;
861
- const presetRaw = typeof presetCandidate === 'string' ? presetCandidate.trim() : '';
871
+ const presetRaw = typeof presetCandidate === "string" ? presetCandidate.trim() : "";
862
872
  const presetName = presetRaw.toLowerCase();
863
873
  if (presetName.length > 0) {
864
- if (presetName === 'card-ready') {
874
+ if (presetName === "card-ready") {
865
875
  logInfo('Preset "card-ready" is now the default hook stack; the flag is optional.');
866
876
  }
867
877
  else {
@@ -874,31 +884,35 @@ program
874
884
  scrollSelector: options.scrollSelector,
875
885
  waitSelector: options.waitForSelector,
876
886
  waitVisible: options.waitVisible === undefined ? undefined : Boolean(options.waitVisible),
877
- waitTimeout: typeof options.waitTimeout === 'number' && Number.isFinite(options.waitTimeout)
887
+ waitTimeout: typeof options.waitTimeout === "number" && Number.isFinite(options.waitTimeout)
878
888
  ? options.waitTimeout
879
889
  : undefined,
880
- delayMs: typeof options.delay === 'number' && Number.isFinite(options.delay) ? options.delay : undefined,
890
+ delayMs: typeof options.delay === "number" && Number.isFinite(options.delay)
891
+ ? options.delay
892
+ : undefined,
881
893
  beforeScript: beforeScriptCode ?? undefined,
882
894
  };
883
895
  const hooks = buildScreenshotHooks(hookOptions);
884
896
  if (hooks.length > 0) {
885
- logInfo(`Applying ${hooks.length} pre-capture hook${hooks.length === 1 ? '' : 's'} before screenshot.`);
897
+ logInfo(`Applying ${hooks.length} pre-capture hook${hooks.length === 1 ? "" : "s"} before screenshot.`);
886
898
  }
887
899
  const payload = {
888
- type: 'screenshot',
900
+ type: "screenshot",
889
901
  id: createSweetLinkCommandId(),
890
902
  mode,
891
903
  selector: options.selector,
892
904
  quality,
893
- timeoutMs: typeof options.timeout === 'number' && !Number.isNaN(options.timeout) ? options.timeout : 30_000,
905
+ timeoutMs: typeof options.timeout === "number" && !Number.isNaN(options.timeout)
906
+ ? options.timeout
907
+ : 30_000,
894
908
  renderer: rendererOverride,
895
909
  hooks: hooks.length > 0 ? hooks : undefined,
896
910
  };
897
911
  const response = await fetchJson(`${config.daemonBaseUrl}/sessions/${encodeURIComponent(resolvedSessionId)}/command`, {
898
- method: 'POST',
912
+ method: "POST",
899
913
  headers: {
900
914
  Authorization: `Bearer ${token}`,
901
- 'Content-Type': 'application/json',
915
+ "Content-Type": "application/json",
902
916
  },
903
917
  body: JSON.stringify(payload),
904
918
  });
@@ -919,7 +933,9 @@ program
919
933
  if (fallbackOutcome.handled) {
920
934
  return;
921
935
  }
922
- const fallbackError = fallbackOutcome.fallbackResult.ok ? null : fallbackOutcome.fallbackResult.error;
936
+ const fallbackError = fallbackOutcome.fallbackResult.ok
937
+ ? null
938
+ : fallbackOutcome.fallbackResult.error;
923
939
  const recovered = await tryDevToolsRecovery({
924
940
  sessionUrl: sessionSummary?.url,
925
941
  devtoolsUrl,
@@ -961,41 +977,44 @@ program
961
977
  return;
962
978
  }
963
979
  await persistScreenshotResult(outputPath, result, { silent: suppressOutput });
964
- await maybeDescribeScreenshot(prompt, outputPath, { silent: suppressOutput, appLabel: config.appLabel });
980
+ await maybeDescribeScreenshot(prompt, outputPath, {
981
+ silent: suppressOutput,
982
+ appLabel: config.appLabel,
983
+ });
965
984
  });
966
985
  program
967
- .command('selectors <sessionId>')
968
- .description('Discover candidate selectors within a SweetLink session')
969
- .option('-l, --limit <count>', 'Maximum number of candidates to return (default 20)', Number)
970
- .option('-m, --max <count>', 'Alias for --limit', Number)
971
- .option('--scope <selector>', 'Restrict discovery to elements inside this selector')
972
- .option('--include-hidden', 'Include off-screen or hidden elements', false)
973
- .option('--json', 'Output JSON payload', false)
986
+ .command("selectors <sessionId>")
987
+ .description("Discover candidate selectors within a SweetLink session")
988
+ .option("-l, --limit <count>", "Maximum number of candidates to return (default 20)", Number)
989
+ .option("-m, --max <count>", "Alias for --limit", Number)
990
+ .option("--scope <selector>", "Restrict discovery to elements inside this selector")
991
+ .option("--include-hidden", "Include off-screen or hidden elements", false)
992
+ .option("--json", "Output JSON payload", false)
974
993
  .action(async (sessionId, options, command) => {
975
994
  const config = resolveConfig(command);
976
995
  const resolvedSessionId = await resolveSessionIdFromHint(sessionId, config);
977
996
  const token = await fetchCliToken(config);
978
997
  const limit = (() => {
979
- if (typeof options.max === 'number' && Number.isFinite(options.max)) {
998
+ if (typeof options.max === "number" && Number.isFinite(options.max)) {
980
999
  return Math.max(1, Math.floor(options.max));
981
1000
  }
982
- if (typeof options.limit === 'number' && Number.isFinite(options.limit)) {
1001
+ if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
983
1002
  return Math.max(1, Math.floor(options.limit));
984
1003
  }
985
1004
  return 20;
986
1005
  })();
987
1006
  const payload = {
988
- type: 'discoverSelectors',
1007
+ type: "discoverSelectors",
989
1008
  id: createSweetLinkCommandId(),
990
1009
  scopeSelector: options.scope ?? null,
991
1010
  limit,
992
1011
  includeHidden: Boolean(options.includeHidden),
993
1012
  };
994
1013
  const response = await fetchJson(`${config.daemonBaseUrl}/sessions/${encodeURIComponent(resolvedSessionId)}/command`, {
995
- method: 'POST',
1014
+ method: "POST",
996
1015
  headers: {
997
1016
  Authorization: `Bearer ${token}`,
998
- 'Content-Type': 'application/json',
1017
+ "Content-Type": "application/json",
999
1018
  },
1000
1019
  body: JSON.stringify(payload),
1001
1020
  });
@@ -1018,15 +1037,15 @@ program
1018
1037
  return;
1019
1038
  }
1020
1039
  if (candidates.length === 0) {
1021
- console.log('No candidates were discovered. Ensure the target UI is mounted and try again.');
1022
- console.log('Tip: use --scope to limit discovery or --include-hidden to inspect collapsed panels.');
1040
+ console.log("No candidates were discovered. Ensure the target UI is mounted and try again.");
1041
+ console.log("Tip: use --scope to limit discovery or --include-hidden to inspect collapsed panels.");
1023
1042
  return;
1024
1043
  }
1025
- console.log(`Discovered ${candidates.length} selector candidate${candidates.length === 1 ? '' : 's'} (limit ${limit}):\n`);
1044
+ console.log(`Discovered ${candidates.length} selector candidate${candidates.length === 1 ? "" : "s"} (limit ${limit}):\n`);
1026
1045
  for (const candidate of candidates.slice(0, limit)) {
1027
1046
  console.log(`• ${candidate.selector}`);
1028
1047
  console.log(` Hook : ${candidate.hook} (score ${candidate.score})`);
1029
- console.log(` Visible : ${candidate.visible ? 'yes' : 'no'} (${candidate.size.width}x${candidate.size.height})`);
1048
+ console.log(` Visible : ${candidate.visible ? "yes" : "no"} (${candidate.size.width}x${candidate.size.height})`);
1030
1049
  console.log(` Text : ${candidate.textSnippet}`);
1031
1050
  if (candidate.dataTarget) {
1032
1051
  console.log(` Target : data-sweetlink-target="${candidate.dataTarget}"`);
@@ -1039,64 +1058,66 @@ program
1039
1058
  }
1040
1059
  console.log(` Position : x=${candidate.position.left}, y=${candidate.position.top}`);
1041
1060
  console.log(` Path : ${candidate.path}`);
1042
- console.log('');
1061
+ console.log("");
1043
1062
  }
1044
1063
  });
1045
- const devtools = program.command('devtools').description('Inspect DevTools-enabled Chrome sessions');
1064
+ const devtools = program
1065
+ .command("devtools")
1066
+ .description("Inspect DevTools-enabled Chrome sessions");
1046
1067
  devtools
1047
- .command('status')
1048
- .description('Show DevTools connection status and telemetry summary')
1049
- .option('--json', 'Output JSON payload', false)
1068
+ .command("status")
1069
+ .description("Show DevTools connection status and telemetry summary")
1070
+ .option("--json", "Output JSON payload", false)
1050
1071
  .action(async (options, command) => {
1051
1072
  await devtoolsStatus(options, command);
1052
1073
  });
1053
1074
  devtools
1054
- .command('tabs')
1055
- .description('List open tabs in the controlled Chrome window')
1056
- .option('--json', 'Output JSON payload', false)
1075
+ .command("tabs")
1076
+ .description("List open tabs in the controlled Chrome window")
1077
+ .option("--json", "Output JSON payload", false)
1057
1078
  .action(async (options) => {
1058
1079
  await devtoolsTabs(options);
1059
1080
  });
1060
1081
  devtools
1061
- .command('console')
1062
- .description('Print buffered console events from the controlled Chrome window')
1063
- .option('--tail <count>', 'Number of entries to show (default 50)', Number, 50)
1064
- .option('--json', 'Output JSON payload', false)
1082
+ .command("console")
1083
+ .description("Print buffered console events from the controlled Chrome window")
1084
+ .option("--tail <count>", "Number of entries to show (default 50)", Number, 50)
1085
+ .option("--json", "Output JSON payload", false)
1065
1086
  .action(async (options) => {
1066
1087
  await devtoolsShowConsole(options);
1067
1088
  });
1068
1089
  devtools
1069
- .command('network')
1070
- .description('Print buffered network requests from the controlled Chrome window')
1071
- .option('--tail <count>', 'Number of entries to show (default 50)', Number, 50)
1072
- .option('--json', 'Output JSON payload', false)
1090
+ .command("network")
1091
+ .description("Print buffered network requests from the controlled Chrome window")
1092
+ .option("--tail <count>", "Number of entries to show (default 50)", Number, 50)
1093
+ .option("--json", "Output JSON payload", false)
1073
1094
  .action(async (options) => {
1074
1095
  await devtoolsShowNetwork(options);
1075
1096
  });
1076
1097
  devtools
1077
- .command('listen')
1078
- .description('Attach to DevTools and buffer console/network telemetry locally')
1079
- .option('--session <sessionId>', 'Associate telemetry with a SweetLink session id')
1080
- .option('--reset', 'Clear existing telemetry buffers before listening', false)
1081
- .option('--background', 'Run without interactive prompts (for automation)', false)
1098
+ .command("listen")
1099
+ .description("Attach to DevTools and buffer console/network telemetry locally")
1100
+ .option("--session <sessionId>", "Associate telemetry with a SweetLink session id")
1101
+ .option("--reset", "Clear existing telemetry buffers before listening", false)
1102
+ .option("--background", "Run without interactive prompts (for automation)", false)
1082
1103
  .action(async (options, command) => {
1083
1104
  await devtoolsListen(options, command);
1084
1105
  });
1085
1106
  devtools
1086
- .command('authorize')
1087
- .description('Attempt to auto-click the OAuth authorize prompt in the controlled browser')
1088
- .option('--url <url>', 'Override the candidate OAuth URL (defaults to the tracked session)')
1107
+ .command("authorize")
1108
+ .description("Attempt to auto-click the OAuth authorize prompt in the controlled browser")
1109
+ .option("--url <url>", "Override the candidate OAuth URL (defaults to the tracked session)")
1089
1110
  .action(async (options, command) => {
1090
1111
  await devtoolsAuthorize(options, command);
1091
1112
  });
1092
- program.hook('preAction', () => {
1113
+ program.hook("preAction", () => {
1093
1114
  // Ensure commander does not swallow promise rejections so we can log helpful messages.
1094
- process.on('unhandledRejection', (error) => {
1115
+ process.on("unhandledRejection", (error) => {
1095
1116
  reportError(error);
1096
1117
  process.exitCode = 1;
1097
1118
  });
1098
1119
  });
1099
- program.addHelpText('afterAll', `
1120
+ program.addHelpText("afterAll", `
1100
1121
  DevTools commands:
1101
1122
  devtools status Show endpoint reachability, viewport, and buffer sizes
1102
1123
  devtools tabs List open tabs in the controlled Chrome window
@@ -1128,19 +1149,19 @@ async function resolveHookSnippet(value) {
1128
1149
  if (trimmed.length === 0) {
1129
1150
  return null;
1130
1151
  }
1131
- if (trimmed.startsWith('@')) {
1152
+ if (trimmed.startsWith("@")) {
1132
1153
  const candidatePath = trimmed.slice(1).trim();
1133
1154
  if (!candidatePath) {
1134
- throw new Error('Expected a file path after @ for --before-script.');
1155
+ throw new Error("Expected a file path after @ for --before-script.");
1135
1156
  }
1136
1157
  const absolute = path.isAbsolute(candidatePath) ? candidatePath : path.resolve(candidatePath);
1137
- const hookContents = await readFile(absolute, 'utf8');
1158
+ const hookContents = await readFile(absolute, "utf8");
1138
1159
  return hookContents.toString();
1139
1160
  }
1140
1161
  return trimmed;
1141
1162
  }
1142
1163
  export function formatPathForDisplay(value) {
1143
- return value.replace(os.homedir(), '~');
1164
+ return value.replace(os.homedir(), "~");
1144
1165
  }
1145
1166
  function reportError(error) {
1146
1167
  if (error instanceof CommanderError) {
@@ -1151,32 +1172,32 @@ function reportError(error) {
1151
1172
  console.error(error.message);
1152
1173
  return;
1153
1174
  }
1154
- console.error('Unexpected error', error);
1175
+ console.error("Unexpected error", error);
1155
1176
  }
1156
1177
  function normalizeScreenshotMethod(input) {
1157
1178
  if (!input) {
1158
- return 'auto';
1179
+ return "auto";
1159
1180
  }
1160
1181
  const normalized = input.toLowerCase().trim();
1161
1182
  switch (normalized) {
1162
- case 'auto': {
1163
- return 'auto';
1183
+ case "auto": {
1184
+ return "auto";
1164
1185
  }
1165
- case 'puppeteer': {
1166
- return 'puppeteer';
1186
+ case "puppeteer": {
1187
+ return "puppeteer";
1167
1188
  }
1168
- case 'html2canvas':
1169
- case 'html-2-canvas': {
1170
- return 'html2canvas';
1189
+ case "html2canvas":
1190
+ case "html-2-canvas": {
1191
+ return "html2canvas";
1171
1192
  }
1172
- case 'html-to-image':
1173
- case 'htmltoimage':
1174
- case 'dom-to-image':
1175
- case 'domtoimage': {
1176
- return 'html-to-image';
1193
+ case "html-to-image":
1194
+ case "htmltoimage":
1195
+ case "dom-to-image":
1196
+ case "domtoimage": {
1197
+ return "html-to-image";
1177
1198
  }
1178
1199
  default: {
1179
- throw new Error('Invalid screenshot method. Use auto, puppeteer, html2canvas, or html-to-image.');
1200
+ throw new Error("Invalid screenshot method. Use auto, puppeteer, html2canvas, or html-to-image.");
1180
1201
  }
1181
1202
  }
1182
1203
  }
@@ -1184,7 +1205,7 @@ async function resolveSmokePrerequisites(params, command) {
1184
1205
  const config = resolveConfig(command);
1185
1206
  const devtoolsConfig = await loadDevToolsConfig();
1186
1207
  if (!devtoolsConfig?.devtoolsUrl) {
1187
- console.error('DevTools endpoint not found. Launch a controlled Chrome window with `pnpm sweetlink open --controlled` first.');
1208
+ console.error("DevTools endpoint not found. Launch a controlled Chrome window with `pnpm sweetlink open --controlled` first.");
1188
1209
  process.exitCode = 1;
1189
1210
  return null;
1190
1211
  }
@@ -1192,7 +1213,7 @@ async function resolveSmokePrerequisites(params, command) {
1192
1213
  ? await resolveSessionIdFromHint(params.sessionHint, config)
1193
1214
  : (devtoolsConfig.sessionId ?? null);
1194
1215
  if (!sessionId) {
1195
- console.error('Unable to determine a SweetLink session. Pass --session or rerun `pnpm sweetlink open --controlled`.');
1216
+ console.error("Unable to determine a SweetLink session. Pass --session or rerun `pnpm sweetlink open --controlled`.");
1196
1217
  process.exitCode = 1;
1197
1218
  return null;
1198
1219
  }
@@ -1201,7 +1222,7 @@ async function resolveSmokePrerequisites(params, command) {
1201
1222
  token = await fetchCliToken(config);
1202
1223
  }
1203
1224
  catch (error) {
1204
- console.error('Unable to fetch SweetLink CLI token:', extractEventMessage(error));
1225
+ console.error("Unable to fetch SweetLink CLI token:", extractEventMessage(error));
1205
1226
  process.exitCode = 1;
1206
1227
  return null;
1207
1228
  }
@@ -1228,18 +1249,18 @@ async function determineSmokeStartIndex(options) {
1228
1249
  }
1229
1250
  catch (error) {
1230
1251
  if (sweetLinkDebug) {
1231
- console.warn('Failed to clear stored smoke progress before run:', error);
1252
+ console.warn("Failed to clear stored smoke progress before run:", error);
1232
1253
  }
1233
1254
  }
1234
1255
  return 0;
1235
1256
  }
1236
1257
  const savedIndex = await loadSmokeProgressIndex(options.baseOrigin, options.routes);
1237
1258
  if (savedIndex === null) {
1238
- console.log('No prior smoke progress found. Starting from the beginning.');
1259
+ console.log("No prior smoke progress found. Starting from the beginning.");
1239
1260
  return 0;
1240
1261
  }
1241
1262
  if (savedIndex >= options.routes.length) {
1242
- console.log('Previous smoke run completed every route. Starting from the beginning.');
1263
+ console.log("Previous smoke run completed every route. Starting from the beginning.");
1243
1264
  return 0;
1244
1265
  }
1245
1266
  if (savedIndex > 0) {
@@ -1265,12 +1286,12 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1265
1286
  lastKnownUrl,
1266
1287
  failure: {
1267
1288
  route: `#${routeIndex + 1}`,
1268
- reason: 'Missing route entry in smoke route list.',
1289
+ reason: "Missing route entry in smoke route list.",
1269
1290
  },
1270
1291
  };
1271
1292
  }
1272
1293
  const targetUrl = buildSmokeRouteUrl(context.baseUrl, route);
1273
- const displayPath = `${targetUrl.pathname}${targetUrl.search || ''}`;
1294
+ const displayPath = `${targetUrl.pathname}${targetUrl.search || ""}`;
1274
1295
  console.log(`\n→ ${displayPath}`);
1275
1296
  const sessionConnected = await ensureSweetLinkSessionConnected({
1276
1297
  config: context.config,
@@ -1283,23 +1304,27 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1283
1304
  candidateUrls: [targetUrl.toString()],
1284
1305
  });
1285
1306
  if (!sessionConnected) {
1286
- console.warn(' Unable to verify active SweetLink session before navigation.');
1307
+ console.warn(" Unable to verify active SweetLink session before navigation.");
1287
1308
  return {
1288
1309
  session,
1289
1310
  lastKnownUrl,
1290
- failure: { route: displayPath, reason: 'session unavailable before navigation' },
1311
+ failure: { route: displayPath, reason: "session unavailable before navigation" },
1291
1312
  };
1292
1313
  }
1293
1314
  const attemptNavigation = async () => {
1294
- await navigateSweetLinkSession({ sessionId: activeSessionId, targetUrl, config: context.config });
1315
+ await navigateSweetLinkSession({
1316
+ sessionId: activeSessionId,
1317
+ targetUrl,
1318
+ config: context.config,
1319
+ });
1295
1320
  };
1296
1321
  try {
1297
1322
  await attemptNavigation();
1298
1323
  }
1299
1324
  catch (error) {
1300
- const reason = extractEventMessage(error, 'navigation failed');
1325
+ const reason = extractEventMessage(error, "navigation failed");
1301
1326
  if (SESSION_RECOVERY_PATTERN.test(reason)) {
1302
- console.warn(' Session went offline during navigation command. Attempting recovery…');
1327
+ console.warn(" Session went offline during navigation command. Attempting recovery…");
1303
1328
  const recovered = await ensureSweetLinkSessionConnected({
1304
1329
  config: context.config,
1305
1330
  token: context.token,
@@ -1315,7 +1340,7 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1315
1340
  await attemptNavigation();
1316
1341
  }
1317
1342
  catch (retryError) {
1318
- const retryReason = extractEventMessage(retryError, 'navigation failed');
1343
+ const retryReason = extractEventMessage(retryError, "navigation failed");
1319
1344
  console.warn(` Navigation failed after recovery: ${retryReason}`);
1320
1345
  return { session, lastKnownUrl, failure: { route: displayPath, reason: retryReason } };
1321
1346
  }
@@ -1338,7 +1363,7 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1338
1363
  devtoolsUrl: context.devtoolsUrl,
1339
1364
  });
1340
1365
  if (!handshake && context.devtoolsUrl) {
1341
- console.warn(' SweetLink session did not reconnect after navigation. Retrying bootstrap…');
1366
+ console.warn(" SweetLink session did not reconnect after navigation. Retrying bootstrap…");
1342
1367
  await triggerSweetLinkCliAuto(context.devtoolsUrl, targetUrl.toString());
1343
1368
  handshake = await waitForSweetLinkSession({
1344
1369
  config: context.config,
@@ -1360,7 +1385,9 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1360
1385
  });
1361
1386
  if (recovered) {
1362
1387
  const refreshed = await getSessionSummaryById(context.config, context.token, activeSessionId);
1363
- const recoveredUrl = typeof refreshed?.url === 'string' && refreshed.url.length > 0 ? refreshed.url : targetUrl.toString();
1388
+ const recoveredUrl = typeof refreshed?.url === "string" && refreshed.url.length > 0
1389
+ ? refreshed.url
1390
+ : targetUrl.toString();
1364
1391
  handshake = {
1365
1392
  sessionId: activeSessionId,
1366
1393
  url: recoveredUrl,
@@ -1369,43 +1396,48 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1369
1396
  }
1370
1397
  }
1371
1398
  if (!handshake) {
1372
- console.warn(' SweetLink session did not come back online after navigation.');
1399
+ console.warn(" SweetLink session did not come back online after navigation.");
1373
1400
  return {
1374
1401
  session,
1375
1402
  lastKnownUrl,
1376
- failure: { route: displayPath, reason: 'session did not reconnect' },
1403
+ failure: { route: displayPath, reason: "session did not reconnect" },
1377
1404
  };
1378
1405
  }
1379
1406
  if (handshake.sessionId && handshake.sessionId !== activeSessionId) {
1380
1407
  setActiveSessionId(handshake.sessionId);
1381
1408
  }
1382
1409
  lastKnownUrl = handshake.url ?? targetUrl.toString();
1383
- session = (await getSessionSummaryById(context.config, context.token, activeSessionId)) ?? session;
1410
+ session =
1411
+ (await getSessionSummaryById(context.config, context.token, activeSessionId)) ?? session;
1384
1412
  const diagnostics = await waitForSmokeRouteReady({
1385
1413
  devtoolsUrl: context.devtoolsUrl,
1386
1414
  targetUrl,
1387
1415
  timeoutMs: context.timeoutSeconds * 1000,
1388
1416
  });
1389
1417
  if (!diagnostics) {
1390
- console.warn(' Timed out waiting for the route to reach a stable state.');
1391
- return { session, lastKnownUrl, failure: { route: displayPath, reason: 'timeout awaiting route readiness' } };
1418
+ console.warn(" Timed out waiting for the route to reach a stable state.");
1419
+ return {
1420
+ session,
1421
+ lastKnownUrl,
1422
+ failure: { route: displayPath, reason: "timeout awaiting route readiness" },
1423
+ };
1392
1424
  }
1393
- const finalHref = diagnostics.locationHref ?? 'unknown';
1425
+ const finalHref = diagnostics.locationHref ?? "unknown";
1394
1426
  if (!urlsRoughlyMatch(finalHref, targetUrl.toString())) {
1395
1427
  console.warn(` Expected ${targetUrl.toString()} but browser reported ${finalHref}.`);
1396
1428
  return {
1397
1429
  session,
1398
1430
  lastKnownUrl,
1399
- failure: { route: displayPath, reason: 'unexpected location after navigation' },
1431
+ failure: { route: displayPath, reason: "unexpected location after navigation" },
1400
1432
  };
1401
1433
  }
1402
1434
  if (diagnosticsContainBlockingIssues(diagnostics)) {
1403
- console.warn(' Blocking diagnostics detected while loading the route:');
1404
- logBootstrapDiagnostics('Smoke', diagnostics);
1435
+ console.warn(" Blocking diagnostics detected while loading the route:");
1436
+ logBootstrapDiagnostics("Smoke", diagnostics);
1405
1437
  return {
1406
1438
  session,
1407
1439
  lastKnownUrl,
1408
- failure: { route: displayPath, reason: 'runtime diagnostics reported blocking issues' },
1440
+ failure: { route: displayPath, reason: "runtime diagnostics reported blocking issues" },
1409
1441
  };
1410
1442
  }
1411
1443
  const consoleEvents = await fetchConsoleEvents(context.config, activeSessionId).catch(() => []);
@@ -1415,26 +1447,26 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1415
1447
  }
1416
1448
  const authEvents = newEvents.filter((event) => consoleEventIndicatesAuthIssue(event));
1417
1449
  if (authEvents.length > 0) {
1418
- console.warn(' Detected authentication failures in the console log:');
1450
+ console.warn(" Detected authentication failures in the console log:");
1419
1451
  for (const event of authEvents.slice(-5)) {
1420
1452
  console.warn(` ${formatConsoleEventSummary(event)}`);
1421
1453
  }
1422
1454
  return {
1423
1455
  session,
1424
1456
  lastKnownUrl,
1425
- failure: { route: displayPath, reason: 'authentication failures detected in console output' },
1457
+ failure: { route: displayPath, reason: "authentication failures detected in console output" },
1426
1458
  };
1427
1459
  }
1428
1460
  const runtimeErrorEvents = newEvents.filter((event) => consoleEventIndicatesRuntimeError(event));
1429
1461
  if (runtimeErrorEvents.length > 0) {
1430
- console.warn(' Detected console errors after the route finished loading:');
1462
+ console.warn(" Detected console errors after the route finished loading:");
1431
1463
  for (const event of runtimeErrorEvents.slice(-5)) {
1432
1464
  console.warn(` ${formatConsoleEventSummary(event)}`);
1433
1465
  }
1434
1466
  return {
1435
1467
  session,
1436
1468
  lastKnownUrl,
1437
- failure: { route: displayPath, reason: 'console errors detected after load' },
1469
+ failure: { route: displayPath, reason: "console errors detected after load" },
1438
1470
  };
1439
1471
  }
1440
1472
  try {
@@ -1442,10 +1474,10 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
1442
1474
  }
1443
1475
  catch (error) {
1444
1476
  if (sweetLinkDebug) {
1445
- console.warn('Failed to persist smoke progress after route completion:', error);
1477
+ console.warn("Failed to persist smoke progress after route completion:", error);
1446
1478
  }
1447
1479
  }
1448
- console.log(' ✅ Route passed without authentication or runtime errors.');
1480
+ console.log(" ✅ Route passed without authentication or runtime errors.");
1449
1481
  return {
1450
1482
  session,
1451
1483
  lastKnownUrl,
@@ -1468,9 +1500,9 @@ async function runSweetLinkSmoke(params, command) {
1468
1500
  const initialConsoleEvents = await fetchConsoleEvents(config, sessionId).catch(() => []);
1469
1501
  const seenConsoleIds = new Set(initialConsoleEvents.map((event) => event.id));
1470
1502
  const remainingRouteCount = params.routes.length - startIndex;
1471
- console.log(`Running SweetLink smoke test across ${remainingRouteCount} route${remainingRouteCount === 1 ? '' : 's'} using session ${formatSessionHeadline(initialSession)}.`);
1503
+ console.log(`Running SweetLink smoke test across ${remainingRouteCount} route${remainingRouteCount === 1 ? "" : "s"} using session ${formatSessionHeadline(initialSession)}.`);
1472
1504
  if (startIndex > 0) {
1473
- console.log(`Skipping ${startIndex} route${startIndex === 1 ? '' : 's'} that already passed in a previous run.`);
1505
+ console.log(`Skipping ${startIndex} route${startIndex === 1 ? "" : "s"} that already passed in a previous run.`);
1474
1506
  }
1475
1507
  let lastKnownUrl = initialSession.url ?? baseUrl.toString();
1476
1508
  const context = {
@@ -1496,11 +1528,11 @@ async function runSweetLinkSmoke(params, command) {
1496
1528
  }
1497
1529
  }
1498
1530
  if (failures.length > 0) {
1499
- console.error(`\nSweetLink smoke test detected issues on ${failures.length} route${failures.length === 1 ? '' : 's'}:`);
1531
+ console.error(`\nSweetLink smoke test detected issues on ${failures.length} route${failures.length === 1 ? "" : "s"}:`);
1500
1532
  for (const failure of failures) {
1501
1533
  console.error(` - ${failure.route}: ${failure.reason}`);
1502
1534
  }
1503
- console.error('Review the diagnostics above, fix the auth flow, and rerun `pnpm sweetlink smoke`.');
1535
+ console.error("Review the diagnostics above, fix the auth flow, and rerun `pnpm sweetlink smoke`.");
1504
1536
  if (process.exitCode === undefined || process.exitCode === 0) {
1505
1537
  process.exitCode = 1;
1506
1538
  }
@@ -1511,18 +1543,18 @@ async function runSweetLinkSmoke(params, command) {
1511
1543
  }
1512
1544
  catch (error) {
1513
1545
  if (sweetLinkDebug) {
1514
- console.warn('Failed to clear smoke progress after successful run:', error);
1546
+ console.warn("Failed to clear smoke progress after successful run:", error);
1515
1547
  }
1516
1548
  }
1517
- console.log('\nSweetLink smoke test passed for all routes.');
1549
+ console.log("\nSweetLink smoke test passed for all routes.");
1518
1550
  }
1519
1551
  }
1520
1552
  function extractPathSegments(routePath) {
1521
1553
  const normalized = trimTrailingSlash(routePath);
1522
- if (normalized === '/' || normalized.length === 0) {
1554
+ if (normalized === "/" || normalized.length === 0) {
1523
1555
  return [];
1524
1556
  }
1525
- return normalized.replace(LEADING_SLASH_PATTERN, '').split('/');
1557
+ return normalized.replace(LEADING_SLASH_PATTERN, "").split("/");
1526
1558
  }
1527
1559
  function suffixSegmentsAllowed(segments) {
1528
1560
  if (segments.length === 0) {
@@ -1540,8 +1572,8 @@ export const __sweetlinkCliTestHelpers = {
1540
1572
  buildClickScript,
1541
1573
  };
1542
1574
  /* biome-ignore lint/performance/noBarrelFile: CLI entrypoint intentionally re-exports runtime helpers for compatibility. */
1543
- export { diagnosticsContainBlockingIssues, logBootstrapDiagnostics } from './runtime/devtools.js';
1544
- export { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, resolvePromptOption, resolveSessionIdFromHint, } from './runtime/session.js';
1575
+ export { diagnosticsContainBlockingIssues, logBootstrapDiagnostics } from "./runtime/devtools.js";
1576
+ export { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, resolvePromptOption, resolveSessionIdFromHint, } from "./runtime/session.js";
1545
1577
  function urlsRoughlyMatch(a, b) {
1546
1578
  const urlA = normalizeUrlForMatch(a);
1547
1579
  const urlB = normalizeUrlForMatch(b);
@@ -1571,7 +1603,7 @@ function urlsRoughlyMatch(a, b) {
1571
1603
  async function devtoolsAuthorize(options, command) {
1572
1604
  const devtoolsConfig = await loadDevToolsConfig();
1573
1605
  if (!devtoolsConfig?.devtoolsUrl) {
1574
- console.log('No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.');
1606
+ console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
1575
1607
  return;
1576
1608
  }
1577
1609
  const cliConfig = resolveConfig(command);
@@ -1604,12 +1636,12 @@ async function devtoolsAuthorize(options, command) {
1604
1636
  }
1605
1637
  catch (error) {
1606
1638
  if (sweetLinkDebug) {
1607
- console.warn('Failed to inspect DevTools tabs for authorize command:', error);
1639
+ console.warn("Failed to inspect DevTools tabs for authorize command:", error);
1608
1640
  }
1609
1641
  }
1610
1642
  }
1611
1643
  if (!sessionUrl) {
1612
- console.error('Unable to determine which tab contains the OAuth consent screen. Pass --url <authorizeUrl> to override.');
1644
+ console.error("Unable to determine which tab contains the OAuth consent screen. Pass --url <authorizeUrl> to override.");
1613
1645
  if (process.exitCode === undefined) {
1614
1646
  process.exitCode = 1;
1615
1647
  }
@@ -1622,18 +1654,18 @@ async function devtoolsAuthorize(options, command) {
1622
1654
  scriptPath: cliConfig.oauthScriptPath,
1623
1655
  });
1624
1656
  if (result.handled) {
1625
- console.log(`Authorize prompt handled via ${result.action ?? 'click'}${result.clickedText ? ` (${result.clickedText})` : ''}.`);
1657
+ console.log(`Authorize prompt handled via ${result.action ?? "click"}${result.clickedText ? ` (${result.clickedText})` : ""}.`);
1626
1658
  }
1627
1659
  else {
1628
- const reason = result.reason ?? 'no authorize button detected';
1660
+ const reason = result.reason ?? "no authorize button detected";
1629
1661
  console.log(`Authorize prompt not handled automatically (${reason}).`);
1630
- if (reason === 'requires-login') {
1631
- console.log('Twitter login inputs detected. Complete login manually and rerun the command.');
1662
+ if (reason === "requires-login") {
1663
+ console.log("Twitter login inputs detected. Complete login manually and rerun the command.");
1632
1664
  }
1633
1665
  }
1634
1666
  }
1635
1667
  catch (error) {
1636
- console.error('Failed to trigger OAuth authorize automation:', extractEventMessage(error));
1668
+ console.error("Failed to trigger OAuth authorize automation:", extractEventMessage(error));
1637
1669
  if (process.exitCode === undefined) {
1638
1670
  process.exitCode = 1;
1639
1671
  }
@@ -1642,13 +1674,13 @@ async function devtoolsAuthorize(options, command) {
1642
1674
  async function devtoolsStatus(options, command) {
1643
1675
  const config = await loadDevToolsConfig();
1644
1676
  if (!config) {
1645
- console.log('No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.');
1677
+ console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
1646
1678
  return;
1647
1679
  }
1648
1680
  let reachable;
1649
1681
  let tabs = [];
1650
1682
  try {
1651
- const response = await fetch(`${config.devtoolsUrl.replace(TRAILING_SLASH_PATTERN, '')}/json/version`, { method: 'GET' });
1683
+ const response = await fetch(`${config.devtoolsUrl.replace(TRAILING_SLASH_PATTERN, "")}/json/version`, { method: "GET" });
1652
1684
  if (response.ok) {
1653
1685
  reachable = true;
1654
1686
  tabs = await fetchDevToolsTabs(config.devtoolsUrl);
@@ -1692,7 +1724,7 @@ async function devtoolsStatus(options, command) {
1692
1724
  process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
1693
1725
  return;
1694
1726
  }
1695
- const reachabilityLabel = isReachable ? 'reachable' : 'offline';
1727
+ const reachabilityLabel = isReachable ? "reachable" : "offline";
1696
1728
  console.log(`DevTools endpoint : ${config.devtoolsUrl} (${reachabilityLabel})`);
1697
1729
  console.log(`Last updated : ${new Date(config.updatedAt).toISOString()}`);
1698
1730
  if (config.viewport) {
@@ -1701,7 +1733,7 @@ async function devtoolsStatus(options, command) {
1701
1733
  }
1702
1734
  console.log(`Tabs : ${tabs.length}`);
1703
1735
  if (matchedSessionId) {
1704
- console.log(`Linked session : ${matchedSessionId}${matchedSessionTitle ? ` (${matchedSessionTitle})` : ''}`);
1736
+ console.log(`Linked session : ${matchedSessionId}${matchedSessionTitle ? ` (${matchedSessionTitle})` : ""}`);
1705
1737
  }
1706
1738
  console.log(`Console buffer : ${state?.console.length ?? 0} entries`);
1707
1739
  console.log(`Network buffer : ${state?.network.length ?? 0} entries`);
@@ -1712,7 +1744,7 @@ async function devtoolsStatus(options, command) {
1712
1744
  async function devtoolsTabs(options) {
1713
1745
  const config = await loadDevToolsConfig();
1714
1746
  if (!config) {
1715
- console.log('No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.');
1747
+ console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
1716
1748
  return;
1717
1749
  }
1718
1750
  const tabs = await fetchDevToolsTabs(config.devtoolsUrl);
@@ -1721,23 +1753,23 @@ async function devtoolsTabs(options) {
1721
1753
  return;
1722
1754
  }
1723
1755
  if (tabs.length === 0) {
1724
- console.log('No tabs reported by DevTools.');
1756
+ console.log("No tabs reported by DevTools.");
1725
1757
  return;
1726
1758
  }
1727
1759
  for (const tab of tabs) {
1728
- console.log(`• ${tab.title || '(untitled)'}`);
1760
+ console.log(`• ${tab.title || "(untitled)"}`);
1729
1761
  console.log(` URL : ${tab.url}`);
1730
1762
  console.log(` ID : ${tab.id}`);
1731
1763
  if (tab.type) {
1732
1764
  console.log(` Type: ${tab.type}`);
1733
1765
  }
1734
- console.log('');
1766
+ console.log("");
1735
1767
  }
1736
1768
  }
1737
1769
  async function devtoolsShowConsole(options) {
1738
1770
  const state = await loadDevToolsState();
1739
1771
  if (!state || state.console.length === 0) {
1740
- console.log('No console events recorded. Run `pnpm sweetlink devtools listen` to start capturing telemetry.');
1772
+ console.log("No console events recorded. Run `pnpm sweetlink devtools listen` to start capturing telemetry.");
1741
1773
  return;
1742
1774
  }
1743
1775
  const tail = Number.isFinite(options.tail) && options.tail > 0 ? Math.floor(options.tail) : 50;
@@ -1748,19 +1780,19 @@ async function devtoolsShowConsole(options) {
1748
1780
  }
1749
1781
  for (const entry of entries) {
1750
1782
  const timestamp = new Date(entry.ts).toISOString();
1751
- let location = '';
1783
+ let location = "";
1752
1784
  if (entry.location?.url) {
1753
- const lineSuffix = entry.location.lineNumber === undefined ? '' : `:${entry.location.lineNumber}`;
1754
- const columnSuffix = entry.location.columnNumber === undefined ? '' : `:${entry.location.columnNumber}`;
1785
+ const lineSuffix = entry.location.lineNumber === undefined ? "" : `:${entry.location.lineNumber}`;
1786
+ const columnSuffix = entry.location.columnNumber === undefined ? "" : `:${entry.location.columnNumber}`;
1755
1787
  location = ` (${entry.location.url}${lineSuffix}${columnSuffix})`;
1756
1788
  }
1757
- let argsSuffix = '';
1789
+ let argsSuffix = "";
1758
1790
  if (entry.args.length > 0) {
1759
1791
  const formattedArgs = [];
1760
1792
  for (const value of entry.args) {
1761
1793
  formattedArgs.push(formatConsoleArg(value));
1762
1794
  }
1763
- argsSuffix = ` ${formattedArgs.join(' ')}`;
1795
+ argsSuffix = ` ${formattedArgs.join(" ")}`;
1764
1796
  }
1765
1797
  console.log(`[${timestamp}] ${entry.type}: ${entry.text}${argsSuffix}${location}`);
1766
1798
  }
@@ -1768,7 +1800,7 @@ async function devtoolsShowConsole(options) {
1768
1800
  async function devtoolsShowNetwork(options) {
1769
1801
  const state = await loadDevToolsState();
1770
1802
  if (!state || state.network.length === 0) {
1771
- console.log('No network entries recorded. Run `pnpm sweetlink devtools listen` to start capturing telemetry.');
1803
+ console.log("No network entries recorded. Run `pnpm sweetlink devtools listen` to start capturing telemetry.");
1772
1804
  return;
1773
1805
  }
1774
1806
  const tail = Number.isFinite(options.tail) && options.tail > 0 ? Math.floor(options.tail) : 50;
@@ -1779,8 +1811,8 @@ async function devtoolsShowNetwork(options) {
1779
1811
  }
1780
1812
  for (const entry of entries) {
1781
1813
  const timestamp = new Date(entry.ts).toISOString();
1782
- const status = entry.status === undefined ? (entry.failureText ?? 'failed') : entry.status;
1783
- const type = entry.resourceType ? ` [${entry.resourceType}]` : '';
1814
+ const status = entry.status === undefined ? (entry.failureText ?? "failed") : entry.status;
1815
+ const type = entry.resourceType ? ` [${entry.resourceType}]` : "";
1784
1816
  console.log(`[${timestamp}] ${entry.method} ${status} ${entry.url}${type}`);
1785
1817
  }
1786
1818
  }
@@ -1789,7 +1821,7 @@ async function devtoolsListen(options, command) {
1789
1821
  const config = await loadDevToolsConfig();
1790
1822
  if (!config) {
1791
1823
  if (!background) {
1792
- console.log('No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.');
1824
+ console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
1793
1825
  }
1794
1826
  return;
1795
1827
  }
@@ -1797,12 +1829,12 @@ async function devtoolsListen(options, command) {
1797
1829
  try {
1798
1830
  await rm(DEVTOOLS_STATE_PATH, { force: true });
1799
1831
  if (!background) {
1800
- console.log('Cleared cached DevTools telemetry state.');
1832
+ console.log("Cleared cached DevTools telemetry state.");
1801
1833
  }
1802
1834
  }
1803
1835
  catch (error) {
1804
- if (!isErrnoException(error) || error.code !== 'ENOENT') {
1805
- console.warn('Failed to reset DevTools state:', error);
1836
+ if (!isErrnoException(error) || error.code !== "ENOENT") {
1837
+ console.warn("Failed to reset DevTools state:", error);
1806
1838
  }
1807
1839
  }
1808
1840
  }
@@ -1818,10 +1850,10 @@ async function devtoolsListen(options, command) {
1818
1850
  await saveDevToolsState(state);
1819
1851
  }
1820
1852
  catch (error) {
1821
- console.warn('Failed to persist reset DevTools state:', error);
1853
+ console.warn("Failed to persist reset DevTools state:", error);
1822
1854
  }
1823
1855
  if (!background) {
1824
- console.log('Reset complete. Re-run without --reset to resume live capture.');
1856
+ console.log("Reset complete. Re-run without --reset to resume live capture.");
1825
1857
  }
1826
1858
  return;
1827
1859
  }
@@ -1837,7 +1869,7 @@ async function devtoolsListen(options, command) {
1837
1869
  await saveDevToolsConfig({ devtoolsUrl: config.devtoolsUrl, sessionId });
1838
1870
  }
1839
1871
  catch (error) {
1840
- logDebugError('Failed to persist DevTools session binding', error);
1872
+ logDebugError("Failed to persist DevTools session binding", error);
1841
1873
  }
1842
1874
  }
1843
1875
  if (!background) {
@@ -1849,8 +1881,8 @@ async function devtoolsListen(options, command) {
1849
1881
  ({ browser, page } = await connectToDevTools(config));
1850
1882
  }
1851
1883
  catch (error) {
1852
- console.error('Failed to connect to the DevTools endpoint:', extractEventMessage(error));
1853
- console.error('Hint: ensure a controlled Chrome window is running via `pnpm sweetlink open --controlled`.');
1884
+ console.error("Failed to connect to the DevTools endpoint:", extractEventMessage(error));
1885
+ console.error("Hint: ensure a controlled Chrome window is running via `pnpm sweetlink open --controlled`.");
1854
1886
  process.exitCode = 1;
1855
1887
  return;
1856
1888
  }
@@ -1864,7 +1896,7 @@ async function devtoolsListen(options, command) {
1864
1896
  await saveDevToolsConfig({ devtoolsUrl: config.devtoolsUrl, viewport });
1865
1897
  }
1866
1898
  catch (error) {
1867
- logDebugError('Failed to persist DevTools viewport configuration', error);
1899
+ logDebugError("Failed to persist DevTools viewport configuration", error);
1868
1900
  }
1869
1901
  const flush = async () => {
1870
1902
  await saveDevToolsState(devtoolsState);
@@ -1879,14 +1911,14 @@ async function devtoolsListen(options, command) {
1879
1911
  timer = setTimeout(() => {
1880
1912
  timer = null;
1881
1913
  flush().catch((error) => {
1882
- console.warn('Failed to persist DevTools telemetry:', error);
1914
+ console.warn("Failed to persist DevTools telemetry:", error);
1883
1915
  });
1884
1916
  }, 300);
1885
1917
  };
1886
1918
  })();
1887
1919
  const attachListeners = (p) => {
1888
1920
  // Capture console and network events for the page and trim buffers as they grow.
1889
- p.on('console', (message) => {
1921
+ p.on("console", (message) => {
1890
1922
  const persistConsole = async () => {
1891
1923
  const entry = await serializeConsoleMessage(message);
1892
1924
  devtoolsState.console.push(entry);
@@ -1894,10 +1926,10 @@ async function devtoolsListen(options, command) {
1894
1926
  scheduleFlush();
1895
1927
  };
1896
1928
  persistConsole().catch((error) => {
1897
- console.warn('Failed to serialize console message:', error);
1929
+ console.warn("Failed to serialize console message:", error);
1898
1930
  });
1899
1931
  });
1900
- p.on('requestfinished', (request) => {
1932
+ p.on("requestfinished", (request) => {
1901
1933
  const persistNetworkEntry = async () => {
1902
1934
  const response = await request.response();
1903
1935
  const entry = createNetworkEntryFromRequest(request, response?.status());
@@ -1906,12 +1938,12 @@ async function devtoolsListen(options, command) {
1906
1938
  scheduleFlush();
1907
1939
  };
1908
1940
  persistNetworkEntry().catch((error) => {
1909
- console.warn('Failed to serialize network request:', error);
1941
+ console.warn("Failed to serialize network request:", error);
1910
1942
  });
1911
1943
  });
1912
- p.on('requestfailed', (request) => {
1944
+ p.on("requestfailed", (request) => {
1913
1945
  const failure = request.failure();
1914
- const entry = createNetworkEntryFromRequest(request, undefined, failure?.errorText ?? 'failed');
1946
+ const entry = createNetworkEntryFromRequest(request, undefined, failure?.errorText ?? "failed");
1915
1947
  devtoolsState.network.push(entry);
1916
1948
  trimBuffer(devtoolsState.network, DEVTOOLS_NETWORK_LIMIT);
1917
1949
  scheduleFlush();
@@ -1919,11 +1951,11 @@ async function devtoolsListen(options, command) {
1919
1951
  };
1920
1952
  attachListeners(page);
1921
1953
  const context = page.context();
1922
- context.on('page', (newPage) => {
1954
+ context.on("page", (newPage) => {
1923
1955
  attachListeners(newPage);
1924
1956
  });
1925
1957
  if (!background) {
1926
- console.log('Listening for console and network events… Press Ctrl+C to stop.');
1958
+ console.log("Listening for console and network events… Press Ctrl+C to stop.");
1927
1959
  }
1928
1960
  const shutdown = async () => {
1929
1961
  try {
@@ -1931,7 +1963,7 @@ async function devtoolsListen(options, command) {
1931
1963
  }
1932
1964
  catch (error) {
1933
1965
  if (sweetLinkDebug) {
1934
- console.warn('Failed to flush DevTools state during shutdown.', error);
1966
+ console.warn("Failed to flush DevTools state during shutdown.", error);
1935
1967
  }
1936
1968
  }
1937
1969
  try {
@@ -1939,22 +1971,22 @@ async function devtoolsListen(options, command) {
1939
1971
  }
1940
1972
  catch (error) {
1941
1973
  if (sweetLinkDebug) {
1942
- console.warn('Failed to close DevTools browser during shutdown.', error);
1974
+ console.warn("Failed to close DevTools browser during shutdown.", error);
1943
1975
  }
1944
1976
  }
1945
1977
  process.exit(0);
1946
1978
  };
1947
- process.once('SIGINT', () => {
1979
+ process.once("SIGINT", () => {
1948
1980
  shutdown().catch((error) => {
1949
1981
  if (sweetLinkDebug) {
1950
- console.warn('Graceful shutdown after SIGINT failed:', error);
1982
+ console.warn("Graceful shutdown after SIGINT failed:", error);
1951
1983
  }
1952
1984
  });
1953
1985
  });
1954
- process.once('SIGTERM', () => {
1986
+ process.once("SIGTERM", () => {
1955
1987
  shutdown().catch((error) => {
1956
1988
  if (sweetLinkDebug) {
1957
- console.warn('Graceful shutdown after SIGTERM failed:', error);
1989
+ console.warn("Graceful shutdown after SIGTERM failed:", error);
1958
1990
  }
1959
1991
  });
1960
1992
  });