Skill: Port Sacred Terminal UI to TypeScript CLI
Also available at https://sacred.computer/llm/skills/port-sacred-terminal-ui-to-typescript-cli/SKILL.md
Take a React Window*.tsx (or any sacred component) and produce a terminal CLI screen written in TypeScript that uses Simulacrum — the sacred CLI framework in scripts/cli/lib/.
See components/AGENTS.md for the canonical catalog of every sacred React component (props, theming tokens, CLI primitive equivalent). Read it before identifying which React surface you are porting from.
When to use
Use this skill whenever you want a CLI version of an existing sacred React surface. Sacred ships a small zero-dependency layout framework that maps every React <Card> / <DataTable> / <ActionButton> concept onto a CLI primitive. The output is identical to the React component minus the canvas-based animations — sacred renders are static.
What you ship
A single .ts file under scripts/cli/templates/ that:
- Uses
tsx as a shebang or npm run cli:typescript as the entry point.
- Imports the framework primitives (
import { ... } from '../lib/card').
- Calls
createApp({ build }).start() with a build(page, innerW, selectedRow) function that returns string[].
Reference implementation
Read these files before starting:
scripts/cli/templates/template.ts — canonical example (run with npm run cli:typescript)
scripts/cli/lib/ansi.ts — ANSI escapes, hex helpers, padding, gradient text
scripts/cli/lib/window.ts — window frame (margin + window bg + shadow)
scripts/cli/lib/card.ts — box-drawing card borders and word-wrap
scripts/cli/lib/table.ts — formatRow, kvTable, kvTableGradient, ColSpec type
scripts/cli/lib/button.ts — button, buttonRow
scripts/cli/lib/app.ts — lifecycle: alt screen, raw mode, resize, paging, selection
scripts/cli/colors.json — sacred-themed color palette (single source of truth)
React-to-CLI concept map
| React surface |
CLI primitive |
Notes |
<Card title="T"> |
cardTop('T', innerW) + cardBot(innerW) |
Top + bottom borders |
Content <div> inside <Card> |
cardRow(text, innerW) |
2ch left indent, padded to inner width |
| Key-value pair with gradient styling |
kvTableGradient([[k, v]]) |
24ch key column, gradient on value |
<thead><tr><td> |
cardHeaderRow(formatRow(TH, COL_SPEC, innerW), innerW) |
#585858 background |
<tbody><tr><td> |
cardRow(formatRow(row, COL_SPEC, innerW), innerW) |
Per-cell alignment, status coloring |
styles.statusOk/statusOff |
colSpec: { status: true } |
ACTIVE/OPEN/APPROVED → bold green; CLOSED/PAID/SUSPENDED → gray |
<ActionButton hotkey="ESC"> |
button('ESC', 'exit') |
Hotkey + label background pair |
<RowSpaceBetween> |
buttonRow(left, right, innerW) |
Left + right justify with windowBg gap |
| Word-wrapped paragraph |
wordWrap(text, innerW - 6) |
Card padding is 3ch each side |
Animated <ASCIICanvas> header |
omit |
Sacred CLI ports are static; the React side keeps the animation |
ColSpec reference
type ColSpec = {
width: number;
align?: 'left' | 'right';
grow?: boolean; // one column per spec absorbs extra width
status?: boolean; // ACTIVE/OPEN/APPROVED → bold green; CLOSED/PAID/SUSPENDED → gray
gap?: number; // inter-column spacing (default 1ch)
};
Step-by-step
- Read the React file. Identify cards, tables, paragraphs, and button rows. Skip the
<ASCIICanvas> (CLI is static).
- Extract data. Move table rows into
const arrays at the top of your TS file. If the React component already imports JSON, share the same JSON.
- Define COL_SPECS. One per
<table>. Mark a single column with grow: true.
- Write
build(page, innerW, selectedRow). Push cardTop → rows → cardBot for each section. Append buttonRow(...) last.
- Run
npm run cli:typescript. Verify margins, shadow, and inner padding match the React component.
- Add interactivity (optional). Pass
interactive: { count, onSelect, persist: true } to createApp and use cardSelectRow(content, innerW, i === selectedRow).
- Add pagination (optional). Pass
totalPages: N (or () => N) and slice your data by page inside build.
Formatting rules
- File starts with
#!/usr/bin/env -S npx tsx.
- Comments use
//NOTE(your_github_username): ....
- Column headers are
UPPER_SNAKE_CASE.
- Dates are ISO 8601 (
2026-04-08T09:00:00).
- Currency uses commas (
$18,920.50).
- Never hardcode hex colors — read from
scripts/cli/colors.json via the framework.
Smoke test
After writing the file:
npm test # JS framework + Python parity suite (chained)
npm run cli:typescript # your screen renders, ESC quits cleanly
If npm test fails, the framework is broken — fix it before continuing. If your screen flickers on resize, you forgot to wrap content in cardRow/cardSelectRow (the framework relies on padded rows for the in-place redraw).
If a JS module changes the bytes coming out of any primitive, the parity test in scripts/python/sacred_cli/__tests__/test_parity.py will fail until the Python mirror under scripts/python/sacred_cli/ is updated to match. Port the change to both runtimes in the same PR — see skills/port-sacred-terminal-ui-to-python/SKILL.md § "Verifying parity".
1---2name: port-sacred-terminal-ui-to-typescript-cli3description: Skill: Port Sacred Terminal UI to TypeScript CLI4---5# Skill: Port Sacred Terminal UI to TypeScript CLI67> Also available at https://sacred.computer/llm/skills/port-sacred-terminal-ui-to-typescript-cli/SKILL.md89Take a React `Window*.tsx` (or any sacred component) and produce a terminal CLI screen written in TypeScript that uses **Simulacrum** — the sacred CLI framework in `scripts/cli/lib/`.1011> See `components/AGENTS.md` for the canonical catalog of every sacred React component (props, theming tokens, CLI primitive equivalent). Read it before identifying which React surface you are porting from.1213## When to use1415Use this skill whenever you want a CLI version of an existing sacred React surface. Sacred ships a small zero-dependency layout framework that maps every React `<Card>` / `<DataTable>` / `<ActionButton>` concept onto a CLI primitive. The output is identical to the React component minus the canvas-based animations — sacred renders are static.1617## What you ship1819A single `.ts` file under `scripts/cli/templates/` that:20211. Uses `tsx` as a shebang or `npm run cli:typescript` as the entry point.222. Imports the framework primitives (`import { ... } from '../lib/card'`).233. Calls `createApp({ build }).start()` with a `build(page, innerW, selectedRow)` function that returns `string[]`.2425## Reference implementation2627Read these files before starting:2829- `scripts/cli/templates/template.ts` — canonical example (run with `npm run cli:typescript`)30- `scripts/cli/lib/ansi.ts` — ANSI escapes, hex helpers, padding, gradient text31- `scripts/cli/lib/window.ts` — window frame (margin + window bg + shadow)32- `scripts/cli/lib/card.ts` — box-drawing card borders and word-wrap33- `scripts/cli/lib/table.ts` — `formatRow`, `kvTable`, `kvTableGradient`, `ColSpec` type34- `scripts/cli/lib/button.ts` — `button`, `buttonRow`35- `scripts/cli/lib/app.ts` — lifecycle: alt screen, raw mode, resize, paging, selection36- `scripts/cli/colors.json` — sacred-themed color palette (single source of truth)3738## React-to-CLI concept map3940| React surface | CLI primitive | Notes |41| ----------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------- |42| `<Card title="T">` | `cardTop('T', innerW)` + `cardBot(innerW)` | Top + bottom borders |43| Content `<div>` inside `<Card>` | `cardRow(text, innerW)` | 2ch left indent, padded to inner width |44| Key-value pair with gradient styling | `kvTableGradient([[k, v]])` | 24ch key column, gradient on value |45| `<thead><tr><td>` | `cardHeaderRow(formatRow(TH, COL_SPEC, innerW), innerW)` | `#585858` background |46| `<tbody><tr><td>` | `cardRow(formatRow(row, COL_SPEC, innerW), innerW)` | Per-cell alignment, status coloring |47| `styles.statusOk/statusOff` | `colSpec: { status: true }` | `ACTIVE`/`OPEN`/`APPROVED` → bold green; `CLOSED`/`PAID`/`SUSPENDED` → gray |48| `<ActionButton hotkey="ESC">` | `button('ESC', 'exit')` | Hotkey + label background pair |49| `<RowSpaceBetween>` | `buttonRow(left, right, innerW)` | Left + right justify with windowBg gap |50| Word-wrapped paragraph | `wordWrap(text, innerW - 6)` | Card padding is 3ch each side |51| Animated `<ASCIICanvas>` header | _omit_ | Sacred CLI ports are static; the React side keeps the animation |5253## ColSpec reference5455```ts56type ColSpec = {57 width: number;58 align?: 'left' | 'right';59 grow?: boolean; // one column per spec absorbs extra width60 status?: boolean; // ACTIVE/OPEN/APPROVED → bold green; CLOSED/PAID/SUSPENDED → gray61 gap?: number; // inter-column spacing (default 1ch)62};63```6465## Step-by-step66671. **Read the React file.** Identify cards, tables, paragraphs, and button rows. Skip the `<ASCIICanvas>` (CLI is static).682. **Extract data.** Move table rows into `const` arrays at the top of your TS file. If the React component already imports JSON, share the same JSON.693. **Define COL_SPECS.** One per `<table>`. Mark a single column with `grow: true`.704. **Write `build(page, innerW, selectedRow)`.** Push `cardTop` → rows → `cardBot` for each section. Append `buttonRow(...)` last.715. **Run `npm run cli:typescript`.** Verify margins, shadow, and inner padding match the React component.726. **Add interactivity (optional).** Pass `interactive: { count, onSelect, persist: true }` to `createApp` and use `cardSelectRow(content, innerW, i === selectedRow)`.737. **Add pagination (optional).** Pass `totalPages: N` (or `() => N`) and slice your data by `page` inside `build`.7475## Formatting rules7677- File starts with `#!/usr/bin/env -S npx tsx`.78- Comments use `//NOTE(your_github_username): ...`.79- Column headers are `UPPER_SNAKE_CASE`.80- Dates are ISO 8601 (`2026-04-08T09:00:00`).81- Currency uses commas (`$18,920.50`).82- Never hardcode hex colors — read from `scripts/cli/colors.json` via the framework.8384## Smoke test8586After writing the file:8788```sh89npm test # JS framework + Python parity suite (chained)90npm run cli:typescript # your screen renders, ESC quits cleanly91```9293If `npm test` fails, the framework is broken — fix it before continuing. If your screen flickers on resize, you forgot to wrap content in `cardRow`/`cardSelectRow` (the framework relies on padded rows for the in-place redraw).9495If a JS module changes the bytes coming out of any primitive, the parity test in `scripts/python/sacred_cli/__tests__/test_parity.py` will fail until the Python mirror under `scripts/python/sacred_cli/` is updated to match. Port the change to both runtimes in the same PR — see `skills/port-sacred-terminal-ui-to-python/SKILL.md` § "Verifying parity".