@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,92 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { CloakSessionManager } from './session-manager.js';
|
|
3
|
+
function fakeContext() {
|
|
4
|
+
const page = {
|
|
5
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
6
|
+
evaluate: vi.fn().mockResolvedValue('ok'),
|
|
7
|
+
title: vi.fn().mockResolvedValue('Title'),
|
|
8
|
+
url: vi.fn().mockReturnValue('https://example.com/'),
|
|
9
|
+
screenshot: vi.fn().mockResolvedValue(Buffer.from('png')),
|
|
10
|
+
isClosed: vi.fn().mockReturnValue(false),
|
|
11
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
context: {
|
|
15
|
+
pages: vi.fn().mockReturnValue([page]),
|
|
16
|
+
newPage: vi.fn().mockResolvedValue(page),
|
|
17
|
+
cookies: vi.fn().mockResolvedValue([{ name: 'sid', value: '1', domain: 'example.com', path: '/' }]),
|
|
18
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
19
|
+
},
|
|
20
|
+
page,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
describe('CloakSessionManager', () => {
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
});
|
|
27
|
+
it('launches one persistent context per profile and reuses named sessions', async () => {
|
|
28
|
+
const launched = fakeContext();
|
|
29
|
+
const launchPersistentContext = vi.fn().mockResolvedValue(launched.context);
|
|
30
|
+
const manager = new CloakSessionManager({
|
|
31
|
+
baseDir: '/tmp/webcmd-test',
|
|
32
|
+
launchPersistentContext,
|
|
33
|
+
});
|
|
34
|
+
const first = await manager.getPage({ profileId: 'default', session: 'work', surface: 'browser' });
|
|
35
|
+
const second = await manager.getPage({ profileId: 'default', session: 'work', surface: 'browser' });
|
|
36
|
+
expect(first.page).toBe(second.page);
|
|
37
|
+
expect(launchPersistentContext).toHaveBeenCalledTimes(1);
|
|
38
|
+
expect(launchPersistentContext.mock.calls[0][0]).toMatchObject({ headless: false });
|
|
39
|
+
});
|
|
40
|
+
it('closes ephemeral adapter sessions when released', async () => {
|
|
41
|
+
const launched = fakeContext();
|
|
42
|
+
const manager = new CloakSessionManager({
|
|
43
|
+
baseDir: '/tmp/webcmd-test',
|
|
44
|
+
launchPersistentContext: vi.fn().mockResolvedValue(launched.context),
|
|
45
|
+
});
|
|
46
|
+
const lease = await manager.getPage({ profileId: 'default', session: 'site:x:uuid', surface: 'adapter', siteSession: 'ephemeral' });
|
|
47
|
+
await manager.release({ profileId: 'default', session: 'site:x:uuid', surface: 'adapter' });
|
|
48
|
+
expect(lease.page.close).toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
it('closes non-persistent leases when their idle timeout expires', async () => {
|
|
51
|
+
vi.useFakeTimers();
|
|
52
|
+
const launched = fakeContext();
|
|
53
|
+
const manager = new CloakSessionManager({
|
|
54
|
+
baseDir: '/tmp/webcmd-test',
|
|
55
|
+
launchPersistentContext: vi.fn().mockResolvedValue(launched.context),
|
|
56
|
+
});
|
|
57
|
+
const lease = await manager.getPage({ profileId: 'default', session: 'work', surface: 'browser', idleTimeout: 25 });
|
|
58
|
+
await vi.advanceTimersByTimeAsync(24);
|
|
59
|
+
expect(lease.page.close).not.toHaveBeenCalled();
|
|
60
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
61
|
+
expect(lease.page.close).toHaveBeenCalled();
|
|
62
|
+
expect(await manager.listPages({ profileId: 'default' })).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
it('refreshes an idle timeout when a lease is reused', async () => {
|
|
65
|
+
vi.useFakeTimers();
|
|
66
|
+
const launched = fakeContext();
|
|
67
|
+
const manager = new CloakSessionManager({
|
|
68
|
+
baseDir: '/tmp/webcmd-test',
|
|
69
|
+
launchPersistentContext: vi.fn().mockResolvedValue(launched.context),
|
|
70
|
+
});
|
|
71
|
+
const first = await manager.getPage({ profileId: 'default', session: 'work', surface: 'browser', idleTimeout: 25 });
|
|
72
|
+
await vi.advanceTimersByTimeAsync(20);
|
|
73
|
+
const second = await manager.getPage({ profileId: 'default', session: 'work', surface: 'browser', idleTimeout: 25 });
|
|
74
|
+
expect(second.page).toBe(first.page);
|
|
75
|
+
await vi.advanceTimersByTimeAsync(20);
|
|
76
|
+
expect(first.page.close).not.toHaveBeenCalled();
|
|
77
|
+
await vi.advanceTimersByTimeAsync(5);
|
|
78
|
+
expect(first.page.close).toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
it('does not close persistent site sessions when their idle timeout expires', async () => {
|
|
81
|
+
vi.useFakeTimers();
|
|
82
|
+
const launched = fakeContext();
|
|
83
|
+
const manager = new CloakSessionManager({
|
|
84
|
+
baseDir: '/tmp/webcmd-test',
|
|
85
|
+
launchPersistentContext: vi.fn().mockResolvedValue(launched.context),
|
|
86
|
+
});
|
|
87
|
+
const lease = await manager.getPage({ profileId: 'default', session: 'site:x:uuid', surface: 'adapter', siteSession: 'persistent', idleTimeout: 25 });
|
|
88
|
+
await vi.advanceTimersByTimeAsync(25);
|
|
89
|
+
expect(lease.page.close).not.toHaveBeenCalled();
|
|
90
|
+
expect(await manager.listPages({ profileId: 'default' })).toHaveLength(1);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BrowserRuntimeCommand, BrowserRuntimeResult, BrowserRuntimeStatus } from '../protocol.js';
|
|
2
|
+
export interface RuntimeStatusOptions {
|
|
3
|
+
contextId?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface BrowserRuntimeProvider {
|
|
6
|
+
status(opts?: RuntimeStatusOptions): Promise<BrowserRuntimeStatus>;
|
|
7
|
+
dispatch(command: BrowserRuntimeCommand): Promise<BrowserRuntimeResult>;
|
|
8
|
+
shutdown(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import '../protocol.js';
|
|
3
|
+
import './provider.js';
|
|
4
|
+
class EchoProvider {
|
|
5
|
+
async status() {
|
|
6
|
+
return {
|
|
7
|
+
runtimeConnected: true,
|
|
8
|
+
runtimeName: 'fake',
|
|
9
|
+
runtimeVersion: 'test',
|
|
10
|
+
profiles: [{ contextId: 'default', runtimeConnected: true, pending: 0 }],
|
|
11
|
+
pending: 0,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async dispatch(command) {
|
|
15
|
+
return {
|
|
16
|
+
id: command.id,
|
|
17
|
+
ok: true,
|
|
18
|
+
data: { action: command.action, session: command.session },
|
|
19
|
+
page: command.page ?? 'page-1',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async shutdown() {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
describe('BrowserRuntimeProvider contract', () => {
|
|
27
|
+
it('uses runtime-named status fields and the existing result envelope', async () => {
|
|
28
|
+
const provider = new EchoProvider();
|
|
29
|
+
await expect(provider.status()).resolves.toMatchObject({
|
|
30
|
+
runtimeConnected: true,
|
|
31
|
+
runtimeName: 'fake',
|
|
32
|
+
profiles: [{ contextId: 'default', runtimeConnected: true }],
|
|
33
|
+
});
|
|
34
|
+
await expect(provider.dispatch({
|
|
35
|
+
id: 'cmd-1',
|
|
36
|
+
action: 'navigate',
|
|
37
|
+
session: 'work',
|
|
38
|
+
surface: 'browser',
|
|
39
|
+
url: 'https://example.com',
|
|
40
|
+
contextId: 'default',
|
|
41
|
+
profileId: 'default',
|
|
42
|
+
})).resolves.toEqual({
|
|
43
|
+
id: 'cmd-1',
|
|
44
|
+
ok: true,
|
|
45
|
+
data: { action: 'navigate', session: 'work' },
|
|
46
|
+
page: 'page-1',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape-based field filter for `browser network --filter <fields>`.
|
|
3
|
+
*
|
|
4
|
+
* Agents know what fields a target request's body should contain
|
|
5
|
+
* (e.g. "author, text, likes") but not which of the captured requests
|
|
6
|
+
* carries that body. This module lets the network command filter
|
|
7
|
+
* entries down to those whose inferred shape exposes every requested
|
|
8
|
+
* field name as some path segment.
|
|
9
|
+
*
|
|
10
|
+
* Matching is "any-segment" (not last-segment-only): a field matches
|
|
11
|
+
* if it equals any segment name of any path in the shape map. This
|
|
12
|
+
* keeps nested-container fields (e.g. `legacy`, `author` used as an
|
|
13
|
+
* object key with further nesting) findable.
|
|
14
|
+
*/
|
|
15
|
+
import type { Shape } from './shape.js';
|
|
16
|
+
export interface ParsedFilter {
|
|
17
|
+
/** Deduped, order-preserving, trimmed non-empty field names. */
|
|
18
|
+
fields: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface FilterParseError {
|
|
21
|
+
/** `invalid_filter` structured error reason for agents. */
|
|
22
|
+
reason: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse `--filter` argument value. Splits on `,`, trims, drops empties,
|
|
26
|
+
* and dedupes (first-seen wins). Returns `FilterParseError` when the
|
|
27
|
+
* result is empty after cleaning — which means the caller passed only
|
|
28
|
+
* whitespace, commas, or an empty string.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseFilter(raw: string): ParsedFilter | FilterParseError;
|
|
31
|
+
/**
|
|
32
|
+
* Extract named segments from a shape path. Drops the leading `$`,
|
|
33
|
+
* strips `[N]` array indices, and unwraps `["key"]` bracket-quoted
|
|
34
|
+
* keys back to their raw string.
|
|
35
|
+
*
|
|
36
|
+
* Examples:
|
|
37
|
+
* `$` → []
|
|
38
|
+
* `$.data.items[0].author` → ['data','items','author']
|
|
39
|
+
* `$.data.user["nick name"]` → ['data','user','nick name']
|
|
40
|
+
* `$.rows[0][1]` → ['rows']
|
|
41
|
+
*/
|
|
42
|
+
export declare function extractSegments(path: string): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Collect the set of segment names used anywhere in a shape map.
|
|
45
|
+
* The returned set is what we test field membership against.
|
|
46
|
+
*/
|
|
47
|
+
export declare function collectShapeSegments(shape: Shape): Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* True iff every field in `fields` equals some segment name in `shape`.
|
|
50
|
+
* AND semantics: all fields must be present.
|
|
51
|
+
*/
|
|
52
|
+
export declare function shapeMatchesFilter(shape: Shape, fields: string[]): boolean;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse `--filter` argument value. Splits on `,`, trims, drops empties,
|
|
3
|
+
* and dedupes (first-seen wins). Returns `FilterParseError` when the
|
|
4
|
+
* result is empty after cleaning — which means the caller passed only
|
|
5
|
+
* whitespace, commas, or an empty string.
|
|
6
|
+
*/
|
|
7
|
+
export function parseFilter(raw) {
|
|
8
|
+
if (typeof raw !== 'string') {
|
|
9
|
+
return { reason: `--filter value must be a non-empty comma-separated field list` };
|
|
10
|
+
}
|
|
11
|
+
const parts = raw.split(',').map((p) => p.trim()).filter((p) => p.length > 0);
|
|
12
|
+
if (parts.length === 0) {
|
|
13
|
+
return { reason: `--filter value must be a non-empty comma-separated field list (got "${raw}")` };
|
|
14
|
+
}
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const fields = [];
|
|
17
|
+
for (const p of parts) {
|
|
18
|
+
if (!seen.has(p)) {
|
|
19
|
+
seen.add(p);
|
|
20
|
+
fields.push(p);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { fields };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract named segments from a shape path. Drops the leading `$`,
|
|
27
|
+
* strips `[N]` array indices, and unwraps `["key"]` bracket-quoted
|
|
28
|
+
* keys back to their raw string.
|
|
29
|
+
*
|
|
30
|
+
* Examples:
|
|
31
|
+
* `$` → []
|
|
32
|
+
* `$.data.items[0].author` → ['data','items','author']
|
|
33
|
+
* `$.data.user["nick name"]` → ['data','user','nick name']
|
|
34
|
+
* `$.rows[0][1]` → ['rows']
|
|
35
|
+
*/
|
|
36
|
+
export function extractSegments(path) {
|
|
37
|
+
if (!path || path === '$')
|
|
38
|
+
return [];
|
|
39
|
+
const out = [];
|
|
40
|
+
// Start past the leading `$`; if path doesn't start with `$` treat
|
|
41
|
+
// it as a raw segment list (keeps us robust to unexpected input).
|
|
42
|
+
let i = path.startsWith('$') ? 1 : 0;
|
|
43
|
+
while (i < path.length) {
|
|
44
|
+
const c = path[i];
|
|
45
|
+
if (c === '.') {
|
|
46
|
+
i++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (c === '[') {
|
|
50
|
+
// Either `[N]` (numeric) or `["key"]` (quoted key). Handle both.
|
|
51
|
+
const end = path.indexOf(']', i);
|
|
52
|
+
if (end === -1)
|
|
53
|
+
break;
|
|
54
|
+
const inner = path.slice(i + 1, end);
|
|
55
|
+
i = end + 1;
|
|
56
|
+
if (inner.length >= 2 && inner.startsWith('"') && inner.endsWith('"')) {
|
|
57
|
+
try {
|
|
58
|
+
out.push(JSON.parse(inner));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
out.push(inner.slice(1, -1));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// numeric index: drop
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Bare identifier: read up to next `.` or `[`
|
|
68
|
+
let j = i;
|
|
69
|
+
while (j < path.length && path[j] !== '.' && path[j] !== '[')
|
|
70
|
+
j++;
|
|
71
|
+
out.push(path.slice(i, j));
|
|
72
|
+
i = j;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Collect the set of segment names used anywhere in a shape map.
|
|
78
|
+
* The returned set is what we test field membership against.
|
|
79
|
+
*/
|
|
80
|
+
export function collectShapeSegments(shape) {
|
|
81
|
+
const acc = new Set();
|
|
82
|
+
for (const p of Object.keys(shape)) {
|
|
83
|
+
for (const seg of extractSegments(p))
|
|
84
|
+
acc.add(seg);
|
|
85
|
+
}
|
|
86
|
+
return acc;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* True iff every field in `fields` equals some segment name in `shape`.
|
|
90
|
+
* AND semantics: all fields must be present.
|
|
91
|
+
*/
|
|
92
|
+
export function shapeMatchesFilter(shape, fields) {
|
|
93
|
+
if (fields.length === 0)
|
|
94
|
+
return true;
|
|
95
|
+
const segs = collectShapeSegments(shape);
|
|
96
|
+
for (const f of fields) {
|
|
97
|
+
if (!segs.has(f))
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { collectShapeSegments, extractSegments, parseFilter, shapeMatchesFilter, } from './shape-filter.js';
|
|
3
|
+
describe('parseFilter', () => {
|
|
4
|
+
it('splits comma-separated fields, trims, and drops empty tokens', () => {
|
|
5
|
+
const r = parseFilter('author, text , likes');
|
|
6
|
+
expect(r).toEqual({ fields: ['author', 'text', 'likes'] });
|
|
7
|
+
});
|
|
8
|
+
it('dedupes while preserving first-seen order', () => {
|
|
9
|
+
const r = parseFilter('a,b,a,c,b');
|
|
10
|
+
expect(r).toEqual({ fields: ['a', 'b', 'c'] });
|
|
11
|
+
});
|
|
12
|
+
it('rejects empty string as invalid_filter', () => {
|
|
13
|
+
const r = parseFilter('');
|
|
14
|
+
expect(r).toMatchObject({ reason: expect.stringContaining('non-empty') });
|
|
15
|
+
});
|
|
16
|
+
it('rejects whitespace-only as invalid_filter', () => {
|
|
17
|
+
const r = parseFilter(' ');
|
|
18
|
+
expect(r).toMatchObject({ reason: expect.stringContaining('non-empty') });
|
|
19
|
+
});
|
|
20
|
+
it('rejects commas-only as invalid_filter', () => {
|
|
21
|
+
const r = parseFilter(',,,');
|
|
22
|
+
expect(r).toMatchObject({ reason: expect.stringContaining('non-empty') });
|
|
23
|
+
});
|
|
24
|
+
it('accepts a single field', () => {
|
|
25
|
+
expect(parseFilter('author')).toEqual({ fields: ['author'] });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('extractSegments', () => {
|
|
29
|
+
it('returns empty for root', () => {
|
|
30
|
+
expect(extractSegments('$')).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
it('splits dotted path and drops $', () => {
|
|
33
|
+
expect(extractSegments('$.data.user.name')).toEqual(['data', 'user', 'name']);
|
|
34
|
+
});
|
|
35
|
+
it('drops numeric array indices', () => {
|
|
36
|
+
expect(extractSegments('$.items[0].author')).toEqual(['items', 'author']);
|
|
37
|
+
expect(extractSegments('$.rows[0][12]')).toEqual(['rows']);
|
|
38
|
+
});
|
|
39
|
+
it('unwraps bracket-quoted keys', () => {
|
|
40
|
+
expect(extractSegments('$.data["weird key"]')).toEqual(['data', 'weird key']);
|
|
41
|
+
});
|
|
42
|
+
it('handles bracket-quoted keys at root', () => {
|
|
43
|
+
expect(extractSegments('$["123bad"]')).toEqual(['123bad']);
|
|
44
|
+
});
|
|
45
|
+
it('mixes bracket keys and dot segments', () => {
|
|
46
|
+
expect(extractSegments('$.data.user["nick name"].age'))
|
|
47
|
+
.toEqual(['data', 'user', 'nick name', 'age']);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('collectShapeSegments', () => {
|
|
51
|
+
it('collects every segment name from every path in a shape', () => {
|
|
52
|
+
const shape = {
|
|
53
|
+
'$': 'object',
|
|
54
|
+
'$.data': 'object',
|
|
55
|
+
'$.data.items': 'array(3)',
|
|
56
|
+
'$.data.items[0]': 'object',
|
|
57
|
+
'$.data.items[0].author': 'string',
|
|
58
|
+
'$.data.items[0].text': 'string',
|
|
59
|
+
};
|
|
60
|
+
const segs = collectShapeSegments(shape);
|
|
61
|
+
expect(segs.has('data')).toBe(true);
|
|
62
|
+
expect(segs.has('items')).toBe(true);
|
|
63
|
+
expect(segs.has('author')).toBe(true);
|
|
64
|
+
expect(segs.has('text')).toBe(true);
|
|
65
|
+
expect(segs.has('$')).toBe(false);
|
|
66
|
+
expect(segs.has('[0]')).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
it('returns an empty set for an empty shape', () => {
|
|
69
|
+
expect(collectShapeSegments({}).size).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('shapeMatchesFilter', () => {
|
|
73
|
+
const shape = {
|
|
74
|
+
'$': 'object',
|
|
75
|
+
'$.data': 'object',
|
|
76
|
+
'$.data.items': 'array(1)',
|
|
77
|
+
'$.data.items[0].author': 'string',
|
|
78
|
+
'$.data.items[0].text': 'string',
|
|
79
|
+
'$.data.items[0].likes': 'number',
|
|
80
|
+
};
|
|
81
|
+
it('returns true when every field matches some path segment (AND)', () => {
|
|
82
|
+
expect(shapeMatchesFilter(shape, ['author', 'text', 'likes'])).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
it('matches nested container names, not just leaves (any-segment rule)', () => {
|
|
85
|
+
// `data` and `items` are container segments, not leaves; still must match.
|
|
86
|
+
expect(shapeMatchesFilter(shape, ['data', 'items'])).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
it('returns false when any field is missing', () => {
|
|
89
|
+
expect(shapeMatchesFilter(shape, ['author', 'missing'])).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
it('is case-sensitive', () => {
|
|
92
|
+
expect(shapeMatchesFilter(shape, ['Author'])).toBe(false);
|
|
93
|
+
expect(shapeMatchesFilter(shape, ['author'])).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
it('empty filter list vacuously matches', () => {
|
|
96
|
+
expect(shapeMatchesFilter(shape, [])).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
it('rejects requests whose shape has no body (empty shape)', () => {
|
|
99
|
+
expect(shapeMatchesFilter({}, ['author'])).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON shape inference for browser network response previews.
|
|
3
|
+
*
|
|
4
|
+
* Produces a flat path → type descriptor map so agents can understand
|
|
5
|
+
* response structure without paying the token cost of the full body.
|
|
6
|
+
*
|
|
7
|
+
* Descriptors:
|
|
8
|
+
* string | number | boolean | null primitives
|
|
9
|
+
* string(len=N) strings longer than sampleStringLen
|
|
10
|
+
* array(0) | array(N) array at depth cap or summarized
|
|
11
|
+
* object | object(empty) objects at depth cap or summarized
|
|
12
|
+
* (truncated) output size budget exceeded
|
|
13
|
+
*/
|
|
14
|
+
export interface InferShapeOptions {
|
|
15
|
+
/** Max path depth to descend into (default 6) */
|
|
16
|
+
maxDepth?: number;
|
|
17
|
+
/** Byte budget for the serialized output; truncates when exceeded (default 2048) */
|
|
18
|
+
maxBytes?: number;
|
|
19
|
+
/** Strings longer than this get summarized as `string(len=N)` (default 80) */
|
|
20
|
+
sampleStringLen?: number;
|
|
21
|
+
}
|
|
22
|
+
export type Shape = Record<string, string>;
|
|
23
|
+
export declare function inferShape(value: unknown, opts?: InferShapeOptions): Shape;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON shape inference for browser network response previews.
|
|
3
|
+
*
|
|
4
|
+
* Produces a flat path → type descriptor map so agents can understand
|
|
5
|
+
* response structure without paying the token cost of the full body.
|
|
6
|
+
*
|
|
7
|
+
* Descriptors:
|
|
8
|
+
* string | number | boolean | null primitives
|
|
9
|
+
* string(len=N) strings longer than sampleStringLen
|
|
10
|
+
* array(0) | array(N) array at depth cap or summarized
|
|
11
|
+
* object | object(empty) objects at depth cap or summarized
|
|
12
|
+
* (truncated) output size budget exceeded
|
|
13
|
+
*/
|
|
14
|
+
const ROOT = '$';
|
|
15
|
+
export function inferShape(value, opts = {}) {
|
|
16
|
+
const maxDepth = opts.maxDepth ?? 6;
|
|
17
|
+
const maxBytes = opts.maxBytes ?? 2048;
|
|
18
|
+
const sampleStringLen = opts.sampleStringLen ?? 80;
|
|
19
|
+
const out = {};
|
|
20
|
+
let bytes = 2; // account for `{}` braces when serialized
|
|
21
|
+
let truncated = false;
|
|
22
|
+
const add = (path, desc) => {
|
|
23
|
+
if (truncated)
|
|
24
|
+
return false;
|
|
25
|
+
const entryBytes = JSON.stringify(path).length + JSON.stringify(desc).length + 2; // ":" + ","
|
|
26
|
+
if (bytes + entryBytes > maxBytes) {
|
|
27
|
+
out['(truncated)'] = `reached ${maxBytes}B budget`;
|
|
28
|
+
truncated = true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
out[path] = desc;
|
|
32
|
+
bytes += entryBytes;
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
35
|
+
const walk = (node, path, depth) => {
|
|
36
|
+
if (truncated)
|
|
37
|
+
return;
|
|
38
|
+
if (node === null) {
|
|
39
|
+
add(path, 'null');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const t = typeof node;
|
|
43
|
+
if (t === 'string') {
|
|
44
|
+
const s = node;
|
|
45
|
+
add(path, s.length > sampleStringLen ? `string(len=${s.length})` : 'string');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (t === 'number' || t === 'boolean') {
|
|
49
|
+
add(path, t);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (t === 'undefined' || t === 'function' || t === 'symbol' || t === 'bigint') {
|
|
53
|
+
add(path, t);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(node)) {
|
|
57
|
+
if (node.length === 0) {
|
|
58
|
+
add(path, 'array(0)');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (depth >= maxDepth) {
|
|
62
|
+
add(path, `array(${node.length})`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!add(path, `array(${node.length})`))
|
|
66
|
+
return;
|
|
67
|
+
walk(node[0], `${path}[0]`, depth + 1);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// plain object
|
|
71
|
+
const obj = node;
|
|
72
|
+
const keys = Object.keys(obj);
|
|
73
|
+
if (keys.length === 0) {
|
|
74
|
+
add(path, 'object(empty)');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (depth >= maxDepth) {
|
|
78
|
+
add(path, `object(keys=${keys.length})`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!add(path, 'object'))
|
|
82
|
+
return;
|
|
83
|
+
for (const k of keys) {
|
|
84
|
+
if (truncated)
|
|
85
|
+
return;
|
|
86
|
+
const childPath = isSafeIdent(k) ? `${path}.${k}` : `${path}[${JSON.stringify(k)}]`;
|
|
87
|
+
walk(obj[k], childPath, depth + 1);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
walk(value, ROOT, 0);
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
function isSafeIdent(key) {
|
|
94
|
+
return /^[A-Za-z_$][\w$]*$/.test(key);
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { inferShape } from './shape.js';
|
|
3
|
+
describe('inferShape', () => {
|
|
4
|
+
it('describes primitives at root', () => {
|
|
5
|
+
expect(inferShape('hello')).toEqual({ $: 'string' });
|
|
6
|
+
expect(inferShape(42)).toEqual({ $: 'number' });
|
|
7
|
+
expect(inferShape(true)).toEqual({ $: 'boolean' });
|
|
8
|
+
expect(inferShape(null)).toEqual({ $: 'null' });
|
|
9
|
+
});
|
|
10
|
+
it('summarizes long strings with their length', () => {
|
|
11
|
+
const long = 'x'.repeat(200);
|
|
12
|
+
expect(inferShape(long, { sampleStringLen: 80 })).toEqual({ $: 'string(len=200)' });
|
|
13
|
+
});
|
|
14
|
+
it('walks nested objects and emits dotted paths', () => {
|
|
15
|
+
const shape = inferShape({ user: { id: 1, name: 'bob' } });
|
|
16
|
+
expect(shape).toEqual({
|
|
17
|
+
$: 'object',
|
|
18
|
+
'$.user': 'object',
|
|
19
|
+
'$.user.id': 'number',
|
|
20
|
+
'$.user.name': 'string',
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it('quotes unsafe keys using bracket notation', () => {
|
|
24
|
+
const shape = inferShape({ 'weird key': 1, '123bad': 2 });
|
|
25
|
+
expect(shape['$["weird key"]']).toBe('number');
|
|
26
|
+
expect(shape['$["123bad"]']).toBe('number');
|
|
27
|
+
});
|
|
28
|
+
it('samples the first array element and reports length', () => {
|
|
29
|
+
const shape = inferShape({ items: [{ a: 1 }, { a: 2 }, { a: 3 }] });
|
|
30
|
+
expect(shape['$.items']).toBe('array(3)');
|
|
31
|
+
expect(shape['$.items[0]']).toBe('object');
|
|
32
|
+
expect(shape['$.items[0].a']).toBe('number');
|
|
33
|
+
});
|
|
34
|
+
it('marks empty containers explicitly', () => {
|
|
35
|
+
const shape = inferShape({ arr: [], obj: {} });
|
|
36
|
+
expect(shape['$.arr']).toBe('array(0)');
|
|
37
|
+
expect(shape['$.obj']).toBe('object(empty)');
|
|
38
|
+
});
|
|
39
|
+
it('collapses subtrees past maxDepth', () => {
|
|
40
|
+
const deep = { a: { b: { c: { d: { e: { f: 'too deep' } } } } } };
|
|
41
|
+
const shape = inferShape(deep, { maxDepth: 2 });
|
|
42
|
+
expect(shape['$.a.b']).toMatch(/^object/);
|
|
43
|
+
expect(shape['$.a.b.c']).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
it('truncates when the byte budget is exhausted', () => {
|
|
46
|
+
const wide = {};
|
|
47
|
+
for (let i = 0; i < 500; i++)
|
|
48
|
+
wide[`field_${i}`] = i;
|
|
49
|
+
const shape = inferShape(wide, { maxBytes: 256 });
|
|
50
|
+
expect(shape['(truncated)']).toMatch(/256B/);
|
|
51
|
+
expect(Object.keys(shape).length).toBeLessThan(500);
|
|
52
|
+
});
|
|
53
|
+
it('stops descending into an array once the budget is hit by its own descriptor', () => {
|
|
54
|
+
// Budget just large enough for `$` + one deep array descriptor, not its element.
|
|
55
|
+
const shape = inferShape({ items: [{ deep: 1 }] }, { maxBytes: 40 });
|
|
56
|
+
expect(shape['$.items[0]']).toBeUndefined();
|
|
57
|
+
expect(shape['(truncated)']).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
it('handles the Twitter UserTweets payload envelope', () => {
|
|
60
|
+
const payload = {
|
|
61
|
+
data: {
|
|
62
|
+
user: {
|
|
63
|
+
result: {
|
|
64
|
+
rest_id: '42',
|
|
65
|
+
timeline_v2: {
|
|
66
|
+
timeline: {
|
|
67
|
+
instructions: [
|
|
68
|
+
{ type: 'TimelinePinEntry', entries: [] },
|
|
69
|
+
{ entries: [{ entryId: 'tweet-1', content: { entryType: 'TimelineTimelineItem' } }] },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const shape = inferShape(payload, { maxDepth: 10 });
|
|
78
|
+
expect(shape['$.data.user.result.rest_id']).toBe('string');
|
|
79
|
+
expect(shape['$.data.user.result.timeline_v2.timeline.instructions']).toBe('array(2)');
|
|
80
|
+
expect(shape['$.data.user.result.timeline_v2.timeline.instructions[0]']).toBe('object');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth anti-detection module.
|
|
3
|
+
*
|
|
4
|
+
* Generates JS code that patches browser globals to hide automation
|
|
5
|
+
* fingerprints (e.g. navigator.webdriver, missing chrome object, empty
|
|
6
|
+
* plugin list). Injected before page scripts run so that websites cannot
|
|
7
|
+
* detect CDP / extension-based control.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by puppeteer-extra-plugin-stealth.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateStealthJs(): string;
|