shellfie 1.0.0 → 1.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -2
  3. data/README.md +258 -97
  4. data/lib/shellfie/animation_frame_builder.rb +31 -7
  5. data/lib/shellfie/animation_timeline.rb +2 -1
  6. data/lib/shellfie/ansi_line_buffer.rb +20 -4
  7. data/lib/shellfie/ansi_normalizer.rb +18 -8
  8. data/lib/shellfie/ansi_parser.rb +120 -7
  9. data/lib/shellfie/cassette.rb +76 -0
  10. data/lib/shellfie/cli.rb +65 -2
  11. data/lib/shellfie/cli_authoring.rb +233 -0
  12. data/lib/shellfie/cli_generate.rb +244 -40
  13. data/lib/shellfie/cli_info.rb +106 -6
  14. data/lib/shellfie/cli_run.rb +167 -0
  15. data/lib/shellfie/config.rb +5 -1
  16. data/lib/shellfie/config_defaults.rb +21 -2
  17. data/lib/shellfie/config_validation.rb +93 -4
  18. data/lib/shellfie/dependency_checker.rb +74 -3
  19. data/lib/shellfie/errors.rb +1 -0
  20. data/lib/shellfie/ffmpeg_encoder.rb +46 -0
  21. data/lib/shellfie/font_resolver.rb +11 -1
  22. data/lib/shellfie/gif_generator.rb +157 -11
  23. data/lib/shellfie/gif_palette.rb +8 -4
  24. data/lib/shellfie/html_renderer.rb +54 -0
  25. data/lib/shellfie/line_layout.rb +19 -10
  26. data/lib/shellfie/output_writer.rb +7 -2
  27. data/lib/shellfie/parser.rb +82 -19
  28. data/lib/shellfie/parser_validation.rb +48 -15
  29. data/lib/shellfie/render_geometry.rb +2 -1
  30. data/lib/shellfie/render_segment.rb +17 -5
  31. data/lib/shellfie/renderer.rb +28 -11
  32. data/lib/shellfie/rendering/text_painter.rb +23 -15
  33. data/lib/shellfie/rendering/window_chrome.rb +2 -2
  34. data/lib/shellfie/reproducibility_manifest.rb +41 -0
  35. data/lib/shellfie/session.rb +111 -0
  36. data/lib/shellfie/session_config.rb +562 -0
  37. data/lib/shellfie/session_runner.rb +689 -0
  38. data/lib/shellfie/svg_renderer.rb +222 -0
  39. data/lib/shellfie/terminal_screen.rb +389 -0
  40. data/lib/shellfie/text_metrics.rb +74 -15
  41. data/lib/shellfie/transcript_renderer.rb +92 -0
  42. data/lib/shellfie/version.rb +1 -1
  43. data/lib/shellfie/yaml_safety.rb +147 -0
  44. data/lib/shellfie.rb +8 -1
  45. data/schema/shellfie-v1.schema.json +153 -0
  46. data/schema/shellfie-v2.schema.json +262 -0
  47. metadata +27 -24
  48. data/.rspec +0 -3
  49. data/Rakefile +0 -8
  50. data/docs/.nojekyll +0 -0
  51. data/docs/index.html +0 -205
  52. data/docs/scripts.js +0 -85
  53. data/docs/styles.css +0 -507
  54. data/examples/animation.yml +0 -33
  55. data/examples/colored.yml +0 -20
  56. data/examples/demo.gif +0 -0
  57. data/examples/demo.png +0 -0
  58. data/examples/demo_animation.yml +0 -31
  59. data/examples/headless.png +0 -0
  60. data/examples/headless.yml +0 -16
  61. data/examples/scrolling.yml +0 -48
  62. data/examples/simple.yml +0 -21
  63. data/examples/theme_macos.png +0 -0
  64. data/examples/theme_ubuntu.png +0 -0
  65. data/examples/theme_windows.png +0 -0
  66. data/shellfie.gemspec +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ab2ae504b1bc34a212d0354f51bc4d39cd9515d2d06703b342615a93dec0e3e
4
- data.tar.gz: b442f32768d3a903c955a9830e6d69038ded4d232df459ee9ce93bc3445c9f47
3
+ metadata.gz: 0dbccd8edad54ded2bda6b8da245ab3f4ab3c9527ffd4bc75ef04cf7b6fc8713
4
+ data.tar.gz: 5fa83b5f80fae2d6281adc4ef685271af2b8c8f303d4116cbe26904ecf12c2a4
5
5
  SHA512:
6
- metadata.gz: bf425a5d9791d0d4856d3a241fca4ce732d2a0730d77677a3e9de739e7df6b3a63eeae5dfa826f01361c2b763d69c9f674f86b926e470203d5d11ba9712d9cd6
7
- data.tar.gz: 47c58a2d9ddc8ec9ce700c4c5220513a7ded9271952aed2d518c5fb50c567de4227ff73fabede8effa54df70ae8d1ddad6908265a3616ec225126f90f973e17b
6
+ metadata.gz: 473c51c3384978e8bc493b0a27cc1b5e9949c99c9f14d2cd6516cae963648c07bd281650175713f4028c09be1ce5d0140c119a4ffd7336da92e8a80075c43f30
7
+ data.tar.gz: fc061b6f9bff4c8f5bf7f512891ed8c13b39bf3342a14a5b98b967b15ac68a7d0dafc582ac884ea402e9d4ba61a0e549ca19d1883575c3af69a4e7e556b1d063
data/CHANGELOG.md CHANGED
@@ -1,6 +1,60 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 1.1.0 - 2026-08-29
4
+
5
+ ### Added
6
+
7
+ - Add executable version 2 sessions with `run`, `record`, and `replay`; PTY sessions support command and key input, waits, assertions, hidden steps, redaction, named captures, editable recordings, and offline cassettes.
8
+ - Add native selectable-text SVG, accessible HTML, MP4, WebM, event-duration PNG sequences, plain and ANSI-preserving text, structured JSON, and asciinema v2 output. Legacy raster-backed SVG remains available as `svg-raster`.
9
+ - Add multiple configured outputs and capture-specific outputs from one session.
10
+ - Add JSON Schemas, file/line/column diagnostics, typo suggestions, and `new`, `format`, `compile`, `schema`, `completion`, and include-aware `watch` authoring commands.
11
+ - Add typed session variables, reusable and repeated step sets, and OS, shell, Ruby, and configured-environment step conditions.
12
+ - Add inferred output names, collision-checked output templates, common aspect presets, bounded parallel batch rendering with `--jobs`, and non-mutating output verification with `generate --check`.
13
+ - Add reproducibility manifests and JSON, SARIF, and JUnit validation reports for CI and editor integrations.
14
+ - Add a reusable GitHub Action for generating or checking outputs and Docker packaging for a consistent rendering environment.
15
+ - Add deterministic animation seeds, separate typing rate, frame timing, and playback speed controls, reverse and ping-pong playback, loop offsets, and format-specific GIF, WebP, and APNG controls.
16
+ - Add configurable East Asian Ambiguous width and report the active Unicode and width-table profile through inspection and manifests.
17
+ - Add broader terminal behavior for cursor movement, erase and insertion controls, alternate screens, scroll regions, colon-form colors, extended underline styles and colors, blink, conceal, and safe OSC 8 links.
18
+
19
+ ### Changed
20
+
21
+ - Treat every frame `delay` as a post-action delay; a `type` frame's delay now overrides the global command delay, and `lines` initialize the screen when `frames` are also present.
22
+ - Treat an explicit WebP format as static unless the input has frames or `--animate` is supplied.
23
+ - Deprecate `--fps` in favor of `--framerate`; use `--typing-rate` to control input speed independently.
24
+
25
+ ### Fixed
26
+
27
+ - Correct terminal tab stops, backspace behavior, wide-cell overwrites, grapheme-cluster typing, split Unicode input, line feeds, autowrap, and scroll-region editing.
28
+ - Correct animation duration rounding, duplicate-frame timing, APNG tail duration, playback-speed timing, and minimum video duration.
29
+ - Prevent output collisions before rendering, stream stdout without an output-sized memory copy, preserve existing outputs on failure, and remove partial animation files after errors or interruption.
30
+ - Improve live-session prompt and exit synchronization, preserve asynchronous output, honor PTY dimensions and working directories, and terminate leftover child processes.
31
+ - Improve `doctor` and dependency checks with real rendering, format delegates, fonts, security policy, and temporary-storage diagnostics.
32
+
33
+ ### Security and reliability
34
+
35
+ - Add include cycle and chain diagnostics, per-file and aggregate size limits, and an optional symlink-aware root policy for includes and session working directories.
36
+ - Add environment allowlists, total and per-step timeouts, bounded regular-expression evaluation, output and control-sequence limits, and rendering workload and temporary-disk budgets.
37
+ - Add opt-in rejection of unsupported terminal graphics and an explicit policy for preserving only bounded `http`, `https`, and `mailto` OSC 8 links.
38
+
39
+ ## 1.0.0 - 2026-05-20
40
+
41
+ ### Added
42
+
43
+ - Add static SVG and WebP output plus animated WebP and APNG output, with format selection by extension or `--format` and binary output through stdout.
44
+ - Add batch generation from multiple files and glob patterns, directory outputs, automatic output-directory creation, and `--quiet` and `--verbose` modes.
45
+ - Add `inspect` for resolved configuration and output dimensions, and `doctor` for Ruby, ImageMagick, output-directory, and encoding checks.
46
+ - Add custom themes, Dracula, One Dark, Solarized Dark, and Catppuccin Mocha color schemes, configurable colors and window decorations, background gradients, font fallbacks, and block, bar, or underline cursors.
47
+ - Add reusable YAML includes, line selection and semantic prompt, command, and output colors.
48
+ - Add clip, wrap, and scroll overflow modes, exact-size and trimmed canvases, maximum height and line controls, configurable tab width, and per-line or persistent ANSI state.
49
+ - Add animation typing jitter and chunking, output and final delays, frame limits, global, adaptive, or theme palettes, optional dithering, and configurable scroll easing.
50
+ - Add public Ruby APIs for parsing, validating, rendering, and inspecting configurations.
51
+
52
+ ### Changed
53
+
54
+ - Correct xterm 256-color values and malformed extended colors, and add dim, reverse, strikethrough, and overline styles plus carriage return, backspace, cursor movement, erase, and tab handling.
55
+ - Improve line measurement, clipping, wrapping, fixed-height scrolling, trailing-line preservation, stable animation dimensions, and Windows, Ubuntu, macOS, and headless window rendering.
56
+ - Refuse to replace an existing output unless `--force` is supplied, write outputs atomically, report errors on stderr, and distinguish filesystem failures with their own exit code.
57
+ - Reject unknown keys, invalid types, unsupported values, excessive lines, frames, characters, pixels, and rendered frames with clearer validation errors before rendering.
4
58
 
5
59
  ## 0.1.1 - 2026-01-12
6
60
 
@@ -8,4 +62,4 @@
8
62
 
9
63
  ## 0.1.0 - 2026-01-12
10
64
 
11
- - Initial release.
65
+ - Initial release.
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # shellfie
2
-
3
- Generate terminal screenshot-style images and animations from YAML.
1
+ <p align="center">
2
+ <img src="assets/logo-header.svg" alt="Shellfie header logo">
3
+ </p>
4
4
 
5
5
  <p align="center">
6
- <img src="assets/logo-header.svg" alt="shellfie">
6
+ <strong>Deterministic terminal visuals and executable sessions from YAML</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -14,38 +14,84 @@ Generate terminal screenshot-style images and animations from YAML.
14
14
  </p>
15
15
 
16
16
  <p align="center">
17
- <img src="examples/demo.gif" alt="shellfie demo" width="560">
17
+ <a href="#features">Features</a> ·
18
+ <a href="#installation">Installation</a> ·
19
+ <a href="#quick-start">Quick Start</a> ·
20
+ <a href="#terminal-sessions">Terminal Sessions</a> ·
21
+ <a href="#configuration">Configuration</a> ·
22
+ <a href="#automation">Automation</a>
18
23
  </p>
19
24
 
20
- ## Install
25
+ ---
21
26
 
22
- Requirements:
27
+ Shellfie compiles YAML into deterministic terminal images, animations, and semantic transcripts. When explicitly requested, it can also run real PTY sessions, assert their output, record cassettes, and replay them offline.
23
28
 
24
- - Ruby 3.0+
25
- - ImageMagick 7+
29
+ <p align="center">
30
+ <img src="examples/demo.gif" alt="Shellfie generating and validating an animated terminal demo" width="720">
31
+ <br>
32
+ <sub>Generated by Shellfie from <a href="examples/demo_animation.yml">examples/demo_animation.yml</a>.</sub>
33
+ </p>
34
+
35
+ ## Features
36
+
37
+ - Deterministic compose mode that never executes configuration content
38
+ - Optional PTY execution with waits, assertions, captures, and redaction
39
+ - Editable recordings and offline cassette replay
40
+ - PNG, WebP, native SVG, HTML, GIF, APNG, MP4, WebM, and PNG sequence output
41
+ - Plain text, ANSI, JSON, and asciinema v2 semantic transcripts
42
+ - macOS, Ubuntu, Windows, and custom window styles, plus a headless mode
43
+ - ANSI colors and controls, Unicode grapheme handling, and configurable ambiguous width
44
+ - JSON Schemas, source-aware diagnostics, formatting, compilation, completion, and watch commands
45
+ - Resource limits, path policies, dependency preflight, and atomic output replacement
46
+
47
+ ## Installation
48
+
49
+ Add Shellfie to your Gemfile:
50
+
51
+ ```ruby
52
+ gem "shellfie"
53
+ ```
54
+
55
+ Then install:
56
+
57
+ ```bash
58
+ bundle install
59
+ ```
60
+
61
+ Or install it directly:
26
62
 
27
63
  ```bash
28
- brew install imagemagick
29
64
  gem install shellfie
30
65
  ```
31
66
 
32
- For Bundler:
67
+ ### Requirements
33
68
 
34
- ```ruby
35
- gem "shellfie"
69
+ - Ruby 3.0+
70
+ - ImageMagick
71
+ - ffmpeg for MP4, WebM, and APNG output
72
+
73
+ Install media dependencies on macOS:
74
+
75
+ ```bash
76
+ brew install imagemagick ffmpeg
36
77
  ```
37
78
 
79
+ On Ubuntu or Debian:
80
+
81
+ ```bash
82
+ sudo apt install imagemagick ffmpeg
83
+ ```
84
+
85
+ Static and animated composition works on macOS, Linux, and Windows. Live PTY sessions are not supported on native Windows.
86
+
38
87
  ## Quick Start
39
88
 
40
89
  Create `terminal.yml`:
41
90
 
42
91
  ```yaml
92
+ version: 1
43
93
  theme: macos
44
- title: "Terminal - zsh"
45
-
46
- window:
47
- width: 600
48
- padding: 20
94
+ title: "Terminal"
49
95
 
50
96
  lines:
51
97
  - prompt: "$ "
@@ -59,82 +105,184 @@ Generate an image:
59
105
  shellfie generate terminal.yml -o terminal.png
60
106
  ```
61
107
 
62
- Use `shf` as a short alias for `shellfie`.
108
+ Without `-o`, Shellfie writes `terminal.png` beside the input. `shf` is available as a short alias for `shellfie`.
109
+
110
+ ### Output Formats
111
+
112
+ | Kind | Formats |
113
+ | --- | --- |
114
+ | Static | PNG, WebP, native selectable-text SVG, accessible HTML |
115
+ | Animated | GIF, WebP, APNG, MP4, WebM, event-duration PNG sequence with `timeline.json` |
116
+ | Semantic | TXT, ANSI, JSON, asciinema v2 (`asciicast` or `cast`) |
117
+
118
+ Use `svg-raster` only when the legacy PNG-backed SVG behavior is required.
63
119
 
64
120
  ## CLI
65
121
 
122
+ | Command | Purpose |
123
+ | --- | --- |
124
+ | `generate` | Render a version 1 compose configuration |
125
+ | `run` | Execute and render a version 2 terminal session |
126
+ | `record` | Execute a session and save a cassette or editable YAML |
127
+ | `replay` | Render a cassette without executing its commands |
128
+ | `new` / `init` | Create or print starter configuration |
129
+ | `format` | Normalize YAML formatting |
130
+ | `compile` | Print the resolved configuration or session IR |
131
+ | `validate` | Validate input as text, JSON, SARIF, or JUnit |
132
+ | `inspect` | Show resolved settings, dimensions, fonts, and resource estimates |
133
+ | `schema` | Print the version 1 or version 2 JSON Schema |
134
+ | `completion` | Print bash, zsh, fish, or PowerShell completion |
135
+ | `watch` | Regenerate when a configuration or include changes |
136
+ | `themes` | List available themes and color schemes |
137
+ | `doctor` | Check media tools, formats, fonts, policies, and storage |
138
+ | `version` | Show the installed Shellfie version |
139
+
140
+ ## Terminal Sessions
141
+
142
+ Version 2 sessions execute commands only through `run` or `record`; review a session before running it. A minimal session looks like this:
143
+
144
+ ```yaml
145
+ version: 2
146
+ mode: run
147
+ title: "Recorded shell"
148
+ theme: macos
149
+
150
+ terminal:
151
+ shell: /bin/sh
152
+ columns: 80
153
+ rows: 16
154
+ timeout: 10s
155
+
156
+ steps:
157
+ - type: printf 'hello from shellfie\n'
158
+ speed: 30cps
159
+ - key: enter
160
+ - expect:
161
+ screen_contains: hello from shellfie
162
+ exit_status: 0
163
+ - capture: complete
164
+
165
+ outputs:
166
+ - path: session.svg
167
+ format: svg
168
+ capture: complete
169
+ - path: session.txt
170
+ format: txt
171
+ ```
172
+
173
+ Run, record, or replay it:
174
+
66
175
  ```bash
67
- shellfie generate config.yml -o output.png
68
- shellfie generate config.yml -o demo.gif --animate
69
- shellfie generate config.yml -o output.svg --format svg
70
- shellfie generate config.yml -o output.png --scale 2 --no-shadow
71
- shellfie generate config.yml -o output.png --no-header
72
- shellfie validate config.yml
73
- shellfie themes
74
- shellfie init
176
+ shellfie run session.yml
177
+ shellfie record session.yml --cassette session.json
178
+ shellfie record session.yml --yaml editable-recording.yml
179
+ shellfie replay session.json -o session.gif --animate
75
180
  ```
76
181
 
77
- Common `generate` options:
182
+ ### Session Actions
78
183
 
79
- | Option | Description |
184
+ | Action | Purpose |
80
185
  | --- | --- |
81
- | `-o, --output PATH` | Output path |
82
- | `-t, --theme NAME` | Override theme |
83
- | `-a, --animate` | Render animation |
84
- | `-s, --scale FACTOR` | Output scale: `1`, `2`, or `3` |
85
- | `-w, --width PIXELS` | Override window width |
86
- | `--format FORMAT` | `png`, `gif`, `svg`, `webp`, or `apng` |
87
- | `--fps FPS` | Override animation typing FPS |
88
- | `--overflow MODE` | `clip`, `wrap`, or `scroll` |
89
- | `--no-shadow` | Disable shadow |
90
- | `--transparent` | Transparent background |
91
- | `--no-header` | Headless output |
92
- | `--force` | Overwrite existing files |
186
+ | `run` | Execute a command directly, hidden by default |
187
+ | `type` | Type visible text at a configurable rate |
188
+ | `key` | Send Enter, arrows, modifiers, and other terminal keys |
189
+ | `sleep` | Add a deterministic presentation pause |
190
+ | `wait` | Wait for screen, line, prompt, exit, or stable-screen conditions |
191
+ | `expect` | Assert screen text, lines, exit status, cursor, elapsed time, or a text golden |
192
+ | `hide` / `show` | Control whether preparation and cleanup are recorded |
193
+ | `capture` | Name an intermediate screen for a specific output |
93
194
 
94
- Static output supports `png`, `svg`, and `webp`.
95
- Animated output supports `gif`, `webp`, and `apng`.
195
+ Sessions also support `requires`, step-level working directories, multiple outputs, reusable includes, environment allowlists, total and per-step timeouts, and redaction patterns. Set `terminal.cwd_policy: root` to confine working directories to the session root. Use `async: true` before interacting with a long-running or full-screen process, then synchronize with `wait`.
96
196
 
97
- ## Configuration
197
+ Variables use `{{name}}` placeholders. Reusable `step_sets` expand through `use`, `repeat` repeats a step or set, and `if` can select steps by OS, shell, Ruby requirement, or explicitly configured environment values. Expansion is bounded and cycles are rejected.
198
+
199
+ ## Authoring
200
+
201
+ Create and inspect configurations without rendering them:
98
202
 
99
- Static content uses `lines`:
203
+ ```bash
204
+ shellfie new terminal.yml --template static
205
+ shellfie new demo.yml --template animation
206
+ shellfie new session.yml --template run
207
+ shellfie format terminal.yml
208
+ shellfie compile terminal.yml --format json
209
+ shellfie validate terminal.yml --format sarif
210
+ shellfie inspect terminal.yml --json
211
+ shellfie watch terminal.yml -o terminal.png
212
+ ```
213
+
214
+ Templates include `static`, `animation`, `run`, `tui`, `ci`, and `theme-gallery`. JSON Schemas live in [`schema/`](schema/); add the following directive for editor validation:
100
215
 
101
216
  ```yaml
102
- theme: macos
103
- title: "Terminal"
217
+ # yaml-language-server: $schema=../schema/shellfie-v1.schema.json
218
+ ```
104
219
 
105
- window:
106
- width: 600
107
- padding: 20
108
- visible_lines: 8
109
- overflow: clip
220
+ Use schema version 2 for executable sessions. Run `shellfie schema 1` or `shellfie schema 2` to print either schema, and `shellfie completion zsh` to generate shell completion.
110
221
 
111
- font:
112
- family: Monaco
113
- size: 14
114
- line_height: 1.4
222
+ ## Automation
115
223
 
116
- lines:
117
- - prompt: "$ "
118
- command: "gem install shellfie"
119
- - output: |
120
- Successfully installed shellfie
121
- 1 gem installed
224
+ Use the Docker-based GitHub Action to generate an output or fail when a committed output is stale:
225
+
226
+ ```yaml
227
+ - uses: ydah/shellfie@main
228
+ with:
229
+ input: examples/simple.yml
230
+ output: docs/terminal.png
231
+ check: "true"
122
232
  ```
123
233
 
124
- Animations use `frames`:
234
+ Or build and run the included container locally:
235
+
236
+ ```bash
237
+ docker build -t shellfie .
238
+ docker run --rm -v "$PWD:/work" shellfie \
239
+ generate /work/examples/simple.yml -o /work/terminal.png --force
240
+ ```
241
+
242
+ Use `--manifest manifest.json` during generation to record configuration and output hashes, Ruby, OS, ImageMagick, ffmpeg, and resolved font fingerprints.
243
+
244
+ ## Configuration
245
+
246
+ ### Compose Configuration
247
+
248
+ | Key | Purpose |
249
+ | --- | --- |
250
+ | `theme` | `macos`, `ubuntu`, `windows`, or `custom` |
251
+ | `window_theme` | Window chrome to use with a custom theme |
252
+ | `color_scheme` | Built-in palette: `dracula`, `one_dark`, `solarized_dark`, or `catppuccin_mocha` |
253
+ | `colors` | Individual color overrides |
254
+ | `window` | Width, height, padding, wrapping, clipping, scrolling, and terminal policies |
255
+ | `font` | Family, fallback families, size, and line height |
256
+ | `animation` | Typing, timing, playback, loop, palette, and encoder controls |
257
+ | `cursor` | Cursor style and color |
258
+ | `headless` | Hide window chrome |
259
+ | `lines` | Initial or static terminal content |
260
+ | `frames` | Animated terminal events |
261
+ | `limits` | Source, output-frame, pixel, and temporary-storage ceilings |
262
+
263
+ Static content uses `lines`; animations add `frames`:
125
264
 
126
265
  ```yaml
266
+ version: 1
127
267
  theme: macos
128
268
  title: "Demo"
129
269
 
270
+ window:
271
+ width: 600
272
+ padding: 20
273
+ visible_lines: 8
274
+ overflow: clip
275
+
130
276
  animation:
131
277
  typing_speed: 50
132
- command_delay: 500
278
+ framerate: 30
279
+ playback_speed: 1.0
133
280
  cursor_blink: true
134
281
  loop: true
135
- palette: global
136
- dither: true
137
- scroll_easing: ease_out
282
+ seed: 1
283
+
284
+ lines:
285
+ - output: "Ready"
138
286
 
139
287
  frames:
140
288
  - prompt: "$ "
@@ -144,50 +292,63 @@ frames:
144
292
  delay: 1000
145
293
  ```
146
294
 
147
- Useful top-level keys:
295
+ A frame `delay` is applied after its action; on a `type` frame it overrides `animation.command_delay`. When both `lines` and `frames` are present, `lines` initialize the screen before animated events run.
148
296
 
149
- | Key | Purpose |
150
- | --- | --- |
151
- | `theme` | `macos`, `ubuntu`, `windows`, or `custom` |
152
- | `color_scheme` | Built-in color scheme such as `dracula` |
153
- | `colors` | Theme color overrides |
154
- | `window` | Size, padding, wrapping, clipping, scrolling |
155
- | `font` | Font family, size, line height |
156
- | `animation` | Typing speed, delays, loop, palette, easing |
157
- | `cursor` | Cursor style and color |
158
- | `headless` | Hide window chrome |
159
- | `lines` | Static terminal content |
160
- | `frames` | Animated terminal content |
297
+ ### Generate Options
161
298
 
162
- ANSI colors and styles are supported in `prompt`, `command`, and `output`, including 8-color, bright, 256-color, and RGB escape sequences.
299
+ | Option | Description |
300
+ | --- | --- |
301
+ | `-o, --output PATH` | Output path or `{name}-{theme}-{scale}.{format}` template |
302
+ | `--preset NAME` | Exact `readme`, `ogp`, `widescreen`, `standard`, or `vertical` canvas |
303
+ | `-t, --theme NAME` | Override the theme |
304
+ | `-a, --animate` | Render animation |
305
+ | `-s, --scale FACTOR` | Output scale: `1`, `2`, or `3` |
306
+ | `-w, --width PIXELS` | Override width |
307
+ | `--format FORMAT` | Select an output format |
308
+ | `--check` | Fail if an existing output is stale without replacing it |
309
+ | `--jobs N` | Render up to 32 inputs in parallel after preflight |
310
+ | `--typing-rate CPS` | Override typing rate in characters per second |
311
+ | `--framerate FPS` | Set output timing precision |
312
+ | `--seed N` | Set the deterministic animation seed |
313
+ | `--playback-speed FACTOR` | Scale the final timeline |
314
+ | `--overflow MODE` | `clip`, `wrap`, or `scroll` |
315
+ | `--no-shadow` | Disable the window shadow |
316
+ | `--transparent` | Use a transparent background |
317
+ | `--no-header` | Hide window chrome |
318
+ | `--force` | Replace an existing output |
163
319
 
164
- ## Themes
320
+ ### Advanced Controls
165
321
 
166
- Built-in window themes:
322
+ - Set `animation.direction` to `forward`, `reverse`, or `ping_pong`; `animation.loop_offset` selects a different initial frame.
323
+ - Tune GIF colors and optimization, WebP quality and lossless mode, APNG prediction and loop count, and scroll easing under `animation`.
324
+ - Set `window.ambiguous_width` to `1` or `2`; inspection and manifests report the active Unicode and width-table profile.
325
+ - Set `window.osc_policy: preserve` to keep bounded `http`, `https`, and `mailto` OSC 8 links in SVG and HTML.
326
+ - Set `window.graphics_policy: error` to reject SIXEL, Kitty, or iTerm2 graphics instead of discarding them.
327
+ - Use `include` for shared YAML. Set `include_policy: root` to reject paths outside the root configuration directory, including symlink escapes.
328
+ - Set resource ceilings under `limits`; Shellfie checks source size, pixels, frame work, and temporary storage before expensive rendering.
167
329
 
168
- - `macos`
169
- - `ubuntu`
170
- - `windows`
330
+ ## How It Works
171
331
 
172
- Headless output removes window chrome:
332
+ 1. Configuration parsing resolves includes, validates input, and reports source locations.
333
+ 2. Compose, live PTY, or cassette input is normalized into terminal events.
334
+ 3. The terminal screen applies Unicode, ANSI, cursor, erase, alternate-screen, and scroll behavior.
335
+ 4. Raster, SVG, HTML, video, or semantic renderers produce the requested outputs.
336
+ 5. Output files are written atomically after dependency, collision, and resource preflight.
173
337
 
174
- ```yaml
175
- headless: true
176
- ```
177
-
178
- or:
179
-
180
- ```bash
181
- shellfie generate config.yml -o output.png --no-header
182
- ```
338
+ `generate` is deterministic and never runs configuration content. `run` and `record` opt into PTY execution. `replay` uses a cassette and does not execute the recorded commands.
183
339
 
184
340
  ## Development
185
341
 
186
342
  ```bash
187
343
  bundle install
188
344
  bundle exec rspec
345
+ gem build shellfie.gemspec
189
346
  ```
190
347
 
348
+ ## Contributing
349
+
350
+ Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md) before submitting changes or security reports.
351
+
191
352
  ## License
192
353
 
193
- [MIT](LICENSE)
354
+ Released under the [MIT License](LICENSE).
@@ -2,12 +2,13 @@
2
2
 
3
3
  require_relative "animation_scroll_easing"
4
4
  require_relative "animation_timeline"
5
+ require_relative "text_metrics"
5
6
 
6
7
  module Shellfie
7
8
  class AnimationFrameBuilder
8
9
  def initialize(config)
9
10
  @config = config
10
- @random = Random.new(0)
11
+ @random = Random.new(config.animation[:seed])
11
12
  @scroll_easing = AnimationScrollEasing.new(config)
12
13
  end
13
14
 
@@ -15,9 +16,12 @@ module Shellfie
15
16
  return [{ lines: @config.lines, delay: @config.animation[:final_delay] }] if @config.frames.empty?
16
17
 
17
18
  frames = []
18
- current_lines = []
19
+ current_lines = @config.lines.flat_map { |line| line_data(line) }
19
20
  AnimationTimeline.new(@config).each do |event|
20
21
  case event.kind
22
+ when :screen
23
+ current_lines.replace(event.frame.screen.map { |line| { output: line } })
24
+ frames << { lines: build_display_lines(current_lines), delay: [event.frame.delay, 1].max }
21
25
  when :command
22
26
  frames.concat(command_frames(current_lines, event.frame))
23
27
  when :output
@@ -62,7 +66,7 @@ module Shellfie
62
66
  frames = []
63
67
  prompt = frame.prompt || ""
64
68
  command = frame.type
65
- chars = command.chars
69
+ chars = TextMetrics.graphemes(command)
66
70
  chunk_size = @config.animation[:typing_chunk_size]
67
71
 
68
72
  (chunk_size..chars.length).step(chunk_size).each do |index|
@@ -93,7 +97,11 @@ module Shellfie
93
97
  end
94
98
 
95
99
  def command_pause_frames(current_lines, frame)
96
- delay = @config.animation[:command_delay]
100
+ delay = if frame.output
101
+ @config.animation[:command_delay]
102
+ else
103
+ frame.delay.positive? ? frame.delay : @config.animation[:command_delay]
104
+ end
97
105
  return [] unless delay.positive?
98
106
  return [{ lines: build_display_lines(current_lines), delay: delay }] unless @config.animation[:cursor_blink]
99
107
 
@@ -109,11 +117,11 @@ module Shellfie
109
117
  output_delay = @config.animation[:output_delay]
110
118
 
111
119
  if output_delay.positive?
112
- output_lines.each_with_index.each_with_object([]) do |(line, index), frames|
120
+ frames = output_lines.each_with_index.each_with_object([]) do |(line, index), result|
113
121
  previous_count = current_lines.size
114
122
  current_lines << { output: line, output_color: frame.output_color }
115
123
  delay = @scroll_easing.output_delay(output_delay, index, output_lines.size)
116
- frames.concat(
124
+ result.concat(
117
125
  @scroll_easing.transition_frames(
118
126
  build_display_lines(current_lines),
119
127
  delay: delay,
@@ -121,9 +129,11 @@ module Shellfie
121
129
  )
122
130
  )
123
131
  end
132
+ frames << { lines: build_display_lines(current_lines), delay: frame.delay } if frame.delay.positive?
133
+ frames
124
134
  else
125
135
  output_lines.each { |line| current_lines << { output: line, output_color: frame.output_color } }
126
- [{ lines: build_display_lines(current_lines), delay: frame.delay || 100 }]
136
+ [{ lines: build_display_lines(current_lines), delay: [frame.delay, 1].max }]
127
137
  end
128
138
  end
129
139
 
@@ -131,6 +141,20 @@ module Shellfie
131
141
  { prompt: prompt, command: command, cursor: cursor, prompt_color: prompt_color, command_color: command_color }
132
142
  end
133
143
 
144
+ def line_data(line)
145
+ data = []
146
+ if line.prompt || line.command
147
+ data << command_line(
148
+ line.prompt,
149
+ line.command,
150
+ prompt_color: line.prompt_color,
151
+ command_color: line.command_color
152
+ )
153
+ end
154
+ data.concat(line.output.to_s.split("\n", -1).map { |output| { output: output, output_color: line.output_color } }) if line.output
155
+ data
156
+ end
157
+
134
158
  def cursor_command_line(frame)
135
159
  command_line(
136
160
  frame.prompt || "",
@@ -12,6 +12,7 @@ module Shellfie
12
12
  return enum_for(:each) unless block_given?
13
13
 
14
14
  @config.frames.each do |frame|
15
+ yield Event.new(kind: :screen, frame: frame) if frame.screen
15
16
  yield Event.new(kind: :command, frame: frame) if frame.type
16
17
  yield Event.new(kind: :output, frame: frame) if frame.output
17
18
  yield Event.new(kind: :pause, frame: frame) if pause_frame?(frame)
@@ -21,7 +22,7 @@ module Shellfie
21
22
  private
22
23
 
23
24
  def pause_frame?(frame)
24
- frame.delay&.positive? && frame.output.nil? && frame.type.nil?
25
+ frame.delay&.positive? && frame.output.nil? && frame.type.nil? && frame.screen.nil?
25
26
  end
26
27
  end
27
28
  end