sweetlink 0.1.0 → 0.2.1

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