Your agent, in your terminal.
One command. A rigged 3D character loads, plays its animation, and turns on a turntable, drawn entirely out of coloured text. There is nothing to install and nothing to run: the frames are rasterized on our servers and streamed to your shell as they are drawn.
curl AXIS/tty
Works in any terminal that renders colour. Press Ctrl-C to stop it.
This is the actual output
Not a video and not a recording. The panel below opens the same HTTP stream
curl would, and paints the escape
sequences it receives.
Render any model, locally
The renderer is a published package. It takes a GLB, decodes it (including
EXT_meshopt_compression, which most
real avatars ship with), evaluates the animation, applies linear blend skinning
on the CPU, and rasterizes with a z-buffer. No GPU is involved at any point, so
it runs the same over SSH, in CI, or in a container with no display.
# a local file
npx @axis/tty-3d ./avatar.glb
# anything on the web
npx @axis/tty-3d /avatars/default.glb --spin 1.4
# a still frame, piped somewhere
npx @axis/tty-3d avatar.glb --once --width 80 --color mono > frame.txt
Arrow keys orbit, w/s
tilt, +/- zoom,
space pauses, q quits.
Or as a library
import { loadModel, createRenderer } from '@axis/tty-3d';
const model = await loadModel('./avatar.glb');
const renderer = createRenderer(model, { width: 80, height: 40 });
// One string per frame. Write it wherever you like.
process.stdout.write(renderer.frame(1.25));
The hosted endpoint
GET /api/tty streams frames as
text/plain, paced in real time, so the
response itself is the animation. Every parameter is optional.
| Parameter | Meaning | Default |
|---|---|---|
avatar | A public AXIS avatar id. Also the path form, /tty/<id>. | the AXIS model |
src | Any public GLB URL. | none |
w, h | Columns and rows. | 76 x 30 |
frames | How many frames to send. 1 gives a still. | 8 seconds |
fps | Frames per second, 4 to 30. | 18 |
t | Where in the clip to start, in seconds. | 0 |
spin | Turntable speed in radians per second. 0 holds still. | 0.9 |
clip | Animation name to play. | the first clip |
color | truecolor, ansi256, or mono. | ansi256 |
zoom, pitch | Camera distance and elevation. | 1, 0.08 |
# your own avatar, wider, held still
curl 'AXIS/tty/<avatar-id>?w=110&spin=0'
# a single frame in plain ASCII, for a README or a commit hook
curl 'AXIS/tty?frames=1&color=mono&w=60'
A browser asking for this URL is redirected here instead, because a browser would
show the escape codes as literal garbage. Pass
?raw=1 to see the bytes anyway.
How it works
Half blocks, not ASCII art
Each cell is a ▀ with its own foreground and background colour, so one character carries two pixels. That doubles vertical resolution and makes pixels square, which is why a sphere comes out round.
Real skinning
Joint matrices are evaluated per frame and blended per vertex, so a rigged avatar actually moves its limbs. It is not a spinning static mesh.
Framing that holds still
The camera radius is fixed from the widest frame of the clip and the centre tracks the pose, so a walk cycle with root motion neither breathes nor walks out of shot.
Silhouette-first shading
A key light, a fill, and a rim term. At this resolution the outline carries the shape, so faces turning away are lifted rather than crushed to black.