sepia v0.5.4text to picture, in the terminal

Describe it.
Watch it ink.

sepia turns a few words into a picture made of text. You type in one terminal window; Claude directs a photo for it, an image model paints it, and a second window draws it as thousands of coloured characters, each one picked for the patch of picture under it.

uv tool install sepia-cli
pink and purple black holeone picture, made right below

Windows 10/11 and macOS, with uv and an Anthropic API key. Painting is free with a Cloudflare account, or local on your own GPU.

Pl. ISepia officinalis72 × 61 = 4,392 characters

Jean-Baptiste Vérany, Sepia officinalis, 1851, drawn by sepia’s own code. Move over it for ripples; click to release ink.

Plate I

A picture, as it surfaces.

This page’s own run: scripts/spike.py made the same calls the prompt window makes.

sepia — prompt window
 › pink and purple black hole
 ⏺ Painting a pink and violet black hole with a glowing lensed ring.
   local GPU · 4.0s
 › /add a comet falling into it
 ⏺ Adding a comet plunging into the black hole.
   local GPU · 5.9s

File 01

Describe. Direct. Paint. Ink.

Four steps, one Claude call, one painter, and a window that draws in type.

Describe: What do you want to see?

Two words are enough. The art director does the rest.

Type a few words in the prompt window: a black hole, NYC nights, a mood. Every request starts a new picture; /add builds on the one you have.

Name a creature, a character or a landmark, or paste a link to a picture, and sepia paints from a reference picture instead of inventing a stranger. A bare name shows that picture as it is.

say it asblack hole/add a cometyunara lola picture link

Pl. IISepia officinalis64 × 62 = 3,968 characters

Ippolito Salviani, Sepia, 1554. The oldest plate on the page.
REQUEST
pink and purple black hole
SCENE
emptya new picture
THEN
/add a comet falling into itthe same scene, built on

Direct: Who decides what it looks like?

The reply has a skeleton too: one fixed JSON schema.

One Claude call per request. Claude, claude-haiku-5-5 at high effort, is told to stay true to the request, the picture a search shows first, and to add a twist only when asked, then writes a detailed English photo prompt made for symbol rendering: one dominant shape, strong light, saturated colour, no text.

The same reply decides whether the request edits the current picture or starts a new one, and carries the one sentence the prompt window shows you.

the replymessageprompteditreferences

Pl. IIISepia officinalis64 × 42 = 2,688 characters

Common cuttlefish, Brehms Tierleben, 1890. Two long arms, kept for the catch.
MESSAGE
Painting a pink and violet black hole with a glowing lensed ring.
PROMPT
Cinematic astrophotography, wide telescope-style view of a supermassive black hole seen nearly edge-on…96 words
EDIT
falsethen true, for /add a comet
TOKENS
7,418 in · 972 out

Paint: Who holds the brush?

Painted at the viewer window’s own shape, so nothing is cut that matters.

The prompt goes to Cloudflare Workers AI first, which runs FLUX.2 [klein] 4B on a free account. When the API fails or rate limits, sepia paints on your own GPU with the same model: CUDA first, then Apple Silicon, then the CPU. /local keeps it there.

After a 429 it leaves Cloudflare alone for 120 seconds instead of asking again on every picture. Once the day’s free allocation is used up, it stops asking until /api or a restart, since a short wait does not bring it back.

painterscloudflarelocal GPU

Pl. IVSepia officinalis96 × 18 = 1,728 characters

Werner Th. Meyer, Sepia officinalis, 1913. The fin runs the whole mantle.
BLACK HOLE
local GPU · 4.0 s800 × 496
COMET
local GPU · 5.9 srepainted from the first
SEED
fixed for the sceneso an edit keeps its shape

Ink: How does a photo become text?

Thirty-one weights of character, from a blank to an @.

The viewer stretches the contrast, boosts the colour and crops the picture to its window. Then every cell becomes one character whose ink covers as much of the cell as the light under it, from a ramp of 31 measured on the terminal’s own font.

An ordered dither breaks smooth skies into mixed characters instead of bands, and each character takes the colour of its patch in 24-bit colour. A resize redraws it all, with no new API call.

the ramp `'-,_^"!<+?|c(i}nfoaXwbO%80WQ@

Pl. VSepia officinalis56 × 54 = 3,024 characters

Cuttle-fish and shell, Popular Science Monthly, 1889. Plain lines, every one kept.
GRID
120 × 38 cellsthis page’s viewer
CELL
8 × 16 pxabout twice as tall as wide
DITHER
4 × 4 Bayerstrongest in the midtones

File 02

Ink or fill. Letters or symbols.

The same picture, four ways. A style change redraws at once, with no new call.

› pink and purple black hole, painted for this page and drawn as 120 × 38 characters. Switch it the way the prompt window does.

/styleHow should the picture be drawn?
/charsetWhich characters should draw it?
classic 31 weights
`'-,_^"!<+?|c(i}nfoaXwbO%80WQ@
symbols 15 weights
.':;^!~+*(%#&@

File 03

Six things it will never do.

Written into the README and SECURITY.md, not left to taste.

  1. Send your words to the painter.

    Cloudflare gets the English prompt Claude wrote, the picture size, the seed and, at most 512 px a side, the reference picture or the one you are adding to. Your own request is not sent, and nothing goes there at all when the picture is painted locally.

    The painter reads a prompt, never you.

  2. Phone home.

    There is no telemetry. Pictures, the request history and the key file stay in your user config folder, and the newest ten sessions are all it keeps. A picture you /save goes to your Pictures folder.

    What it knows about you fits in one folder you can open.

  3. Write your key anywhere you did not put it.

    Keys come from the environment or a .env file and are never written to code, logs or error messages. Asked on the first run or with /keys, each key is typed hidden and saved to the config folder; on macOS that file is mode 600.

    Hidden as you type it, and after.

  4. Paint something explicit.

    Even with /mature on: no sex acts, no visible genitals, no one who is or looks under 18, no real person undressed. Such a request gets the nearest acceptable picture instead of a refusal.

    Dark and sexy, yes. Explicit, never.

  5. Touch your terminal’s settings.

    On Windows the viewer gets its own sepia profile, written as a Windows Terminal fragment in its own folder. Your settings.json is never opened; delete the folder and the profile is gone.

    A purple tab, and nothing changed behind it.

  6. Listen beyond this machine.

    The two windows talk on 127.0.0.1 only. The viewer must pass an HMAC challenge with a fresh 32-byte key each session, and messages are JSON, never unpickled: a peer can send data, not code.

    Two windows, one loopback, one key a session.

Pl. VISepia officinalis120 × 27 = 3,240 characters

Jean-Baptiste Vérany, Sepia officinalis, plate 24, 1851. Every stripe where the engraver put it.

File 04

Measured, not claimed.

Each figure says where it comes from: the README, or this page’s own run.

  1. ~$0.001per request

    for the art director, Claude Haiku at the default effort

    README, cost; plus $0.01 for each web search Claude makes

  2. 4.0seconds

    to paint the black hole on this page, on a laptop GPU

    this page’s run, FLUX.2 [klein] 4B; the README says about 3 s at 768 × 512

  3. ~5GB of VRAM

    held by the local model, loaded in 4-bit on CUDA

    README, painting backends

  4. 9,520characters

    in the README’s waterfall, each chosen and coloured for the patch under it

    README, the picture at the top

  5. 31weights of character

    in the classic ramp; 15 in symbols, punctuation only

    src/sepia/ascii_art.py

  6. 120seconds

    that a rate-limited painter is left alone before sepia asks it again

    README, painting backends

Pl. VIISepia officinalis72 × 15 = 1,080 characters

Common cuttlefish, Brehms Tierleben, 1890. Every stripe counted.

where the numbers come from

  • Costs: the README’s table, from list prices.
  • Seconds: the metadata scripts/spike.py saved with this page’s two pictures.
  • Characters and weights: the viewer’s own constants.

No benchmark of taste here: only what can be counted.

File 05

Where the ink runs out.

A tool that draws in plain sight should also say what it cannot draw.

  1. No viewer on Linux.

    sepia imports and passes its tests there, but the second window cannot be opened. Windows 10/11 and macOS only.

  2. Terminal.app may miss the pointer.

    The sepia trail needs the terminal to report the mouse. Windows Terminal and iTerm2 do; macOS Terminal.app may not.

  3. The free tier runs out.

    Cloudflare’s free tier is 10,000 neurons a day, about 100 pictures, reset at 00:00 UTC. Once it is used up, sepia paints on your own GPU for the rest of the session; without the local model, it cannot paint until the reset.

  4. The local model is large.

    About 16 GB to download and 3 GB of packages, and it holds about 5 GB of VRAM from the moment sepia opens until it closes.

  5. It only makes pictures.

    It is not a chat assistant. Every request starts a new picture, and /add is how you build on one.

File 06

Two windows, one command.

Install it, type sepia, then a few words. The second window opens itself.

In the prompt window

/add <text>
add to the current picture instead of starting a new one
/again
paint the picture again with fresh noise, no new prompt
/undo
go back to the picture before
/cam [off]
show the webcam live; /cam again keeps the frame
/play <link|file|off>
play a video: a link up to a minute, a file whole
/open <file>
show a picture from this computer; no key needed
/clear
clear the screen and start a fresh picture
/style [ink|fill]
choose how the picture is drawn
/charset [classic|symbols]
choose the characters that draw it
/save
save the picture in the window to Pictures/sepia
/copy
copy the picture as text for Discord or GitHub
/model [haiku|sonnet|opus|fable]
choose which Claude writes the prompts
/effort [low|medium|high|xhigh|max]
choose how long Claude thinks
/config
turn clarifying questions, mature pictures and update notices on or off
/mature [on|off]
allow sexy and dark pictures, never explicit
/api
paint with Cloudflare, on this machine when it fails
/local
paint only on this machine’s GPU
/keys [anthropic|cloudflare]
set the keys sepia uses
/usage
show what Claude used and cost, now and in all
/help
show this list
/exit
close sepia and its window (also Ctrl+D, or Ctrl+C twice)

/style, /charset, /model, /effort, /mature and /keys without an argument open a menu: the arrow keys and Enter pick, Esc cancels. /config lists the on/off settings. Up and down walk the request history. Typing / opens a menu of the matching commands above the chat bar: Tab completes, Enter runs.

Install

uv tool install sepia-cli

The first run asks for your keys one by one, typed hidden, and keeps them in your config folder.

The local painter

FLUX.2 [klein] 4B on your own GPU, whenever the API fails or rate limits. About 3 GB of packages:

macOS Apple Silicon: bf16 weights on MPS

uv tool install "sepia-cli[local]"

Windows CUDA builds of torch come from the PyTorch index

uv tool install "sepia-cli[local]" --index https://download.pytorch.org/whl/cu130 --index-strategy unsafe-best-match

then once: about 16 GB from Hugging Face

sepia --download

Settings

From the environment, then .env in the current folder, then.env in the user config folder. A value already set is never overridden.

VariableDefaultWhat it sets
ANTHROPIC_API_KEYasked on first runthe art director; the one key sepia needs
CLOUDFLARE_ACCOUNT_IDnonethe Cloudflare painter, together with the token
CLOUDFLARE_API_TOKENnonea free Workers AI token; without both, only the local model paints
BRAVE_SEARCH_API_KEYnonenamed subjects from an image search
SEPIA_MODELclaude-haiku-5-5the art director model
SEPIA_EFFORThighhow long Claude thinks per picture
SEPIA_STYLEinkink or fill
SEPIA_CHARSETclassicclassic or symbols
SEPIA_MATUREoffstart with mature pictures on or off
SEPIA_ASKonlet Claude ask about a vague request first
SEPIA_UPDATE_CHECKonsay when a new sepia is out