playhead-cli 0.1.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/Dockerfile +24 -0
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/action.yml +71 -0
- package/dist/audio/mux.d.ts +29 -0
- package/dist/audio/mux.d.ts.map +1 -0
- package/dist/audio/timing.d.ts +6 -0
- package/dist/audio/timing.d.ts.map +1 -0
- package/dist/audio/tts.d.ts +23 -0
- package/dist/audio/tts.d.ts.map +1 -0
- package/dist/authoring/author.d.ts +40 -0
- package/dist/authoring/author.d.ts.map +1 -0
- package/dist/authoring/catalog.d.ts +31 -0
- package/dist/authoring/catalog.d.ts.map +1 -0
- package/dist/authoring/explore.d.ts +30 -0
- package/dist/authoring/explore.d.ts.map +1 -0
- package/dist/authoring/serialize.d.ts +26 -0
- package/dist/authoring/serialize.d.ts.map +1 -0
- package/dist/authoring/validate.d.ts +31 -0
- package/dist/authoring/validate.d.ts.map +1 -0
- package/dist/beforeafter.d.ts +25 -0
- package/dist/beforeafter.d.ts.map +1 -0
- package/dist/bundle/hash.d.ts +3 -0
- package/dist/bundle/hash.d.ts.map +1 -0
- package/dist/bundle/reader.d.ts +6 -0
- package/dist/bundle/reader.d.ts.map +1 -0
- package/dist/bundle/types.d.ts +54 -0
- package/dist/bundle/types.d.ts.map +1 -0
- package/dist/bundle/writer.d.ts +35 -0
- package/dist/bundle/writer.d.ts.map +1 -0
- package/dist/capture/driver-api.d.ts +190 -0
- package/dist/capture/driver-api.d.ts.map +1 -0
- package/dist/capture/events.d.ts +78 -0
- package/dist/capture/events.d.ts.map +1 -0
- package/dist/capture/executor.d.ts +19 -0
- package/dist/capture/executor.d.ts.map +1 -0
- package/dist/capture/playwright-driver.d.ts +122 -0
- package/dist/capture/playwright-driver.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +6648 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/compose/camera/interpolate.d.ts +9 -0
- package/dist/compose/camera/interpolate.d.ts.map +1 -0
- package/dist/compose/camera/planner.d.ts +19 -0
- package/dist/compose/camera/planner.d.ts.map +1 -0
- package/dist/compose/captions.d.ts +22 -0
- package/dist/compose/captions.d.ts.map +1 -0
- package/dist/compose/cursor/path.d.ts +17 -0
- package/dist/compose/cursor/path.d.ts.map +1 -0
- package/dist/compose/cursor/render.d.ts +10 -0
- package/dist/compose/cursor/render.d.ts.map +1 -0
- package/dist/compose/index.d.ts +25 -0
- package/dist/compose/index.d.ts.map +1 -0
- package/dist/compose/kinds.d.ts +27 -0
- package/dist/compose/kinds.d.ts.map +1 -0
- package/dist/compose/overlays/build.d.ts +7 -0
- package/dist/compose/overlays/build.d.ts.map +1 -0
- package/dist/compose/overlays/draw.d.ts +12 -0
- package/dist/compose/overlays/draw.d.ts.map +1 -0
- package/dist/compose/overlays/text.d.ts +18 -0
- package/dist/compose/overlays/text.d.ts.map +1 -0
- package/dist/compose/render/encoder.d.ts +13 -0
- package/dist/compose/render/encoder.d.ts.map +1 -0
- package/dist/compose/render/frameStore.d.ts +27 -0
- package/dist/compose/render/frameStore.d.ts.map +1 -0
- package/dist/compose/render/renderer.d.ts +5 -0
- package/dist/compose/render/renderer.d.ts.map +1 -0
- package/dist/compose/timeline/pacing.d.ts +36 -0
- package/dist/compose/timeline/pacing.d.ts.map +1 -0
- package/dist/compose/types.d.ts +236 -0
- package/dist/compose/types.d.ts.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4963 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/jira.d.ts +44 -0
- package/dist/integrations/jira.d.ts.map +1 -0
- package/dist/mcp/bin.d.ts +3 -0
- package/dist/mcp/bin.d.ts.map +1 -0
- package/dist/mcp/bin.js +5749 -0
- package/dist/mcp/bin.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/shared/easing.d.ts +9 -0
- package/dist/shared/easing.d.ts.map +1 -0
- package/dist/shared/exit.d.ts +32 -0
- package/dist/shared/exit.d.ts.map +1 -0
- package/dist/shared/ffmpeg.d.ts +19 -0
- package/dist/shared/ffmpeg.d.ts.map +1 -0
- package/dist/shared/geometry.d.ts +47 -0
- package/dist/shared/geometry.d.ts.map +1 -0
- package/dist/shared/log.d.ts +10 -0
- package/dist/shared/log.d.ts.map +1 -0
- package/dist/shared/version.d.ts +4 -0
- package/dist/shared/version.d.ts.map +1 -0
- package/dist/spec/locators.d.ts +62 -0
- package/dist/spec/locators.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +16 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/spec/schema.d.ts +365 -0
- package/dist/spec/schema.d.ts.map +1 -0
- package/dist/theme/index.d.ts +23 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/verify/checks.d.ts +27 -0
- package/dist/verify/checks.d.ts.map +1 -0
- package/dist/verify/contact-sheet.d.ts +7 -0
- package/dist/verify/contact-sheet.d.ts.map +1 -0
- package/dist/verify/media.d.ts +42 -0
- package/dist/verify/media.d.ts.map +1 -0
- package/dist/verify/report.d.ts +12 -0
- package/dist/verify/report.d.ts.map +1 -0
- package/dist/verify/runner.d.ts +13 -0
- package/dist/verify/runner.d.ts.map +1 -0
- package/dist/verify/types.d.ts +63 -0
- package/dist/verify/types.d.ts.map +1 -0
- package/dist/verify/vision.d.ts +19 -0
- package/dist/verify/vision.d.ts.map +1 -0
- package/package.json +74 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Playhead in a container: capture + compose + verify with no host dependencies.
|
|
2
|
+
# The Playwright base image ships Chromium, its sandbox deps, and system fonts.
|
|
3
|
+
#
|
|
4
|
+
# docker build -t playhead .
|
|
5
|
+
# docker run --rm -v "$PWD:/work" -w /work --shm-size=1g playhead \
|
|
6
|
+
# render specs/examples/add-user.yaml -o out --json
|
|
7
|
+
#
|
|
8
|
+
# Notes:
|
|
9
|
+
# - --shm-size=1g (or --disable-dev-shm-usage) keeps Chromium from OOMing in /dev/shm.
|
|
10
|
+
# - TTS narration (`output.audio.narration: tts`) uses macOS `say` and degrades LOUDLY to a
|
|
11
|
+
# silent render on Linux; a Linux TTS provider is planned behind the same seam.
|
|
12
|
+
# - ffmpeg/ffprobe come from the npm static binaries — nothing to apt-install.
|
|
13
|
+
FROM mcr.microsoft.com/playwright:v1.49.0-noble
|
|
14
|
+
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
COPY package.json package-lock.json ./
|
|
17
|
+
RUN npm ci --omit=dev --ignore-scripts \
|
|
18
|
+
&& npx playwright install chromium
|
|
19
|
+
|
|
20
|
+
COPY dist ./dist
|
|
21
|
+
COPY demo ./demo
|
|
22
|
+
|
|
23
|
+
ENTRYPOINT ["node", "dist/cli/index.js"]
|
|
24
|
+
CMD ["--help"]
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aman Rai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Playhead
|
|
2
|
+
|
|
3
|
+
**Product video as a build artifact, not a performance.** Give Playhead a small text
|
|
4
|
+
spec and a URL; it drives your real web app with real input, records clean footage plus a
|
|
5
|
+
structured event log, applies all presentation at compose time, and reviews its own output
|
|
6
|
+
before delivering a verified MP4.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
spec + live product → capture → compose → verify → out.mp4 + verdict.json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Change the spec or the product, regenerate in minutes. **Re-render, don't re-record.**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
npx playwright install chromium
|
|
21
|
+
npm run demo & # bundled demo app on http://localhost:4173
|
|
22
|
+
npm run playhead -- render specs/examples/add-user.yaml -o out
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Let an agent write the spec
|
|
26
|
+
|
|
27
|
+
You don't have to hand-write locators. Point the agent at a running app and describe the
|
|
28
|
+
flow — it walks the app, reads each screen's accessibility tree, and authors a spec where
|
|
29
|
+
**every locator is validated against the live DOM** (no more "the name was `☰Contacts`, not
|
|
30
|
+
`Contacts`" surprises):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export ANTHROPIC_API_KEY=... # the agent that drafts the spec
|
|
34
|
+
npx playhead author http://localhost:4173/crm.html "create a new deal for Globex" -o deal.yaml
|
|
35
|
+
npx playhead render deal.yaml -o out
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or just inspect what's addressable on a screen (great input for writing specs by hand):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx playhead explore http://localhost:4173/crm.html
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
See [VISION.md](VISION.md) for where this is going — Playhead as the video-evidence layer
|
|
45
|
+
for the whole development lifecycle (CI, Jira, PRs, and an MCP tool for coding agents).
|
|
46
|
+
|
|
47
|
+
## Use it from your coding agent (MCP)
|
|
48
|
+
|
|
49
|
+
Playhead ships an MCP server, so any agent (Claude Code, Cursor, …) can explore your app,
|
|
50
|
+
write a grounded spec, render it, and read the verdict — inside your own repo. Register it:
|
|
51
|
+
|
|
52
|
+
```jsonc
|
|
53
|
+
// .mcp.json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"playhead": { "command": "npx", "args": ["playhead", "mcp"] }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Tools the agent gets: `playhead_explore` (grounded, validated locators for the current
|
|
62
|
+
screen), `playhead_validate_spec` (schema + locator check, no render), `playhead_author`
|
|
63
|
+
(auto-draft a spec toward a goal), and `playhead_render` (capture → compose → verify,
|
|
64
|
+
returning the publish/no-publish verdict with reasons and artifact paths). The agent's loop
|
|
65
|
+
becomes: *explore → write spec → render → read verdict → fix → re-render.*
|
|
66
|
+
|
|
67
|
+
Outputs land in `out/`:
|
|
68
|
+
|
|
69
|
+
- `out/out.mp4` — the finished 1080p walkthrough (title card, planned camera moves, rendered
|
|
70
|
+
cursor, auto-written numbered captions).
|
|
71
|
+
- `out/capture/` — the re-renderable **capture bundle** (frames + `events.json` + manifest).
|
|
72
|
+
- `out/compose-manifest.json` — everything the compositor rendered, and what verification checks against.
|
|
73
|
+
- `out/verify/verdict.json` — `publishable` / `not-publishable` with per-check reasons.
|
|
74
|
+
- `out/verify/contact-sheet.png` — key moments as an annotated grid, so you can trust the
|
|
75
|
+
output without watching it end to end.
|
|
76
|
+
|
|
77
|
+
`render` exits `0` only when the verdict is publishable — drop it in CI as an end-to-end test
|
|
78
|
+
with video evidence attached.
|
|
79
|
+
|
|
80
|
+
## The spec
|
|
81
|
+
|
|
82
|
+
A spec is small and reviewable on purpose. Elements are addressed the resilient way — by
|
|
83
|
+
role, label, and text, never brittle CSS — which is also what lets Playhead auto-write the
|
|
84
|
+
instruction captions.
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
playhead: 1
|
|
88
|
+
title: "Add a user in Acme Admin"
|
|
89
|
+
subtitle: "Admin in 30 seconds" # title-card subtitle (defaults to the app host)
|
|
90
|
+
vars:
|
|
91
|
+
base: ${env.APP_URL:-http://localhost:4173}
|
|
92
|
+
app:
|
|
93
|
+
url: "{{base}}"
|
|
94
|
+
viewport: 1280x720
|
|
95
|
+
storageState: session.json # from `playhead login` (authed apps)
|
|
96
|
+
assertLoggedIn: 'role=link[name="Deals"]' # fail fast on stale auth
|
|
97
|
+
environment: { timezone: UTC, locale: en-US, fixedTime: "2026-01-01T12:00:00Z" }
|
|
98
|
+
network:
|
|
99
|
+
block: ["**analytics**"] # never film third-party noise
|
|
100
|
+
output:
|
|
101
|
+
kind: demo # walkthrough | demo | bug-repro | before-after | release | social
|
|
102
|
+
audio: { narration: tts, sfx: true } # spoken captions + click ticks, loudness-normalized
|
|
103
|
+
endCard: { title: "Acme Admin", subtitle: "acme.dev" }
|
|
104
|
+
masking:
|
|
105
|
+
- target: 'testid=env-banner' # redacted at capture; never reaches a frame
|
|
106
|
+
style: solid
|
|
107
|
+
scenes:
|
|
108
|
+
- id: add-user
|
|
109
|
+
title: "Add a user"
|
|
110
|
+
steps:
|
|
111
|
+
- click: 'role=button[name="Add user"]'
|
|
112
|
+
- type: { target: 'label=Full name', text: "Jane Doe" }
|
|
113
|
+
- type: { target: 'label=Temporary password', text: "s3cret!", mask: true }
|
|
114
|
+
- click: 'role=button[name="Create user"]'
|
|
115
|
+
caption: "Create it."
|
|
116
|
+
narration: "One click, and the account is live." # spoken line ≠ on-screen card
|
|
117
|
+
focus: 'text="User added"' # act→react: click here, camera lands THERE
|
|
118
|
+
- expect: { target: 'text="User added"', visible: true }
|
|
119
|
+
- wait: { for: 'text="Jane Doe"', state: visible } # structured wait, never a sleep
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Elements inside iframes are addressed with a frame chain: `frame=#preview >> role=button[name="Save"]`.
|
|
123
|
+
Per-step extras: `caption`, `narration`, `focus` (`target`/`wide`/a locator), `shot: cut|continue`,
|
|
124
|
+
`timeout`, `mask`. Specs compose: `extends: ./base.yaml` merges a base file underneath.
|
|
125
|
+
Scaffold one with `npx playhead init`.
|
|
126
|
+
|
|
127
|
+
## Commands
|
|
128
|
+
|
|
129
|
+
| Command | What it does |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `playhead render <specs...>` | capture → compose → verify; `--workers N` pools multiple specs; `--json` / `--junit <path>` for CI |
|
|
132
|
+
| `playhead validate <spec> --live` | preflight EVERY locator against the running app in one pass, with suggestions — before any frame is captured |
|
|
133
|
+
| `playhead capture <spec>` | produce a capture bundle only; `--from-scene <id>` fast-forwards earlier steps unfilmed |
|
|
134
|
+
| `playhead compose <bundle>` | re-render from a bundle — **no live app needed**; `--spec edited.yaml` re-composes with new text/theme/audio; `--expect-sha` asserts determinism |
|
|
135
|
+
| `playhead verify <bundle> <mp4>` | run the verification suite; `--json` / `--junit` |
|
|
136
|
+
| `playhead jira report <KEY>` | attach evidence (video/failure clip, verdict, contact sheet, console log) to an existing Jira issue + post a verdict comment; or `render --jira <KEY>` (env: JIRA_BASE_URL/EMAIL/API_TOKEN) |
|
|
137
|
+
| `playhead attest <verify-dir>` | re-verify a verdict: artifact hashes + HMAC signature (`PLAYHEAD_SIGNING_KEY`) |
|
|
138
|
+
| `playhead explore <url>` | grounded locator catalog (regions, stability marks, iframes); `--storage-state` for authed screens |
|
|
139
|
+
| `playhead login <url>` | you log in in a headed browser; the session is saved for `app.storageState` |
|
|
140
|
+
| `playhead init [path]` | scaffold a starter spec |
|
|
141
|
+
| `playhead doctor [--ci]` | check ffmpeg, browser, fonts — and container/TTS/signing prerequisites |
|
|
142
|
+
|
|
143
|
+
Add `--vision` to `render`/`verify` for an optional Claude review of the contact sheet (needs
|
|
144
|
+
`ANTHROPIC_API_KEY`; skipped cleanly without one, so CI stays deterministic).
|
|
145
|
+
|
|
146
|
+
**Exit codes are a contract:** `0` publishable · `1` the app broke the flow (a partial bundle
|
|
147
|
+
**and a watchable failure clip** are written) · `2` rendered but failed verification ·
|
|
148
|
+
`3` infra · `4` bad spec/arguments.
|
|
149
|
+
|
|
150
|
+
## CI
|
|
151
|
+
|
|
152
|
+
`action.yml` ships a composite GitHub Action (render → JUnit → artifact upload), and the
|
|
153
|
+
`Dockerfile` runs everything in the Playwright base image. Verdicts carry provenance (version,
|
|
154
|
+
spec hash, capture time, host) and an HMAC signature when `PLAYHEAD_SIGNING_KEY` is set —
|
|
155
|
+
`playhead attest` proves a verdict describes exactly these files and was never edited.
|
|
156
|
+
|
|
157
|
+
## How it works
|
|
158
|
+
|
|
159
|
+
1. **Capture** (`src/capture/`) drives the app through an in-house `Driver` interface
|
|
160
|
+
(Playwright behind it, swappable). It records 2×-resolution frames on a timestamp index,
|
|
161
|
+
an event log (action kind, accessible label, measured bounding box, timestamps, cursor
|
|
162
|
+
path), and applies masking in the DOM before any pixel is painted.
|
|
163
|
+
2. **Compose** (`src/compose/`) is a pure function of the bundle. It plans pacing (holds,
|
|
164
|
+
trimmed idle, min step duration), a camera path from measured UI geometry (quantized zoom,
|
|
165
|
+
never cropping a target), a re-synthesized cursor, and auto-written captions — then renders
|
|
166
|
+
per frame with skia canvas piped to ffmpeg. All presentation is here, so any visual change
|
|
167
|
+
is a re-render.
|
|
168
|
+
3. **Verify** (`src/verify/`) runs ten deterministic checks (target framed and legible, cursor
|
|
169
|
+
on target, action visibly effective, delivered pixels match the plan, no frozen/blank
|
|
170
|
+
footage, captions fit, pacing sane, encode intact, masks opaque + log clean, narration
|
|
171
|
+
audible in its windows at target loudness) and emits a signed verdict and contact sheet.
|
|
172
|
+
|
|
173
|
+
The **capture bundle is the only artifact that touches the live app** — compose and verify
|
|
174
|
+
never hit the network, which is what makes re-render deterministic and CI-safe.
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm run typecheck
|
|
180
|
+
npm test # unit + seeded-failure fixtures + full e2e against the demo app
|
|
181
|
+
npm run build
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
See [AGENTS.md](AGENTS.md) for architecture notes and conventions.
|
package/action.yml
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: 'Playhead Render'
|
|
2
|
+
description: >
|
|
3
|
+
Render verified product videos from Playhead specs: capture the live app, compose the video,
|
|
4
|
+
verify it, and publish a JUnit report. Exit codes distinguish flow failure (the app broke — a
|
|
5
|
+
failure clip is rendered) from video-quality failure from infra problems.
|
|
6
|
+
branding:
|
|
7
|
+
icon: 'film'
|
|
8
|
+
color: 'purple'
|
|
9
|
+
|
|
10
|
+
inputs:
|
|
11
|
+
specs:
|
|
12
|
+
description: 'Space-separated spec paths (or a glob expanded by the shell step calling this action)'
|
|
13
|
+
required: true
|
|
14
|
+
out:
|
|
15
|
+
description: 'Output directory for bundles, videos, verdicts'
|
|
16
|
+
default: 'playhead-out'
|
|
17
|
+
workers:
|
|
18
|
+
description: 'Concurrent renders when given multiple specs'
|
|
19
|
+
default: '2'
|
|
20
|
+
signing-key:
|
|
21
|
+
description: 'PLAYHEAD_SIGNING_KEY — when set, verdicts are HMAC-signed and attestable'
|
|
22
|
+
required: false
|
|
23
|
+
|
|
24
|
+
outputs:
|
|
25
|
+
junit:
|
|
26
|
+
description: 'Path to the JUnit XML report'
|
|
27
|
+
value: ${{ steps.render.outputs.junit }}
|
|
28
|
+
|
|
29
|
+
runs:
|
|
30
|
+
using: 'composite'
|
|
31
|
+
steps:
|
|
32
|
+
- name: Cache Chromium
|
|
33
|
+
uses: actions/cache@v4
|
|
34
|
+
with:
|
|
35
|
+
path: ~/.cache/ms-playwright
|
|
36
|
+
key: playwright-chromium-${{ runner.os }}
|
|
37
|
+
|
|
38
|
+
- name: Install browsers
|
|
39
|
+
shell: bash
|
|
40
|
+
run: npx playwright install --with-deps chromium
|
|
41
|
+
|
|
42
|
+
- name: Render + verify
|
|
43
|
+
id: render
|
|
44
|
+
shell: bash
|
|
45
|
+
env:
|
|
46
|
+
PLAYHEAD_SIGNING_KEY: ${{ inputs.signing-key }}
|
|
47
|
+
run: |
|
|
48
|
+
set +e
|
|
49
|
+
npx playhead render ${{ inputs.specs }} \
|
|
50
|
+
-o "${{ inputs.out }}" \
|
|
51
|
+
--workers "${{ inputs.workers }}" \
|
|
52
|
+
--junit "${{ inputs.out }}/junit.xml" \
|
|
53
|
+
--json > "${{ inputs.out }}/verdict-stream.json"
|
|
54
|
+
code=$?
|
|
55
|
+
echo "junit=${{ inputs.out }}/junit.xml" >> "$GITHUB_OUTPUT"
|
|
56
|
+
# 0 pass · 1 flow failure (failure clip rendered) · 2 quality · 3 infra · 4 usage
|
|
57
|
+
echo "playhead exit code: $code"
|
|
58
|
+
exit $code
|
|
59
|
+
|
|
60
|
+
- name: Upload artifacts (videos, verdicts, contact sheets, failure clips)
|
|
61
|
+
if: always()
|
|
62
|
+
uses: actions/upload-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: playhead-${{ github.run_id }}
|
|
65
|
+
path: |
|
|
66
|
+
${{ inputs.out }}/**/*.mp4
|
|
67
|
+
${{ inputs.out }}/**/verdict.json
|
|
68
|
+
${{ inputs.out }}/**/contact-sheet.png
|
|
69
|
+
${{ inputs.out }}/**/failure.json
|
|
70
|
+
${{ inputs.out }}/junit.xml
|
|
71
|
+
if-no-files-found: ignore
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { StepMapEntry, ClickRipple } from '../compose/types.js';
|
|
2
|
+
import type { NarrationClip } from './tts.js';
|
|
3
|
+
export interface PlacedClip {
|
|
4
|
+
stepRef: string;
|
|
5
|
+
/** Output-clock ms the clip starts playing. */
|
|
6
|
+
tStart: number;
|
|
7
|
+
durMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AudioBedOptions {
|
|
10
|
+
/** Narration clips per stepRef (absent = no narration). */
|
|
11
|
+
narration?: Map<string, NarrationClip>;
|
|
12
|
+
/** Click-beat times (output clock) that get a soft synthesized tick. */
|
|
13
|
+
sfxBeats?: ClickRipple[];
|
|
14
|
+
/** User-supplied music bed (no asset is bundled). */
|
|
15
|
+
music?: {
|
|
16
|
+
file: string;
|
|
17
|
+
gainDb: number;
|
|
18
|
+
};
|
|
19
|
+
/** Directory for the synthesized click sample (cached). */
|
|
20
|
+
cacheDir: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the full audio track — narration clips at their step starts, click ticks at ripple
|
|
24
|
+
* beats, an optional music bed ducked under narration — normalize loudness (EBU R128, I=-16),
|
|
25
|
+
* and mux it onto the rendered MP4. Returns where each narration clip landed so verification
|
|
26
|
+
* can reason about voice windows.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mixAudio(videoPath: string, steps: StepMapEntry[], durationMs: number, opts: AudioBedOptions): Promise<PlacedClip[]>;
|
|
29
|
+
//# sourceMappingURL=mux.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mux.d.ts","sourceRoot":"","sources":["../../src/audio/mux.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAO9C,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,qDAAqD;IACrD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,EAAE,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,UAAU,EAAE,CAAC,CAyFvB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Narration placement constants, shared by the timeline (dwell fitting) and the muxer. */
|
|
2
|
+
/** A step's spoken line starts this long after the step (and its caption) begin. */
|
|
3
|
+
export declare const AUDIO_LEAD_MS = 150;
|
|
4
|
+
/** Silence kept after a spoken line finishes before the step is allowed to end. */
|
|
5
|
+
export declare const AUDIO_TAIL_MS = 450;
|
|
6
|
+
//# sourceMappingURL=timing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../src/audio/timing.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAE3F,oFAAoF;AACpF,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,mFAAmF;AACnF,eAAO,MAAM,aAAa,MAAM,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface NarrationOptions {
|
|
2
|
+
/** Only 'say' (macOS local TTS) is implemented today; the seam is here for cloud providers. */
|
|
3
|
+
provider: 'say';
|
|
4
|
+
voice?: string;
|
|
5
|
+
/** Words per minute (say-specific). */
|
|
6
|
+
rate?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface NarrationLine {
|
|
9
|
+
stepRef: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NarrationClip {
|
|
13
|
+
stepRef: string;
|
|
14
|
+
file: string;
|
|
15
|
+
durationMs: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Synthesize one clip per line. Clips are cached by a content hash of
|
|
19
|
+
* (provider, voice, rate, text) so re-renders reuse them — keeping compose offline
|
|
20
|
+
* and deterministic (same text+voice → same audio → same timing).
|
|
21
|
+
*/
|
|
22
|
+
export declare function synthesizeNarration(lines: NarrationLine[], opts: NarrationOptions, cacheDir: string): Promise<Map<string, NarrationClip>>;
|
|
23
|
+
//# sourceMappingURL=tts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../src/audio/tts.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,QAAQ,EAAE,KAAK,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAqCD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,aAAa,EAAE,EACtB,IAAI,EAAE,gBAAgB,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAWrC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Driver } from '../capture/driver-api.js';
|
|
2
|
+
import { type PageSnapshot } from './explore.js';
|
|
3
|
+
import { type AuthoredStep } from './serialize.js';
|
|
4
|
+
export interface StepDecision {
|
|
5
|
+
done: boolean;
|
|
6
|
+
reason?: string;
|
|
7
|
+
scene: string;
|
|
8
|
+
action?: AuthoredStep['action'];
|
|
9
|
+
locator?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
value?: string;
|
|
12
|
+
keys?: string;
|
|
13
|
+
caption?: string;
|
|
14
|
+
mask?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Chooses the next step from the current screen. Default is Claude; injectable for tests/policies. */
|
|
17
|
+
export type Decider = (goal: string, snap: PageSnapshot, soFar: AuthoredStep[]) => Promise<StepDecision>;
|
|
18
|
+
export interface AuthorOptions {
|
|
19
|
+
url: string;
|
|
20
|
+
goal: string;
|
|
21
|
+
outPath: string;
|
|
22
|
+
title?: string;
|
|
23
|
+
viewport: {
|
|
24
|
+
w: number;
|
|
25
|
+
h: number;
|
|
26
|
+
};
|
|
27
|
+
maxSteps: number;
|
|
28
|
+
headless: boolean;
|
|
29
|
+
driver?: Driver;
|
|
30
|
+
/** Override the step decider (defaults to the Claude-backed one). */
|
|
31
|
+
decide?: Decider;
|
|
32
|
+
}
|
|
33
|
+
/** Agent-author a grounded, validated spec by walking the app toward the goal. Needs ANTHROPIC_API_KEY. */
|
|
34
|
+
export declare function authorSpec(opts: AuthorOptions): Promise<{
|
|
35
|
+
specPath: string;
|
|
36
|
+
steps: AuthoredStep[];
|
|
37
|
+
}>;
|
|
38
|
+
/** The default decider: ask Claude for the next step, forcing a structured tool call. */
|
|
39
|
+
export declare function claudeDecider(): Decider;
|
|
40
|
+
//# sourceMappingURL=author.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"author.d.ts","sourceRoot":"","sources":["../../src/authoring/author.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGrF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,uGAAuG;AACvG,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAEzG,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAsCD,2GAA2G;AAC3G,wBAAsB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC,CAgD1G;AAED,yFAAyF;AACzF,wBAAgB,aAAa,IAAI,OAAO,CA0CvC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RawInteractable } from '../capture/driver-api.js';
|
|
2
|
+
export type CatalogKind = 'button' | 'link' | 'input' | 'select' | 'toggle' | 'heading' | 'other';
|
|
3
|
+
export interface CatalogEntry {
|
|
4
|
+
kind: CatalogKind;
|
|
5
|
+
role: string | null;
|
|
6
|
+
name: string | null;
|
|
7
|
+
/** The chosen, validated Playhead locator string. */
|
|
8
|
+
locator: string;
|
|
9
|
+
/** Whether `locator` resolves to exactly one element. */
|
|
10
|
+
unique: boolean;
|
|
11
|
+
/** How likely the locator survives app changes: testid/role+name high, label medium, text low. */
|
|
12
|
+
stability: 'high' | 'medium' | 'low';
|
|
13
|
+
/** Landmark region: nav | header | sidebar | main | dialog | footer | page. */
|
|
14
|
+
region: string;
|
|
15
|
+
bbox: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
};
|
|
21
|
+
visible: boolean;
|
|
22
|
+
options?: string[];
|
|
23
|
+
}
|
|
24
|
+
/** Stability of a locator string by its engine — a maintenance signal for authors. */
|
|
25
|
+
export declare function stabilityOf(locatorRaw: string): 'high' | 'medium' | 'low';
|
|
26
|
+
/** Ordered locator candidates for an element, best (most resilient/readable) first. */
|
|
27
|
+
export declare function candidateLocators(el: RawInteractable): string[];
|
|
28
|
+
export declare function kindOf(el: RawInteractable): CatalogKind;
|
|
29
|
+
/** Quote for an EXACT locator match; fall back to a regex when the text has awkward characters. */
|
|
30
|
+
export declare function quote(s: string): string;
|
|
31
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/authoring/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAElG,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,MAAM,EAAE,OAAO,CAAC;IAChB,kGAAkG;IAClG,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACrC,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAMzE;AAKD,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,eAAe,GAAG,MAAM,EAAE,CAkB/D;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,eAAe,GAAG,WAAW,CAQvD;AAED,mGAAmG;AACnG,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Driver } from '../capture/driver-api.js';
|
|
2
|
+
import { type CatalogEntry } from './catalog.js';
|
|
3
|
+
export interface PageSnapshot {
|
|
4
|
+
url: string;
|
|
5
|
+
title: string;
|
|
6
|
+
aria: string;
|
|
7
|
+
catalog: CatalogEntry[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Explore the app in its current state: read every interactable, choose the first locator
|
|
11
|
+
* candidate that resolves uniquely against the live DOM, and return a grounded catalog.
|
|
12
|
+
* This is what makes authored specs correct by construction — locators are validated here,
|
|
13
|
+
* not discovered to be wrong at render time.
|
|
14
|
+
*/
|
|
15
|
+
export declare function snapshotPage(driver: Driver): Promise<PageSnapshot>;
|
|
16
|
+
/** One-shot exploration: launch, navigate, snapshot, close. Used by `playhead explore`. */
|
|
17
|
+
export declare function exploreUrl(url: string, opts?: {
|
|
18
|
+
viewport?: {
|
|
19
|
+
w: number;
|
|
20
|
+
h: number;
|
|
21
|
+
};
|
|
22
|
+
headless?: boolean;
|
|
23
|
+
driver?: Driver;
|
|
24
|
+
/** storageState JSON (from `playhead login`) so authed screens are exploreable. */
|
|
25
|
+
storageStatePath?: string;
|
|
26
|
+
}): Promise<PageSnapshot>;
|
|
27
|
+
/** Render a catalog as a compact, human/agent-readable table: grouped by page REGION (where
|
|
28
|
+
* the element lives), then by kind, with a stability signal per locator. */
|
|
29
|
+
export declare function formatCatalog(snap: PageSnapshot): string;
|
|
30
|
+
//# sourceMappingURL=explore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explore.d.ts","sourceRoot":"","sources":["../../src/authoring/explore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAA0C,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEzF,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA+CxE;AAED,2FAA2F;AAC3F,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IACL,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GACA,OAAO,CAAC,YAAY,CAAC,CAkBvB;AAED;6EAC6E;AAC7E,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAyBxD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** An action the author loop decided on and (usually) executed to advance the app. */
|
|
2
|
+
export interface AuthoredStep {
|
|
3
|
+
action: 'goto' | 'click' | 'dblclick' | 'hover' | 'type' | 'press' | 'select' | 'scroll' | 'expect';
|
|
4
|
+
scene: string;
|
|
5
|
+
locator?: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
keys?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
visible?: boolean;
|
|
11
|
+
caption?: string;
|
|
12
|
+
mask?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface AuthoredSpec {
|
|
15
|
+
title: string;
|
|
16
|
+
url: string;
|
|
17
|
+
viewport: {
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
};
|
|
21
|
+
kind: string;
|
|
22
|
+
steps: AuthoredStep[];
|
|
23
|
+
}
|
|
24
|
+
/** Serialize an authored spec into clean, human-editable Playhead YAML, grouped into scenes. */
|
|
25
|
+
export declare function serializeSpec(spec: AuthoredSpec): string;
|
|
26
|
+
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/authoring/serialize.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACpG,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AA6BD,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CA4BxD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Spec } from '../spec/schema.js';
|
|
2
|
+
import type { Driver } from '../capture/driver-api.js';
|
|
3
|
+
export interface ValidateIssue {
|
|
4
|
+
stepRef: string;
|
|
5
|
+
step: string;
|
|
6
|
+
locator: string;
|
|
7
|
+
severity: 'error' | 'warn';
|
|
8
|
+
message: string;
|
|
9
|
+
suggestions: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface ValidateResult {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
checked: number;
|
|
14
|
+
issues: ValidateIssue[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Live preflight: walk the spec against the running app in ONE browser pass, checking every
|
|
18
|
+
* locator (the action target, the focus hint, waits, expects) *before* any frame is ever
|
|
19
|
+
* captured — and report ALL problems at once with nearest-candidate suggestions from the
|
|
20
|
+
* current screen. Steps are executed fast (no cursor choreography, no filming) so later
|
|
21
|
+
* locators are checked against the state they'll actually meet.
|
|
22
|
+
*
|
|
23
|
+
* This collapses "one bad locator = one full capture run" into a single cheap pass.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateLive(spec: Spec, opts?: {
|
|
26
|
+
headless?: boolean;
|
|
27
|
+
driver?: Driver;
|
|
28
|
+
}): Promise<ValidateResult>;
|
|
29
|
+
/** Human-readable report for the CLI. */
|
|
30
|
+
export declare function formatValidateResult(res: ValidateResult): string;
|
|
31
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/authoring/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAG9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAMvD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAAC,cAAc,CAAC,CAsFzB;AAsFD,yCAAyC;AACzC,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAiBhE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Spec } from './spec/schema.js';
|
|
2
|
+
import type { Verdict } from './verify/types.js';
|
|
3
|
+
export interface BeforeAfterResult {
|
|
4
|
+
videoPath: string;
|
|
5
|
+
before: {
|
|
6
|
+
videoPath: string;
|
|
7
|
+
verdict: Verdict;
|
|
8
|
+
};
|
|
9
|
+
after: {
|
|
10
|
+
videoPath: string;
|
|
11
|
+
verdict: Verdict;
|
|
12
|
+
};
|
|
13
|
+
verdict: 'publishable' | 'not-publishable';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Render a before/after comparison: run the SAME spec against two versions of the app
|
|
17
|
+
* (app.compareUrl = before, app.url = after), compose each with the full pipeline, verify both,
|
|
18
|
+
* and stitch them into one titled clip. Publishable only if both halves are.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderBeforeAfter(spec: Spec, opts: {
|
|
21
|
+
outDir: string;
|
|
22
|
+
headless?: boolean;
|
|
23
|
+
vision?: boolean;
|
|
24
|
+
}): Promise<BeforeAfterResult>;
|
|
25
|
+
//# sourceMappingURL=beforeafter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beforeafter.d.ts","sourceRoot":"","sources":["../src/beforeafter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAK7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIjD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,OAAO,EAAE,aAAa,GAAG,iBAAiB,CAAC;CAC5C;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7D,OAAO,CAAC,iBAAiB,CAAC,CAiC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/bundle/hash.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BundleData, FrameIndexEntry } from './types.js';
|
|
2
|
+
export declare function openBundle(dir: string): Promise<BundleData>;
|
|
3
|
+
/** Index of the latest frame with t ≤ tMs (hold semantics); clamps to the first frame. */
|
|
4
|
+
export declare function frameIndexForTime(frames: FrameIndexEntry[], tMs: number): number;
|
|
5
|
+
export declare function framePath(bundle: BundleData, index: number): string;
|
|
6
|
+
//# sourceMappingURL=reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../src/bundle/reader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAkB,eAAe,EAAc,MAAM,YAAY,CAAC;AAG1F,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBjE;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAWhF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|