@jevitate/cli 0.1.0 → 0.3.0
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 +23 -0
- package/dist/ai-cli.d.ts +31 -11
- package/dist/ai-cli.js +131 -88
- package/dist/ai-cli.js.map +1 -1
- package/dist/author-journey-cli.d.ts +4 -0
- package/dist/author-journey-cli.js +111 -0
- package/dist/author-journey-cli.js.map +1 -0
- package/dist/bin.js +60349 -16477
- package/dist/bin.js.map +1 -1
- package/dist/browser-run-options.d.ts +89 -0
- package/dist/browser-run-options.js +175 -0
- package/dist/browser-run-options.js.map +1 -0
- package/dist/build-info.generated.d.ts +2 -0
- package/dist/build-info.generated.js +10 -0
- package/dist/build-info.generated.js.map +1 -0
- package/dist/campaign-api.d.ts +151 -0
- package/dist/campaign-api.js +446 -0
- package/dist/campaign-api.js.map +1 -0
- package/dist/campaign-cli.d.ts +6 -0
- package/dist/campaign-cli.js +66 -0
- package/dist/campaign-cli.js.map +1 -0
- package/dist/check-api.d.ts +5 -0
- package/dist/check-api.js +234 -0
- package/dist/check-api.js.map +1 -0
- package/dist/check-budget.d.ts +26 -0
- package/dist/check-budget.js +90 -0
- package/dist/check-budget.js.map +1 -0
- package/dist/check-cli.d.ts +31 -0
- package/dist/check-cli.js +109 -0
- package/dist/check-cli.js.map +1 -0
- package/dist/check-execute.d.ts +29 -0
- package/dist/check-execute.js +420 -0
- package/dist/check-execute.js.map +1 -0
- package/dist/check-plan.d.ts +118 -0
- package/dist/check-plan.js +516 -0
- package/dist/check-plan.js.map +1 -0
- package/dist/check-suite.d.ts +184 -0
- package/dist/check-suite.js +540 -0
- package/dist/check-suite.js.map +1 -0
- package/dist/check-types.d.ts +181 -0
- package/dist/check-types.js +35 -0
- package/dist/check-types.js.map +1 -0
- package/dist/cli-args.d.ts +26 -0
- package/dist/cli-args.js +50 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-output.d.ts +106 -0
- package/dist/cli-output.js +563 -0
- package/dist/cli-output.js.map +1 -0
- package/dist/cli-reference.d.ts +3 -0
- package/dist/cli-reference.js +81 -0
- package/dist/cli-reference.js.map +1 -0
- package/dist/cli-refusal.d.ts +12 -0
- package/dist/cli-refusal.js +52 -0
- package/dist/cli-refusal.js.map +1 -0
- package/dist/cli-shared.d.ts +340 -0
- package/dist/cli-shared.js +537 -0
- package/dist/cli-shared.js.map +1 -0
- package/dist/cli-surface.d.ts +33 -0
- package/dist/cli-surface.js +48 -0
- package/dist/cli-surface.js.map +1 -0
- package/dist/conversation-options.d.ts +15 -0
- package/dist/conversation-options.js +10 -0
- package/dist/conversation-options.js.map +1 -0
- package/dist/credentials-file.d.ts +15 -0
- package/dist/credentials-file.js +56 -0
- package/dist/credentials-file.js.map +1 -0
- package/dist/data-dir.d.ts +1 -1
- package/dist/data-dir.js +1 -1
- package/dist/defect-evidence.d.ts +141 -0
- package/dist/defect-evidence.js +505 -0
- package/dist/defect-evidence.js.map +1 -0
- package/dist/demo-aspect-api.d.ts +209 -0
- package/dist/demo-aspect-api.js +398 -0
- package/dist/demo-aspect-api.js.map +1 -0
- package/dist/demo-aspect-cli.d.ts +3 -0
- package/dist/demo-aspect-cli.js +243 -0
- package/dist/demo-aspect-cli.js.map +1 -0
- package/dist/demo-capture.d.ts +142 -0
- package/dist/demo-capture.js +537 -0
- package/dist/demo-capture.js.map +1 -0
- package/dist/engine.d.ts +35 -0
- package/dist/engine.js +25 -0
- package/dist/engine.js.map +1 -0
- package/dist/environments.d.ts +155 -0
- package/dist/environments.js +364 -0
- package/dist/environments.js.map +1 -0
- package/dist/exit-codes.d.ts +46 -0
- package/dist/exit-codes.js +91 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/explore-adversarial.d.ts +144 -0
- package/dist/explore-adversarial.js +227 -0
- package/dist/explore-adversarial.js.map +1 -0
- package/dist/explore-api.d.ts +7 -187
- package/dist/explore-api.js +7 -301
- package/dist/explore-api.js.map +1 -1
- package/dist/explore-author.d.ts +79 -0
- package/dist/explore-author.js +86 -0
- package/dist/explore-author.js.map +1 -0
- package/dist/explore-cli.d.ts +7 -0
- package/dist/explore-cli.js +1199 -0
- package/dist/explore-cli.js.map +1 -0
- package/dist/explore-coverage.d.ts +173 -0
- package/dist/explore-coverage.js +246 -0
- package/dist/explore-coverage.js.map +1 -0
- package/dist/explore-feature.d.ts +125 -0
- package/dist/explore-feature.js +218 -0
- package/dist/explore-feature.js.map +1 -0
- package/dist/explore-goal.d.ts +271 -0
- package/dist/explore-goal.js +417 -0
- package/dist/explore-goal.js.map +1 -0
- package/dist/explore-shared.d.ts +177 -0
- package/dist/explore-shared.js +149 -0
- package/dist/explore-shared.js.map +1 -0
- package/dist/explore-specs.d.ts +53 -0
- package/dist/explore-specs.js +333 -0
- package/dist/explore-specs.js.map +1 -0
- package/dist/findings-filing.d.ts +64 -0
- package/dist/findings-filing.js +108 -0
- package/dist/findings-filing.js.map +1 -0
- package/dist/fixture-auth.d.ts +63 -0
- package/dist/fixture-auth.js +95 -0
- package/dist/fixture-auth.js.map +1 -0
- package/dist/fixture-cli.d.ts +99 -0
- package/dist/fixture-cli.js +292 -0
- package/dist/fixture-cli.js.map +1 -0
- package/dist/github-issue-filer.d.ts +62 -0
- package/dist/github-issue-filer.js +189 -0
- package/dist/github-issue-filer.js.map +1 -0
- package/dist/host-health-run.d.ts +25 -0
- package/dist/host-health-run.js +33 -0
- package/dist/host-health-run.js.map +1 -0
- package/dist/http-5xx-outcome.d.ts +6 -0
- package/dist/http-5xx-outcome.js +39 -0
- package/dist/http-5xx-outcome.js.map +1 -0
- package/dist/inbox-cli.d.ts +7 -0
- package/dist/inbox-cli.js +197 -0
- package/dist/inbox-cli.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init-cli.d.ts +4 -0
- package/dist/init-cli.js +156 -0
- package/dist/init-cli.js.map +1 -0
- package/dist/init-keys.d.ts +30 -2
- package/dist/init-keys.js +19 -5
- package/dist/init-keys.js.map +1 -1
- package/dist/init-next-steps.d.ts +35 -0
- package/dist/init-next-steps.js +79 -0
- package/dist/init-next-steps.js.map +1 -0
- package/dist/invariants-file.d.ts +34 -0
- package/dist/invariants-file.js +68 -0
- package/dist/invariants-file.js.map +1 -0
- package/dist/invariants-validate.d.ts +30 -0
- package/dist/invariants-validate.js +105 -0
- package/dist/invariants-validate.js.map +1 -0
- package/dist/journey-annotate-api.d.ts +76 -0
- package/dist/journey-annotate-api.js +269 -0
- package/dist/journey-annotate-api.js.map +1 -0
- package/dist/journey-annotate-cli.d.ts +9 -0
- package/dist/journey-annotate-cli.js +181 -0
- package/dist/journey-annotate-cli.js.map +1 -0
- package/dist/journey-api.d.ts +104 -2
- package/dist/journey-api.js +163 -20
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-cli.d.ts +4 -0
- package/dist/journey-cli.js +411 -0
- package/dist/journey-cli.js.map +1 -0
- package/dist/journey-demo-api.d.ts +114 -0
- package/dist/journey-demo-api.js +405 -0
- package/dist/journey-demo-api.js.map +1 -0
- package/dist/journey-demo-cli.d.ts +10 -0
- package/dist/journey-demo-cli.js +158 -0
- package/dist/journey-demo-cli.js.map +1 -0
- package/dist/journey-prefix.d.ts +194 -0
- package/dist/journey-prefix.js +279 -0
- package/dist/journey-prefix.js.map +1 -0
- package/dist/key-report.d.ts +49 -0
- package/dist/key-report.js +101 -0
- package/dist/key-report.js.map +1 -0
- package/dist/kill-signal.d.ts +193 -0
- package/dist/kill-signal.js +283 -0
- package/dist/kill-signal.js.map +1 -0
- package/dist/launch-armed.d.ts +37 -0
- package/dist/launch-armed.js +31 -0
- package/dist/launch-armed.js.map +1 -0
- package/dist/ledger-api.d.ts +106 -0
- package/dist/ledger-api.js +239 -0
- package/dist/ledger-api.js.map +1 -0
- package/dist/ledger-cli.d.ts +18 -0
- package/dist/ledger-cli.js +107 -0
- package/dist/ledger-cli.js.map +1 -0
- package/dist/load-api.d.ts +29 -11
- package/dist/load-api.js +23 -15
- package/dist/load-api.js.map +1 -1
- package/dist/load-cli.d.ts +4 -0
- package/dist/load-cli.js +97 -0
- package/dist/load-cli.js.map +1 -0
- package/dist/log-correlation.d.ts +200 -0
- package/dist/log-correlation.js +394 -0
- package/dist/log-correlation.js.map +1 -0
- package/dist/log-lines.d.ts +131 -0
- package/dist/log-lines.js +389 -0
- package/dist/log-lines.js.map +1 -0
- package/dist/log-sources.d.ts +71 -0
- package/dist/log-sources.js +236 -0
- package/dist/log-sources.js.map +1 -0
- package/dist/log-trace.d.ts +80 -0
- package/dist/log-trace.js +210 -0
- package/dist/log-trace.js.map +1 -0
- package/dist/logs-cli.d.ts +3 -0
- package/dist/logs-cli.js +51 -0
- package/dist/logs-cli.js.map +1 -0
- package/dist/logs-retention.d.ts +28 -0
- package/dist/logs-retention.js +85 -0
- package/dist/logs-retention.js.map +1 -0
- package/dist/masked-input.d.ts +22 -0
- package/dist/masked-input.js +99 -0
- package/dist/masked-input.js.map +1 -0
- package/dist/mcp-api.d.ts +91 -1
- package/dist/mcp-api.js +514 -18
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-args.d.ts +50 -0
- package/dist/mcp-args.js +164 -0
- package/dist/mcp-args.js.map +1 -0
- package/dist/mcp-cli-bridge.d.ts +34 -0
- package/dist/mcp-cli-bridge.js +62 -0
- package/dist/mcp-cli-bridge.js.map +1 -0
- package/dist/mcp-cli-runner.d.ts +29 -0
- package/dist/mcp-cli-runner.js +44 -0
- package/dist/mcp-cli-runner.js.map +1 -0
- package/dist/mcp-cli-tools.d.ts +129 -0
- package/dist/mcp-cli-tools.js +737 -0
- package/dist/mcp-cli-tools.js.map +1 -0
- package/dist/mcp-paths.d.ts +28 -0
- package/dist/mcp-paths.js +76 -0
- package/dist/mcp-paths.js.map +1 -0
- package/dist/mission-actors.d.ts +40 -0
- package/dist/mission-actors.js +72 -0
- package/dist/mission-actors.js.map +1 -0
- package/dist/mission-api.d.ts +35 -0
- package/dist/mission-api.js +60 -3
- package/dist/mission-api.js.map +1 -1
- package/dist/mission-cli.d.ts +4 -0
- package/dist/mission-cli.js +248 -0
- package/dist/mission-cli.js.map +1 -0
- package/dist/mission-exit.d.ts +22 -0
- package/dist/mission-exit.js +26 -0
- package/dist/mission-exit.js.map +1 -0
- package/dist/mission-fixtures.d.ts +234 -0
- package/dist/mission-fixtures.js +862 -0
- package/dist/mission-fixtures.js.map +1 -0
- package/dist/mission-journal.d.ts +60 -0
- package/dist/mission-journal.js +115 -0
- package/dist/mission-journal.js.map +1 -0
- package/dist/mission-queue-cli.d.ts +10 -0
- package/dist/mission-queue-cli.js +198 -0
- package/dist/mission-queue-cli.js.map +1 -0
- package/dist/mission-queue-runner.d.ts +126 -0
- package/dist/mission-queue-runner.js +406 -0
- package/dist/mission-queue-runner.js.map +1 -0
- package/dist/multi-run-cli.d.ts +37 -0
- package/dist/multi-run-cli.js +116 -0
- package/dist/multi-run-cli.js.map +1 -0
- package/dist/multi-run.d.ts +339 -0
- package/dist/multi-run.js +619 -0
- package/dist/multi-run.js.map +1 -0
- package/dist/openrouter-call.d.ts +22 -0
- package/dist/openrouter-call.js +64 -0
- package/dist/openrouter-call.js.map +1 -0
- package/dist/profile-cli.d.ts +4 -0
- package/dist/profile-cli.js +57 -0
- package/dist/profile-cli.js.map +1 -0
- package/dist/program.d.ts +3 -75
- package/dist/program.js +123 -1921
- package/dist/program.js.map +1 -1
- package/dist/project-dir.d.ts +96 -0
- package/dist/project-dir.js +221 -0
- package/dist/project-dir.js.map +1 -0
- package/dist/record-api.d.ts +13 -7
- package/dist/record-api.js +44 -30
- package/dist/record-api.js.map +1 -1
- package/dist/record-cli.d.ts +4 -0
- package/dist/record-cli.js +76 -0
- package/dist/record-cli.js.map +1 -0
- package/dist/recording-cli.d.ts +4 -0
- package/dist/recording-cli.js +248 -0
- package/dist/recording-cli.js.map +1 -0
- package/dist/regression-api.d.ts +107 -0
- package/dist/regression-api.js +387 -6
- package/dist/regression-api.js.map +1 -1
- package/dist/regression-cli.d.ts +4 -0
- package/dist/regression-cli.js +284 -0
- package/dist/regression-cli.js.map +1 -0
- package/dist/report-api.d.ts +146 -0
- package/dist/report-api.js +431 -0
- package/dist/report-api.js.map +1 -0
- package/dist/report-cli.d.ts +12 -0
- package/dist/report-cli.js +147 -0
- package/dist/report-cli.js.map +1 -0
- package/dist/resource-preflight.d.ts +87 -0
- package/dist/resource-preflight.js +160 -0
- package/dist/resource-preflight.js.map +1 -0
- package/dist/result-schema.d.ts +22 -0
- package/dist/result-schema.js +28 -0
- package/dist/result-schema.js.map +1 -0
- package/dist/run-index.d.ts +29 -0
- package/dist/run-index.js +70 -0
- package/dist/run-index.js.map +1 -0
- package/dist/run-key-preflight.d.ts +42 -0
- package/dist/run-key-preflight.js +82 -0
- package/dist/run-key-preflight.js.map +1 -0
- package/dist/run-screenshots.d.ts +123 -0
- package/dist/run-screenshots.js +235 -0
- package/dist/run-screenshots.js.map +1 -0
- package/dist/runtime-env.d.ts +8 -0
- package/dist/runtime-env.js +23 -0
- package/dist/runtime-env.js.map +1 -0
- package/dist/secret-args.d.ts +23 -0
- package/dist/secret-args.js +45 -0
- package/dist/secret-args.js.map +1 -0
- package/dist/serve-cli.d.ts +4 -0
- package/dist/serve-cli.js +95 -0
- package/dist/serve-cli.js.map +1 -0
- package/dist/server-log-verify.d.ts +36 -0
- package/dist/server-log-verify.js +120 -0
- package/dist/server-log-verify.js.map +1 -0
- package/dist/session-check.d.ts +14 -0
- package/dist/session-check.js +44 -0
- package/dist/session-check.js.map +1 -0
- package/dist/site-cli.d.ts +4 -0
- package/dist/site-cli.js +127 -0
- package/dist/site-cli.js.map +1 -0
- package/dist/site-gate-cli.d.ts +28 -0
- package/dist/site-gate-cli.js +59 -0
- package/dist/site-gate-cli.js.map +1 -0
- package/dist/source-api.js +20 -4
- package/dist/source-api.js.map +1 -1
- package/dist/source-cli.d.ts +4 -0
- package/dist/source-cli.js +284 -0
- package/dist/source-cli.js.map +1 -0
- package/dist/source-run-api.d.ts +27 -1
- package/dist/source-run-api.js +22 -11
- package/dist/source-run-api.js.map +1 -1
- package/dist/storage-state-snapshot.d.ts +50 -0
- package/dist/storage-state-snapshot.js +87 -0
- package/dist/storage-state-snapshot.js.map +1 -0
- package/dist/suite-explore-options.d.ts +321 -0
- package/dist/suite-explore-options.js +162 -0
- package/dist/suite-explore-options.js.map +1 -0
- package/dist/target-config.d.ts +115 -0
- package/dist/target-config.js +225 -0
- package/dist/target-config.js.map +1 -0
- package/dist/transcript-file.d.ts +12 -0
- package/dist/transcript-file.js +23 -0
- package/dist/transcript-file.js.map +1 -0
- package/dist/type-fixture-file.d.ts +9 -0
- package/dist/type-fixture-file.js +48 -0
- package/dist/type-fixture-file.js.map +1 -0
- package/dist/ui-api.d.ts +3 -0
- package/dist/ui-api.js +11 -4
- package/dist/ui-api.js.map +1 -1
- package/dist/usability-capture.d.ts +43 -0
- package/dist/usability-capture.js +327 -0
- package/dist/usability-capture.js.map +1 -0
- package/dist/usage-config.d.ts +36 -0
- package/dist/usage-config.js +114 -0
- package/dist/usage-config.js.map +1 -0
- package/dist/ux-api.d.ts +3 -75
- package/dist/ux-api.js +3 -220
- package/dist/ux-api.js.map +1 -1
- package/dist/ux-claim-probe.d.ts +45 -0
- package/dist/ux-claim-probe.js +386 -0
- package/dist/ux-claim-probe.js.map +1 -0
- package/dist/ux-cli.d.ts +4 -0
- package/dist/ux-cli.js +109 -0
- package/dist/ux-cli.js.map +1 -0
- package/dist/ux-config.d.ts +29 -0
- package/dist/ux-config.js +103 -0
- package/dist/ux-config.js.map +1 -0
- package/dist/ux-evidence.d.ts +54 -0
- package/dist/ux-evidence.js +197 -0
- package/dist/ux-evidence.js.map +1 -0
- package/dist/ux-product.d.ts +14 -0
- package/dist/ux-product.js +39 -0
- package/dist/ux-product.js.map +1 -0
- package/dist/ux-review.d.ts +116 -0
- package/dist/ux-review.js +171 -0
- package/dist/ux-review.js.map +1 -0
- package/dist/ux-usability.d.ts +263 -0
- package/dist/ux-usability.js +580 -0
- package/dist/ux-usability.js.map +1 -0
- package/dist/verify-fix-api.d.ts +194 -0
- package/dist/verify-fix-api.js +506 -0
- package/dist/verify-fix-api.js.map +1 -0
- package/dist/verify-fix-cli.d.ts +4 -0
- package/dist/verify-fix-cli.js +115 -0
- package/dist/verify-fix-cli.js.map +1 -0
- package/package.json +23 -18
- package/skills/jevitate-ci-check/SKILL.md +77 -0
- package/skills/jevitate-demo/SKILL.md +61 -0
- package/skills/jevitate-explore/SKILL.md +96 -87
- package/skills/jevitate-getting-started/SKILL.md +89 -0
- package/skills/jevitate-load-test/SKILL.md +11 -2
- package/skills/jevitate-mission-scope/SKILL.md +19 -11
- package/skills/jevitate-record/SKILL.md +23 -5
- package/skills/jevitate-run-journey/SKILL.md +61 -17
- package/skills/jevitate-sources/SKILL.md +57 -14
- package/skills/jevitate-test-campaign/SKILL.md +203 -0
- package/skills/jevitate-ux-review/SKILL.md +106 -16
- package/skills/jevitate-verify-fix/SKILL.md +76 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { rm } from "node:fs/promises";
|
|
3
|
+
import { REVEALED_SECRET_SELECTORS, REVEALED_SECRET_SHAPES, revealedSecretsIn, secretForms } from "@jevitate/ai-core";
|
|
4
|
+
import { DEMO_OVERLAY_HIDE_STYLE } from "@jevitate/explore";
|
|
5
|
+
import { descriptorToLocator } from "@jevitate/recorder";
|
|
6
|
+
import { clock } from "@jevitate/domain";
|
|
7
|
+
/** The screenshot options a set of layers produces (the overlay-hiding style always first). */
|
|
8
|
+
export async function captureOptions(page, ctx, layers) {
|
|
9
|
+
const styles = [DEMO_OVERLAY_HIDE_STYLE];
|
|
10
|
+
const mask = [];
|
|
11
|
+
let maskColor;
|
|
12
|
+
for (const layer of layers) {
|
|
13
|
+
const c = await layer.prepare(page, ctx);
|
|
14
|
+
if (c === undefined)
|
|
15
|
+
continue;
|
|
16
|
+
if (c.style !== undefined && c.style !== "")
|
|
17
|
+
styles.push(c.style);
|
|
18
|
+
if (c.mask !== undefined)
|
|
19
|
+
mask.push(...c.mask);
|
|
20
|
+
if (c.maskColor !== undefined)
|
|
21
|
+
maskColor = c.maskColor;
|
|
22
|
+
}
|
|
23
|
+
return { style: styles.join("\n"), mask, ...(maskColor === undefined ? {} : { maskColor }) };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Writes one step's PNG at `path`: the viewport (or `clip`, a page-coordinate region — #198's cropped
|
|
27
|
+
* finding shots), overlay hidden, every layer applied. Throws on failure.
|
|
28
|
+
*/
|
|
29
|
+
export async function captureStepScreenshot(page, path, ctx, layers = [], clip) {
|
|
30
|
+
const { style, mask, maskColor } = await captureOptions(page, ctx, layers);
|
|
31
|
+
await page.screenshot({
|
|
32
|
+
path,
|
|
33
|
+
type: "png",
|
|
34
|
+
animations: "disabled",
|
|
35
|
+
style,
|
|
36
|
+
...(clip === undefined ? {} : { clip: { ...clip } }),
|
|
37
|
+
...(mask.length === 0 ? {} : { mask }),
|
|
38
|
+
...(maskColor === undefined ? {} : { maskColor }),
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
for (const layer of layers)
|
|
42
|
+
await layer.confirm?.(page, ctx);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
await rm(path, { force: true });
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// ── #250/#251: the pixel mask ────────────────────────────────────────────────────────────────────
|
|
50
|
+
/** The solid colour a secret is painted over with (the usability capture's mask colour, pinned). */
|
|
51
|
+
export const PIXEL_MASK_COLOR = "#FF00FF";
|
|
52
|
+
/** The attribute marking the mask layer's host element (never hidden by the capture style). */
|
|
53
|
+
export const PIXEL_MASK_ATTR = "data-jevitate-mask-layer";
|
|
54
|
+
/**
|
|
55
|
+
* #298 — a secret the app REVEALS mid-run (a freshly minted API key, a one-time reveal panel, an
|
|
56
|
+
* invite or reset link) is not known in advance, so it cannot be registered with `--secret`. The
|
|
57
|
+
* pixel mask also covers, with no registration:
|
|
58
|
+
*
|
|
59
|
+
* - elements the target marks as secret: these selectors (a target opts in with the
|
|
60
|
+
* `data-jevitate-mask` attribute; the rest are common one-time-secret markers);
|
|
61
|
+
* - text and field values shaped like a credential ({@link REVEALED_SECRET_SHAPES}).
|
|
62
|
+
*
|
|
63
|
+
* A value found either way is LEARNED for the rest of the run: masked wherever it appears later
|
|
64
|
+
* (another screen, a frame) and redacted from the screenshot index. Learned values live only in
|
|
65
|
+
* memory (the run's mask), never on disk. Limits (docs/safety.md): a secret with no marker and
|
|
66
|
+
* no credential shape (a short code, a plain word) is not masked; a password field shows dots.
|
|
67
|
+
*/
|
|
68
|
+
export { REVEALED_SECRET_SELECTORS, REVEALED_SECRET_SHAPES, revealedSecretsIn };
|
|
69
|
+
/** The mask could not be applied or proven: the capture is skipped (fail closed), never written. */
|
|
70
|
+
export class MaskUnavailableError extends Error {
|
|
71
|
+
code = "E_PIXEL_MASK";
|
|
72
|
+
constructor(message) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "MaskUnavailableError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* BROWSER CODE (a string: evaluated as-is, never transpiled). `__CFG__` is replaced by the JSON
|
|
79
|
+
* config `{ name, secrets, fill, attr }`. Idempotent per document; installs `window[name]`
|
|
80
|
+
* (non-enumerable, non-writable, non-configurable — a random name per run, so a page cannot
|
|
81
|
+
* pre-empt or fake it). Secret values live only inside this closure.
|
|
82
|
+
*/
|
|
83
|
+
const MASK_RUNTIME = String.raw `((cfg) => {
|
|
84
|
+
if (window[cfg.name]) return true;
|
|
85
|
+
const SECRETS = cfg.secrets.filter((s) => typeof s === "string" && s.trim().length > 0);
|
|
86
|
+
// #298: secrets the app reveals mid-run — marked elements and credential-shaped values — are
|
|
87
|
+
// learned into SECRETS (masked everywhere after) and reported to the run's mask via learned().
|
|
88
|
+
const SHAPES = cfg.shapes.map((src) => new RegExp(src, "g"));
|
|
89
|
+
const MARKERS = cfg.markers.join(",");
|
|
90
|
+
const LEARNED = [];
|
|
91
|
+
const learn = (v) => {
|
|
92
|
+
if (typeof v !== "string") return;
|
|
93
|
+
const t = v.trim();
|
|
94
|
+
if (t.length < 8 || SECRETS.includes(t)) return;
|
|
95
|
+
SECRETS.push(t);
|
|
96
|
+
LEARNED.push(t);
|
|
97
|
+
};
|
|
98
|
+
const learnShapes = (v) => {
|
|
99
|
+
if (typeof v !== "string" || v.length < 8) return;
|
|
100
|
+
for (const re of SHAPES) { re.lastIndex = 0; for (const m of v.matchAll(re)) learn(m[0]); }
|
|
101
|
+
};
|
|
102
|
+
// A marked element is masked whole; what is LEARNED from it (masked elsewhere too) is its field
|
|
103
|
+
// value, or its credential-like words: 12+ chars, no space, letters and digits (never a label
|
|
104
|
+
// word like "Generate", which would then be masked on every screen).
|
|
105
|
+
const learnMarked = (el) => {
|
|
106
|
+
const tag = el.tagName;
|
|
107
|
+
if (tag === "INPUT" || tag === "TEXTAREA") { if (!/\s/.test(el.value)) learn(el.value); learnShapes(el.value); return; }
|
|
108
|
+
const text = el.textContent || "";
|
|
109
|
+
learnShapes(text);
|
|
110
|
+
for (const tok of text.split(/\s+/)) if (tok.length >= 12 && /[0-9]/.test(tok) && /[A-Za-z]/.test(tok)) learn(tok.replace(/^['"(]+|['")\].,;:]+$/g, ""));
|
|
111
|
+
};
|
|
112
|
+
const SKIP = new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE", "HEAD", "TITLE", "META", "LINK"]);
|
|
113
|
+
const PAD = 2;
|
|
114
|
+
const CSS = ":host{all:initial}*{pointer-events:none !important;box-sizing:border-box}[hidden]{display:none !important}" +
|
|
115
|
+
".m{position:fixed;background:" + cfg.fill + ";border-radius:2px}" +
|
|
116
|
+
".h{position:fixed;border:3px solid #f59e0b;border-radius:6px;box-shadow:0 0 0 4px rgba(245,158,11,.35)}";
|
|
117
|
+
let host = null;
|
|
118
|
+
let layer = null;
|
|
119
|
+
let hiBox = null;
|
|
120
|
+
let dirty = true;
|
|
121
|
+
let targets = [];
|
|
122
|
+
const boxes = [];
|
|
123
|
+
let drawn = [];
|
|
124
|
+
let highlightEl = null;
|
|
125
|
+
let lastError = "";
|
|
126
|
+
const holds = (s) => typeof s === "string" && s.length > 0 && SECRETS.some((x) => s.includes(x));
|
|
127
|
+
const ensureHost = () => {
|
|
128
|
+
const root = document.documentElement;
|
|
129
|
+
if (!root) return false;
|
|
130
|
+
if (host === null) {
|
|
131
|
+
host = document.createElement("jevitate-mask");
|
|
132
|
+
host.setAttribute(cfg.attr, "");
|
|
133
|
+
host.setAttribute("aria-hidden", "true");
|
|
134
|
+
host.setAttribute("inert", "");
|
|
135
|
+
const st = { display: "block", visibility: "visible", opacity: "1", position: "fixed", top: "0", left: "0",
|
|
136
|
+
width: "0", height: "0", overflow: "visible", "pointer-events": "none", "z-index": "2147483647",
|
|
137
|
+
transform: "none", filter: "none", "clip-path": "none", margin: "0", padding: "0", border: "0" };
|
|
138
|
+
for (const k of Object.keys(st)) host.style.setProperty(k, st[k], "important");
|
|
139
|
+
const shadow = host.attachShadow({ mode: "closed" });
|
|
140
|
+
try {
|
|
141
|
+
const sheet = new CSSStyleSheet();
|
|
142
|
+
sheet.replaceSync(CSS);
|
|
143
|
+
shadow.adoptedStyleSheets = [sheet];
|
|
144
|
+
} catch (e) {
|
|
145
|
+
const s = document.createElement("style");
|
|
146
|
+
s.textContent = CSS;
|
|
147
|
+
shadow.appendChild(s);
|
|
148
|
+
}
|
|
149
|
+
layer = document.createElement("div");
|
|
150
|
+
hiBox = document.createElement("div");
|
|
151
|
+
hiBox.className = "h";
|
|
152
|
+
hiBox.hidden = true;
|
|
153
|
+
shadow.append(layer, hiBox);
|
|
154
|
+
}
|
|
155
|
+
// The last child of <html>: painted after (above) every same-z-index page layer.
|
|
156
|
+
if (host.parentNode !== root || root.lastElementChild !== host) root.appendChild(host);
|
|
157
|
+
return true;
|
|
158
|
+
};
|
|
159
|
+
const collect = () => {
|
|
160
|
+
const out = [];
|
|
161
|
+
const roots = [document];
|
|
162
|
+
// Marked elements first (#298): masked whole, and their secret learned before the text scan.
|
|
163
|
+
const marked = new Set();
|
|
164
|
+
const markRoot = (r) => {
|
|
165
|
+
if (MARKERS === "") return;
|
|
166
|
+
for (const el of Array.from(r.querySelectorAll(MARKERS))) {
|
|
167
|
+
if (el === host || SKIP.has(el.tagName) || marked.has(el)) continue;
|
|
168
|
+
marked.add(el);
|
|
169
|
+
learnMarked(el);
|
|
170
|
+
out.push({ kind: "el", el });
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
markRoot(document);
|
|
174
|
+
const perSecretRanges = new Map();
|
|
175
|
+
for (let i = 0; i < roots.length; i++) {
|
|
176
|
+
const w = document.createTreeWalker(roots[i], NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
177
|
+
for (let n = w.nextNode(); n !== null; n = w.nextNode()) {
|
|
178
|
+
if (n.nodeType === 3) {
|
|
179
|
+
const p = n.parentElement;
|
|
180
|
+
if (p !== null && SKIP.has(p.tagName)) continue;
|
|
181
|
+
const v = n.nodeValue;
|
|
182
|
+
if (!v) continue;
|
|
183
|
+
learnShapes(v);
|
|
184
|
+
for (const s of SECRETS) {
|
|
185
|
+
let at = v.indexOf(s);
|
|
186
|
+
while (at >= 0) {
|
|
187
|
+
out.push({ kind: "range", node: n, start: at, end: at + s.length });
|
|
188
|
+
perSecretRanges.set(s, (perSecretRanges.get(s) || 0) + 1);
|
|
189
|
+
at = v.indexOf(s, at + s.length);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const el = n;
|
|
195
|
+
if (el === host) continue;
|
|
196
|
+
if (el.shadowRoot) { roots.push(el.shadowRoot); markRoot(el.shadowRoot); }
|
|
197
|
+
if (SKIP.has(el.tagName)) continue;
|
|
198
|
+
if (marked.has(el)) continue;
|
|
199
|
+
let hit = false;
|
|
200
|
+
const tag = el.tagName;
|
|
201
|
+
if (tag === "INPUT" || tag === "TEXTAREA") {
|
|
202
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
203
|
+
if (!(tag === "INPUT" && type === "password")) learnShapes(el.value);
|
|
204
|
+
if (!(tag === "INPUT" && type === "password") && holds(el.value)) hit = true;
|
|
205
|
+
} else if (tag === "SELECT") {
|
|
206
|
+
const t = Array.from(el.selectedOptions || []).map((o) => o.text).join(" ");
|
|
207
|
+
if (holds(t) || holds(el.value)) hit = true;
|
|
208
|
+
}
|
|
209
|
+
if (!hit) for (const a of Array.from(el.attributes)) if (holds(a.value)) { hit = true; break; }
|
|
210
|
+
if (hit) out.push({ kind: "el", el });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// A secret split across text nodes (e.g. "sec<b>ret</b>"): mask the deepest element holding it whole.
|
|
214
|
+
const body = document.body;
|
|
215
|
+
if (body) {
|
|
216
|
+
const text = body.textContent || "";
|
|
217
|
+
for (const s of SECRETS) {
|
|
218
|
+
let total = 0;
|
|
219
|
+
for (let at = text.indexOf(s); at >= 0; at = text.indexOf(s, at + s.length)) total++;
|
|
220
|
+
if (total <= (perSecretRanges.get(s) || 0)) continue;
|
|
221
|
+
const walk = (el) => {
|
|
222
|
+
let child = false;
|
|
223
|
+
for (const c of Array.from(el.children)) {
|
|
224
|
+
if (SKIP.has(c.tagName)) continue;
|
|
225
|
+
if ((c.textContent || "").includes(s)) { child = true; walk(c); }
|
|
226
|
+
}
|
|
227
|
+
if (!child && !out.some((t) => t.kind === "range" && el.contains(t.node))) out.push({ kind: "el", el });
|
|
228
|
+
};
|
|
229
|
+
walk(body);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
};
|
|
234
|
+
const rectsOf = (t) => {
|
|
235
|
+
if (t.kind === "range") {
|
|
236
|
+
if (!t.node.isConnected) return null;
|
|
237
|
+
const r = document.createRange();
|
|
238
|
+
try { r.setStart(t.node, t.start); r.setEnd(t.node, t.end); } catch (e) { return null; }
|
|
239
|
+
return Array.from(r.getClientRects());
|
|
240
|
+
}
|
|
241
|
+
if (!t.el.isConnected) return null;
|
|
242
|
+
return [t.el.getBoundingClientRect()];
|
|
243
|
+
};
|
|
244
|
+
const fieldText = (el) => el.tagName === "SELECT" ? Array.from(el.selectedOptions || []).map((o) => o.text).join(" ") + " " + el.value : el.value;
|
|
245
|
+
const shaped = (v) => typeof v === "string" && v.length >= 8 && SHAPES.some((re) => { re.lastIndex = 0; return re.test(v); });
|
|
246
|
+
const inputsChanged = () => {
|
|
247
|
+
const flagged = new Set(targets.filter((t) => t.kind === "el").map((t) => t.el));
|
|
248
|
+
for (const el of Array.from(document.querySelectorAll("input, textarea, select"))) {
|
|
249
|
+
if (el.tagName === "INPUT" && (el.getAttribute("type") || "").toLowerCase() === "password") continue;
|
|
250
|
+
if ((holds(fieldText(el)) || shaped(fieldText(el))) && !flagged.has(el)) return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
};
|
|
254
|
+
const place = (d, r, pad) => {
|
|
255
|
+
d.style.left = (r.left - pad) + "px";
|
|
256
|
+
d.style.top = (r.top - pad) + "px";
|
|
257
|
+
d.style.width = (r.width + 2 * pad) + "px";
|
|
258
|
+
d.style.height = (r.height + 2 * pad) + "px";
|
|
259
|
+
};
|
|
260
|
+
const update = () => {
|
|
261
|
+
if (!ensureHost()) return;
|
|
262
|
+
if (dirty || inputsChanged()) { targets = collect(); dirty = false; }
|
|
263
|
+
const rects = [];
|
|
264
|
+
for (const t of targets) {
|
|
265
|
+
const rs = rectsOf(t);
|
|
266
|
+
if (rs === null) { dirty = true; continue; }
|
|
267
|
+
for (const r of rs) if (r.width > 0 || r.height > 0) rects.push(r);
|
|
268
|
+
}
|
|
269
|
+
while (boxes.length < rects.length) {
|
|
270
|
+
const d = document.createElement("div");
|
|
271
|
+
d.className = "m";
|
|
272
|
+
layer.appendChild(d);
|
|
273
|
+
boxes.push(d);
|
|
274
|
+
}
|
|
275
|
+
for (let i = 0; i < boxes.length; i++) {
|
|
276
|
+
const d = boxes[i];
|
|
277
|
+
if (i < rects.length) { place(d, rects[i], PAD); d.hidden = false; } else d.hidden = true;
|
|
278
|
+
}
|
|
279
|
+
drawn = rects;
|
|
280
|
+
if (highlightEl !== null && highlightEl.isConnected) {
|
|
281
|
+
place(hiBox, highlightEl.getBoundingClientRect(), 5);
|
|
282
|
+
hiBox.hidden = false;
|
|
283
|
+
} else hiBox.hidden = true;
|
|
284
|
+
};
|
|
285
|
+
const tick = () => {
|
|
286
|
+
try { update(); lastError = ""; } catch (e) { lastError = String((e && e.message) || e).slice(0, 200); }
|
|
287
|
+
requestAnimationFrame(tick);
|
|
288
|
+
};
|
|
289
|
+
const mo = new MutationObserver((records) => {
|
|
290
|
+
for (const r of records) {
|
|
291
|
+
if (r.type === "attributes" && (r.attributeName === "style" || r.attributeName === "class")) continue;
|
|
292
|
+
if (r.type === "childList" && r.target === document.documentElement &&
|
|
293
|
+
Array.from(r.addedNodes).concat(Array.from(r.removedNodes)).every((x) => x === host)) continue;
|
|
294
|
+
dirty = true;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
mo.observe(document, { subtree: true, childList: true, characterData: true, attributes: true });
|
|
299
|
+
const mark = () => { dirty = true; };
|
|
300
|
+
window.addEventListener("input", mark, true);
|
|
301
|
+
window.addEventListener("change", mark, true);
|
|
302
|
+
const covers = (b, r) => b.left <= r.left + 0.5 && b.top <= r.top + 0.5 && b.right >= r.right - 0.5 && b.bottom >= r.bottom - 0.5;
|
|
303
|
+
const api = {
|
|
304
|
+
verify() {
|
|
305
|
+
dirty = true;
|
|
306
|
+
try { update(); } catch (e) {
|
|
307
|
+
return { ok: false, occurrences: 0, masked: 0, reason: "the mask update failed: " + String((e && e.message) || e).slice(0, 200), rects: [] };
|
|
308
|
+
}
|
|
309
|
+
const problems = [];
|
|
310
|
+
{
|
|
311
|
+
if (host === null || !host.isConnected) problems.push("the mask layer is not attached");
|
|
312
|
+
else {
|
|
313
|
+
const cs = getComputedStyle(host);
|
|
314
|
+
if (cs.display === "none" || cs.visibility !== "visible" || Number(cs.opacity) < 1) problems.push("the mask layer is hidden");
|
|
315
|
+
if (document.documentElement.lastElementChild !== host) problems.push("the mask layer is not the last layer");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (drawn.length > 0 && document.querySelector(":modal, :popover-open, :fullscreen") !== null) {
|
|
319
|
+
problems.push("a top-layer element (modal dialog, popover or fullscreen) could paint over the mask");
|
|
320
|
+
}
|
|
321
|
+
let masked = 0;
|
|
322
|
+
const live = boxes.filter((d) => !d.hidden).map((d) => d.getBoundingClientRect());
|
|
323
|
+
for (const r of drawn) if (live.some((b) => covers(b, r))) masked++;
|
|
324
|
+
if (masked < drawn.length) problems.push((drawn.length - masked) + " occurrence(s) not covered by a mask box");
|
|
325
|
+
if (lastError !== "") problems.push("the mask loop failed: " + lastError);
|
|
326
|
+
const out = { ok: problems.length === 0, occurrences: drawn.length, masked, rects: drawn.map((r) => ({ x: r.left, y: r.top, width: r.width, height: r.height })) };
|
|
327
|
+
if (problems.length > 0) out.reason = problems.join("; ");
|
|
328
|
+
return out;
|
|
329
|
+
},
|
|
330
|
+
learn(values) { if (Array.isArray(values)) for (const v of values) learn(v); dirty = true; return true; },
|
|
331
|
+
learned() { return LEARNED.slice(); },
|
|
332
|
+
highlight(el) { highlightEl = el || null; try { update(); } catch (e) { /* presentation only */ } return highlightEl !== null; },
|
|
333
|
+
clearHighlight() { highlightEl = null; try { update(); } catch (e) { /* presentation only */ } return true; },
|
|
334
|
+
};
|
|
335
|
+
Object.defineProperty(window, cfg.name, { value: Object.freeze(api), enumerable: false, configurable: false, writable: false });
|
|
336
|
+
requestAnimationFrame(tick);
|
|
337
|
+
return true;
|
|
338
|
+
})(__CFG__)`;
|
|
339
|
+
/** Bound on one mask round-trip (ms). */
|
|
340
|
+
const MASK_CALL_MS = 5_000;
|
|
341
|
+
async function within(p, what) {
|
|
342
|
+
let timer;
|
|
343
|
+
try {
|
|
344
|
+
return await Promise.race([
|
|
345
|
+
p,
|
|
346
|
+
new Promise((_, reject) => {
|
|
347
|
+
timer = clock.setTimeout(() => reject(new MaskUnavailableError(`${what} timed out after ${MASK_CALL_MS}ms`)), MASK_CALL_MS);
|
|
348
|
+
}),
|
|
349
|
+
]);
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
if (timer !== undefined)
|
|
353
|
+
clock.clearTimeout(timer);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function errText(e) {
|
|
357
|
+
return (e instanceof Error ? e.message : String(e)).split("\n")[0] ?? "unknown error";
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* One run's pixel mask over its registered secrets (raw and URL-encoded forms, as `redactText`).
|
|
361
|
+
* `install` (per browser context, before its first navigation — every later document and frame gets
|
|
362
|
+
* it from an init script) keeps a VIDEO masked; `verify`/`layer()` prove it at each capture and
|
|
363
|
+
* `assertMasked` throws {@link MaskUnavailableError} when it cannot be proven. With no secrets there
|
|
364
|
+
* is nothing to mask: `verify` is trivially ok and nothing is injected for it.
|
|
365
|
+
*/
|
|
366
|
+
export class SecretPixelMask {
|
|
367
|
+
#source;
|
|
368
|
+
#name;
|
|
369
|
+
#contexts = new WeakSet();
|
|
370
|
+
/** Secrets the app revealed mid-run (#298), learned by the page mask — in memory only, never written. */
|
|
371
|
+
#learned = new Set();
|
|
372
|
+
/**
|
|
373
|
+
* Always true (#298): even with no registered secret, a secret the app reveals mid-run (a marked
|
|
374
|
+
* element, a credential-shaped value) is masked, so every capture installs and proves the mask.
|
|
375
|
+
*/
|
|
376
|
+
active = true;
|
|
377
|
+
constructor(secrets) {
|
|
378
|
+
const forms = [...new Set(secrets.filter((s) => typeof s === "string" && s.trim() !== "").flatMap((s) => [...secretForms(s)]))];
|
|
379
|
+
this.#name = `__jevitateMask_${randomBytes(8).toString("hex")}`;
|
|
380
|
+
// JSON inside a JS expression (valid JS since ES2019); `<` escaped so no value can close a script context.
|
|
381
|
+
const cfg = JSON.stringify({
|
|
382
|
+
name: this.#name,
|
|
383
|
+
secrets: forms,
|
|
384
|
+
shapes: REVEALED_SECRET_SHAPES,
|
|
385
|
+
markers: REVEALED_SECRET_SELECTORS,
|
|
386
|
+
fill: PIXEL_MASK_COLOR,
|
|
387
|
+
attr: PIXEL_MASK_ATTR,
|
|
388
|
+
}).replace(/</g, "\\u003c");
|
|
389
|
+
this.#source = MASK_RUNTIME.replace("__CFG__", () => cfg);
|
|
390
|
+
}
|
|
391
|
+
/** Installs the live mask in `page`'s context (every future document/frame) and every current frame. Throws when it cannot. */
|
|
392
|
+
async install(page) {
|
|
393
|
+
const ctx = page.context();
|
|
394
|
+
if (!this.#contexts.has(ctx)) {
|
|
395
|
+
try {
|
|
396
|
+
await within(ctx.addInitScript({ content: this.#source }), "installing the pixel mask");
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
throw new MaskUnavailableError(`could not install the pixel mask: ${errText(e)}`);
|
|
400
|
+
}
|
|
401
|
+
this.#contexts.add(ctx);
|
|
402
|
+
}
|
|
403
|
+
for (const p of ctx.pages())
|
|
404
|
+
for (const f of p.frames())
|
|
405
|
+
await this.#inject(f);
|
|
406
|
+
}
|
|
407
|
+
async #inject(frame) {
|
|
408
|
+
if (frame.isDetached())
|
|
409
|
+
return;
|
|
410
|
+
try {
|
|
411
|
+
await within(frame.evaluate(this.#source), "injecting the pixel mask");
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
if (frame.isDetached())
|
|
415
|
+
return;
|
|
416
|
+
throw new MaskUnavailableError(`could not apply the pixel mask to a frame: ${errText(e)}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* The secrets the app revealed during the run (#298), as learned so far — for redacting what the
|
|
421
|
+
* run writes about its captures (the screenshot index). Never persisted.
|
|
422
|
+
*/
|
|
423
|
+
revealed() {
|
|
424
|
+
return [...this.#learned];
|
|
425
|
+
}
|
|
426
|
+
/** Shares what the run learned with `frame`'s mask, and collects what that frame learned. */
|
|
427
|
+
async #sync(frame) {
|
|
428
|
+
const learned = await within(frame.evaluate(([name, known]) => {
|
|
429
|
+
const api = window[name];
|
|
430
|
+
if (api === undefined)
|
|
431
|
+
return null;
|
|
432
|
+
api.learn(known);
|
|
433
|
+
return api.learned();
|
|
434
|
+
}, [this.#name, [...this.#learned]]), "syncing the pixel mask");
|
|
435
|
+
for (const v of learned ?? [])
|
|
436
|
+
this.#learned.add(v);
|
|
437
|
+
}
|
|
438
|
+
/** Proves the mask covers every visible secret occurrence in every frame of `page`, now. */
|
|
439
|
+
async verify(page) {
|
|
440
|
+
const known = this.#learned.size;
|
|
441
|
+
const r = await this.#verifyOnce(page);
|
|
442
|
+
// A secret one frame revealed (learned this pass) is re-checked in every frame once (#298).
|
|
443
|
+
return this.#learned.size > known ? this.#verifyOnce(page) : r;
|
|
444
|
+
}
|
|
445
|
+
async #verifyOnce(page) {
|
|
446
|
+
let occurrences = 0;
|
|
447
|
+
let masked = 0;
|
|
448
|
+
let rects = [];
|
|
449
|
+
const reasons = [];
|
|
450
|
+
for (const frame of page.frames()) {
|
|
451
|
+
if (frame.isDetached())
|
|
452
|
+
continue;
|
|
453
|
+
try {
|
|
454
|
+
await this.#inject(frame);
|
|
455
|
+
await this.#sync(frame);
|
|
456
|
+
const r = await within(frame.evaluate((name) => window[name]?.verify() ?? null, this.#name), "verifying the pixel mask");
|
|
457
|
+
if (r === null) {
|
|
458
|
+
reasons.push("the mask is not installed in a frame");
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
occurrences += r.occurrences;
|
|
462
|
+
masked += r.masked;
|
|
463
|
+
if (frame === page.mainFrame())
|
|
464
|
+
rects = r.rects;
|
|
465
|
+
if (!r.ok)
|
|
466
|
+
reasons.push(r.reason ?? "the mask could not be proven");
|
|
467
|
+
await this.#sync(frame);
|
|
468
|
+
}
|
|
469
|
+
catch (e) {
|
|
470
|
+
if (frame.isDetached())
|
|
471
|
+
continue;
|
|
472
|
+
reasons.push(errText(e));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return { ok: reasons.length === 0, occurrences, masked, rects, ...(reasons.length === 0 ? {} : { reason: reasons.join("; ") }) };
|
|
476
|
+
}
|
|
477
|
+
/** Throws {@link MaskUnavailableError} unless {@link verify} proves the mask. */
|
|
478
|
+
async assertMasked(page) {
|
|
479
|
+
const r = await this.verify(page);
|
|
480
|
+
if (!r.ok)
|
|
481
|
+
throw new MaskUnavailableError(`secret masking could not be proven: ${r.reason ?? "unknown"}`);
|
|
482
|
+
return r;
|
|
483
|
+
}
|
|
484
|
+
/** The capture layer: proven before the screenshot and again after it (a failure deletes the file). */
|
|
485
|
+
layer() {
|
|
486
|
+
return {
|
|
487
|
+
name: "secret-pixel-mask",
|
|
488
|
+
prepare: async (page) => {
|
|
489
|
+
await this.assertMasked(page);
|
|
490
|
+
},
|
|
491
|
+
confirm: async (page) => {
|
|
492
|
+
await this.assertMasked(page);
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/** Draws a capture highlight box around `target`'s first match (display-only). True when shown. */
|
|
497
|
+
async highlight(page, target) {
|
|
498
|
+
return this.highlightLocator(page, descriptorToLocator(page, target));
|
|
499
|
+
}
|
|
500
|
+
/** #198: the same display-only highlight box around `locator`'s first match (a control or a quoted text). */
|
|
501
|
+
async highlightLocator(page, locator) {
|
|
502
|
+
try {
|
|
503
|
+
await this.#inject(page.mainFrame());
|
|
504
|
+
return await within(locator.first().evaluate((el, name) => window[name]?.highlight(el) === true, this.#name, { timeout: 2_000 }), "highlighting the failing element");
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
return false; // a highlight is presentation only
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/** Removes the capture highlight (best-effort). */
|
|
511
|
+
async clearHighlight(page) {
|
|
512
|
+
await within(page.evaluate((name) => window[name]?.clearHighlight(), this.#name), "clearing the highlight").catch(() => undefined);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* A port whose every session carries the live pixel mask from before its first navigation — so a
|
|
517
|
+
* `recordVideo` session's every frame is masked. A session whose mask cannot be installed is closed
|
|
518
|
+
* and the open throws {@link MaskUnavailableError}: nothing is recorded unmasked.
|
|
519
|
+
*/
|
|
520
|
+
export function maskingPort(port, mask) {
|
|
521
|
+
if (!mask.active)
|
|
522
|
+
return port;
|
|
523
|
+
return {
|
|
524
|
+
open: async (o) => {
|
|
525
|
+
const session = await port.open(o);
|
|
526
|
+
try {
|
|
527
|
+
await mask.install(session.page);
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
await session.close().catch(() => undefined);
|
|
531
|
+
throw e;
|
|
532
|
+
}
|
|
533
|
+
return session;
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
//# sourceMappingURL=demo-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo-capture.js","sourceRoot":"","sources":["../src/demo-capture.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build identity for every mission result, issue draft, and `--version` (issue #83): "It's hard
|
|
3
|
+
* to attribute results to a build" — the dev workflow (`npm link` to a live working tree) means
|
|
4
|
+
* `jevitate --version`'s package.json number alone doesn't change between rebuilds, so two
|
|
5
|
+
* results from different builds in the same dogfooding session were indistinguishable.
|
|
6
|
+
*
|
|
7
|
+
* `commit`/`builtAt` come from ONE of two places, and both fall back to `"unknown"` — never a
|
|
8
|
+
* fabricated value:
|
|
9
|
+
*
|
|
10
|
+
* - the esbuild bundle (`pnpm --filter @jevitate/cli run bundle`, what npm actually publishes)
|
|
11
|
+
* folds them in as literals via an esbuild `define` (`build.mjs`), computed there from
|
|
12
|
+
* `git rev-parse` at bundle time;
|
|
13
|
+
* - the plain `tsc --build` dist (what a dev `npm link` install runs — no bundler
|
|
14
|
+
* substitution ever touches it) reads the SAME git-rev-parse-or-"unknown" computation from a
|
|
15
|
+
* small generated module written just before `tsc --build` runs
|
|
16
|
+
* (`scripts/generate-build-info.mjs`, hooked into the package's "build" script).
|
|
17
|
+
*
|
|
18
|
+
* Both compute identically; only the injection mechanism differs by build path, so this module
|
|
19
|
+
* simply prefers whichever one actually ran.
|
|
20
|
+
*/
|
|
21
|
+
export interface EngineInfo {
|
|
22
|
+
readonly version: string;
|
|
23
|
+
readonly commit: string;
|
|
24
|
+
readonly builtAt: string;
|
|
25
|
+
}
|
|
26
|
+
/** This build's identity: the published version plus which commit/build produced it. */
|
|
27
|
+
export declare function currentEngineInfo(): EngineInfo;
|
|
28
|
+
/**
|
|
29
|
+
* Stamps a CLI result with the build that produced it (#112) — every envelope a command emits
|
|
30
|
+
* (`journey run`, `load run`, `source run`, `verify-fix`, `regression capture`, `ux`) carries the
|
|
31
|
+
* same `engine` the explore results do, so evidence from any surface can be tied to a build.
|
|
32
|
+
*/
|
|
33
|
+
export declare function withEngine<T extends object>(result: T, engine?: EngineInfo): T & {
|
|
34
|
+
engine: EngineInfo;
|
|
35
|
+
};
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GENERATED_BUILT_AT, GENERATED_COMMIT } from "./build-info.generated.js";
|
|
2
|
+
import { readCliVersion } from "./version.js";
|
|
3
|
+
function bundledCommit() {
|
|
4
|
+
return typeof __JEVITATE_BUILD_COMMIT__ !== "undefined" ? __JEVITATE_BUILD_COMMIT__ : undefined;
|
|
5
|
+
}
|
|
6
|
+
function bundledBuiltAt() {
|
|
7
|
+
return typeof __JEVITATE_BUILT_AT__ !== "undefined" ? __JEVITATE_BUILT_AT__ : undefined;
|
|
8
|
+
}
|
|
9
|
+
/** This build's identity: the published version plus which commit/build produced it. */
|
|
10
|
+
export function currentEngineInfo() {
|
|
11
|
+
return {
|
|
12
|
+
version: readCliVersion(),
|
|
13
|
+
commit: bundledCommit() ?? GENERATED_COMMIT,
|
|
14
|
+
builtAt: bundledBuiltAt() ?? GENERATED_BUILT_AT,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Stamps a CLI result with the build that produced it (#112) — every envelope a command emits
|
|
19
|
+
* (`journey run`, `load run`, `source run`, `verify-fix`, `regression capture`, `ux`) carries the
|
|
20
|
+
* same `engine` the explore results do, so evidence from any surface can be tied to a build.
|
|
21
|
+
*/
|
|
22
|
+
export function withEngine(result, engine = currentEngineInfo()) {
|
|
23
|
+
return { ...result, engine };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAkC9C,SAAS,aAAa;IACpB,OAAO,OAAO,yBAAyB,KAAK,WAAW,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,SAAS,CAAC;AAClG,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,OAAO,qBAAqB,KAAK,WAAW,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1F,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,OAAO,EAAE,cAAc,EAAE;QACzB,MAAM,EAAE,aAAa,EAAE,IAAI,gBAAgB;QAC3C,OAAO,EAAE,cAAc,EAAE,IAAI,kBAAkB;KAChD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAmB,MAAS,EAAE,SAAqB,iBAAiB,EAAE;IAC9F,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC"}
|