svg-terminal 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Zujkowski
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,384 @@
1
+ # svg-terminal
2
+
3
+ [![CI](https://github.com/williamzujkowski/svg-terminal/actions/workflows/ci.yml/badge.svg)](https://github.com/williamzujkowski/svg-terminal/actions/workflows/ci.yml)
4
+ [![CodeQL](https://github.com/williamzujkowski/svg-terminal/actions/workflows/codeql.yml/badge.svg)](https://github.com/williamzujkowski/svg-terminal/actions/workflows/codeql.yml)
5
+ [![npm version](https://img.shields.io/npm/v/svg-terminal)](https://www.npmjs.com/package/svg-terminal)
6
+ [![npm downloads](https://img.shields.io/npm/dm/svg-terminal)](https://www.npmjs.com/package/svg-terminal)
7
+ [![Node 22+](https://img.shields.io/badge/node-%E2%89%A522-brightgreen)](https://nodejs.org/)
8
+ [![MIT license](https://img.shields.io/github/license/williamzujkowski/svg-terminal)](./LICENSE)
9
+
10
+ Generate animated SVG terminals from a declarative YAML config. The output is a single self-contained SVG that works inside GitHub's sandbox — no script, no external assets.
11
+
12
+ ![svg-terminal demo](./examples/demo.svg)
13
+
14
+ <sub>Demo above is the actual SVG this library produces. Source: [`examples/demo.yml`](./examples/demo.yml). Regenerate with `npm run demo`.</sub>
15
+
16
+ ### Try in 60 seconds
17
+
18
+ ```bash
19
+ npx svg-terminal init # writes terminal.yml
20
+ npx svg-terminal generate # writes terminal.svg
21
+ npx svg-terminal blocks # lists all 47 blocks
22
+ ```
23
+
24
+ Or as a GitHub Action — refresh your profile README on a schedule:
25
+
26
+ ```yaml
27
+ - uses: williamzujkowski/svg-terminal@v1
28
+ with:
29
+ config: terminal.yml
30
+ output: terminal.svg
31
+ commit: true
32
+ ```
33
+
34
+ See the full [GitHub Action](#github-action) section below, the [block catalog](./examples/blocks/) (47 blocks, one preview each), and the [12-theme gallery](#themes).
35
+
36
+ ### What's in the box
37
+
38
+ - **Declarative YAML config** — write blocks, pick a theme, run the CLI
39
+ - **47 built-in blocks** — across identity, retro / fake-system, status, ASCII art, single-line animation, and humor categories. Browse the [block catalog](./examples/blocks/) for previews of each
40
+ - **12 built-in themes** — dracula, nord, monokai, amber, green-phosphor, cyberpunk, solarized-dark, win95, catppuccin, tokyo-night, gruvbox, high-contrast (with chrome to match)
41
+ - **Single-line frame animation** — `BlockResult.animation = { frames, fps, loop }` powers the 9 animated blocks (spinners, clock, dice, progress bar, etc.). Multi-line is a known restriction
42
+ - **Dynamic-block cache** — the 5 cacheable blocks (weather, github-stats, github-languages, quote, fun-fact) write to `.svg-terminal-cache.json`. Pair with `--frozen-cache` for offline CI builds
43
+ - **Reduced-motion respected** — `@media (prefers-reduced-motion)` clamps the CSS fade-ins AND (since v0.17) the frame cycle. SMIL-driven typing reveal, cursor walk, and scroll-on-overflow remain animated; pair with `--static` for full stillness
44
+ - **Schema-validated, XSS-safe** — strict zod schema on every config field; user-controllable values are escaped at SVG emit sites. See [SECURITY.md](./SECURITY.md)
45
+ - **No runtime deps in the output** — SMIL + CSS animation, inline, GitHub-sandbox-safe
46
+ - **CLI + library** — `npx svg-terminal generate`, or `import { generate } from 'svg-terminal'`. Requires Node 22+
47
+
48
+ ## Quick Start
49
+
50
+ ```bash
51
+ npx svg-terminal init # Creates terminal.yml
52
+ npx svg-terminal generate # Generates terminal.svg
53
+ npx svg-terminal generate --watch # Rebuild on every save
54
+ npx svg-terminal blocks <name> # Inspect a block's config schema
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ Edit `terminal.yml`:
60
+
61
+ ```yaml
62
+ theme: dracula
63
+
64
+ window:
65
+ title: "dev@my-machine:~"
66
+
67
+ terminal:
68
+ prompt: "dev@box:~$ "
69
+
70
+ blocks:
71
+ - block: neofetch
72
+ config:
73
+ username: dev
74
+ hostname: my-machine
75
+ role: Full-Stack Developer
76
+ languages: TypeScript, Rust, Go
77
+
78
+ - block: fortune
79
+ config:
80
+ fortunes:
81
+ - "The best code is no code at all."
82
+ - "Talk is cheap. Show me the code."
83
+
84
+ - block: custom
85
+ config:
86
+ command: echo "Hello!"
87
+ lines:
88
+ - "[[fg:green]]Welcome to my terminal![[/fg]]"
89
+ ```
90
+
91
+ ## Themes
92
+
93
+ | Theme | Description |
94
+ |-------|-------------|
95
+ | `dracula` | Dark purple/green theme (default) |
96
+ | `nord` | Arctic blue/frost palette |
97
+ | `monokai` | Classic warm dark theme |
98
+ | `amber` | Vintage amber CRT (pairs well with `effects.textGlow: true`) |
99
+ | `green-phosphor` | Classic green-on-black phosphor (pair with glow) |
100
+ | `cyberpunk` | Neon magenta/cyan on near-black |
101
+ | `solarized-dark` | Ethan Schoonover's solarized dark palette (lifted prompt/comment for WCAG AA) |
102
+ | `win95` | Authentic Windows 95 chrome — auto-switches `window.style: win95` |
103
+ | `catppuccin` | Catppuccin Mocha — soothing pastel dark theme |
104
+ | `tokyo-night` | Tokyo Night (storm variant) — popular for Vim/Neovim |
105
+ | `gruvbox` | Gruvbox Dark medium — retro warm contrast |
106
+ | `high-contrast` | WCAG AAA pure-black-on-white palette — accessibility / slides / projector |
107
+
108
+ Special value: `theme: random` rotates through all themes deterministically by day of year — gives you a different look every day without committing to one.
109
+
110
+ <details>
111
+ <summary>Theme gallery — click to expand all 12</summary>
112
+
113
+ Each is the same 2-block config (motd + neofetch) rendered against the named theme. Source in [`examples/gallery/_template.yml`](./examples/gallery/_template.yml).
114
+
115
+ | | |
116
+ |---|---|
117
+ | **dracula**<br><img src="./examples/gallery/dracula.svg" width="380" alt="dracula theme preview"/> | **nord**<br><img src="./examples/gallery/nord.svg" width="380" alt="nord theme preview"/> |
118
+ | **monokai**<br><img src="./examples/gallery/monokai.svg" width="380" alt="monokai theme preview"/> | **amber**<br><img src="./examples/gallery/amber.svg" width="380" alt="amber theme preview"/> |
119
+ | **green-phosphor**<br><img src="./examples/gallery/green-phosphor.svg" width="380" alt="green-phosphor theme preview"/> | **cyberpunk**<br><img src="./examples/gallery/cyberpunk.svg" width="380" alt="cyberpunk theme preview"/> |
120
+ | **solarized-dark**<br><img src="./examples/gallery/solarized-dark.svg" width="380" alt="solarized-dark theme preview"/> | **win95**<br><img src="./examples/gallery/win95.svg" width="380" alt="win95 theme preview"/> |
121
+ | **catppuccin**<br><img src="./examples/gallery/catppuccin.svg" width="380" alt="catppuccin theme preview"/> | **tokyo-night**<br><img src="./examples/gallery/tokyo-night.svg" width="380" alt="tokyo-night theme preview"/> |
122
+ | **gruvbox**<br><img src="./examples/gallery/gruvbox.svg" width="380" alt="gruvbox theme preview"/> | **high-contrast**<br><img src="./examples/gallery/high-contrast.svg" width="380" alt="high-contrast theme preview"/> |
123
+
124
+ </details>
125
+
126
+ ## Blocks
127
+
128
+ Run `svg-terminal blocks` to list all 47 (cacheable ones marked `*`), or `svg-terminal blocks <name>` to print one block's config schema directly without grepping the source.
129
+
130
+ | Block | Description |
131
+ |-------|-------------|
132
+ | `neofetch` | System-info display with configurable fields |
133
+ | `fortune` | Random quote/fortune in ASCII box |
134
+ | `custom` | Arbitrary text with `[[fg:color]]` markup |
135
+ | `motd` | Welcome banner / message of the day |
136
+ | `dad-joke` | Q&A joke in an ASCII box (daily rotation) |
137
+ | `htop` | Colorful process/resource monitor display |
138
+ | `profile` | Developer profile info card |
139
+ | `goodbye` | Farewell message with well-wishes |
140
+ | `npm-install` | Humorous npm dependency tree |
141
+ | `blog-post` | Blog post title in a box |
142
+ | `national-day` | Fun national day celebration |
143
+ | `systemctl` | Fake systemd service status |
144
+ | `weather` | Live weather from wttr.in (also embeds in MOTD) |
145
+ | `github-stats` | Live GitHub user stats (repos, followers) |
146
+ | `github-languages` | Top languages across a user's public repos, with percentage bars |
147
+ | `quote` | Random quote from dummyjson.com |
148
+ | `fun-fact` | Random fun fact from uselessfacts.jsph.pl |
149
+ | `vim-exit` | The eternal "how do I quit vim?" meme |
150
+ | `sudo-sandwich` | xkcd 149 callback |
151
+ | `rm-rf` | Dramatic fake `rm -rf /` with commentary |
152
+ | `fork-bomb` | Mock fork-bomb warning ("turn your laptop fan into a leaf blower") |
153
+ | `kernel-panic` | Friendly BSOD spoof in terminal text |
154
+ | `segfault` | Fake core dump with corrupted backtrace |
155
+ | `whoami` | Username + existential identity bullets |
156
+ | `last-login` | `last` output with awkward 3am login timestamps |
157
+ | `finger` | Faux finger(1) card with snarky plan lines |
158
+ | `who` | `who` listing with ghost users (debugger, coffee, sanity) |
159
+ | `uptime` | Ridiculous uptime ("up 632 days, that one incident") |
160
+ | `matrix-rain` | Single-frame Matrix rain screen with ACCESS GRANTED footer |
161
+ | `cowsay` | Speech bubble + ASCII cow (with word-wrap) |
162
+ | `loading-spinner` | Braille spinner cycling at configurable fps |
163
+ | `heartbeat` | Pulsing heart — emotional hook for a project you love |
164
+ | `spinning-gear` | Rotating `\|/-\\` gear for DevOps/infra vibes |
165
+ | `blinking-eyes` | Kaomoji mascot that blinks every few seconds |
166
+ | `countdown` | T-minus N..0..GO! launch stinger (plays once, freezes on GO) |
167
+ | `sparkline` | ASCII sparkline (`▁▂▄▇▆▅▃▂`) from a numeric series |
168
+ | `bbs-login` | Retro 1980s BBS welcome banner — pairs with `amber` / `green-phosphor` |
169
+ | `build-badge` | Terminal-style project status card (tests / lint / coverage) |
170
+ | `license-card` | Boxed License / Copyright card |
171
+ | `ascii-clock` | HH:MM:SS clock with pulsing colon separators (12h / 24h) |
172
+ | `progress-bar` | Fake build progress bar that fills 0% → 100% |
173
+ | `bouncing-dot` | Single glyph bouncing left ↔ right |
174
+ | `dice-roll` | N d6 dice that tumble and land on a result |
175
+ | `palette-swatch` | One-line render of all 16 theme palette colors |
176
+ | `semver-bump` | Current semver + bump preview (major/minor/patch) |
177
+ | `ascii-calendar` | Current-month calendar grid with today highlighted |
178
+ | `toc` | Auto-generated markdown anchor-link table of contents |
179
+
180
+ ### Dynamic API Blocks
181
+
182
+ Blocks marked with "Live" fetch data at build time from free, SFW APIs. They gracefully fall back to static content on failure.
183
+
184
+ ```yaml
185
+ # Weather in MOTD banner
186
+ - block: motd
187
+ config:
188
+ title: "MY TERMINAL"
189
+ weather:
190
+ location: NYC # City name or coordinates
191
+ units: imperial # imperial, metric, or both
192
+
193
+ # Standalone weather block
194
+ - block: weather
195
+ config:
196
+ location: "Los Angeles"
197
+ units: metric
198
+ compact: false
199
+
200
+ # GitHub profile stats
201
+ - block: github-stats
202
+ config:
203
+ username: your-github-username
204
+
205
+ # Top languages across a user's public repos
206
+ - block: github-languages
207
+ config:
208
+ username: your-github-username
209
+ top: 5 # top N languages (1-10, default 5)
210
+ barWidth: 20 # bar width in chars (5-40, default 20)
211
+
212
+ # Random fun fact
213
+ - block: fun-fact
214
+ ```
215
+
216
+ Set `fetchTimeout` at the top level to control API timeout (default: 10000ms):
217
+
218
+ ```yaml
219
+ fetchTimeout: 15000 # 15 seconds — generous for slow APIs
220
+ ```
221
+
222
+ ### Accessibility
223
+
224
+ Every generated SVG carries `role="img"`, an `aria-label` summary of the first commands, plus a `<title>` and `<desc>` as its first children. The `<desc>` contains the full final-frame content (every command prefixed with the prompt, every output line with color markup stripped) so screen-reader users can read more than the 5-command summary.
225
+
226
+ Opt out if your terminal output is sensitive and you don't want it duplicated as plain text inside the SVG payload:
227
+
228
+ ```yaml
229
+ accessibility:
230
+ describe: false # default true — emit <desc> with full content
231
+ ```
232
+
233
+ **Reduced-motion caveat.** The SVG emits an inline `@media (prefers-reduced-motion: reduce)` rule, but it only applies to CSS animations. The typing reveal, cursor walk, scroll, and frame-cycle animations are SMIL (`<animate>` elements) and SMIL doesn't read the same CSS media query. Users who set the OS-level reduced-motion preference will still see full-speed animation. If that's a problem for your audience, generate with `--static` — same content, no motion at all.
234
+
235
+ ### Caching API responses
236
+
237
+ Dynamic blocks cache their responses in `.svg-terminal-cache.json` next to your config file (24h TTL by default). Commit that file alongside the YAML and CI builds become deterministic — no upstream hits, no diff churn from quote-of-the-minute drift.
238
+
239
+ ```yaml
240
+ # Top-level overrides
241
+ cacheTTL: 86400 # seconds (default 86400 = 24h)
242
+ cachePath: ".cache.json" # relative to the config file (must stay inside)
243
+ ```
244
+
245
+ CLI control:
246
+
247
+ ```bash
248
+ svg-terminal generate # use cache if fresh, fetch + write back when stale
249
+ svg-terminal generate --refresh-cache # ignore cache entries, re-fetch everything
250
+ svg-terminal generate --frozen-cache # serve only cached values, never fetch (CI offline)
251
+ svg-terminal generate --no-cache # bypass cache entirely (don't read, don't write)
252
+ ```
253
+
254
+ **Reproducibility note:** committed cache + a generous `cacheTTL` makes CI _reproducible_; pair with `--frozen-cache` to make it _truly offline_ (the build fails loudly if any block lacks a cached entry, rather than silently reaching for the network).
255
+
256
+ **Privacy note:** the cache file stores the raw API payloads. If a block fetches data you'd rather not commit (e.g. a private GitHub profile), either skip that block in versioned configs or add `.svg-terminal-cache.json` to `.gitignore`.
257
+
258
+ ### Custom Blocks
259
+
260
+ Custom blocks declare a strict zod `configSchema` so typos throw `BlockConfigError` at config-load time instead of silently falling back to defaults. Skipping the schema is allowed but discouraged — see [CONTRIBUTING.md](./CONTRIBUTING.md#adding-a-new-block).
261
+
262
+ ```typescript
263
+ import { z } from 'zod';
264
+ import { registerBlock, generate } from 'svg-terminal';
265
+
266
+ registerBlock({
267
+ name: 'my-block',
268
+ configSchema: z.object({
269
+ greeting: z.string().optional(),
270
+ }).strict(),
271
+ render(context, config) {
272
+ const greeting = (config.greeting as string) ?? 'hello';
273
+ return {
274
+ command: 'my-command',
275
+ lines: [`[[fg:green]]${greeting}, world[[/fg]]`],
276
+ };
277
+ },
278
+ });
279
+ ```
280
+
281
+ ## Programmatic API
282
+
283
+ ```typescript
284
+ // ESM. Run as `node --experimental-vm-modules` or save as .mjs / set "type":"module".
285
+ import { generate, generateStatic } from 'svg-terminal';
286
+
287
+ async function main() {
288
+ const svg = await generate({
289
+ theme: 'nord',
290
+ blocks: [
291
+ { block: 'neofetch', config: { username: 'dev' } },
292
+ { block: 'custom', config: { command: 'date', lines: ['2026-05-25'] } },
293
+ ]
294
+ }, {
295
+ // All fields optional. configPath anchors cachePath resolution if you
296
+ // want the cache; cacheMode is one of 'normal' | 'refresh' | 'frozen' | 'off';
297
+ // now lets you pin context.now for reproducible test/demo output.
298
+ configPath: '/abs/path/to/config.yml',
299
+ cacheMode: 'frozen',
300
+ now: new Date('2026-05-25T13:37:00Z'),
301
+ });
302
+ }
303
+ main();
304
+ ```
305
+
306
+ `generateStatic` returns the same content as a non-animated SVG — useful for accessibility fallbacks and social-preview cards.
307
+
308
+ `inspectCache(userConfig, configPath)` returns `{filePath, results}` where each result reports per-cacheable-block status (`OK` / `STALE` / `MISS`) with age in seconds — useful for building your own "is the cache hot?" CI gates without invoking the `svg-terminal cache check` subcommand. `userConfig` is the parsed config object; `configPath` anchors cache-path resolution.
309
+
310
+ ## GitHub Action
311
+
312
+ Complete `.github/workflows/refresh-svg.yml` for a weekly README refresh:
313
+
314
+ ```yaml
315
+ name: Refresh SVG terminal
316
+ on:
317
+ schedule:
318
+ - cron: '0 12 * * MON' # Mondays at 12:00 UTC
319
+ workflow_dispatch: # also run on demand
320
+
321
+ jobs:
322
+ refresh:
323
+ runs-on: ubuntu-latest
324
+ permissions:
325
+ contents: write # required for commit: true
326
+ steps:
327
+ - uses: actions/checkout@v4
328
+ - uses: williamzujkowski/svg-terminal@v1
329
+ with:
330
+ config: terminal.yml
331
+ output: terminal.svg
332
+ commit: true
333
+ ```
334
+
335
+ For maximally reproducible CI, commit `.svg-terminal-cache.json` alongside `terminal.yml` and set `cache-mode: frozen` — every build will serve cached payloads with zero network calls. The build fails loudly if a cacheable block is missing an entry:
336
+
337
+ ```yaml
338
+ - uses: williamzujkowski/svg-terminal@v1
339
+ with:
340
+ config: terminal.yml
341
+ output: terminal.svg
342
+ cache-mode: frozen # normal | refresh | frozen | off
343
+ static: false # set true to skip animation
344
+ minify: false # set true to strip inter-element whitespace
345
+ commit: true
346
+ commit-message: chore(readme): refresh terminal svg
347
+ ```
348
+
349
+ The action commits as `github-actions[bot]`; the `commit` input only runs `git add output + git commit + git push` against the current branch. Skip `commit: true` and add your own commit step if you need signed commits or a custom author.
350
+
351
+ ## Text Markup
352
+
353
+ Blocks support inline color markup:
354
+
355
+ ```
356
+ [[fg:green]]green text[[/fg]]
357
+ [[fg:cyan]][[bold]]bold cyan[[/bold]][[/fg]]
358
+ [[dim]]dimmed text[[/dim]]
359
+ ```
360
+
361
+ Available colors: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `orange`, `purple`, `pink`, `comment`, plus `bright_*` variants.
362
+
363
+ ## ASCII Boxes
364
+
365
+ The box generator supports multiple styles:
366
+
367
+ ```typescript
368
+ import { createBox } from 'svg-terminal';
369
+
370
+ createBox({ style: 'rounded', width: 40, lines: ['Hello!'] });
371
+ // ╭──────────────────────────────────────╮
372
+ // │ Hello! │
373
+ // ╰──────────────────────────────────────╯
374
+ ```
375
+
376
+ Styles: `double` (╔═╗), `rounded` (╭─╮), `single` (┌─┐), `heavy` (┏━┓), `dashed` (┌╌┐)
377
+
378
+ ## Contributing
379
+
380
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the local dev loop, the block/theme contribution recipes, and PR conventions.
381
+
382
+ ## License
383
+
384
+ MIT