@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,155 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseJsonOrThrowLoginWall, throwIfLoginWall, BROWSER_JSON_SNIFF_FN, } from './utils.js';
|
|
3
|
+
import { LoginWallError } from './errors.js';
|
|
4
|
+
function makeResponse(body, opts = {}) {
|
|
5
|
+
return new Response(body, {
|
|
6
|
+
status: opts.status ?? 200,
|
|
7
|
+
headers: { 'content-type': opts.contentType ?? 'application/json' },
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
describe('parseJsonOrThrowLoginWall', () => {
|
|
11
|
+
it('returns parsed JSON on a normal application/json response', async () => {
|
|
12
|
+
const res = makeResponse(JSON.stringify({ hello: 'world', n: 42 }));
|
|
13
|
+
const parsed = await parseJsonOrThrowLoginWall(res);
|
|
14
|
+
expect(parsed).toEqual({ hello: 'world', n: 42 });
|
|
15
|
+
});
|
|
16
|
+
it('throws LoginWallError when content-type is text/html', async () => {
|
|
17
|
+
const res = makeResponse('<html><body>Please log in</body></html>', {
|
|
18
|
+
status: 401,
|
|
19
|
+
contentType: 'text/html; charset=utf-8',
|
|
20
|
+
});
|
|
21
|
+
await expect(parseJsonOrThrowLoginWall(res, { url: 'https://example.com/api' })).rejects.toMatchObject({
|
|
22
|
+
name: 'LoginWallError',
|
|
23
|
+
code: 'LOGIN_WALL',
|
|
24
|
+
status: 401,
|
|
25
|
+
url: 'https://example.com/api',
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('throws LoginWallError when body starts with <!DOCTYPE even if content-type is missing', async () => {
|
|
29
|
+
// application/json content-type but body is actually HTML — WAFs sometimes do this
|
|
30
|
+
const res = new Response('<!DOCTYPE html><html><head></head><body>blocked</body></html>', {
|
|
31
|
+
status: 200,
|
|
32
|
+
headers: { 'content-type': 'application/json' },
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
await parseJsonOrThrowLoginWall(res, { url: 'https://x.com/api/list' });
|
|
36
|
+
throw new Error('expected throw');
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
expect(err).toBeInstanceOf(LoginWallError);
|
|
40
|
+
const e = err;
|
|
41
|
+
expect(e.status).toBe(200);
|
|
42
|
+
expect(e.url).toBe('https://x.com/api/list');
|
|
43
|
+
expect(e.bodyPreview.startsWith('<!DOCTYPE')).toBe(true);
|
|
44
|
+
expect(e.exitCode).toBe(77); // NOPERM
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it('throws LoginWallError when body starts with <html (no DOCTYPE)', async () => {
|
|
48
|
+
const res = new Response('<html lang="en"><body>nope</body></html>', {
|
|
49
|
+
status: 429,
|
|
50
|
+
headers: { 'content-type': 'application/json' },
|
|
51
|
+
});
|
|
52
|
+
await expect(parseJsonOrThrowLoginWall(res)).rejects.toBeInstanceOf(LoginWallError);
|
|
53
|
+
});
|
|
54
|
+
it('throws LoginWallError when body has leading whitespace before <!DOCTYPE', async () => {
|
|
55
|
+
const res = new Response(' \n\n<!DOCTYPE html><html></html>', {
|
|
56
|
+
status: 200,
|
|
57
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
58
|
+
});
|
|
59
|
+
await expect(parseJsonOrThrowLoginWall(res)).rejects.toBeInstanceOf(LoginWallError);
|
|
60
|
+
});
|
|
61
|
+
it('throws a regular Error (NOT LoginWallError) on real malformed JSON', async () => {
|
|
62
|
+
const res = makeResponse('{not really json,');
|
|
63
|
+
try {
|
|
64
|
+
await parseJsonOrThrowLoginWall(res);
|
|
65
|
+
throw new Error('expected throw');
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
expect(err).toBeInstanceOf(Error);
|
|
69
|
+
expect(err).not.toBeInstanceOf(LoginWallError);
|
|
70
|
+
const msg = err.message;
|
|
71
|
+
expect(msg).toContain('JSON parse failed');
|
|
72
|
+
expect(msg).toContain('body[0..50]=');
|
|
73
|
+
// body preview must be present so debugging doesn't require a repro
|
|
74
|
+
expect(msg).toContain('not really json');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
it('preserves first 100 chars of body in bodyPreview', async () => {
|
|
78
|
+
const longHtml = '<!DOCTYPE html><html><head><title>' + 'x'.repeat(500) + '</title></head></html>';
|
|
79
|
+
const res = new Response(longHtml, { status: 403, headers: { 'content-type': 'text/html' } });
|
|
80
|
+
try {
|
|
81
|
+
await parseJsonOrThrowLoginWall(res);
|
|
82
|
+
throw new Error('expected throw');
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const e = err;
|
|
86
|
+
expect(e.bodyPreview.length).toBe(100);
|
|
87
|
+
expect(e.bodyPreview.startsWith('<!DOCTYPE')).toBe(true);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('throwIfLoginWall', () => {
|
|
92
|
+
it('returns the value unchanged when it is not a login-wall sentinel', () => {
|
|
93
|
+
const data = { data: { foo: 'bar' } };
|
|
94
|
+
expect(throwIfLoginWall(data)).toBe(data);
|
|
95
|
+
expect(throwIfLoginWall('hello')).toBe('hello');
|
|
96
|
+
expect(throwIfLoginWall(null)).toBe(null);
|
|
97
|
+
expect(throwIfLoginWall(undefined)).toBe(undefined);
|
|
98
|
+
expect(throwIfLoginWall(42)).toBe(42);
|
|
99
|
+
expect(throwIfLoginWall([1, 2, 3])).toEqual([1, 2, 3]);
|
|
100
|
+
});
|
|
101
|
+
it('returns objects that happen to have unrelated __loginWall-ish keys unchanged', () => {
|
|
102
|
+
// Must NOT trigger on partial matches — sentinel needs __loginWall === true AND numeric status
|
|
103
|
+
expect(throwIfLoginWall({ __loginWall: false })).toEqual({ __loginWall: false });
|
|
104
|
+
expect(throwIfLoginWall({ __loginWall: true })).toEqual({ __loginWall: true }); // missing status → not a real sentinel
|
|
105
|
+
});
|
|
106
|
+
it('throws LoginWallError when value is the browser-side sentinel', () => {
|
|
107
|
+
const signal = {
|
|
108
|
+
__loginWall: true,
|
|
109
|
+
status: 403,
|
|
110
|
+
url: 'https://x.com/i/api/graphql/...',
|
|
111
|
+
contentType: 'text/html',
|
|
112
|
+
bodyPreview: '<!DOCTYPE html><html><head><title>Login</title>',
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
throwIfLoginWall(signal);
|
|
116
|
+
throw new Error('expected throw');
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
expect(err).toBeInstanceOf(LoginWallError);
|
|
120
|
+
const e = err;
|
|
121
|
+
expect(e.status).toBe(403);
|
|
122
|
+
expect(e.url).toBe('https://x.com/i/api/graphql/...');
|
|
123
|
+
expect(e.bodyPreview).toContain('<!DOCTYPE');
|
|
124
|
+
expect(e.code).toBe('LOGIN_WALL');
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
it('opts.url overrides the URL embedded in the sentinel', () => {
|
|
128
|
+
const signal = {
|
|
129
|
+
__loginWall: true,
|
|
130
|
+
status: 401,
|
|
131
|
+
url: '',
|
|
132
|
+
contentType: 'text/html',
|
|
133
|
+
bodyPreview: '<html>',
|
|
134
|
+
};
|
|
135
|
+
try {
|
|
136
|
+
throwIfLoginWall(signal, { url: 'https://override.example.com/api' });
|
|
137
|
+
throw new Error('expected throw');
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
expect(err.url).toBe('https://override.example.com/api');
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('BROWSER_JSON_SNIFF_FN', () => {
|
|
145
|
+
it('is a non-empty string that defines fetchJsonOrLoginWall', () => {
|
|
146
|
+
expect(typeof BROWSER_JSON_SNIFF_FN).toBe('string');
|
|
147
|
+
expect(BROWSER_JSON_SNIFF_FN).toContain('fetchJsonOrLoginWall');
|
|
148
|
+
expect(BROWSER_JSON_SNIFF_FN).toContain('__loginWall');
|
|
149
|
+
});
|
|
150
|
+
it('can be evaluated as JS without syntax errors', () => {
|
|
151
|
+
// We can't run the actual fetch path here (no Response polyfill loop), but
|
|
152
|
+
// we CAN confirm the fragment parses cleanly when embedded inside an async IIFE.
|
|
153
|
+
expect(() => new Function(`(async () => { ${BROWSER_JSON_SNIFF_FN} })`)).not.toThrow();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface CommandValidationResult {
|
|
2
|
+
/** Display label: "site/name" or source path if available */
|
|
3
|
+
label: string;
|
|
4
|
+
errors: string[];
|
|
5
|
+
warnings: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface ValidationReport {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
results: CommandValidationResult[];
|
|
10
|
+
errors: number;
|
|
11
|
+
warnings: number;
|
|
12
|
+
commands: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validate registered CLI commands from the in-memory registry.
|
|
16
|
+
*
|
|
17
|
+
* The `_dirs` parameter is kept for call-site compatibility but is no longer
|
|
18
|
+
* used — validation now operates on the registry populated by `discoverClis()`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateClisWithTarget(_dirs: string[], target?: string): ValidationReport;
|
|
21
|
+
export declare function renderValidationReport(report: ValidationReport): string;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** Validate CLI definitions from the registry (JS-first). */
|
|
2
|
+
import { getRegistry, fullName } from './registry.js';
|
|
3
|
+
import { getRegisteredStepNames } from './pipeline/registry.js';
|
|
4
|
+
import { CLI_COMMAND } from './brand.js';
|
|
5
|
+
/**
|
|
6
|
+
* Pipeline step names — derived from the live pipeline registry on each
|
|
7
|
+
* validate call so a new step registered in src/pipeline/registry.ts (or by
|
|
8
|
+
* a plugin at runtime) is automatically allowlisted here (no parallel
|
|
9
|
+
* hand-maintained list, no stale-snapshot drift).
|
|
10
|
+
*/
|
|
11
|
+
function getKnownStepNames() {
|
|
12
|
+
return new Set(getRegisteredStepNames());
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validate registered CLI commands from the in-memory registry.
|
|
16
|
+
*
|
|
17
|
+
* The `_dirs` parameter is kept for call-site compatibility but is no longer
|
|
18
|
+
* used — validation now operates on the registry populated by `discoverClis()`.
|
|
19
|
+
*/
|
|
20
|
+
export function validateClisWithTarget(_dirs, target) {
|
|
21
|
+
const registry = getRegistry();
|
|
22
|
+
const results = [];
|
|
23
|
+
let errors = 0;
|
|
24
|
+
let warnings = 0;
|
|
25
|
+
if (registry.size === 0) {
|
|
26
|
+
const r = {
|
|
27
|
+
label: '(registry)',
|
|
28
|
+
errors: [],
|
|
29
|
+
warnings: ['Registry is empty — no commands discovered. Did discoverClis() run?'],
|
|
30
|
+
};
|
|
31
|
+
return { ok: true, results: [r], errors: 0, warnings: 1, commands: 0 };
|
|
32
|
+
}
|
|
33
|
+
// Resolve alias target: if target is "site/alias", resolve to canonical "site/name"
|
|
34
|
+
let resolvedTarget = target;
|
|
35
|
+
if (target?.includes('/')) {
|
|
36
|
+
const cmd = registry.get(target);
|
|
37
|
+
if (cmd)
|
|
38
|
+
resolvedTarget = fullName(cmd);
|
|
39
|
+
}
|
|
40
|
+
// Deduplicate: registry maps both canonical "site/name" and aliases to the same command
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
for (const [key, cmd] of registry) {
|
|
43
|
+
if (seen.has(cmd))
|
|
44
|
+
continue;
|
|
45
|
+
// Only validate via canonical key to avoid duplicates from aliases
|
|
46
|
+
if (key !== fullName(cmd))
|
|
47
|
+
continue;
|
|
48
|
+
seen.add(cmd);
|
|
49
|
+
// Target filter: "site" or "site/name"
|
|
50
|
+
if (resolvedTarget) {
|
|
51
|
+
if (resolvedTarget.includes('/')) {
|
|
52
|
+
if (key !== resolvedTarget)
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (cmd.site !== resolvedTarget)
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const r = validateCommand(cmd);
|
|
61
|
+
results.push(r);
|
|
62
|
+
errors += r.errors.length;
|
|
63
|
+
warnings += r.warnings.length;
|
|
64
|
+
}
|
|
65
|
+
return { ok: errors === 0, results, errors, warnings, commands: results.length };
|
|
66
|
+
}
|
|
67
|
+
function validateCommand(cmd) {
|
|
68
|
+
const label = fullName(cmd);
|
|
69
|
+
const errors = [];
|
|
70
|
+
const warnings = [];
|
|
71
|
+
if (!cmd.description)
|
|
72
|
+
warnings.push('Missing description');
|
|
73
|
+
// Browser commands should specify a domain for authenticated browser context
|
|
74
|
+
if (cmd.browser && !cmd.domain) {
|
|
75
|
+
warnings.push('Browser command without "domain" — authenticated browser context may not work');
|
|
76
|
+
}
|
|
77
|
+
// Pipeline validation: check step names for typos
|
|
78
|
+
if (Array.isArray(cmd.pipeline)) {
|
|
79
|
+
const knownStepNames = getKnownStepNames();
|
|
80
|
+
for (let i = 0; i < cmd.pipeline.length; i++) {
|
|
81
|
+
const step = cmd.pipeline[i];
|
|
82
|
+
if (step && typeof step === 'object') {
|
|
83
|
+
for (const key of Object.keys(step)) {
|
|
84
|
+
if (!knownStepNames.has(key)) {
|
|
85
|
+
warnings.push(`Pipeline step ${i}: unknown step name "${key}" (did you mean one of: ${[...knownStepNames].join(', ')}?)`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Commands should have either func, pipeline, or be a lazy-loaded module
|
|
92
|
+
const internal = cmd;
|
|
93
|
+
if (!cmd.func && !cmd.pipeline && !internal._lazy) {
|
|
94
|
+
errors.push('Command has neither "func" nor "pipeline" — it cannot execute');
|
|
95
|
+
}
|
|
96
|
+
// Arg validation
|
|
97
|
+
if (cmd.args && cmd.args.length > 0) {
|
|
98
|
+
const argNames = new Set();
|
|
99
|
+
let seenNonPositional = false;
|
|
100
|
+
for (const arg of cmd.args) {
|
|
101
|
+
if (argNames.has(arg.name)) {
|
|
102
|
+
errors.push(`Duplicate arg name "${arg.name}"`);
|
|
103
|
+
}
|
|
104
|
+
argNames.add(arg.name);
|
|
105
|
+
if (arg.positional && seenNonPositional) {
|
|
106
|
+
warnings.push(`Positional arg "${arg.name}" appears after named args`);
|
|
107
|
+
}
|
|
108
|
+
if (!arg.positional)
|
|
109
|
+
seenNonPositional = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { label, errors, warnings };
|
|
113
|
+
}
|
|
114
|
+
export function renderValidationReport(report) {
|
|
115
|
+
const lines = [
|
|
116
|
+
`${CLI_COMMAND} validate: ${report.ok ? 'PASS' : 'FAIL'}`,
|
|
117
|
+
`Checked ${report.commands} command(s)`,
|
|
118
|
+
`Errors: ${report.errors} Warnings: ${report.warnings}`,
|
|
119
|
+
];
|
|
120
|
+
for (const r of report.results) {
|
|
121
|
+
if (r.errors.length > 0 || r.warnings.length > 0) {
|
|
122
|
+
lines.push(`\n${r.label}:`);
|
|
123
|
+
for (const e of r.errors)
|
|
124
|
+
lines.push(` ❌ ${e}`);
|
|
125
|
+
for (const w of r.warnings)
|
|
126
|
+
lines.push(` ⚠️ ${w}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return lines.join('\n');
|
|
130
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/validate.ts.
|
|
3
|
+
*
|
|
4
|
+
* Focus: regression guards for the "single source of truth" link between
|
|
5
|
+
* pipeline step registry (src/pipeline/registry.ts) and validate.ts step
|
|
6
|
+
* allowlist. A new step registered via `registerStep()` must automatically
|
|
7
|
+
* be allowlisted by `webcmd validate` — no parallel hand-maintained list.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/validate.ts.
|
|
3
|
+
*
|
|
4
|
+
* Focus: regression guards for the "single source of truth" link between
|
|
5
|
+
* pipeline step registry (src/pipeline/registry.ts) and validate.ts step
|
|
6
|
+
* allowlist. A new step registered via `registerStep()` must automatically
|
|
7
|
+
* be allowlisted by `webcmd validate` — no parallel hand-maintained list.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { getRegisteredStepNames, registerStep } from './pipeline/registry.js';
|
|
11
|
+
import { cli, getRegistry, Strategy } from './registry.js';
|
|
12
|
+
import { validateClisWithTarget } from './validate.js';
|
|
13
|
+
describe('validate.ts pipeline step allowlist', () => {
|
|
14
|
+
it('uses every step name registered in pipeline/registry.ts', () => {
|
|
15
|
+
const registered = getRegisteredStepNames();
|
|
16
|
+
expect(registered).toContain('navigate');
|
|
17
|
+
expect(registered).toContain('click');
|
|
18
|
+
expect(registered).toContain('type');
|
|
19
|
+
expect(registered).toContain('fill');
|
|
20
|
+
expect(registered).toContain('fetch');
|
|
21
|
+
expect(registered.length).toBeGreaterThanOrEqual(15);
|
|
22
|
+
});
|
|
23
|
+
it('does not warn for any step name currently registered in the pipeline registry', () => {
|
|
24
|
+
// Snapshot the registry before mutating it for the test.
|
|
25
|
+
const reg = getRegistry();
|
|
26
|
+
const original = reg.get('validate-allowlist-test/all-steps');
|
|
27
|
+
if (original)
|
|
28
|
+
reg.delete('validate-allowlist-test/all-steps');
|
|
29
|
+
const allRegisteredSteps = getRegisteredStepNames();
|
|
30
|
+
cli({
|
|
31
|
+
site: 'validate-allowlist-test',
|
|
32
|
+
name: 'all-steps',
|
|
33
|
+
access: 'read',
|
|
34
|
+
browser: false,
|
|
35
|
+
strategy: Strategy.PUBLIC,
|
|
36
|
+
args: [],
|
|
37
|
+
pipeline: allRegisteredSteps.map(stepName => ({ [stepName]: {} })),
|
|
38
|
+
func: async () => [],
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
const report = validateClisWithTarget([], 'validate-allowlist-test/all-steps');
|
|
42
|
+
const r = report.results[0];
|
|
43
|
+
const unknownStepWarning = r.warnings.find(w => w.startsWith('Pipeline step '));
|
|
44
|
+
expect(unknownStepWarning).toBeUndefined();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
reg.delete('validate-allowlist-test/all-steps');
|
|
48
|
+
if (original)
|
|
49
|
+
reg.set('validate-allowlist-test/all-steps', original);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
it('newly registered step automatically appears in validator allowlist', () => {
|
|
53
|
+
const customStep = '__test_custom_step__';
|
|
54
|
+
expect(getRegisteredStepNames()).not.toContain(customStep);
|
|
55
|
+
registerStep(customStep, async (_p, _params, data) => data);
|
|
56
|
+
try {
|
|
57
|
+
expect(getRegisteredStepNames()).toContain(customStep);
|
|
58
|
+
const reg = getRegistry();
|
|
59
|
+
const original = reg.get('validate-dynamic-test/uses-custom');
|
|
60
|
+
if (original)
|
|
61
|
+
reg.delete('validate-dynamic-test/uses-custom');
|
|
62
|
+
cli({
|
|
63
|
+
site: 'validate-dynamic-test',
|
|
64
|
+
name: 'uses-custom',
|
|
65
|
+
access: 'read',
|
|
66
|
+
browser: false,
|
|
67
|
+
strategy: Strategy.PUBLIC,
|
|
68
|
+
args: [],
|
|
69
|
+
pipeline: [{ [customStep]: {} }],
|
|
70
|
+
func: async () => [],
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const report = validateClisWithTarget([], 'validate-dynamic-test/uses-custom');
|
|
74
|
+
const r = report.results[0];
|
|
75
|
+
const unknownStepWarning = r.warnings.find(w => w.includes(customStep));
|
|
76
|
+
expect(unknownStepWarning).toBeUndefined();
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
reg.delete('validate-dynamic-test/uses-custom');
|
|
80
|
+
if (original)
|
|
81
|
+
reg.set('validate-dynamic-test/uses-custom', original);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
// Best-effort cleanup of the test step. There is no `unregisterStep` —
|
|
86
|
+
// leaving it registered is harmless because the test step name is
|
|
87
|
+
// namespaced and never used outside this file.
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification: runs validation and optional smoke test.
|
|
3
|
+
*
|
|
4
|
+
* The smoke test is intentionally kept as a stub — full browser-based
|
|
5
|
+
* smoke testing requires a running browser session and is better suited
|
|
6
|
+
* to the `webcmd test` command or CI pipelines.
|
|
7
|
+
*/
|
|
8
|
+
import { type ValidationReport } from './validate.js';
|
|
9
|
+
export interface VerifyOptions {
|
|
10
|
+
builtinClis: string;
|
|
11
|
+
userClis: string;
|
|
12
|
+
target?: string;
|
|
13
|
+
smoke?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface VerifyReport {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
validation: ValidationReport;
|
|
18
|
+
smoke: null | {
|
|
19
|
+
requested: boolean;
|
|
20
|
+
executed: boolean;
|
|
21
|
+
ok: boolean;
|
|
22
|
+
summary: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function verifyClis(opts: VerifyOptions): Promise<VerifyReport>;
|
|
26
|
+
export declare function renderVerifyReport(report: VerifyReport): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification: runs validation and optional smoke test.
|
|
3
|
+
*
|
|
4
|
+
* The smoke test is intentionally kept as a stub — full browser-based
|
|
5
|
+
* smoke testing requires a running browser session and is better suited
|
|
6
|
+
* to the `webcmd test` command or CI pipelines.
|
|
7
|
+
*/
|
|
8
|
+
import { validateClisWithTarget, renderValidationReport } from './validate.js';
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
export async function verifyClis(opts) {
|
|
13
|
+
const report = validateClisWithTarget([opts.builtinClis, opts.userClis], opts.target);
|
|
14
|
+
let smoke = null;
|
|
15
|
+
if (opts.smoke) {
|
|
16
|
+
smoke = await runSmokeTests(opts.builtinClis);
|
|
17
|
+
}
|
|
18
|
+
return { ok: report.ok && (smoke?.ok ?? true), validation: report, smoke };
|
|
19
|
+
}
|
|
20
|
+
export function renderVerifyReport(report) {
|
|
21
|
+
const base = renderValidationReport(report.validation);
|
|
22
|
+
if (!report.smoke)
|
|
23
|
+
return base;
|
|
24
|
+
const status = report.smoke.ok ? 'PASS' : 'FAIL';
|
|
25
|
+
const mode = report.smoke.executed ? 'executed' : 'skipped';
|
|
26
|
+
return `${base}\nSmoke: ${status} (${mode}) — ${report.smoke.summary}`;
|
|
27
|
+
}
|
|
28
|
+
async function runSmokeTests(builtinClis) {
|
|
29
|
+
const projectRoot = path.resolve(builtinClis, '..', '..');
|
|
30
|
+
const smokeDir = path.join(projectRoot, 'tests', 'smoke');
|
|
31
|
+
if (!fs.existsSync(smokeDir)) {
|
|
32
|
+
return {
|
|
33
|
+
requested: true,
|
|
34
|
+
executed: false,
|
|
35
|
+
ok: false,
|
|
36
|
+
summary: 'Smoke tests are unavailable in this package/environment.',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const child = spawn(npx, ['vitest', 'run', 'tests/smoke/', '--reporter=dot'], {
|
|
42
|
+
cwd: projectRoot,
|
|
43
|
+
env: { ...process.env },
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
45
|
+
});
|
|
46
|
+
let stderr = '';
|
|
47
|
+
child.stderr.on('data', (chunk) => {
|
|
48
|
+
stderr += chunk.toString();
|
|
49
|
+
});
|
|
50
|
+
child.on('error', (error) => {
|
|
51
|
+
resolve({
|
|
52
|
+
requested: true,
|
|
53
|
+
executed: false,
|
|
54
|
+
ok: false,
|
|
55
|
+
summary: `Failed to start smoke tests: ${error.message}`,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
child.on('close', (code) => {
|
|
59
|
+
resolve({
|
|
60
|
+
requested: true,
|
|
61
|
+
executed: true,
|
|
62
|
+
ok: code === 0,
|
|
63
|
+
summary: code === 0 ? 'tests/smoke passed' : (stderr.trim() || `vitest exited with code ${code}`),
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for package version.
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
// Dev: __dirname is src/ (one level to root).
|
|
9
|
+
// Prod: __dirname is dist/src/ (two levels to root).
|
|
10
|
+
let _pkgDir = path.resolve(__dirname, '..');
|
|
11
|
+
if (!fs.existsSync(path.join(_pkgDir, 'package.json'))) {
|
|
12
|
+
_pkgDir = path.resolve(_pkgDir, '..');
|
|
13
|
+
}
|
|
14
|
+
const pkgJsonPath = path.join(_pkgDir, 'package.json');
|
|
15
|
+
export const PKG_VERSION = (() => {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')).version;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return '0.0.0';
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
async function loadModule() {
|
|
3
|
+
// @ts-expect-error JS adapter — no type declarations
|
|
4
|
+
return import('../clis/weixin/download.js');
|
|
5
|
+
}
|
|
6
|
+
describe('weixin publish time extraction', () => {
|
|
7
|
+
it('prefers publish_time text over create_time-like date strings', async () => {
|
|
8
|
+
const mod = await loadModule();
|
|
9
|
+
expect(mod.extractWechatPublishTime('2026年3月24日 22:38', 'var create_time = "2026年3月24日 22:38";')).toBe('2026年3月24日 22:38');
|
|
10
|
+
});
|
|
11
|
+
it('falls back to unix timestamp create_time values', async () => {
|
|
12
|
+
const mod = await loadModule();
|
|
13
|
+
expect(mod.extractWechatPublishTime('', 'var create_time = "1711291080";')).toBe('2024-03-24 22:38:00');
|
|
14
|
+
});
|
|
15
|
+
it('rejects malformed create_time values', async () => {
|
|
16
|
+
const mod = await loadModule();
|
|
17
|
+
expect(mod.extractWechatPublishTime('', 'var create_time = "2026年3月24日 22:38";')).toBe('');
|
|
18
|
+
expect(mod.extractWechatPublishTime('', 'var create_time = "1711291080abc";')).toBe('');
|
|
19
|
+
expect(mod.extractWechatPublishTime('', 'var create_time = "17112910800";')).toBe('');
|
|
20
|
+
});
|
|
21
|
+
it('builds a self-contained browser helper that matches fallback behavior', async () => {
|
|
22
|
+
const mod = await loadModule();
|
|
23
|
+
const extractInPage = eval(mod.buildExtractWechatPublishTimeJs());
|
|
24
|
+
expect(extractInPage('', 'var create_time = "1711291080";')).toBe('2024-03-24 22:38:00');
|
|
25
|
+
});
|
|
26
|
+
it('browser helper still prefers DOM publish_time text', async () => {
|
|
27
|
+
const mod = await loadModule();
|
|
28
|
+
const extractInPage = eval(mod.buildExtractWechatPublishTimeJs());
|
|
29
|
+
expect(extractInPage('2026年3月24日 22:38', 'var create_time = "1711291080";')).toBe('2026年3月24日 22:38');
|
|
30
|
+
});
|
|
31
|
+
it('detects WeChat verification gate pages', async () => {
|
|
32
|
+
const mod = await loadModule();
|
|
33
|
+
expect(mod.detectWechatAccessIssue('环境异常 当前环境异常,完成验证后即可继续访问。 去验证', '<html><body><a id="js_verify">去验证</a></body></html>')).toBe('environment verification required');
|
|
34
|
+
});
|
|
35
|
+
it('browser access detector matches the server-side verifier', async () => {
|
|
36
|
+
const mod = await loadModule();
|
|
37
|
+
const detectInPage = eval(mod.buildDetectWechatAccessIssueJs());
|
|
38
|
+
expect(detectInPage('环境异常 当前环境异常,完成验证后即可继续访问。 去验证', '<html>secitptpage/verify.html<a id="js_verify">去验证</a></html>')).toBe('environment verification required');
|
|
39
|
+
});
|
|
40
|
+
it('picks the first non-empty WeChat metadata field', async () => {
|
|
41
|
+
const mod = await loadModule();
|
|
42
|
+
expect(mod.pickFirstWechatMetaText('', 'Name cleared', '数字生命卡兹克')).toBe('数字生命卡兹克');
|
|
43
|
+
expect(mod.pickFirstWechatMetaText('', ' 聊聊刚刚上线的PixVerse V6视频模型。 ')).toBe('聊聊刚刚上线的PixVerse V6视频模型。');
|
|
44
|
+
});
|
|
45
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentrhq/webcmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Turn websites, browser sessions, desktop apps, and local tools into deterministic CLI surfaces for humans and AI agents.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"dev": "tsx src/main.ts",
|
|
42
42
|
"dev:bun": "bun src/main.ts",
|
|
43
|
-
"build": "npm run clean-dist && npm run copy-yaml && npm run build-manifest",
|
|
44
|
-
"
|
|
43
|
+
"build": "npm run clean-dist && npm run copy-yaml && npm run compile && npm run build-manifest",
|
|
44
|
+
"compile": "tsc --build && node -e \"require('fs').chmodSync('dist/src/main.js', 0o755)\"",
|
|
45
45
|
"build-manifest": "tsx src/build-manifest.ts",
|
|
46
46
|
"clean-dist": "node scripts/clean-dist.cjs",
|
|
47
47
|
"copy-yaml": "node scripts/copy-yaml.cjs",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"test:all": "vitest run",
|
|
59
59
|
"test:e2e": "vitest run --project e2e-fixed-port --project e2e",
|
|
60
60
|
"advise:listing-id-pairing": "node scripts/check-listing-id-pairing.mjs",
|
|
61
|
+
"check:package-bin": "node scripts/check-package-bin.mjs",
|
|
61
62
|
"check:silent-column-drop": "node scripts/check-silent-column-drop.mjs",
|
|
62
63
|
"check:typed-error-lint": "node scripts/check-typed-error-lint.mjs"
|
|
63
64
|
},
|