@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,118 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { parseByteSize, pruneTraceArtifacts } from './retention.js';
|
|
6
|
+
describe('trace artifact retention', () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
let tracesDir;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webcmd-trace-retention-'));
|
|
11
|
+
tracesDir = path.join(tmpDir, 'profiles', 'default', 'traces');
|
|
12
|
+
fs.mkdirSync(tracesDir, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
it('parses byte budgets with units', () => {
|
|
18
|
+
expect(parseByteSize(128)).toBe(128);
|
|
19
|
+
expect(parseByteSize('128')).toBe(128);
|
|
20
|
+
expect(parseByteSize('2KB')).toBe(2 * 1024);
|
|
21
|
+
expect(parseByteSize('1.5MB')).toBe(Math.floor(1.5 * 1024 * 1024));
|
|
22
|
+
expect(parseByteSize('1 GB')).toBe(1024 ** 3);
|
|
23
|
+
expect(() => parseByteSize('many')).toThrow('Invalid byte size');
|
|
24
|
+
});
|
|
25
|
+
it('prunes traces older than the age budget', () => {
|
|
26
|
+
const now = Date.parse('2026-05-03T00:00:00.000Z');
|
|
27
|
+
const old = createTraceDir('old', '2026-04-24T00:00:00.000Z');
|
|
28
|
+
const recent = createTraceDir('recent', '2026-05-02T00:00:00.000Z');
|
|
29
|
+
const result = pruneTraceArtifacts(tracesDir, {
|
|
30
|
+
now: () => now,
|
|
31
|
+
policy: { maxAgeDays: 7, maxCountPerProfile: 20, maxBytesPerProfile: '500MB' },
|
|
32
|
+
warn: vi.fn(),
|
|
33
|
+
});
|
|
34
|
+
expect(result.deleted).toEqual([old]);
|
|
35
|
+
expect(fs.existsSync(old)).toBe(false);
|
|
36
|
+
expect(fs.existsSync(recent)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it('prunes oldest traces over the count budget', () => {
|
|
39
|
+
const oldest = createTraceDir('oldest', '2026-05-01T00:00:00.000Z');
|
|
40
|
+
const middle = createTraceDir('middle', '2026-05-02T00:00:00.000Z');
|
|
41
|
+
const newest = createTraceDir('newest', '2026-05-03T00:00:00.000Z');
|
|
42
|
+
const result = pruneTraceArtifacts(tracesDir, {
|
|
43
|
+
now: () => Date.parse('2026-05-03T12:00:00.000Z'),
|
|
44
|
+
policy: { maxAgeDays: 30, maxCountPerProfile: 2, maxBytesPerProfile: '500MB' },
|
|
45
|
+
warn: vi.fn(),
|
|
46
|
+
});
|
|
47
|
+
expect(result.deleted).toEqual([oldest]);
|
|
48
|
+
expect(fs.existsSync(oldest)).toBe(false);
|
|
49
|
+
expect(fs.existsSync(middle)).toBe(true);
|
|
50
|
+
expect(fs.existsSync(newest)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it('prunes oldest traces over the byte budget', () => {
|
|
53
|
+
const oldest = createTraceDir('oldest', '2026-05-01T00:00:00.000Z', 512);
|
|
54
|
+
const middle = createTraceDir('middle', '2026-05-02T00:00:00.000Z', 64);
|
|
55
|
+
const newest = createTraceDir('newest', '2026-05-03T00:00:00.000Z', 64);
|
|
56
|
+
const budget = directorySize(middle) + directorySize(newest) + 1;
|
|
57
|
+
const result = pruneTraceArtifacts(tracesDir, {
|
|
58
|
+
now: () => Date.parse('2026-05-03T12:00:00.000Z'),
|
|
59
|
+
policy: { maxAgeDays: 30, maxCountPerProfile: 20, maxBytesPerProfile: budget },
|
|
60
|
+
warn: vi.fn(),
|
|
61
|
+
});
|
|
62
|
+
expect(result.deleted).toEqual([oldest]);
|
|
63
|
+
expect(fs.existsSync(oldest)).toBe(false);
|
|
64
|
+
expect(fs.existsSync(middle)).toBe(true);
|
|
65
|
+
expect(fs.existsSync(newest)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it('falls back to directory mtime when receipt is missing', () => {
|
|
68
|
+
const now = Date.parse('2026-05-03T00:00:00.000Z');
|
|
69
|
+
const old = createTraceDir('old-no-receipt', undefined);
|
|
70
|
+
const recent = createTraceDir('recent', '2026-05-02T00:00:00.000Z');
|
|
71
|
+
const oldDate = new Date('2026-04-20T00:00:00.000Z');
|
|
72
|
+
fs.utimesSync(old, oldDate, oldDate);
|
|
73
|
+
const result = pruneTraceArtifacts(tracesDir, {
|
|
74
|
+
now: () => now,
|
|
75
|
+
policy: { maxAgeDays: 7, maxCountPerProfile: 20, maxBytesPerProfile: '500MB' },
|
|
76
|
+
warn: vi.fn(),
|
|
77
|
+
});
|
|
78
|
+
expect(result.deleted).toEqual([old]);
|
|
79
|
+
expect(fs.existsSync(old)).toBe(false);
|
|
80
|
+
expect(fs.existsSync(recent)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
it('does not delete the protected trace exported by the current run', () => {
|
|
83
|
+
const old = createTraceDir('oldest', '2026-05-01T00:00:00.000Z');
|
|
84
|
+
const current = createTraceDir('current', '2026-04-01T00:00:00.000Z', 1024);
|
|
85
|
+
const result = pruneTraceArtifacts(tracesDir, {
|
|
86
|
+
now: () => Date.parse('2026-05-03T12:00:00.000Z'),
|
|
87
|
+
policy: { maxAgeDays: 0, maxCountPerProfile: 0, maxBytesPerProfile: 1 },
|
|
88
|
+
protectedTraceDirs: [current],
|
|
89
|
+
warn: vi.fn(),
|
|
90
|
+
});
|
|
91
|
+
expect(result.deleted).toEqual([old]);
|
|
92
|
+
expect(fs.existsSync(old)).toBe(false);
|
|
93
|
+
expect(fs.existsSync(current)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
function createTraceDir(name, createdAt, payloadBytes = 8) {
|
|
96
|
+
const dir = path.join(tracesDir, name);
|
|
97
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
98
|
+
fs.writeFileSync(path.join(dir, 'trace.jsonl'), 'x'.repeat(payloadBytes), 'utf-8');
|
|
99
|
+
if (createdAt) {
|
|
100
|
+
fs.writeFileSync(path.join(dir, 'receipt.json'), JSON.stringify({ createdAt }, null, 2), 'utf-8');
|
|
101
|
+
const date = new Date(createdAt);
|
|
102
|
+
fs.utimesSync(dir, date, date);
|
|
103
|
+
}
|
|
104
|
+
return dir;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
function directorySize(dir) {
|
|
108
|
+
let total = 0;
|
|
109
|
+
for (const name of fs.readdirSync(dir)) {
|
|
110
|
+
const item = path.join(dir, name);
|
|
111
|
+
const stat = fs.lstatSync(item);
|
|
112
|
+
if (stat.isDirectory())
|
|
113
|
+
total += directorySize(item);
|
|
114
|
+
else
|
|
115
|
+
total += stat.size;
|
|
116
|
+
}
|
|
117
|
+
return total;
|
|
118
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface RingBufferOptions {
|
|
2
|
+
maxAgeMs?: number;
|
|
3
|
+
maxItems?: number;
|
|
4
|
+
now?: () => number;
|
|
5
|
+
}
|
|
6
|
+
export declare class RingBuffer<T extends {
|
|
7
|
+
ts: number;
|
|
8
|
+
}> {
|
|
9
|
+
private readonly maxAgeMs;
|
|
10
|
+
private readonly maxItems;
|
|
11
|
+
private readonly now;
|
|
12
|
+
private items;
|
|
13
|
+
constructor(opts?: RingBufferOptions);
|
|
14
|
+
push(item: T): void;
|
|
15
|
+
values(opts?: {
|
|
16
|
+
since?: number;
|
|
17
|
+
until?: number;
|
|
18
|
+
}): T[];
|
|
19
|
+
clear(): void;
|
|
20
|
+
get size(): number;
|
|
21
|
+
private prune;
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class RingBuffer {
|
|
2
|
+
maxAgeMs;
|
|
3
|
+
maxItems;
|
|
4
|
+
now;
|
|
5
|
+
items = [];
|
|
6
|
+
constructor(opts = {}) {
|
|
7
|
+
this.maxAgeMs = opts.maxAgeMs ?? 120_000;
|
|
8
|
+
this.maxItems = opts.maxItems ?? 1_000;
|
|
9
|
+
this.now = opts.now ?? Date.now;
|
|
10
|
+
}
|
|
11
|
+
push(item) {
|
|
12
|
+
this.items.push(item);
|
|
13
|
+
this.prune();
|
|
14
|
+
}
|
|
15
|
+
values(opts = {}) {
|
|
16
|
+
this.prune();
|
|
17
|
+
return this.items.filter((item) => {
|
|
18
|
+
if (opts.since !== undefined && item.ts < opts.since)
|
|
19
|
+
return false;
|
|
20
|
+
if (opts.until !== undefined && item.ts > opts.until)
|
|
21
|
+
return false;
|
|
22
|
+
return true;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
clear() {
|
|
26
|
+
this.items = [];
|
|
27
|
+
}
|
|
28
|
+
get size() {
|
|
29
|
+
this.prune();
|
|
30
|
+
return this.items.length;
|
|
31
|
+
}
|
|
32
|
+
prune() {
|
|
33
|
+
const minTs = this.now() - this.maxAgeMs;
|
|
34
|
+
if (this.items.length > this.maxItems) {
|
|
35
|
+
this.items = this.items.slice(this.items.length - this.maxItems);
|
|
36
|
+
}
|
|
37
|
+
if (this.maxAgeMs > 0) {
|
|
38
|
+
const firstKept = this.items.findIndex((item) => item.ts >= minTs);
|
|
39
|
+
if (firstKept > 0)
|
|
40
|
+
this.items = this.items.slice(firstKept);
|
|
41
|
+
else if (firstKept === -1)
|
|
42
|
+
this.items = [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { RingBuffer } from './ring-buffer.js';
|
|
3
|
+
describe('RingBuffer', () => {
|
|
4
|
+
it('keeps items in event order and filters by time window', () => {
|
|
5
|
+
let now = 10_000;
|
|
6
|
+
const buffer = new RingBuffer({ maxAgeMs: 5_000, now: () => now });
|
|
7
|
+
buffer.push({ ts: 4_000, value: 'old' });
|
|
8
|
+
buffer.push({ ts: 7_000, value: 'kept' });
|
|
9
|
+
buffer.push({ ts: 9_000, value: 'new' });
|
|
10
|
+
expect(buffer.values().map((item) => item.value)).toEqual(['kept', 'new']);
|
|
11
|
+
expect(buffer.values({ since: 8_000 }).map((item) => item.value)).toEqual(['new']);
|
|
12
|
+
now = 13_000;
|
|
13
|
+
expect(buffer.values().map((item) => item.value)).toEqual(['new']);
|
|
14
|
+
});
|
|
15
|
+
it('caps by item count', () => {
|
|
16
|
+
const buffer = new RingBuffer({ maxItems: 2, maxAgeMs: 100_000, now: () => 10 });
|
|
17
|
+
buffer.push({ ts: 1, value: 1 });
|
|
18
|
+
buffer.push({ ts: 2, value: 2 });
|
|
19
|
+
buffer.push({ ts: 3, value: 3 });
|
|
20
|
+
expect(buffer.values().map((item) => item.value)).toEqual([2, 3]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ObservationEvent, ObservationEventInput, ObservationScope, ObservationStream } from './events.js';
|
|
2
|
+
export declare const DEFAULT_OBSERVATION_WINDOW_MS = 120000;
|
|
3
|
+
export interface ObservationSessionOptions {
|
|
4
|
+
id?: string;
|
|
5
|
+
scope: ObservationScope;
|
|
6
|
+
windowMs?: number;
|
|
7
|
+
maxEventsPerStream?: number;
|
|
8
|
+
now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
export declare class ObservationSession {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly scope: ObservationScope;
|
|
13
|
+
readonly startedAt: number;
|
|
14
|
+
private readonly now;
|
|
15
|
+
private counter;
|
|
16
|
+
private readonly buffers;
|
|
17
|
+
constructor(opts: ObservationSessionOptions);
|
|
18
|
+
record(input: ObservationEventInput): ObservationEvent;
|
|
19
|
+
events(opts?: {
|
|
20
|
+
stream?: ObservationStream;
|
|
21
|
+
since?: number;
|
|
22
|
+
until?: number;
|
|
23
|
+
}): ObservationEvent[];
|
|
24
|
+
}
|
|
25
|
+
export declare function createTraceId(now?: number | (() => number)): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { RingBuffer } from './ring-buffer.js';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
export const DEFAULT_OBSERVATION_WINDOW_MS = 120_000;
|
|
4
|
+
export class ObservationSession {
|
|
5
|
+
id;
|
|
6
|
+
scope;
|
|
7
|
+
startedAt;
|
|
8
|
+
now;
|
|
9
|
+
counter = 0;
|
|
10
|
+
buffers;
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.id = opts.id ?? createTraceId(opts.now?.() ?? Date.now());
|
|
13
|
+
this.scope = opts.scope;
|
|
14
|
+
this.now = opts.now ?? Date.now;
|
|
15
|
+
this.startedAt = this.now();
|
|
16
|
+
const bufferOpts = {
|
|
17
|
+
maxAgeMs: opts.windowMs ?? DEFAULT_OBSERVATION_WINDOW_MS,
|
|
18
|
+
maxItems: opts.maxEventsPerStream ?? 1_000,
|
|
19
|
+
now: this.now,
|
|
20
|
+
};
|
|
21
|
+
this.buffers = {
|
|
22
|
+
action: new RingBuffer(bufferOpts),
|
|
23
|
+
network: new RingBuffer(bufferOpts),
|
|
24
|
+
console: new RingBuffer(bufferOpts),
|
|
25
|
+
screenshot: new RingBuffer({ ...bufferOpts, maxItems: Math.min(opts.maxEventsPerStream ?? 50, 50) }),
|
|
26
|
+
state: new RingBuffer({ ...bufferOpts, maxItems: Math.min(opts.maxEventsPerStream ?? 50, 50) }),
|
|
27
|
+
error: new RingBuffer(bufferOpts),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
record(input) {
|
|
31
|
+
const event = {
|
|
32
|
+
...input,
|
|
33
|
+
id: input.id ?? `${this.id}-${++this.counter}`,
|
|
34
|
+
ts: input.ts ?? this.now(),
|
|
35
|
+
};
|
|
36
|
+
this.buffers[event.stream].push(event);
|
|
37
|
+
return event;
|
|
38
|
+
}
|
|
39
|
+
events(opts = {}) {
|
|
40
|
+
const streams = opts.stream ? [opts.stream] : Object.keys(this.buffers);
|
|
41
|
+
return streams
|
|
42
|
+
.flatMap((stream) => this.buffers[stream].values({ since: opts.since, until: opts.until }))
|
|
43
|
+
.sort((a, b) => a.ts - b.ts || a.id.localeCompare(b.id));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function createTraceId(now = Date.now) {
|
|
47
|
+
const ts = typeof now === 'function' ? now() : now;
|
|
48
|
+
const rand = randomBytes(4).toString('hex');
|
|
49
|
+
return `${new Date(ts).toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}-${rand}`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting: table, JSON, Markdown, CSV, YAML.
|
|
3
|
+
*/
|
|
4
|
+
export interface RenderOptions {
|
|
5
|
+
fmt?: string;
|
|
6
|
+
/** True when the user explicitly passed -f on the command line */
|
|
7
|
+
fmtExplicit?: boolean;
|
|
8
|
+
columns?: string[];
|
|
9
|
+
title?: string;
|
|
10
|
+
elapsed?: number;
|
|
11
|
+
source?: string;
|
|
12
|
+
footerExtra?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function render(data: unknown, opts?: RenderOptions): void;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting: table, JSON, Markdown, CSV, YAML.
|
|
3
|
+
*/
|
|
4
|
+
import Table from 'cli-table3';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
function normalizeRows(data) {
|
|
7
|
+
if (Array.isArray(data))
|
|
8
|
+
return data;
|
|
9
|
+
if (data && typeof data === 'object')
|
|
10
|
+
return [data];
|
|
11
|
+
return [{ value: data }];
|
|
12
|
+
}
|
|
13
|
+
function resolveColumns(rows, opts) {
|
|
14
|
+
return opts.columns ?? Object.keys(rows[0] ?? {});
|
|
15
|
+
}
|
|
16
|
+
export function render(data, opts = {}) {
|
|
17
|
+
let fmt = opts.fmt ?? 'table';
|
|
18
|
+
// Non-TTY auto-downgrade only when format was NOT explicitly passed by user.
|
|
19
|
+
if (!opts.fmtExplicit) {
|
|
20
|
+
if (fmt === 'table' && !process.stdout.isTTY)
|
|
21
|
+
fmt = 'yaml';
|
|
22
|
+
}
|
|
23
|
+
if (data === null || data === undefined) {
|
|
24
|
+
console.log(data);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
switch (fmt) {
|
|
28
|
+
case 'json':
|
|
29
|
+
renderJson(data);
|
|
30
|
+
break;
|
|
31
|
+
case 'plain':
|
|
32
|
+
renderPlain(data, opts);
|
|
33
|
+
break;
|
|
34
|
+
case 'md':
|
|
35
|
+
case 'markdown':
|
|
36
|
+
renderMarkdown(data, opts);
|
|
37
|
+
break;
|
|
38
|
+
case 'csv':
|
|
39
|
+
renderCsv(data, opts);
|
|
40
|
+
break;
|
|
41
|
+
case 'yaml':
|
|
42
|
+
case 'yml':
|
|
43
|
+
renderYaml(data);
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
renderTable(data, opts);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function renderTable(data, opts) {
|
|
51
|
+
const rows = normalizeRows(data);
|
|
52
|
+
if (!rows.length) {
|
|
53
|
+
console.log('(no data)');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const columns = resolveColumns(rows, opts);
|
|
57
|
+
const header = columns.map(c => capitalize(c));
|
|
58
|
+
const table = new Table({
|
|
59
|
+
head: header.map(h => h),
|
|
60
|
+
style: { head: [], border: [] },
|
|
61
|
+
wordWrap: true,
|
|
62
|
+
wrapOnWordBoundary: true,
|
|
63
|
+
});
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
table.push(columns.map(c => {
|
|
66
|
+
const v = row[c];
|
|
67
|
+
return v === null || v === undefined ? '' : String(v);
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
console.log();
|
|
71
|
+
if (opts.title)
|
|
72
|
+
console.log(` ${opts.title}`);
|
|
73
|
+
console.log(table.toString());
|
|
74
|
+
const footer = [];
|
|
75
|
+
footer.push(`${rows.length} items`);
|
|
76
|
+
if (opts.elapsed !== undefined)
|
|
77
|
+
footer.push(`${opts.elapsed.toFixed(1)}s`);
|
|
78
|
+
if (opts.source)
|
|
79
|
+
footer.push(opts.source);
|
|
80
|
+
if (opts.footerExtra)
|
|
81
|
+
footer.push(opts.footerExtra);
|
|
82
|
+
console.log(footer.join(' · '));
|
|
83
|
+
}
|
|
84
|
+
function renderJson(data) {
|
|
85
|
+
console.log(JSON.stringify(data, null, 2));
|
|
86
|
+
}
|
|
87
|
+
function renderPlain(data, opts) {
|
|
88
|
+
const rows = normalizeRows(data);
|
|
89
|
+
if (!rows.length)
|
|
90
|
+
return;
|
|
91
|
+
// Single-row single-field shortcuts for chat-style commands.
|
|
92
|
+
if (rows.length === 1) {
|
|
93
|
+
const row = rows[0];
|
|
94
|
+
const entries = Object.entries(row);
|
|
95
|
+
if (entries.length === 1) {
|
|
96
|
+
const [key, value] = entries[0];
|
|
97
|
+
if (key === 'response' || key === 'content' || key === 'markdown' || key === 'text' || key === 'value') {
|
|
98
|
+
console.log(String(value ?? ''));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
rows.forEach((row, index) => {
|
|
104
|
+
const entries = Object.entries(row).filter(([, value]) => value !== undefined && value !== null && String(value) !== '');
|
|
105
|
+
entries.forEach(([key, value]) => {
|
|
106
|
+
console.log(`${key}: ${value}`);
|
|
107
|
+
});
|
|
108
|
+
if (index < rows.length - 1)
|
|
109
|
+
console.log('');
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function renderMarkdown(data, opts) {
|
|
113
|
+
const rows = normalizeRows(data);
|
|
114
|
+
if (!rows.length)
|
|
115
|
+
return;
|
|
116
|
+
if (rows.length === 1) {
|
|
117
|
+
const entries = Object.entries(rows[0]);
|
|
118
|
+
if (entries.length === 1) {
|
|
119
|
+
const [key, value] = entries[0];
|
|
120
|
+
if (key === 'content' || key === 'markdown' || key === 'text' || key === 'value') {
|
|
121
|
+
console.log(String(value ?? ''));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const columns = resolveColumns(rows, opts);
|
|
127
|
+
console.log('| ' + columns.join(' | ') + ' |');
|
|
128
|
+
console.log('| ' + columns.map(() => '---').join(' | ') + ' |');
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
console.log('| ' + columns.map(c => String(row[c] ?? '')).join(' | ') + ' |');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function renderCsv(data, opts) {
|
|
134
|
+
const rows = normalizeRows(data);
|
|
135
|
+
if (!rows.length)
|
|
136
|
+
return;
|
|
137
|
+
const columns = resolveColumns(rows, opts);
|
|
138
|
+
console.log(columns.join(','));
|
|
139
|
+
for (const row of rows) {
|
|
140
|
+
console.log(columns.map(c => {
|
|
141
|
+
const v = String(row[c] ?? '');
|
|
142
|
+
return v.includes(',') || v.includes('"') || v.includes('\n') || v.includes('\r')
|
|
143
|
+
? `"${v.replace(/"/g, '""')}"` : v;
|
|
144
|
+
}).join(','));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function renderYaml(data) {
|
|
148
|
+
console.log(yaml.dump(data, { sortKeys: false, lineWidth: 120, noRefs: true }));
|
|
149
|
+
}
|
|
150
|
+
function capitalize(s) {
|
|
151
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { render } from './output.js';
|
|
3
|
+
describe('output TTY detection', () => {
|
|
4
|
+
const originalIsTTY = process.stdout.isTTY;
|
|
5
|
+
let logSpy;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, writable: true });
|
|
11
|
+
logSpy.mockRestore();
|
|
12
|
+
});
|
|
13
|
+
it('outputs YAML in non-TTY when format is default table', () => {
|
|
14
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
15
|
+
// commanderAdapter always passes fmt:'table' as default — this must still trigger downgrade
|
|
16
|
+
render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
|
|
17
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
18
|
+
expect(out).toContain('name: alice');
|
|
19
|
+
expect(out).toContain('score: 10');
|
|
20
|
+
});
|
|
21
|
+
it('outputs table in TTY when format is default table', () => {
|
|
22
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: true, writable: true });
|
|
23
|
+
render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
|
|
24
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
25
|
+
expect(out).toContain('alice');
|
|
26
|
+
});
|
|
27
|
+
it('respects explicit -f json even in non-TTY', () => {
|
|
28
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
29
|
+
render([{ name: 'alice' }], { fmt: 'json' });
|
|
30
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
31
|
+
expect(JSON.parse(out)).toEqual([{ name: 'alice' }]);
|
|
32
|
+
});
|
|
33
|
+
it('shows elapsed time when elapsed is 0', () => {
|
|
34
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: true, writable: true });
|
|
35
|
+
render([{ name: 'alice' }], { fmt: 'table', columns: ['name'], elapsed: 0 });
|
|
36
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
37
|
+
expect(out).toContain('0.0s');
|
|
38
|
+
});
|
|
39
|
+
it('explicit -f table overrides non-TTY auto-downgrade', () => {
|
|
40
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
|
|
41
|
+
render([{ name: 'alice' }], { fmt: 'table', fmtExplicit: true, columns: ['name'] });
|
|
42
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
43
|
+
// Should be table output, not YAML
|
|
44
|
+
expect(out).not.toContain('name: alice');
|
|
45
|
+
expect(out).toContain('alice');
|
|
46
|
+
});
|
|
47
|
+
it('prints single markdown payloads without wrapping them in a table', () => {
|
|
48
|
+
render([{ markdown: '# Title\n\nBody' }], { fmt: 'md' });
|
|
49
|
+
const out = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
50
|
+
expect(out).toBe('# Title\n\nBody');
|
|
51
|
+
expect(out).not.toContain('| markdown |');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for package exports.
|
|
3
|
+
*
|
|
4
|
+
* Ensures adapter files use @agentrhq/webcmd public package imports
|
|
5
|
+
* (not fragile relative paths) and that all declared exports resolve
|
|
6
|
+
* to real files.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { builtinModules } from 'node:module';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import ts from 'typescript';
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
16
|
+
const CLIS_DIR = path.join(ROOT, 'clis');
|
|
17
|
+
/** Recursively collect all JS adapter files in a directory. */
|
|
18
|
+
function collectAdapterFiles(dir, opts) {
|
|
19
|
+
const results = [];
|
|
20
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
21
|
+
const full = path.join(dir, entry.name);
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
results.push(...collectAdapterFiles(full, opts));
|
|
24
|
+
}
|
|
25
|
+
else if (entry.name.endsWith('.js') && !entry.name.endsWith('.d.js')) {
|
|
26
|
+
if (opts?.excludeTests && (entry.name.endsWith('.test.js') || entry.name.startsWith('test-')))
|
|
27
|
+
continue;
|
|
28
|
+
results.push(full);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
const ALLOWED_BARE_IMPORTS = new Set([
|
|
34
|
+
'@agentrhq/webcmd',
|
|
35
|
+
...builtinModules.flatMap((name) => name.startsWith('node:')
|
|
36
|
+
? [name, name.slice(5)]
|
|
37
|
+
: [name, `node:${name}`]),
|
|
38
|
+
]);
|
|
39
|
+
function isAllowedImport(specifier) {
|
|
40
|
+
return specifier.startsWith('./')
|
|
41
|
+
|| specifier.startsWith('../')
|
|
42
|
+
|| specifier.startsWith('/')
|
|
43
|
+
|| specifier.startsWith('@agentrhq/webcmd/')
|
|
44
|
+
|| ALLOWED_BARE_IMPORTS.has(specifier);
|
|
45
|
+
}
|
|
46
|
+
/** Forbidden relative import patterns that should have been replaced.
|
|
47
|
+
* Uses (?:\.\./)+ to catch any depth of ../ traversal.
|
|
48
|
+
* Covers: import/export from, vi.mock(), vi.importActual(). */
|
|
49
|
+
const FORBIDDEN_PATTERNS = [
|
|
50
|
+
/(?:from|mock|importActual)\s*\(?['"](?:\.\.\/)+src\//,
|
|
51
|
+
/(?:from|mock|importActual)\s*\(?['"](?:\.\.\/)+browser\//,
|
|
52
|
+
/(?:from|mock|importActual)\s*\(?['"](?:\.\.\/)+download\//,
|
|
53
|
+
/(?:from|mock|importActual)\s*\(?['"](?:\.\.\/)+pipeline\//,
|
|
54
|
+
];
|
|
55
|
+
describe('adapter imports use package exports', () => {
|
|
56
|
+
const adapterFiles = collectAdapterFiles(CLIS_DIR);
|
|
57
|
+
const runtimeAdapterFiles = collectAdapterFiles(CLIS_DIR, { excludeTests: true });
|
|
58
|
+
it('found adapter files to check', () => {
|
|
59
|
+
expect(adapterFiles.length).toBeGreaterThan(100);
|
|
60
|
+
});
|
|
61
|
+
it('no adapter uses relative imports to src/, browser/, download/, or pipeline/', () => {
|
|
62
|
+
const violations = [];
|
|
63
|
+
for (const file of adapterFiles) {
|
|
64
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
65
|
+
for (const pattern of FORBIDDEN_PATTERNS) {
|
|
66
|
+
if (pattern.test(content)) {
|
|
67
|
+
const rel = path.relative(ROOT, file);
|
|
68
|
+
const match = content.match(pattern)?.[0];
|
|
69
|
+
violations.push(`${rel}: ${match}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
expect(violations).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
it('non-test adapters only import node builtins, relative modules, or webcmd public APIs', () => {
|
|
76
|
+
const violations = [];
|
|
77
|
+
for (const file of runtimeAdapterFiles) {
|
|
78
|
+
const source = fs.readFileSync(file, 'utf-8');
|
|
79
|
+
const module = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
|
80
|
+
for (const stmt of module.statements) {
|
|
81
|
+
if (!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt))
|
|
82
|
+
continue;
|
|
83
|
+
const specifier = stmt.moduleSpecifier?.getText(module).slice(1, -1);
|
|
84
|
+
if (specifier && !isAllowedImport(specifier)) {
|
|
85
|
+
violations.push({
|
|
86
|
+
file: path.relative(ROOT, file),
|
|
87
|
+
specifier,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
expect(violations).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('package.json exports resolve to real files', () => {
|
|
96
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
|
97
|
+
const exports = pkgJson.exports;
|
|
98
|
+
it('has exports defined', () => {
|
|
99
|
+
expect(Object.keys(exports).length).toBeGreaterThan(5);
|
|
100
|
+
});
|
|
101
|
+
for (const [exportPath, target] of Object.entries(exports)) {
|
|
102
|
+
it(`export "${exportPath}" → ${target} has a source file`, () => {
|
|
103
|
+
// Export targets point to dist/ (compiled). Verify the source .ts exists.
|
|
104
|
+
// dist/src/foo.js → src/foo.ts
|
|
105
|
+
const sourcePath = target
|
|
106
|
+
.replace(/^\.\/dist\//, './')
|
|
107
|
+
.replace(/\.js$/, '.ts');
|
|
108
|
+
const fullPath = path.join(ROOT, sourcePath);
|
|
109
|
+
expect(fs.existsSync(fullPath), `Missing source: ${sourcePath}`).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PackageJsonLike {
|
|
2
|
+
bin?: string | Record<string, string>;
|
|
3
|
+
main?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function findPackageRoot(startFile: string, fileExists?: (candidate: string) => boolean): string;
|
|
6
|
+
export declare function getBuiltEntryCandidates(packageRoot: string, readFile?: (filePath: string) => string): string[];
|
|
7
|
+
export declare function getCliManifestPath(clisDir: string): string;
|
|
8
|
+
export declare function getFetchAdaptersScriptPath(packageRoot: string): string;
|