@agentrhq/webcmd 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -24
- package/dist/src/adapter-shadow.d.ts +11 -0
- package/dist/src/adapter-shadow.js +72 -0
- package/dist/src/adapter-shadow.test.d.ts +1 -0
- package/dist/src/adapter-shadow.test.js +49 -0
- package/dist/src/adapter-source.d.ts +11 -0
- package/dist/src/adapter-source.js +24 -0
- package/dist/src/adapter-source.test.d.ts +1 -0
- package/dist/src/adapter-source.test.js +29 -0
- package/dist/src/brand.d.ts +9 -0
- package/dist/src/brand.js +9 -0
- package/dist/src/brand.test.d.ts +1 -0
- package/dist/src/brand.test.js +15 -0
- package/dist/src/browser/analyze.d.ts +118 -0
- package/dist/src/browser/analyze.js +405 -0
- package/dist/src/browser/analyze.test.d.ts +1 -0
- package/dist/src/browser/analyze.test.js +209 -0
- package/dist/src/browser/article-extract.d.ts +57 -0
- package/dist/src/browser/article-extract.e2e.test.d.ts +1 -0
- package/dist/src/browser/article-extract.e2e.test.js +105 -0
- package/dist/src/browser/article-extract.js +169 -0
- package/dist/src/browser/article-extract.test.d.ts +1 -0
- package/dist/src/browser/article-extract.test.js +94 -0
- package/dist/src/browser/ax-snapshot.d.ts +37 -0
- package/dist/src/browser/ax-snapshot.js +217 -0
- package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
- package/dist/src/browser/ax-snapshot.test.js +91 -0
- package/dist/src/browser/base-page.d.ts +151 -0
- package/dist/src/browser/base-page.js +1060 -0
- package/dist/src/browser/base-page.test.d.ts +1 -0
- package/dist/src/browser/base-page.test.js +803 -0
- package/dist/src/browser/bridge-readiness.d.ts +14 -0
- package/dist/src/browser/bridge-readiness.js +24 -0
- package/dist/src/browser/bridge-readiness.test.d.ts +1 -0
- package/dist/src/browser/bridge-readiness.test.js +58 -0
- package/dist/src/browser/bridge.d.ts +26 -0
- package/dist/src/browser/bridge.js +58 -0
- package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
- package/dist/src/browser/cdp-click-fixture.test.js +87 -0
- package/dist/src/browser/cdp.d.ts +46 -0
- package/dist/src/browser/cdp.js +536 -0
- package/dist/src/browser/cdp.test.d.ts +1 -0
- package/dist/src/browser/cdp.test.js +79 -0
- package/dist/src/browser/compound.d.ts +59 -0
- package/dist/src/browser/compound.js +112 -0
- package/dist/src/browser/compound.test.d.ts +1 -0
- package/dist/src/browser/compound.test.js +175 -0
- package/dist/src/browser/config.d.ts +2 -0
- package/dist/src/browser/config.js +14 -0
- package/dist/src/browser/daemon-client.d.ts +32 -0
- package/dist/src/browser/daemon-client.js +135 -0
- package/dist/src/browser/daemon-client.test.d.ts +1 -0
- package/dist/src/browser/daemon-client.test.js +326 -0
- package/dist/src/browser/daemon-lifecycle.d.ts +37 -0
- package/dist/src/browser/daemon-lifecycle.js +147 -0
- package/dist/src/browser/daemon-transport.d.ts +54 -0
- package/dist/src/browser/daemon-transport.js +68 -0
- package/dist/src/browser/daemon-version.d.ts +4 -0
- package/dist/src/browser/daemon-version.js +12 -0
- package/dist/src/browser/dom-helpers.d.ts +41 -0
- package/dist/src/browser/dom-helpers.js +239 -0
- package/dist/src/browser/dom-helpers.test.d.ts +1 -0
- package/dist/src/browser/dom-helpers.test.js +106 -0
- package/dist/src/browser/dom-snapshot.d.ts +93 -0
- package/dist/src/browser/dom-snapshot.js +894 -0
- package/dist/src/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/src/browser/dom-snapshot.test.js +313 -0
- package/dist/src/browser/errors.d.ts +38 -0
- package/dist/src/browser/errors.js +79 -0
- package/dist/src/browser/errors.test.d.ts +1 -0
- package/dist/src/browser/errors.test.js +60 -0
- package/dist/src/browser/extract.d.ts +69 -0
- package/dist/src/browser/extract.js +132 -0
- package/dist/src/browser/extract.test.d.ts +1 -0
- package/dist/src/browser/extract.test.js +129 -0
- package/dist/src/browser/find.d.ts +84 -0
- package/dist/src/browser/find.js +398 -0
- package/dist/src/browser/find.test.d.ts +1 -0
- package/dist/src/browser/find.test.js +180 -0
- package/dist/src/browser/html-tree.d.ts +75 -0
- package/dist/src/browser/html-tree.js +112 -0
- package/dist/src/browser/html-tree.test.d.ts +1 -0
- package/dist/src/browser/html-tree.test.js +181 -0
- package/dist/src/browser/index.d.ts +14 -0
- package/dist/src/browser/index.js +12 -0
- package/dist/src/browser/network-cache.d.ts +49 -0
- package/dist/src/browser/network-cache.js +78 -0
- package/dist/src/browser/network-cache.test.d.ts +1 -0
- package/dist/src/browser/network-cache.test.js +75 -0
- package/dist/src/browser/network-interceptor.d.ts +11 -0
- package/dist/src/browser/network-interceptor.js +11 -0
- package/dist/src/browser/network-key.d.ts +22 -0
- package/dist/src/browser/network-key.js +66 -0
- package/dist/src/browser/network-key.test.d.ts +1 -0
- package/dist/src/browser/network-key.test.js +49 -0
- package/dist/src/browser/page.d.ts +83 -0
- package/dist/src/browser/page.js +453 -0
- package/dist/src/browser/page.test.d.ts +1 -0
- package/dist/src/browser/page.test.js +406 -0
- package/dist/src/browser/profile.d.ts +14 -0
- package/dist/src/browser/profile.js +86 -0
- package/dist/src/browser/protocol.d.ts +63 -0
- package/dist/src/browser/protocol.js +1 -0
- package/dist/src/browser/runtime/local-cloak/actions.d.ts +3 -0
- package/dist/src/browser/runtime/local-cloak/actions.js +240 -0
- package/dist/src/browser/runtime/local-cloak/downloads.d.ts +3 -0
- package/dist/src/browser/runtime/local-cloak/downloads.js +28 -0
- package/dist/src/browser/runtime/local-cloak/network.d.ts +28 -0
- package/dist/src/browser/runtime/local-cloak/network.js +120 -0
- package/dist/src/browser/runtime/local-cloak/network.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/network.test.js +124 -0
- package/dist/src/browser/runtime/local-cloak/profiles.d.ts +6 -0
- package/dist/src/browser/runtime/local-cloak/profiles.js +17 -0
- package/dist/src/browser/runtime/local-cloak/profiles.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/profiles.test.js +19 -0
- package/dist/src/browser/runtime/local-cloak/provider.d.ts +15 -0
- package/dist/src/browser/runtime/local-cloak/provider.js +27 -0
- package/dist/src/browser/runtime/local-cloak/provider.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/provider.test.js +244 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +77 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.js +280 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.test.js +92 -0
- package/dist/src/browser/runtime/provider.d.ts +9 -0
- package/dist/src/browser/runtime/provider.js +1 -0
- package/dist/src/browser/runtime/provider.test.d.ts +2 -0
- package/dist/src/browser/runtime/provider.test.js +49 -0
- package/dist/src/browser/shape-filter.d.ts +52 -0
- package/dist/src/browser/shape-filter.js +101 -0
- package/dist/src/browser/shape-filter.test.d.ts +1 -0
- package/dist/src/browser/shape-filter.test.js +101 -0
- package/dist/src/browser/shape.d.ts +23 -0
- package/dist/src/browser/shape.js +95 -0
- package/dist/src/browser/shape.test.d.ts +1 -0
- package/dist/src/browser/shape.test.js +82 -0
- package/dist/src/browser/stealth.d.ts +11 -0
- package/dist/src/browser/stealth.js +359 -0
- package/dist/src/browser/stealth.test.d.ts +1 -0
- package/dist/src/browser/stealth.test.js +134 -0
- package/dist/src/browser/tabs.d.ts +13 -0
- package/dist/src/browser/tabs.js +70 -0
- package/dist/src/browser/target-errors.d.ts +39 -0
- package/dist/src/browser/target-errors.js +45 -0
- package/dist/src/browser/target-errors.test.d.ts +1 -0
- package/dist/src/browser/target-errors.test.js +94 -0
- package/dist/src/browser/target-resolver.d.ts +158 -0
- package/dist/src/browser/target-resolver.js +570 -0
- package/dist/src/browser/target-resolver.test.d.ts +1 -0
- package/dist/src/browser/target-resolver.test.js +118 -0
- package/dist/src/browser/utils.d.ts +20 -0
- package/dist/src/browser/utils.js +64 -0
- package/dist/src/browser/utils.test.d.ts +1 -0
- package/dist/src/browser/utils.test.js +29 -0
- package/dist/src/browser/verify-fixture.d.ts +65 -0
- package/dist/src/browser/verify-fixture.js +318 -0
- package/dist/src/browser/verify-fixture.test.d.ts +1 -0
- package/dist/src/browser/verify-fixture.test.js +219 -0
- package/dist/src/browser/visual-refs.d.ts +11 -0
- package/dist/src/browser/visual-refs.js +108 -0
- package/dist/src/browser.test.d.ts +1 -0
- package/dist/src/browser.test.js +297 -0
- package/dist/src/build-manifest.d.ts +103 -0
- package/dist/src/build-manifest.js +363 -0
- package/dist/src/build-manifest.test.d.ts +1 -0
- package/dist/src/build-manifest.test.js +341 -0
- package/dist/src/capabilityRouting.d.ts +19 -0
- package/dist/src/capabilityRouting.js +56 -0
- package/dist/src/capabilityRouting.test.d.ts +1 -0
- package/dist/src/capabilityRouting.test.js +63 -0
- package/dist/src/cli-argv-preprocess.d.ts +63 -0
- package/dist/src/cli-argv-preprocess.js +303 -0
- package/dist/src/cli-argv-preprocess.test.d.ts +1 -0
- package/dist/src/cli-argv-preprocess.test.js +304 -0
- package/dist/src/cli.d.ts +79 -0
- package/dist/src/cli.js +3350 -0
- package/dist/src/cli.test.d.ts +1 -0
- package/dist/src/cli.test.js +3041 -0
- package/dist/src/commanderAdapter.d.ts +21 -0
- package/dist/src/commanderAdapter.js +208 -0
- package/dist/src/commanderAdapter.test.d.ts +1 -0
- package/dist/src/commanderAdapter.test.js +352 -0
- package/dist/src/commands/auth.d.ts +40 -0
- package/dist/src/commands/auth.js +434 -0
- package/dist/src/commands/auth.test.d.ts +1 -0
- package/dist/src/commands/auth.test.js +252 -0
- package/dist/src/commands/daemon.d.ts +9 -0
- package/dist/src/commands/daemon.js +91 -0
- package/dist/src/commands/daemon.test.d.ts +1 -0
- package/dist/src/commands/daemon.test.js +293 -0
- package/dist/src/completion-fast.d.ts +22 -0
- package/dist/src/completion-fast.js +88 -0
- package/dist/src/completion-shared.d.ts +13 -0
- package/dist/src/completion-shared.js +61 -0
- package/dist/src/completion.d.ts +21 -0
- package/dist/src/completion.js +68 -0
- package/dist/src/completion.test.d.ts +1 -0
- package/dist/src/completion.test.js +24 -0
- package/dist/src/constants.d.ts +18 -0
- package/dist/src/constants.js +41 -0
- package/dist/src/convention-audit.d.ts +50 -0
- package/dist/src/convention-audit.js +553 -0
- package/dist/src/convention-audit.test.d.ts +1 -0
- package/dist/src/convention-audit.test.js +255 -0
- package/dist/src/daemon/server.d.ts +13 -0
- package/dist/src/daemon/server.js +185 -0
- package/dist/src/daemon/server.test.d.ts +1 -0
- package/dist/src/daemon/server.test.js +132 -0
- package/dist/src/daemon-utils.d.ts +18 -0
- package/dist/src/daemon-utils.js +37 -0
- package/dist/src/daemon.d.ts +1 -0
- package/dist/src/daemon.js +27 -0
- package/dist/src/daemon.test.d.ts +1 -0
- package/dist/src/daemon.test.js +60 -0
- package/dist/src/discovery.d.ts +46 -0
- package/dist/src/discovery.js +341 -0
- package/dist/src/doctor.d.ts +39 -0
- package/dist/src/doctor.js +168 -0
- package/dist/src/doctor.test.d.ts +1 -0
- package/dist/src/doctor.test.js +302 -0
- package/dist/src/download/article-download.d.ts +72 -0
- package/dist/src/download/article-download.js +305 -0
- package/dist/src/download/article-download.test.d.ts +1 -0
- package/dist/src/download/article-download.test.js +235 -0
- package/dist/src/download/index.d.ts +71 -0
- package/dist/src/download/index.js +363 -0
- package/dist/src/download/index.test.d.ts +1 -0
- package/dist/src/download/index.test.js +174 -0
- package/dist/src/download/media-download.d.ts +49 -0
- package/dist/src/download/media-download.js +127 -0
- package/dist/src/download/media-download.test.d.ts +1 -0
- package/dist/src/download/media-download.test.js +112 -0
- package/dist/src/download/progress.d.ts +36 -0
- package/dist/src/download/progress.js +120 -0
- package/dist/src/download/progress.test.d.ts +1 -0
- package/dist/src/download/progress.test.js +36 -0
- package/dist/src/electron-apps.d.ts +31 -0
- package/dist/src/electron-apps.js +91 -0
- package/dist/src/electron-apps.test.d.ts +1 -0
- package/dist/src/electron-apps.test.js +76 -0
- package/dist/src/engine.test.d.ts +1 -0
- package/dist/src/engine.test.js +312 -0
- package/dist/src/errors.d.ts +114 -0
- package/dist/src/errors.js +174 -0
- package/dist/src/errors.test.d.ts +1 -0
- package/dist/src/errors.test.js +109 -0
- package/dist/src/execution.d.ts +24 -0
- package/dist/src/execution.js +509 -0
- package/dist/src/execution.test.d.ts +1 -0
- package/dist/src/execution.test.js +645 -0
- package/dist/src/external.d.ts +49 -0
- package/dist/src/external.js +211 -0
- package/dist/src/external.test.d.ts +1 -0
- package/dist/src/external.test.js +110 -0
- package/dist/src/help.d.ts +88 -0
- package/dist/src/help.js +556 -0
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +58 -0
- package/dist/src/hooks.d.ts +46 -0
- package/dist/src/hooks.js +58 -0
- package/dist/src/hooks.test.d.ts +4 -0
- package/dist/src/hooks.test.js +92 -0
- package/dist/src/interceptor.d.ts +44 -0
- package/dist/src/interceptor.js +183 -0
- package/dist/src/interceptor.test.d.ts +4 -0
- package/dist/src/interceptor.test.js +81 -0
- package/dist/src/launcher.d.ts +41 -0
- package/dist/src/launcher.js +226 -0
- package/dist/src/launcher.test.d.ts +1 -0
- package/dist/src/launcher.test.js +153 -0
- package/dist/src/logger.d.ts +26 -0
- package/dist/src/logger.js +49 -0
- package/dist/src/main.d.ts +5 -0
- package/dist/src/main.js +177 -0
- package/dist/src/manifest-types.d.ts +43 -0
- package/dist/src/manifest-types.js +9 -0
- package/dist/src/node-network.d.ts +10 -0
- package/dist/src/node-network.js +174 -0
- package/dist/src/node-network.test.d.ts +1 -0
- package/dist/src/node-network.test.js +55 -0
- package/dist/src/observation/artifact.d.ts +16 -0
- package/dist/src/observation/artifact.js +260 -0
- package/dist/src/observation/artifact.test.d.ts +1 -0
- package/dist/src/observation/artifact.test.js +121 -0
- package/dist/src/observation/events.d.ts +89 -0
- package/dist/src/observation/events.js +1 -0
- package/dist/src/observation/index.d.ts +7 -0
- package/dist/src/observation/index.js +7 -0
- package/dist/src/observation/manager.d.ts +9 -0
- package/dist/src/observation/manager.js +27 -0
- package/dist/src/observation/manager.test.d.ts +1 -0
- package/dist/src/observation/manager.test.js +13 -0
- package/dist/src/observation/redaction.d.ts +11 -0
- package/dist/src/observation/redaction.js +81 -0
- package/dist/src/observation/redaction.test.d.ts +1 -0
- package/dist/src/observation/redaction.test.js +32 -0
- package/dist/src/observation/retention.d.ts +32 -0
- package/dist/src/observation/retention.js +160 -0
- package/dist/src/observation/retention.test.d.ts +1 -0
- package/dist/src/observation/retention.test.js +118 -0
- package/dist/src/observation/ring-buffer.d.ts +22 -0
- package/dist/src/observation/ring-buffer.js +45 -0
- package/dist/src/observation/ring-buffer.test.d.ts +1 -0
- package/dist/src/observation/ring-buffer.test.js +22 -0
- package/dist/src/observation/session.d.ts +25 -0
- package/dist/src/observation/session.js +50 -0
- package/dist/src/output.d.ts +14 -0
- package/dist/src/output.js +152 -0
- package/dist/src/output.test.d.ts +1 -0
- package/dist/src/output.test.js +53 -0
- package/dist/src/package-exports.test.d.ts +1 -0
- package/dist/src/package-exports.test.js +112 -0
- package/dist/src/package-paths.d.ts +8 -0
- package/dist/src/package-paths.js +41 -0
- package/dist/src/pipeline/executor.d.ts +11 -0
- package/dist/src/pipeline/executor.js +93 -0
- package/dist/src/pipeline/executor.test.d.ts +4 -0
- package/dist/src/pipeline/executor.test.js +182 -0
- package/dist/src/pipeline/index.d.ts +5 -0
- package/dist/src/pipeline/index.js +5 -0
- package/dist/src/pipeline/registry.d.ts +28 -0
- package/dist/src/pipeline/registry.js +55 -0
- package/dist/src/pipeline/steps/browser.d.ts +13 -0
- package/dist/src/pipeline/steps/browser.js +86 -0
- package/dist/src/pipeline/steps/download.d.ts +18 -0
- package/dist/src/pipeline/steps/download.js +252 -0
- package/dist/src/pipeline/steps/download.test.d.ts +1 -0
- package/dist/src/pipeline/steps/download.test.js +102 -0
- package/dist/src/pipeline/steps/fetch.d.ts +5 -0
- package/dist/src/pipeline/steps/fetch.js +122 -0
- package/dist/src/pipeline/steps/fetch.test.d.ts +1 -0
- package/dist/src/pipeline/steps/fetch.test.js +117 -0
- package/dist/src/pipeline/steps/intercept.d.ts +5 -0
- package/dist/src/pipeline/steps/intercept.js +50 -0
- package/dist/src/pipeline/steps/tap.d.ts +12 -0
- package/dist/src/pipeline/steps/tap.js +93 -0
- package/dist/src/pipeline/steps/transform.d.ts +9 -0
- package/dist/src/pipeline/steps/transform.js +70 -0
- package/dist/src/pipeline/template.d.ts +17 -0
- package/dist/src/pipeline/template.js +344 -0
- package/dist/src/pipeline/template.test.d.ts +4 -0
- package/dist/src/pipeline/template.test.js +197 -0
- package/dist/src/pipeline/transform.test.d.ts +4 -0
- package/dist/src/pipeline/transform.test.js +133 -0
- package/dist/src/plugin-manifest.d.ts +70 -0
- package/dist/src/plugin-manifest.js +160 -0
- package/dist/src/plugin-manifest.test.d.ts +4 -0
- package/dist/src/plugin-manifest.test.js +179 -0
- package/dist/src/plugin-scaffold.d.ts +28 -0
- package/dist/src/plugin-scaffold.js +143 -0
- package/dist/src/plugin-scaffold.test.d.ts +4 -0
- package/dist/src/plugin-scaffold.test.js +83 -0
- package/dist/src/plugin.d.ts +146 -0
- package/dist/src/plugin.js +1303 -0
- package/dist/src/plugin.test.d.ts +4 -0
- package/dist/src/plugin.test.js +1363 -0
- package/dist/src/registry-api.d.ts +13 -0
- package/dist/src/registry-api.js +10 -0
- package/dist/src/registry.d.ts +123 -0
- package/dist/src/registry.js +125 -0
- package/dist/src/registry.test.d.ts +4 -0
- package/dist/src/registry.test.js +209 -0
- package/dist/src/runtime-copy.test.d.ts +1 -0
- package/dist/src/runtime-copy.test.js +29 -0
- package/dist/src/runtime-detect.d.ts +31 -0
- package/dist/src/runtime-detect.js +47 -0
- package/dist/src/runtime-detect.test.d.ts +1 -0
- package/dist/src/runtime-detect.test.js +39 -0
- package/dist/src/runtime-identity.test.d.ts +1 -0
- package/dist/src/runtime-identity.test.js +18 -0
- package/dist/src/runtime.d.ts +47 -0
- package/dist/src/runtime.js +54 -0
- package/dist/src/scripts/framework.d.ts +7 -0
- package/dist/src/scripts/framework.js +25 -0
- package/dist/src/scripts/interact.d.ts +4 -0
- package/dist/src/scripts/interact.js +20 -0
- package/dist/src/scripts/store.d.ts +13 -0
- package/dist/src/scripts/store.js +48 -0
- package/dist/src/serialization.d.ts +42 -0
- package/dist/src/serialization.js +103 -0
- package/dist/src/serialization.test.d.ts +1 -0
- package/dist/src/serialization.test.js +78 -0
- package/dist/src/skills.d.ts +14 -0
- package/dist/src/skills.js +127 -0
- package/dist/src/skills.test.d.ts +1 -0
- package/dist/src/skills.test.js +71 -0
- package/dist/src/snapshotFormatter.d.ts +11 -0
- package/dist/src/snapshotFormatter.js +338 -0
- package/dist/src/snapshotFormatter.test.d.ts +7 -0
- package/dist/src/snapshotFormatter.test.js +521 -0
- package/dist/src/tui.d.ts +28 -0
- package/dist/src/tui.js +179 -0
- package/dist/src/tui.test.d.ts +1 -0
- package/dist/src/tui.test.js +19 -0
- package/dist/src/types.d.ts +236 -0
- package/dist/src/types.js +7 -0
- package/dist/src/update-check.d.ts +66 -0
- package/dist/src/update-check.js +206 -0
- package/dist/src/update-check.test.d.ts +1 -0
- package/dist/src/update-check.test.js +117 -0
- package/dist/src/utils.d.ts +57 -0
- package/dist/src/utils.js +152 -0
- package/dist/src/utils.test.d.ts +1 -0
- package/dist/src/utils.test.js +155 -0
- package/dist/src/validate.d.ts +21 -0
- package/dist/src/validate.js +130 -0
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/dist/src/verify.d.ts +26 -0
- package/dist/src/verify.js +67 -0
- package/dist/src/version.d.ts +4 -0
- package/dist/src/version.js +22 -0
- package/dist/src/weixin-download.test.d.ts +1 -0
- package/dist/src/weixin-download.test.js +45 -0
- package/package.json +4 -3
- package/scripts/check-package-bin.mjs +95 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-blocking update checker.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: register exit-hook + kick-off-background-fetch
|
|
5
|
+
* - On startup: kick off background fetch (non-blocking)
|
|
6
|
+
* - On process exit: read cache, print notice if newer version exists
|
|
7
|
+
* - Check interval: 24 hours
|
|
8
|
+
* - Notice appears AFTER command output, not before (same as npm/gh/yarn)
|
|
9
|
+
* - Never delays or blocks the CLI command
|
|
10
|
+
*
|
|
11
|
+
* Cache is shared between the CLI process (writes latestVersion / latestExtensionVersion
|
|
12
|
+
* via background fetch) and the daemon process (writes currentExtensionVersion /
|
|
13
|
+
* extensionLastSeenAt via `recordExtensionVersion` on each hello). Writes use a
|
|
14
|
+
* read-merge-write pattern so neither side clobbers the other.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import * as os from 'node:os';
|
|
19
|
+
import { PKG_VERSION } from './version.js';
|
|
20
|
+
import { EXTENSION_ARTIFACT_PREFIX, PACKAGE_NAME, PRODUCT_DISPLAY_NAME, PRODUCT_NAME } from './brand.js';
|
|
21
|
+
const CACHE_DIR = path.join(os.homedir(), '.webcmd');
|
|
22
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'update-check.json');
|
|
23
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
24
|
+
const EXTENSION_STALE_MS = 7 * 24 * 60 * 60 * 1000; // 7d
|
|
25
|
+
const NPM_REGISTRY_URL = 'https://invalid.example/webcmd/latest';
|
|
26
|
+
const GITHUB_RELEASES_URL = 'https://invalid.example/webcmd/releases';
|
|
27
|
+
const UPDATE_CHECKS_ENABLED = false;
|
|
28
|
+
function readCacheSync() {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Read cache once at module load — shared by both exported functions
|
|
37
|
+
const _cache = readCacheSync();
|
|
38
|
+
function writeCacheMerge(updates) {
|
|
39
|
+
try {
|
|
40
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
41
|
+
const existing = readCacheSync() ?? {};
|
|
42
|
+
const merged = { ...existing, ...updates };
|
|
43
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(merged), 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Best-effort; never fail
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Compare semver strings. Returns true if `a` is strictly newer than `b`. */
|
|
50
|
+
function isNewer(a, b) {
|
|
51
|
+
const parse = (v) => v.replace(/^v/, '').split('-')[0].split('.').map(Number);
|
|
52
|
+
const pa = parse(a);
|
|
53
|
+
const pb = parse(b);
|
|
54
|
+
if (pa.some(isNaN) || pb.some(isNaN))
|
|
55
|
+
return false;
|
|
56
|
+
const [aMaj, aMin, aPat] = pa;
|
|
57
|
+
const [bMaj, bMin, bPat] = pb;
|
|
58
|
+
if (aMaj !== bMaj)
|
|
59
|
+
return aMaj > bMaj;
|
|
60
|
+
if (aMin !== bMin)
|
|
61
|
+
return aMin > bMin;
|
|
62
|
+
return aPat > bPat;
|
|
63
|
+
}
|
|
64
|
+
function isCI() {
|
|
65
|
+
return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION);
|
|
66
|
+
}
|
|
67
|
+
/** Pure function: derive notice text from cache state. Exported for tests. */
|
|
68
|
+
function buildUpdateNotices({ cliVersion, cache, now }) {
|
|
69
|
+
if (!cache)
|
|
70
|
+
return {};
|
|
71
|
+
const lines = {};
|
|
72
|
+
if (cache.latestVersion && isNewer(cache.latestVersion, cliVersion)) {
|
|
73
|
+
lines.cli =
|
|
74
|
+
`\n Update available: v${cliVersion} → v${cache.latestVersion}\n` +
|
|
75
|
+
` Run: npm install -g ${PACKAGE_NAME}\n`;
|
|
76
|
+
}
|
|
77
|
+
const { currentExtensionVersion, latestExtensionVersion, extensionLastSeenAt } = cache;
|
|
78
|
+
if (currentExtensionVersion &&
|
|
79
|
+
latestExtensionVersion &&
|
|
80
|
+
extensionLastSeenAt &&
|
|
81
|
+
now - extensionLastSeenAt < EXTENSION_STALE_MS &&
|
|
82
|
+
isNewer(latestExtensionVersion, currentExtensionVersion)) {
|
|
83
|
+
lines.extension =
|
|
84
|
+
`\n Runtime update available: v${currentExtensionVersion} → v${latestExtensionVersion}\n` +
|
|
85
|
+
` Update the ${PRODUCT_DISPLAY_NAME} Cloak runtime from AgentR release artifacts.\n`;
|
|
86
|
+
}
|
|
87
|
+
return lines;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Register a process exit hook that prints an update notice if a newer
|
|
91
|
+
* version was found on the last background check.
|
|
92
|
+
* Notice appears after command output — same pattern as npm/gh/yarn.
|
|
93
|
+
* Skipped during --get-completions to avoid polluting shell completion output.
|
|
94
|
+
*/
|
|
95
|
+
export function registerUpdateNoticeOnExit() {
|
|
96
|
+
if (isCI())
|
|
97
|
+
return;
|
|
98
|
+
if (process.argv.includes('--get-completions'))
|
|
99
|
+
return;
|
|
100
|
+
process.on('exit', (code) => {
|
|
101
|
+
if (code !== 0)
|
|
102
|
+
return; // Don't show update notice on error exit
|
|
103
|
+
const { cli, extension } = buildUpdateNotices({
|
|
104
|
+
cliVersion: PKG_VERSION,
|
|
105
|
+
cache: _cache,
|
|
106
|
+
now: Date.now(),
|
|
107
|
+
});
|
|
108
|
+
if (!cli && !extension)
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
process.stderr.write(`${cli ?? ''}${extension ?? ''}\n`);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Ignore broken pipe (stderr closed before process exits)
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function extractLatestExtensionVersionFromReleases(releases) {
|
|
119
|
+
for (const release of releases) {
|
|
120
|
+
for (const asset of release.assets ?? []) {
|
|
121
|
+
const assetMatch = asset.name.match(new RegExp(`^${EXTENSION_ARTIFACT_PREFIX}-v(.+)\\.zip$`));
|
|
122
|
+
if (assetMatch)
|
|
123
|
+
return assetMatch[1];
|
|
124
|
+
}
|
|
125
|
+
const tagMatch = release.tag_name.match(/^ext-v(.+)$/);
|
|
126
|
+
if (tagMatch)
|
|
127
|
+
return tagMatch[1];
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
/** Fetch the latest extension version from GitHub Releases. */
|
|
132
|
+
async function fetchLatestExtensionVersion() {
|
|
133
|
+
try {
|
|
134
|
+
const controller = new AbortController();
|
|
135
|
+
const timer = setTimeout(() => controller.abort(), 3000);
|
|
136
|
+
const res = await fetch(GITHUB_RELEASES_URL, {
|
|
137
|
+
signal: controller.signal,
|
|
138
|
+
headers: { 'User-Agent': `${PRODUCT_NAME}/${PKG_VERSION}`, Accept: 'application/vnd.github+json' },
|
|
139
|
+
});
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
if (!res.ok)
|
|
142
|
+
return undefined;
|
|
143
|
+
const releases = await res.json();
|
|
144
|
+
return extractLatestExtensionVersionFromReleases(releases);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Kick off a background fetch to npm registry. Writes to cache for next run.
|
|
152
|
+
* Fully non-blocking — never awaited.
|
|
153
|
+
*/
|
|
154
|
+
export function checkForUpdateBackground() {
|
|
155
|
+
if (!UPDATE_CHECKS_ENABLED)
|
|
156
|
+
return;
|
|
157
|
+
if (isCI())
|
|
158
|
+
return;
|
|
159
|
+
if (_cache?.lastCheck && Date.now() - _cache.lastCheck < CHECK_INTERVAL_MS)
|
|
160
|
+
return;
|
|
161
|
+
void (async () => {
|
|
162
|
+
try {
|
|
163
|
+
const controller = new AbortController();
|
|
164
|
+
const timer = setTimeout(() => controller.abort(), 3000);
|
|
165
|
+
const res = await fetch(NPM_REGISTRY_URL, {
|
|
166
|
+
signal: controller.signal,
|
|
167
|
+
headers: { 'User-Agent': `${PRODUCT_NAME}/${PKG_VERSION}` },
|
|
168
|
+
});
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
if (!res.ok)
|
|
171
|
+
return;
|
|
172
|
+
const data = await res.json();
|
|
173
|
+
if (typeof data.version === 'string') {
|
|
174
|
+
const extVersion = await fetchLatestExtensionVersion();
|
|
175
|
+
const updates = { lastCheck: Date.now(), latestVersion: data.version };
|
|
176
|
+
if (extVersion)
|
|
177
|
+
updates.latestExtensionVersion = extVersion;
|
|
178
|
+
writeCacheMerge(updates);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Network error: silently skip, try again next run
|
|
183
|
+
}
|
|
184
|
+
})();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Stash the current extension version into the shared cache. Called by the
|
|
188
|
+
* daemon on each hello handshake. Lets the next CLI process compare against
|
|
189
|
+
* the latest known release and print an exit notice without any extra I/O.
|
|
190
|
+
*/
|
|
191
|
+
export function recordExtensionVersion(version) {
|
|
192
|
+
if (typeof version !== 'string' || !version.trim())
|
|
193
|
+
return;
|
|
194
|
+
writeCacheMerge({
|
|
195
|
+
currentExtensionVersion: version.trim(),
|
|
196
|
+
extensionLastSeenAt: Date.now(),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get the cached latest extension version (if available).
|
|
201
|
+
* Used by `webcmd doctor` to report extension updates.
|
|
202
|
+
*/
|
|
203
|
+
export function getCachedLatestExtensionVersion() {
|
|
204
|
+
return _cache?.latestExtensionVersion;
|
|
205
|
+
}
|
|
206
|
+
export { extractLatestExtensionVersionFromReleases as _extractLatestExtensionVersionFromReleases, buildUpdateNotices as _buildUpdateNotices, EXTENSION_STALE_MS as _EXTENSION_STALE_MS, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { _extractLatestExtensionVersionFromReleases as extractLatestExtensionVersionFromReleases, _buildUpdateNotices as buildUpdateNotices, _EXTENSION_STALE_MS as EXTENSION_STALE_MS, } from './update-check.js';
|
|
3
|
+
describe('extractLatestExtensionVersionFromReleases', () => {
|
|
4
|
+
it('reads the extension version from a versioned asset on a normal CLI release', () => {
|
|
5
|
+
expect(extractLatestExtensionVersionFromReleases([
|
|
6
|
+
{
|
|
7
|
+
tag_name: 'v1.7.3',
|
|
8
|
+
assets: [
|
|
9
|
+
{ name: 'webcmd-extension.zip' },
|
|
10
|
+
{ name: 'webcmd-extension-v1.0.2.zip' },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
])).toBe('1.0.2');
|
|
14
|
+
});
|
|
15
|
+
it('falls back to ext-v tags for extension-only releases', () => {
|
|
16
|
+
expect(extractLatestExtensionVersionFromReleases([
|
|
17
|
+
{
|
|
18
|
+
tag_name: 'ext-v1.1.0',
|
|
19
|
+
assets: [{ name: 'webcmd-extension.zip' }],
|
|
20
|
+
},
|
|
21
|
+
])).toBe('1.1.0');
|
|
22
|
+
});
|
|
23
|
+
it('returns undefined when no extension version source exists', () => {
|
|
24
|
+
expect(extractLatestExtensionVersionFromReleases([
|
|
25
|
+
{
|
|
26
|
+
tag_name: 'v1.7.3',
|
|
27
|
+
assets: [{ name: 'webcmd-extension.zip' }],
|
|
28
|
+
},
|
|
29
|
+
])).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('buildUpdateNotices', () => {
|
|
33
|
+
const now = 1_700_000_000_000;
|
|
34
|
+
it('returns nothing when cache is empty', () => {
|
|
35
|
+
expect(buildUpdateNotices({ cliVersion: '1.0.0', cache: null, now })).toEqual({});
|
|
36
|
+
});
|
|
37
|
+
it('emits a CLI notice when registry version is newer', () => {
|
|
38
|
+
const lines = buildUpdateNotices({
|
|
39
|
+
cliVersion: '1.0.0',
|
|
40
|
+
cache: { lastCheck: now, latestVersion: '1.0.1' },
|
|
41
|
+
now,
|
|
42
|
+
});
|
|
43
|
+
expect(lines.cli).toContain('v1.0.0 → v1.0.1');
|
|
44
|
+
expect(lines.extension).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
it('emits a runtime notice when current runtime is older and cache is fresh', () => {
|
|
47
|
+
const lines = buildUpdateNotices({
|
|
48
|
+
cliVersion: '1.0.0',
|
|
49
|
+
cache: {
|
|
50
|
+
lastCheck: now,
|
|
51
|
+
latestVersion: '1.0.0',
|
|
52
|
+
latestExtensionVersion: '2.1.0',
|
|
53
|
+
currentExtensionVersion: '2.0.0',
|
|
54
|
+
extensionLastSeenAt: now - 60_000,
|
|
55
|
+
},
|
|
56
|
+
now,
|
|
57
|
+
});
|
|
58
|
+
expect(lines.cli).toBeUndefined();
|
|
59
|
+
expect(lines.extension).toContain('Runtime update available');
|
|
60
|
+
expect(lines.extension).toContain('v2.0.0 → v2.1.0');
|
|
61
|
+
});
|
|
62
|
+
it('skips the runtime notice when lastSeenAt is older than the stale window', () => {
|
|
63
|
+
const lines = buildUpdateNotices({
|
|
64
|
+
cliVersion: '1.0.0',
|
|
65
|
+
cache: {
|
|
66
|
+
lastCheck: now,
|
|
67
|
+
latestVersion: '1.0.0',
|
|
68
|
+
latestExtensionVersion: '2.1.0',
|
|
69
|
+
currentExtensionVersion: '2.0.0',
|
|
70
|
+
extensionLastSeenAt: now - EXTENSION_STALE_MS - 1,
|
|
71
|
+
},
|
|
72
|
+
now,
|
|
73
|
+
});
|
|
74
|
+
expect(lines.extension).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
it('skips the extension notice when current and latest are equal', () => {
|
|
77
|
+
const lines = buildUpdateNotices({
|
|
78
|
+
cliVersion: '1.0.0',
|
|
79
|
+
cache: {
|
|
80
|
+
lastCheck: now,
|
|
81
|
+
latestVersion: '1.0.0',
|
|
82
|
+
latestExtensionVersion: '2.0.0',
|
|
83
|
+
currentExtensionVersion: '2.0.0',
|
|
84
|
+
extensionLastSeenAt: now,
|
|
85
|
+
},
|
|
86
|
+
now,
|
|
87
|
+
});
|
|
88
|
+
expect(lines.extension).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
it('does not throw when cache has only daemon-written fields and no latestVersion', () => {
|
|
91
|
+
const lines = buildUpdateNotices({
|
|
92
|
+
cliVersion: '1.0.0',
|
|
93
|
+
cache: {
|
|
94
|
+
currentExtensionVersion: '2.0.0',
|
|
95
|
+
extensionLastSeenAt: now,
|
|
96
|
+
},
|
|
97
|
+
now,
|
|
98
|
+
});
|
|
99
|
+
expect(lines.cli).toBeUndefined();
|
|
100
|
+
expect(lines.extension).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
it('emits both notices when both are out of date', () => {
|
|
103
|
+
const lines = buildUpdateNotices({
|
|
104
|
+
cliVersion: '1.0.0',
|
|
105
|
+
cache: {
|
|
106
|
+
lastCheck: now,
|
|
107
|
+
latestVersion: '1.1.0',
|
|
108
|
+
latestExtensionVersion: '2.1.0',
|
|
109
|
+
currentExtensionVersion: '2.0.0',
|
|
110
|
+
extensionLastSeenAt: now,
|
|
111
|
+
},
|
|
112
|
+
now,
|
|
113
|
+
});
|
|
114
|
+
expect(lines.cli).toContain('v1.0.0 → v1.1.0');
|
|
115
|
+
expect(lines.extension).toContain('v2.0.0 → v2.1.0');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility functions used across the codebase.
|
|
3
|
+
*/
|
|
4
|
+
import TurndownService from 'turndown';
|
|
5
|
+
/** Type guard: checks if a value is a non-null, non-array object. */
|
|
6
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
7
|
+
/** Simple async concurrency limiter. */
|
|
8
|
+
export declare function mapConcurrent<T, R>(items: T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
9
|
+
/** Pause for the given number of milliseconds. */
|
|
10
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
11
|
+
/** Save a base64-encoded string to a file, creating parent directories as needed. */
|
|
12
|
+
export declare function saveBase64ToFile(base64: string, filePath: string): Promise<void>;
|
|
13
|
+
export declare function createMarkdownConverter(configure?: (td: TurndownService) => void): TurndownService;
|
|
14
|
+
export declare function htmlToMarkdown(value: string, configure?: (td: TurndownService) => void): string;
|
|
15
|
+
/** Sentinel shape that browser-side `fetch` wrappers return when they detect an
|
|
16
|
+
* HTML response in place of JSON. Kept as a plain object so it survives the
|
|
17
|
+
* `page.evaluate` JSON round-trip. */
|
|
18
|
+
export interface LoginWallSignal {
|
|
19
|
+
__loginWall: true;
|
|
20
|
+
status: number;
|
|
21
|
+
url: string;
|
|
22
|
+
contentType: string;
|
|
23
|
+
bodyPreview: string;
|
|
24
|
+
}
|
|
25
|
+
/** Throw a `LoginWallError` if `value` is the sentinel returned by the
|
|
26
|
+
* browser-side sniffer; otherwise return `value` unchanged. Adapters that
|
|
27
|
+
* fetch from inside `page.evaluate` call this on the result before consuming
|
|
28
|
+
* it, so the Node-side gets a typed error instead of a JSON-parse stack
|
|
29
|
+
* trace. */
|
|
30
|
+
export declare function throwIfLoginWall<T>(value: T, opts?: {
|
|
31
|
+
url?: string;
|
|
32
|
+
}): T;
|
|
33
|
+
/** Parse a `Response` body as JSON, throwing `LoginWallError` if the server
|
|
34
|
+
* returned an HTML page (login wall / rate limit / WAF interception) instead
|
|
35
|
+
* of the expected JSON. Catches the common case of `<!DOCTYPE` or `<html`
|
|
36
|
+
* leading the body \u2014 naive `JSON.parse` on these gives a cryptic
|
|
37
|
+
* `SyntaxError` that callers can't distinguish from "real" malformed JSON.
|
|
38
|
+
*
|
|
39
|
+
* On real (non-HTML) JSON-parse failures, throws a regular `Error` with a
|
|
40
|
+
* body preview attached so debugging doesn't require a packet capture. */
|
|
41
|
+
export declare function parseJsonOrThrowLoginWall(response: Response, opts?: {
|
|
42
|
+
url?: string;
|
|
43
|
+
}): Promise<unknown>;
|
|
44
|
+
/** Browser-side JS source fragment (as a string) that performs a `fetch` and
|
|
45
|
+
* either returns the parsed JSON body or a `LoginWallSignal` sentinel when
|
|
46
|
+
* the response is HTML. Intended to be embedded inside an adapter's
|
|
47
|
+
* `page.evaluate` block.
|
|
48
|
+
*
|
|
49
|
+
* Usage from inside a `page.evaluate` IIFE:
|
|
50
|
+
*
|
|
51
|
+
* ${BROWSER_JSON_SNIFF_FN}
|
|
52
|
+
* const res = await fetchJsonOrLoginWall('/some/path.json', { credentials: 'include' });
|
|
53
|
+
* // res is the parsed JSON object, OR { __loginWall: true, status, url, contentType, bodyPreview }
|
|
54
|
+
* return res;
|
|
55
|
+
*
|
|
56
|
+
* The Node side then calls `throwIfLoginWall(res, { url })` on the result. */
|
|
57
|
+
export declare const BROWSER_JSON_SNIFF_FN = "\nasync function fetchJsonOrLoginWall(input, init) {\n const r = await fetch(input, init);\n const contentType = r.headers.get('content-type') || '';\n const text = await r.text();\n const trimmed = text.replace(/^\\s+/, '');\n const looksLikeHtml =\n contentType.toLowerCase().includes('text/html')\n || trimmed.startsWith('<!DOCTYPE')\n || trimmed.startsWith('<!doctype')\n || trimmed.startsWith('<html')\n || trimmed.startsWith('<HTML');\n if (looksLikeHtml) {\n return {\n __loginWall: true,\n status: r.status,\n url: r.url || (typeof input === 'string' ? input : ''),\n contentType,\n bodyPreview: trimmed.slice(0, 100),\n };\n }\n if (!r.ok) {\n return { error: r.status };\n }\n try {\n return JSON.parse(text);\n } catch (err) {\n throw new Error(\n 'JSON parse failed (status=' + r.status + ', body[0..50]=' + JSON.stringify(trimmed.slice(0, 50)) + '): '\n + (err && err.message ? err.message : String(err))\n );\n }\n}\n";
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility functions used across the codebase.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import TurndownService from 'turndown';
|
|
7
|
+
import { LoginWallError } from './errors.js';
|
|
8
|
+
/** Type guard: checks if a value is a non-null, non-array object. */
|
|
9
|
+
export function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/** Simple async concurrency limiter. */
|
|
13
|
+
export async function mapConcurrent(items, limit, fn) {
|
|
14
|
+
const results = new Array(items.length);
|
|
15
|
+
let index = 0;
|
|
16
|
+
async function worker() {
|
|
17
|
+
while (index < items.length) {
|
|
18
|
+
const i = index++;
|
|
19
|
+
results[i] = await fn(items[i], i);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
23
|
+
await Promise.all(workers);
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
/** Pause for the given number of milliseconds. */
|
|
27
|
+
export function sleep(ms) {
|
|
28
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
/** Save a base64-encoded string to a file, creating parent directories as needed. */
|
|
31
|
+
export async function saveBase64ToFile(base64, filePath) {
|
|
32
|
+
const dir = path.dirname(filePath);
|
|
33
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
34
|
+
await fs.promises.writeFile(filePath, Buffer.from(base64, 'base64'));
|
|
35
|
+
}
|
|
36
|
+
export function createMarkdownConverter(configure) {
|
|
37
|
+
const td = new TurndownService({
|
|
38
|
+
headingStyle: 'atx',
|
|
39
|
+
codeBlockStyle: 'fenced',
|
|
40
|
+
bulletListMarker: '-',
|
|
41
|
+
});
|
|
42
|
+
td.addRule('linebreak', {
|
|
43
|
+
filter: 'br',
|
|
44
|
+
replacement: () => '\n',
|
|
45
|
+
});
|
|
46
|
+
if (configure)
|
|
47
|
+
configure(td);
|
|
48
|
+
return td;
|
|
49
|
+
}
|
|
50
|
+
export function htmlToMarkdown(value, configure) {
|
|
51
|
+
return createMarkdownConverter(configure).turndown(value || '')
|
|
52
|
+
.replace(/\u00a0/g, ' ')
|
|
53
|
+
.replace(/\n{4,}/g, '\n\n\n')
|
|
54
|
+
.replace(/[ \t]+$/gm, '')
|
|
55
|
+
.trim();
|
|
56
|
+
}
|
|
57
|
+
function isLoginWallSignal(v) {
|
|
58
|
+
return (typeof v === 'object'
|
|
59
|
+
&& v !== null
|
|
60
|
+
&& v.__loginWall === true
|
|
61
|
+
&& typeof v.status === 'number');
|
|
62
|
+
}
|
|
63
|
+
/** Throw a `LoginWallError` if `value` is the sentinel returned by the
|
|
64
|
+
* browser-side sniffer; otherwise return `value` unchanged. Adapters that
|
|
65
|
+
* fetch from inside `page.evaluate` call this on the result before consuming
|
|
66
|
+
* it, so the Node-side gets a typed error instead of a JSON-parse stack
|
|
67
|
+
* trace. */
|
|
68
|
+
export function throwIfLoginWall(value, opts = {}) {
|
|
69
|
+
if (isLoginWallSignal(value)) {
|
|
70
|
+
throw new LoginWallError(`Server returned HTML instead of JSON (status=${value.status}). `
|
|
71
|
+
+ `Likely a login wall, rate limit, or WAF challenge.`, value.status, opts.url || value.url || '', value.bodyPreview);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
/** Parse a `Response` body as JSON, throwing `LoginWallError` if the server
|
|
76
|
+
* returned an HTML page (login wall / rate limit / WAF interception) instead
|
|
77
|
+
* of the expected JSON. Catches the common case of `<!DOCTYPE` or `<html`
|
|
78
|
+
* leading the body \u2014 naive `JSON.parse` on these gives a cryptic
|
|
79
|
+
* `SyntaxError` that callers can't distinguish from "real" malformed JSON.
|
|
80
|
+
*
|
|
81
|
+
* On real (non-HTML) JSON-parse failures, throws a regular `Error` with a
|
|
82
|
+
* body preview attached so debugging doesn't require a packet capture. */
|
|
83
|
+
export async function parseJsonOrThrowLoginWall(response, opts = {}) {
|
|
84
|
+
const contentType = response.headers.get('content-type') || '';
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
const trimmed = text.trimStart();
|
|
87
|
+
const looksLikeHtml = contentType.toLowerCase().includes('text/html')
|
|
88
|
+
|| trimmed.startsWith('<!DOCTYPE')
|
|
89
|
+
|| trimmed.startsWith('<!doctype')
|
|
90
|
+
|| trimmed.startsWith('<html')
|
|
91
|
+
|| trimmed.startsWith('<HTML');
|
|
92
|
+
if (looksLikeHtml) {
|
|
93
|
+
throw new LoginWallError(`Server returned HTML instead of JSON (status=${response.status}). `
|
|
94
|
+
+ `Likely a login wall, rate limit, or WAF challenge.`, response.status, opts.url || response.url || '', trimmed.slice(0, 100));
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(text);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Real malformed JSON \u2014 surface body preview alongside the parser message
|
|
101
|
+
// so we don't have to repro to know what came back.
|
|
102
|
+
throw new Error(`JSON parse failed (status=${response.status}, body[0..50]=${JSON.stringify(trimmed.slice(0, 50))}): `
|
|
103
|
+
+ (err instanceof Error ? err.message : String(err)));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Browser-side JS source fragment (as a string) that performs a `fetch` and
|
|
107
|
+
* either returns the parsed JSON body or a `LoginWallSignal` sentinel when
|
|
108
|
+
* the response is HTML. Intended to be embedded inside an adapter's
|
|
109
|
+
* `page.evaluate` block.
|
|
110
|
+
*
|
|
111
|
+
* Usage from inside a `page.evaluate` IIFE:
|
|
112
|
+
*
|
|
113
|
+
* ${BROWSER_JSON_SNIFF_FN}
|
|
114
|
+
* const res = await fetchJsonOrLoginWall('/some/path.json', { credentials: 'include' });
|
|
115
|
+
* // res is the parsed JSON object, OR { __loginWall: true, status, url, contentType, bodyPreview }
|
|
116
|
+
* return res;
|
|
117
|
+
*
|
|
118
|
+
* The Node side then calls `throwIfLoginWall(res, { url })` on the result. */
|
|
119
|
+
export const BROWSER_JSON_SNIFF_FN = `
|
|
120
|
+
async function fetchJsonOrLoginWall(input, init) {
|
|
121
|
+
const r = await fetch(input, init);
|
|
122
|
+
const contentType = r.headers.get('content-type') || '';
|
|
123
|
+
const text = await r.text();
|
|
124
|
+
const trimmed = text.replace(/^\\s+/, '');
|
|
125
|
+
const looksLikeHtml =
|
|
126
|
+
contentType.toLowerCase().includes('text/html')
|
|
127
|
+
|| trimmed.startsWith('<!DOCTYPE')
|
|
128
|
+
|| trimmed.startsWith('<!doctype')
|
|
129
|
+
|| trimmed.startsWith('<html')
|
|
130
|
+
|| trimmed.startsWith('<HTML');
|
|
131
|
+
if (looksLikeHtml) {
|
|
132
|
+
return {
|
|
133
|
+
__loginWall: true,
|
|
134
|
+
status: r.status,
|
|
135
|
+
url: r.url || (typeof input === 'string' ? input : ''),
|
|
136
|
+
contentType,
|
|
137
|
+
bodyPreview: trimmed.slice(0, 100),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (!r.ok) {
|
|
141
|
+
return { error: r.status };
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(text);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'JSON parse failed (status=' + r.status + ', body[0..50]=' + JSON.stringify(trimmed.slice(0, 50)) + '): '
|
|
148
|
+
+ (err && err.message ? err.message : String(err))
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|