frameyard 0.0.0-stage → 0.2.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/GLOSSARY.md +145 -0
  3. package/LICENSE +21 -0
  4. package/README.md +311 -2
  5. package/dist/assets/executable.d.ts +16 -0
  6. package/dist/assets/executable.d.ts.map +1 -0
  7. package/dist/assets/executable.js +59 -0
  8. package/dist/assets/executable.js.map +1 -0
  9. package/dist/assets/files.d.ts +4 -0
  10. package/dist/assets/files.d.ts.map +1 -0
  11. package/dist/assets/files.js +30 -0
  12. package/dist/assets/files.js.map +1 -0
  13. package/dist/assets/integrations.d.ts +15 -0
  14. package/dist/assets/integrations.d.ts.map +1 -0
  15. package/dist/assets/integrations.js +15 -0
  16. package/dist/assets/integrations.js.map +1 -0
  17. package/dist/assets/process.d.ts +29 -0
  18. package/dist/assets/process.d.ts.map +1 -0
  19. package/dist/assets/process.js +97 -0
  20. package/dist/assets/process.js.map +1 -0
  21. package/dist/assets/provenance.d.ts +77 -0
  22. package/dist/assets/provenance.d.ts.map +1 -0
  23. package/dist/assets/provenance.js +302 -0
  24. package/dist/assets/provenance.js.map +1 -0
  25. package/dist/assets/transcribe.d.ts +38 -0
  26. package/dist/assets/transcribe.d.ts.map +1 -0
  27. package/dist/assets/transcribe.js +357 -0
  28. package/dist/assets/transcribe.js.map +1 -0
  29. package/dist/assets/tts.d.ts +29 -0
  30. package/dist/assets/tts.d.ts.map +1 -0
  31. package/dist/assets/tts.js +180 -0
  32. package/dist/assets/tts.js.map +1 -0
  33. package/dist/cli.d.ts +3 -0
  34. package/dist/cli.d.ts.map +1 -0
  35. package/dist/cli.js +795 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/contracts.d.ts +106 -0
  38. package/dist/contracts.d.ts.map +1 -0
  39. package/dist/contracts.js +297 -0
  40. package/dist/contracts.js.map +1 -0
  41. package/dist/doctor.d.ts +56 -0
  42. package/dist/doctor.d.ts.map +1 -0
  43. package/dist/doctor.js +476 -0
  44. package/dist/doctor.js.map +1 -0
  45. package/dist/errors.d.ts +10 -0
  46. package/dist/errors.d.ts.map +1 -0
  47. package/dist/errors.js +18 -0
  48. package/dist/errors.js.map +1 -0
  49. package/dist/index.d.ts +8 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +5 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/preview/main.d.ts +19 -0
  54. package/dist/preview/main.d.ts.map +1 -0
  55. package/dist/preview/main.js +480 -0
  56. package/dist/preview/main.js.map +1 -0
  57. package/dist/project.d.ts +16 -0
  58. package/dist/project.d.ts.map +1 -0
  59. package/dist/project.js +88 -0
  60. package/dist/project.js.map +1 -0
  61. package/dist/renderer/audio.d.ts +31 -0
  62. package/dist/renderer/audio.d.ts.map +1 -0
  63. package/dist/renderer/audio.js +88 -0
  64. package/dist/renderer/audio.js.map +1 -0
  65. package/dist/renderer/browser.d.ts +22 -0
  66. package/dist/renderer/browser.d.ts.map +1 -0
  67. package/dist/renderer/browser.js +175 -0
  68. package/dist/renderer/browser.js.map +1 -0
  69. package/dist/renderer/ffmpeg.d.ts +58 -0
  70. package/dist/renderer/ffmpeg.d.ts.map +1 -0
  71. package/dist/renderer/ffmpeg.js +268 -0
  72. package/dist/renderer/ffmpeg.js.map +1 -0
  73. package/dist/renderer/index.d.ts +72 -0
  74. package/dist/renderer/index.d.ts.map +1 -0
  75. package/dist/renderer/index.js +281 -0
  76. package/dist/renderer/index.js.map +1 -0
  77. package/dist/renderer/transcripts.d.ts +30 -0
  78. package/dist/renderer/transcripts.d.ts.map +1 -0
  79. package/dist/renderer/transcripts.js +86 -0
  80. package/dist/renderer/transcripts.js.map +1 -0
  81. package/dist/runtime/clock.d.ts +5 -0
  82. package/dist/runtime/clock.d.ts.map +1 -0
  83. package/dist/runtime/clock.js +16 -0
  84. package/dist/runtime/clock.js.map +1 -0
  85. package/dist/runtime/index.d.ts +29 -0
  86. package/dist/runtime/index.d.ts.map +1 -0
  87. package/dist/runtime/index.js +289 -0
  88. package/dist/runtime/index.js.map +1 -0
  89. package/dist/runtime/random.d.ts +8 -0
  90. package/dist/runtime/random.d.ts.map +1 -0
  91. package/dist/runtime/random.js +21 -0
  92. package/dist/runtime/random.js.map +1 -0
  93. package/dist/scaffold.d.ts +3 -0
  94. package/dist/scaffold.d.ts.map +1 -0
  95. package/dist/scaffold.js +176 -0
  96. package/dist/scaffold.js.map +1 -0
  97. package/dist/server/preview-html.d.ts +2 -0
  98. package/dist/server/preview-html.d.ts.map +1 -0
  99. package/dist/server/preview-html.js +229 -0
  100. package/dist/server/preview-html.js.map +1 -0
  101. package/dist/server/server.d.ts +17 -0
  102. package/dist/server/server.d.ts.map +1 -0
  103. package/dist/server/server.js +237 -0
  104. package/dist/server/server.js.map +1 -0
  105. package/dist/transcript.d.ts +44 -0
  106. package/dist/transcript.d.ts.map +1 -0
  107. package/dist/transcript.js +130 -0
  108. package/dist/transcript.js.map +1 -0
  109. package/dist/version.d.ts +2 -0
  110. package/dist/version.d.ts.map +1 -0
  111. package/dist/version.js +2 -0
  112. package/dist/version.js.map +1 -0
  113. package/docs/adr/0001-keep-composition-authoring-web-native.md +3 -0
  114. package/docs/adr/0002-make-frame-and-props-the-only-animation-state.md +3 -0
  115. package/docs/adr/0003-render-browser-frames-offline.md +3 -0
  116. package/docs/adr/0004-produce-local-ai-assets-outside-rendering.md +3 -0
  117. package/docs/v0.1-design.md +238 -0
  118. package/examples/demo/README.md +24 -0
  119. package/examples/demo/assets/chime.wav +0 -0
  120. package/examples/demo/compositions/demo.js +206 -0
  121. package/examples/demo/props/demo.json +5 -0
  122. package/examples/demo/video.config.js +5 -0
  123. package/package.json +73 -3
  124. package/skills/frameyard/README.md +35 -0
  125. package/skills/frameyard/SKILL.md +60 -0
  126. package/skills/frameyard/references/audio-assets.md +86 -0
  127. package/skills/frameyard/references/deterministic-authoring.md +107 -0
  128. package/skills/frameyard/references/failure-recovery.md +57 -0
  129. package/skills/frameyard/references/json-results.md +203 -0
  130. package/skills/frameyard/references/subtitles-karaoke.md +95 -0
  131. package/skills/frameyard/scripts/frameyard.mjs +60 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ All notable changes to Frameyard are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ Frameyard uses `0.x` versions until the composition, configuration, CLI,
8
+ JSON-result, transcript, and skill contracts stabilize. Before `1.0.0`, a
9
+ contract change may ship in a minor release and is called out explicitly below.
10
+
11
+ ## [0.2.0] - 2026-10-29
12
+
13
+ ### Added
14
+
15
+ - `init` scaffolds an `AGENTS.md` project guide with the determinism rules and
16
+ the `check`, `still`, and `render` workflow for coding agents.
17
+ - `init` scaffolds a private `package.json` whose `dev`, `check`, `render`, and
18
+ `still` scripts pin the scaffolding CLI version, so `pnpm dev` and
19
+ `npm run dev` work without a global install. Project names are normalized to
20
+ valid npm package names.
21
+ - `init --json` reports the additional scaffold files.
22
+
23
+ ### Changed
24
+
25
+ - **Skill contract:** the official agent skill moved from `skill/` to
26
+ `skills/frameyard/` so agent hosts discover it automatically. The npm package
27
+ ships `skills/` instead of `skill/`. Update copied or linked installations
28
+ from the old path.
29
+ - User-facing documentation, CLI help, and generated project guides use
30
+ `npx frameyard`; scaffolded scripts pin `npx --yes frameyard@<version>`.
31
+ - Development tooling moved to `oxlint` and `oxfmt`, with pre-commit hooks and
32
+ matching CI and release workflow checks.
33
+
34
+ ### Fixed
35
+
36
+ - Corrected documentation links and command examples after the skill relocation,
37
+ and enforced the `npx frameyard` convention in `test/docs.test.ts`.
38
+
39
+ [0.2.0]: https://github.com/vladiantio/frameyard/compare/v0.1.0...v0.2.0
40
+
41
+ ## [0.1.0] - 2026-10-28
42
+
43
+ ### Added
44
+
45
+ - Native JavaScript composition contract with fixed `width`, `height`, `fps`,
46
+ and `durationInFrames`, JSON props, and synchronous seek-safe `update()`.
47
+ - Browser runtime with deterministic frame time, keyed `random()`, CSS and Web
48
+ Animations seeking, and preview diagnostics for non-deterministic clocks,
49
+ randomness, and CSS transitions.
50
+ - Interactive preview with composition selection, prop editing, frame
51
+ scrubbing, playback, and audio synchronization.
52
+ - CLI commands: `init`, `dev`, `check`, `still`, `render`, `tts`, `transcribe`,
53
+ and `doctor`, with `--json` result envelopes and stable error codes.
54
+ - Deterministic Chromium capture through Playwright and FFmpeg H.264/AAC
55
+ encoding, with atomic output publication and explicit overwrite flags.
56
+ - Audio clips placed on composition frames with source trimming in seconds and
57
+ an offline FFmpeg mix.
58
+ - Canonical transcript validation, measured word timings, and SRT/VTT
59
+ formatting, including transcript requirements attached to compositions.
60
+ - Local `qwen-tts` speech synthesis and `whisper-cli` transcription wrappers
61
+ with executable resolution, atomic publishing, and a versioned provenance
62
+ manifest.
63
+ - `frameyard doctor` environment, integration, and manifest inspection without
64
+ downloading, repairing, or modifying anything.
65
+ - Host-neutral agent skill under `skill/`, versioned with the npm package API.
66
+ - 30-second reference composition, deterministic render benchmark, public-seam
67
+ test suite, design baseline, and architecture decision records.
68
+
69
+ [0.1.0]: https://github.com/vladiantio/frameyard/releases/tag/v0.1.0
package/GLOSSARY.md ADDED
@@ -0,0 +1,145 @@
1
+ # Frameyard
2
+
3
+ Frameyard is a local-first framework for creating short, parameterized videos with HTML, CSS, and JavaScript. Its domain separates reusable compositions from the fixed inputs and deterministic renders that turn them into videos.
4
+
5
+ ## Language
6
+
7
+ ### Authoring
8
+
9
+ **Project**:
10
+ A collection of related compositions and the assets and configuration they share.
11
+ _Avoid_: Video, render
12
+
13
+ **Composition**:
14
+ A uniquely identified, reusable video definition with fixed dimensions, frame rate, duration in frames, and content parameterized by props.
15
+ _Avoid_: Scene, template, video
16
+
17
+ **Composition metadata**:
18
+ The fixed dimensions, frame rate, and duration in frames that define a composition's output contract.
19
+ _Avoid_: Render options, props
20
+
21
+ **Composition lifecycle**:
22
+ The `mount → update → dispose` sequence that governs a composition's frame production.
23
+ _Avoid_: Render loop, playback lifecycle
24
+
25
+ **Composition revision**:
26
+ A specific version of a composition's source and metadata.
27
+ _Avoid_: Build, release
28
+
29
+ **Props**:
30
+ The immutable, JSON-serializable inputs supplied to a composition for one render.
31
+ _Avoid_: Options, settings, mutable state
32
+
33
+ **Asset**:
34
+ A local file required by a composition, whether static or generated.
35
+ _Avoid_: Resource, media file
36
+
37
+ ### Deterministic execution
38
+
39
+ **Frame**:
40
+ An integer-addressed position on a composition's timeline.
41
+ _Avoid_: Timestamp, animation tick
42
+
43
+ **Deterministic execution**:
44
+ The rule that a composition's visual state at any frame depends only on that frame, its props, and pre-loaded data and assets.
45
+ _Avoid_: Playback-driven animation
46
+
47
+ **Frame clock**:
48
+ The single source of animation time, derived from the current frame and the composition's frame rate.
49
+ _Avoid_: Wall clock, playback timer
50
+
51
+ **Seek safety**:
52
+ The property that any frame can be produced correctly without depending on previously produced frames.
53
+ _Avoid_: History-dependent updates
54
+
55
+ **Deterministic randomness**:
56
+ Random values derived statelessly from a key so they do not depend on call order or rendering history.
57
+ _Avoid_: Unseeded randomness
58
+
59
+ **Frame production**:
60
+ The phase in which the runtime produces the visual state of a requested frame, without network access or model inference.
61
+ _Avoid_: Real-time playback
62
+
63
+ **Loading phase**:
64
+ The phase before frame production when required data and assets become available.
65
+ _Avoid_: Per-frame fetching
66
+
67
+ ### Audio
68
+
69
+ **Audio clip**:
70
+ An audio source placed on a composition's timeline with a start frame, source trim, and volume.
71
+ _Avoid_: Audio track
72
+
73
+ **Audio mix**:
74
+ The single offline mix of a composition's audio clips for one render.
75
+ _Avoid_: Soundtrack, audio track, final cut
76
+
77
+ ### Rendering
78
+
79
+ **Preview**:
80
+ An interactive, frame-accurate inspection of a composition using the same composition behavior as a render.
81
+ _Avoid_: Visual editor
82
+
83
+ **Check**:
84
+ A validation operation that checks configuration, props, assets, audio, and transcript requirements, then mounts a composition and evaluates every frame without capturing or encoding.
85
+ _Avoid_: Test render, dry run
86
+
87
+ **Render**:
88
+ The production of one complete video from a composition and a fixed set of props.
89
+ _Avoid_: Recording, export
90
+
91
+ **Video**:
92
+ The finished audiovisual output of a successful render.
93
+ _Avoid_: Composition, project
94
+
95
+ **Still**:
96
+ A single frame captured as a PNG through the production rendering path for debugging.
97
+ _Avoid_: Frame export, image sequence
98
+
99
+ **Reproducibility**:
100
+ The guarantee that, for the same composition revision, props, assets, and frame under a pinned runtime/browser environment, the visual state is identical. It does not imply identical encoded bytes, even on the same machine; encoded output depends on the encoder and its runtime.
101
+ _Avoid_: Bit-for-bit encoding equality
102
+
103
+ ### Agent operation
104
+
105
+ **Agent-assisted authoring and operation**:
106
+ The creation and operation of a Frameyard project by an AI agent acting on a user's behalf.
107
+ _Avoid_: Built-in generative AI
108
+
109
+ **Agent skill**:
110
+ An API-version-matched set of instructions and resources through which an AI agent uses Frameyard's public authoring and operation contracts.
111
+ _Avoid_: Agent runtime, MCP server
112
+
113
+ ### Local asset production
114
+
115
+ **Generated asset**:
116
+ A frozen local input produced by speech synthesis or transcription before a composition is loaded or rendered.
117
+ _Avoid_: Per-frame generation
118
+
119
+ **Provenance manifest**:
120
+ A versioned, project-local record of generated assets, their content hashes, and the inputs and processes that produced them.
121
+ _Avoid_: Render cache
122
+
123
+ **Speech synthesis** (TTS):
124
+ The local production of a spoken audio asset from text using a named speaker.
125
+ _Avoid_: Voice generation, voice cloning, voice design
126
+
127
+ **Transcription**:
128
+ The local production of timed text from source audio or video.
129
+ _Avoid_: Subtitle rendering
130
+
131
+ **Transcript**:
132
+ The canonical, versioned artifact containing full text, source identity, and source-relative timed segments, with optional word timings.
133
+ _Avoid_: Subtitle file, raw generator output
134
+
135
+ **Transcript segment**:
136
+ A timed span of transcript text with source-relative start and end times.
137
+ _Avoid_: Subtitle cue, line
138
+
139
+ **Word timing**:
140
+ The measured, source-relative start and end of a word in source audio.
141
+ _Avoid_: Interpolated segment timing
142
+
143
+ **Karaoke subtitle**:
144
+ A subtitle presentation pattern that highlights words according to actual word timings.
145
+ _Avoid_: Segment-timed highlighting
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frameyard contributors
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 CHANGED
@@ -1,3 +1,312 @@
1
- # Temporary Holding Version
1
+ # Frameyard
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Frameyard is a local-first framework for building short, parameterized videos with HTML, CSS, and JavaScript and rendering them deterministically.
4
+
5
+ The v0.1 design is implemented: composition contracts, browser runtime and preview, `init`, `dev`, `check`, `still`, `render`, `tts`, `transcribe`, `doctor`, deterministic Chromium capture, FFmpeg output, provenance manifests, transcript validation, and the official agent skill.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 22.12 or newer
10
+ - FFmpeg and FFprobe on `PATH`, built with H.264 (`libx264`) and AAC support
11
+ - The Playwright-managed Chromium build
12
+ - Optional: `qwen-tts` from [`qwentts.cpp`](https://github.com/ServeurpersoCom/qwentts.cpp) for local speech synthesis
13
+ - Optional: `whisper-cli` from [`whisper.cpp`](https://github.com/ggml-org/whisper.cpp) for local transcription
14
+
15
+ Install the Playwright Chromium build with `npx playwright install chromium`. Run the CLI with `npx frameyard`; no global install is required.
16
+
17
+ The wrappers target the CLI surfaces of qwentts.cpp commit `5916bf7c79dbb4e66fc059cdc507a79a23a28bb4` and whisper.cpp commit `284029c07012d2000941a08d38a31ffb71f4734e`.
18
+
19
+ ## Quick start
20
+
21
+ Scaffold a project:
22
+
23
+ ```sh
24
+ npx frameyard init my-video
25
+ cd my-video
26
+ npx frameyard dev
27
+ ```
28
+
29
+ `dev` prints a local preview URL. Select a composition, edit its JSON props, scrub frames, and play the composition with its audio timeline.
30
+
31
+ The scaffold includes an `AGENTS.md` guide for coding agents working in the project, covering the determinism rules and the `check`, `still`, and `render` workflow. It also includes a `package.json` with `dev`, `check`, `render`, and `still` scripts pinned to the scaffolding CLI version, so `pnpm dev` or `npm run dev` works without a global install.
32
+
33
+ `npx frameyard` resolves the latest published version. Pin it with `npx frameyard@<version>`, or install the package locally, when a project needs a fixed CLI version.
34
+
35
+ Validate and render:
36
+
37
+ ```sh
38
+ npx frameyard doctor
39
+ npx frameyard check hello
40
+ npx frameyard render hello --props props/hello.json --out hello.mp4
41
+ npx frameyard still hello --frame 42 --out hello-frame.png
42
+ ```
43
+
44
+ The 30-second reference composition lives in [`examples/demo`](./examples/demo). Its README has the commands for running the demo from a repository checkout.
45
+
46
+ ## Project format
47
+
48
+ A project is ordinary HTML, CSS, assets, and native JavaScript ES modules. `video.config.js` explicitly lists composition entry points; there is no directory scanning:
49
+
50
+ ```js
51
+ export default {
52
+ compositions: {
53
+ hello: "./compositions/hello.js",
54
+ },
55
+ };
56
+ ```
57
+
58
+ A composition default-exports a definition. `defineComposition` is optional but gives early metadata validation and editor types:
59
+
60
+ ```js
61
+ import { defineComposition } from "frameyard";
62
+ import { random } from "frameyard/runtime";
63
+
64
+ let title;
65
+
66
+ export default defineComposition({
67
+ width: 1920,
68
+ height: 1080,
69
+ fps: 30,
70
+ durationInFrames: 900,
71
+ defaultProps: { title: "Hello" },
72
+ validateProps(props) {
73
+ return typeof props.title === "string" ? [] : ["title must be a string"];
74
+ },
75
+ audio: [{ src: "assets/hit.wav", startFrame: 15, volume: 0.8 }],
76
+
77
+ mount({ container }) {
78
+ container.innerHTML = "<h1 id='title'></h1>";
79
+ title = container.querySelector("#title");
80
+ },
81
+
82
+ update({ frame, props }) {
83
+ title.textContent = props.title;
84
+ title.style.transform = `translateX(${random("title:" + frame) * 40}px)`;
85
+ },
86
+
87
+ dispose() {
88
+ title = undefined;
89
+ },
90
+ });
91
+ ```
92
+
93
+ The runtime supplies an import map for `frameyard` and `frameyard/runtime`, so project code needs no bundling step and no `node_modules` import.
94
+
95
+ ### Composition lifecycle
96
+
97
+ - `mount({ container, props })` creates the visual structure once and may finish asynchronous work.
98
+ - `update({ frame, props })` makes that structure represent the frame. It must be synchronous and seek-safe.
99
+ - `dispose()` releases resources after the last frame.
100
+
101
+ The only animation inputs are the integer frame and immutable props. Metadata (`width`, `height`, `fps`, `durationInFrames`) is fixed and validated before mounting. Props are JSON-serializable, deep-frozen, and merged shallowly over `defaultProps`.
102
+
103
+ Use `random("stable:key")` instead of `Math.random()`. The runtime pauses CSS animations and the Web Animations API inside the composition container and seeks them to the current frame. Independent `requestAnimationFrame` loops, timers, transitions, and wall-clock reads are not deterministic animation drivers.
104
+
105
+ ## Audio timeline
106
+
107
+ Audio clips are declarative:
108
+
109
+ ```js
110
+ audio: [
111
+ {
112
+ src: "assets/voice.wav", // project-relative local file
113
+ startFrame: 30, // composition timeline position
114
+ trimStart: 0.25, // seconds trimmed from the source
115
+ trimDuration: 4.2, // optional seconds to use
116
+ volume: 0.8, // 0..1
117
+ },
118
+ ];
119
+ ```
120
+
121
+ Preview synchronizes the timeline in the browser. Production mixes the same clips offline with FFmpeg. `check` reports clip durations and warnings when a clip extends beyond the composition. Frameyard never time-stretches audio or changes composition duration to fit it.
122
+
123
+ ## Transcripts
124
+
125
+ A composition can declare canonical transcript assets that `check`, `still`, and `render` must validate:
126
+
127
+ ```js
128
+ transcripts: [
129
+ { src: "assets/voice.transcript.json", words: true },
130
+ ],
131
+ ```
132
+
133
+ Transcript paths are project-relative. `words: true` requires actual measured word timings for karaoke-style presentation; Frameyard fails with `TRANSCRIPT_WORDS_UNAVAILABLE` rather than interpolating them. See [subtitles and karaoke](./skills/frameyard/references/subtitles-karaoke.md) for the canonical JSON shape and the seek-safe frame conversion recipe.
134
+
135
+ ## Commands
136
+
137
+ | Command | Purpose |
138
+ | --- | --- |
139
+ | `frameyard init [directory]` | Scaffold a minimal project, including an `AGENTS.md` agent guide and pinned `package.json` scripts |
140
+ | `frameyard dev [--port <port>]` | Start the preview server |
141
+ | `frameyard check <id> [--props <file>]` | Validate props, assets, audio, transcripts, and every frame without capture |
142
+ | `frameyard still <id> --frame <n>` | Capture one PNG through the production path |
143
+ | `frameyard render <id> --out <video.mp4>` | Capture frames with Chromium and encode with FFmpeg |
144
+ | `frameyard tts ...` | Produce one frozen 24 kHz mono WAV with local qwentts.cpp |
145
+ | `frameyard transcribe ...` | Produce canonical transcript JSON with optional word timings and SRT/VTT exports |
146
+ | `frameyard doctor` | Inspect core requirements, optional integrations, and manifest integrity |
147
+
148
+ Common options: `--project <dir>`, `--json`, `--help`, and `--version`. `still` and `render` accept `--overwrite`; `tts` and `transcribe` accept `--force`. `init` takes the target directory as a positional argument. Output paths are relative to the current working directory.
149
+
150
+ A render fails immediately on an uncaught error, unhandled rejection, missing required asset, or invalid composition output. It writes to a temporary file and publishes only after FFmpeg succeeds. Existing outputs are not replaced unless `--overwrite` is supplied.
151
+
152
+ ### JSON results
153
+
154
+ Non-interactive commands support `--json`. Results go to stdout and human progress/diagnostics go to stderr:
155
+
156
+ ```json
157
+ {
158
+ "ok": true,
159
+ "command": "render",
160
+ "composition": "hello",
161
+ "path": "/absolute/path/hello.mp4",
162
+ "width": 1920,
163
+ "height": 1080,
164
+ "fps": 30,
165
+ "durationInFrames": 900,
166
+ "framesRendered": 900,
167
+ "audio": { "clips": 1, "warnings": [] },
168
+ "transcripts": { "clips": 1, "warnings": [] }
169
+ }
170
+ ```
171
+
172
+ Errors use the same envelope:
173
+
174
+ ```json
175
+ {
176
+ "ok": false,
177
+ "command": "render",
178
+ "error": { "code": "OUTPUT_EXISTS", "message": "Output already exists: ..." }
179
+ }
180
+ ```
181
+
182
+ `doctor --json` reports findings rather than throwing for environment problems: `ok: true` means inspection completed, while `healthy` reports whether required checks, manifest integrity, explicitly supplied models, and integration compatibility passed. See the [JSON result reference](./skills/frameyard/references/json-results.md) for all command shapes.
183
+
184
+ ## Local asset production
185
+
186
+ `tts` and `transcribe` wrap optional local executables. Frameyard never builds upstream tools, downloads models, or runs inference during composition loading or frame production.
187
+
188
+ ```sh
189
+ # One frozen 24 kHz mono WAV. Text comes from --text-file or stdin.
190
+ npx frameyard tts \
191
+ --speaker vivian \
192
+ --talker-model models/qwen-talker-1.7b-customvoice-Q8_0.gguf \
193
+ --codec-model models/qwen-tokenizer-12hz-Q8_0.gguf \
194
+ --language English \
195
+ --text-file script.txt \
196
+ --out assets/voice.wav
197
+
198
+ # Canonical transcript with measured word timings and derived SRT/VTT.
199
+ npx frameyard transcribe assets/voice.wav \
200
+ --model models/ggml-base.en.bin \
201
+ --out assets/voice.transcript.json \
202
+ --words --export srt,vtt
203
+ ```
204
+
205
+ - Executable resolution: `--executable`, then `FRAMEYARD_QWEN_TTS` or `FRAMEYARD_WHISPER_CLI`, then `PATH`.
206
+ - `tts` supports CustomVoice only: a named speaker is required. Reference-audio cloning and VoiceDesign are deferred.
207
+ - `--device CPU` forces qwentts.cpp onto the CPU when a GPU does not have enough VRAM.
208
+ - `transcribe` normalizes the source with FFmpeg to 16 kHz mono PCM WAV before `whisper-cli`.
209
+ - Outputs are atomic. Existing assets are replaced only with `--force`.
210
+ - A partial operation leaves no apparently valid artifact and no manifest entry.
211
+ - Transcription never fabricates word timings and never executes Whisper's generated karaoke script.
212
+
213
+ ### Provenance
214
+
215
+ Successful operations update `frameyard.assets.json` in the project root, or the file passed to `--manifest`. The versioned manifest records artifact SHA-256 hashes and sizes, generator command and executable, Frameyard version, integration revision, model identities, operation parameters, and (for transcription) source identity and derived exports. Commit this manifest with the project.
216
+
217
+ ## Doctor
218
+
219
+ ```sh
220
+ npx frameyard doctor
221
+ npx frameyard doctor --json
222
+ npx frameyard doctor --talker-model models/talker.gguf --codec-model models/codec.gguf
223
+ ```
224
+
225
+ `doctor` checks Node, FFmpeg and required encoders, FFprobe, Playwright Chromium, manifest integrity (artifact, source, model, and derived-export hashes), explicitly supplied model readability, and optional `qwen-tts`/`whisper-cli` capabilities. It does not repair, download, or modify anything. Missing optional integrations do not fail, but a found executable with incompatible capabilities does.
226
+
227
+ ## Agent skill
228
+
229
+ The optional host-neutral agent skill lives in [`skills/frameyard/`](./skills/frameyard). It uses the same project format and CLI as humans, documents deterministic authoring, audio assets, subtitle/karaoke recipes, JSON results, and failure recovery, and ships a thin CLI wrapper. The npm package includes the skill files matching its API version. Install it with `npx skills add vladiantio/frameyard`, or see [`skills/frameyard/README.md`](./skills/frameyard/README.md) for host-specific installation or referencing.
230
+
231
+ ## Rendering and reproducibility
232
+
233
+ Production rendering:
234
+
235
+ 1. Starts the same preview server and browser runtime used by `dev`.
236
+ 2. Launches pinned Chromium through Playwright.
237
+ 3. Seeks explicitly to each frame and waits for composition updates.
238
+ 4. Captures the viewport through the Chrome DevTools Protocol.
239
+ 5. Pipes PNG frames and the offline audio mix to FFmpeg.
240
+ 6. Encodes H.264 video with `yuv420p` and AAC audio into an MP4.
241
+
242
+ Rendering is sequential in v0.1. For the same composition revision, props, assets, frame, and pinned runtime/browser environment, the visual state is identical. Encoded bytes are not a reproducibility guarantee, even on the same machine; they depend on the FFmpeg build, encoder threading, and runtime environment.
243
+
244
+ For a repeatable local measurement of seek, capture, encode, and full-render time, run `pnpm bench`. The demo and the controlled fixtures under `bench/fixtures/` are the reference workloads.
245
+
246
+ ## Current limits
247
+
248
+ Deferred from this implementation slice:
249
+
250
+ - Built-in subtitle rendering, automatic caption burn-in, and karaoke rendering
251
+ - `qwentts.cpp` reference-audio voice cloning and VoiceDesign
252
+ - Automatic long-text chunking, streaming synthesis, and alternative generated-audio formats
253
+ - Automatic upstream builds, model downloads, environment repair, and hosted rendering
254
+ - Parallel rendering, frame caches, and general image-sequence output
255
+ - React or other UI-framework adapters, plugin APIs, and agent-only runtimes
256
+ - TypeScript transpilation for project compositions and npm dependency optimization
257
+
258
+ See the [v0.1 design baseline](./docs/v0.1-design.md) for the full product contract and the [domain glossary](./GLOSSARY.md) for terminology.
259
+
260
+ ## Repository layout
261
+
262
+ - `src/contracts.ts`: shared composition, props, audio, and transcript requirement contracts
263
+ - `src/transcript.ts`: canonical transcript validation and SRT/VTT formatting
264
+ - `src/runtime/`: browser lifecycle, frame clock, deterministic randomness
265
+ - `src/preview/`: interactive preview client
266
+ - `src/server/`: native ES-module server and import map
267
+ - `src/renderer/`: Chromium capture, audio mixing, transcript inspection, FFmpeg encoding
268
+ - `src/assets/`: local TTS/transcription wrappers, executable resolution, atomic publishing, provenance
269
+ - `src/doctor.ts`: environment, integration, and manifest inspection
270
+ - `src/scaffold.ts`: `init` scaffold templates, including the generated package manifest, project README, and agent guide
271
+ - `src/cli.ts`: command-line entry point
272
+ - `skills/`: official host-neutral agent skill
273
+ - `examples/demo/`: the 30-second reference composition
274
+ - `bench/fixtures/`: controlled compositions for the render benchmark
275
+ - `scripts/`: benchmark, versioning, and git-hook installation
276
+ - `docs/adr/`: architecture decision records
277
+
278
+ ## Development
279
+
280
+ From a repository checkout:
281
+
282
+ ```sh
283
+ pnpm install
284
+ pnpm exec playwright install chromium
285
+ pnpm build
286
+ pnpm lint
287
+ pnpm format:check
288
+ node dist/cli.js doctor
289
+ ```
290
+
291
+ These commands use the local build; `npx frameyard` runs the published package.
292
+
293
+ `pnpm install` installs the repository's git hooks. Pre-commit runs lint, formatting checks, typecheck, test typecheck, and unit tests; pre-push runs the full suite. CI runs the same checks on Linux with Node 22 and 24.
294
+
295
+ CI uses fake `qwen-tts` and `whisper-cli` executables. Real-model tests are opt-in:
296
+
297
+ ```sh
298
+ FRAMEYARD_REAL_INTEGRATIONS=1 \
299
+ FRAMEYARD_REAL_TALKER=/path/to/talker.gguf \
300
+ FRAMEYARD_REAL_CODEC=/path/to/codec.gguf \
301
+ FRAMEYARD_REAL_WHISPER_MODEL=/path/to/ggml-model.bin \
302
+ FRAMEYARD_REAL_WHISPER_SOURCE=/path/to/source.wav \
303
+ # optional, for GPUs with too little VRAM:
304
+ FRAMEYARD_REAL_TTS_DEVICE=CPU \
305
+ pnpm vitest run test/real-integrations.test.ts
306
+ ```
307
+
308
+ Releases are tag-driven. See [`RELEASING.md`](./RELEASING.md) for version policy, npm trusted publishing, and the release checklist, and [`CHANGELOG.md`](./CHANGELOG.md) for release notes.
309
+
310
+ ## License
311
+
312
+ MIT
@@ -0,0 +1,16 @@
1
+ export interface ResolvedExecutable {
2
+ /** The default command name, such as "qwen-tts". */
3
+ command: string;
4
+ /** Absolute path to the executable that will be used. */
5
+ path: string;
6
+ source: "option" | "environment" | "path";
7
+ }
8
+ /** Resolves an integration executable: explicit option, environment, then PATH. */
9
+ export declare function resolveExecutable(options: {
10
+ command: string;
11
+ explicit?: string;
12
+ envVar: string;
13
+ cwd?: string;
14
+ installHint: string;
15
+ }): Promise<ResolvedExecutable>;
16
+ //# sourceMappingURL=executable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executable.d.ts","sourceRoot":"","sources":["../../src/assets/executable.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,aAAa,GAAG,MAAM,CAAC;CAC3C;AAwCD,mFAAmF;AACnF,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA2B9B"}
@@ -0,0 +1,59 @@
1
+ import { constants } from "node:fs";
2
+ import { access, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { FrameyardError } from "../errors.js";
5
+ async function isExecutable(filePath) {
6
+ try {
7
+ const info = await stat(filePath);
8
+ if (!info.isFile()) {
9
+ return false;
10
+ }
11
+ await access(filePath, constants.X_OK);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ function hasPathSeparator(value) {
19
+ return value.includes("/") || value.includes("\\");
20
+ }
21
+ function pathCandidates(command) {
22
+ if (process.platform !== "win32") {
23
+ return [command];
24
+ }
25
+ const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
26
+ return path.extname(command) ? [command] : extensions.map((extension) => `${command}${extension}`);
27
+ }
28
+ async function findOnPath(command, cwd) {
29
+ const entries = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
30
+ for (const entry of entries) {
31
+ for (const candidate of pathCandidates(command)) {
32
+ const absolute = path.resolve(cwd, entry, candidate);
33
+ if (await isExecutable(absolute)) {
34
+ return absolute;
35
+ }
36
+ }
37
+ }
38
+ return undefined;
39
+ }
40
+ /** Resolves an integration executable: explicit option, environment, then PATH. */
41
+ export async function resolveExecutable(options) {
42
+ const cwd = options.cwd ?? process.cwd();
43
+ const configured = options.explicit ?? process.env[options.envVar];
44
+ const source = options.explicit !== undefined ? "option" : process.env[options.envVar] !== undefined ? "environment" : "path";
45
+ if (configured !== undefined) {
46
+ const absolute = hasPathSeparator(configured) ? path.resolve(cwd, configured) : await findOnPath(configured, cwd);
47
+ if (absolute && (await isExecutable(absolute))) {
48
+ return { command: options.command, path: absolute, source };
49
+ }
50
+ const sourceLabel = source === "environment" ? ` in ${options.envVar}` : "";
51
+ throw new FrameyardError("EXECUTABLE_NOT_FOUND", `Could not find an executable at "${configured}"${sourceLabel}. ${options.installHint}`);
52
+ }
53
+ const fromPath = await findOnPath(options.command, cwd);
54
+ if (fromPath) {
55
+ return { command: options.command, path: fromPath, source: "path" };
56
+ }
57
+ throw new FrameyardError("EXECUTABLE_NOT_FOUND", `${options.command} was not found on PATH. ${options.installHint} You can also pass --executable or set ${options.envVar}.`);
58
+ }
59
+ //# sourceMappingURL=executable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executable.js","sourceRoot":"","sources":["../../src/assets/executable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAU9C,KAAK,UAAU,YAAY,CAAC,QAAgB;IAC1C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACnB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,cAAc,CAAC,OAAe;IACrC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;IACD,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,qBAAqB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7F,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,OAAO,GAAG,SAAS,EAAE,CAAC,CAAC;AACrG,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,OAAe,EAAE,GAAW;IACpD,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/E,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,KAAK,MAAM,SAAS,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;YACrD,IAAI,MAAM,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,OAAO,QAAQ,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAMvC;IACC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,MAAM,MAAM,GACV,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;IAEjH,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAClH,IAAI,QAAQ,IAAI,CAAC,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC9D,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,cAAc,CACtB,sBAAsB,EACtB,oCAAoC,UAAU,IAAI,WAAW,KAAK,OAAO,CAAC,WAAW,EAAE,CACxF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACxD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACtE,CAAC;IAED,MAAM,IAAI,cAAc,CACtB,sBAAsB,EACtB,GAAG,OAAO,CAAC,OAAO,2BAA2B,OAAO,CAAC,WAAW,0CAA0C,OAAO,CAAC,MAAM,GAAG,CAC5H,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Fails early with an actionable error when an input file is missing or unreadable. */
2
+ export declare function assertReadableFile(filePath: string, label: string): Promise<number>;
3
+ export declare function requireExtension(filePath: string, extension: string, label: string): void;
4
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/assets/files.ts"],"names":[],"mappings":"AAKA,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBzF;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIzF"}