no gpu · no webgl · no install

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.

curl AXIS/tty idle

			

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.

ParameterMeaningDefault
avatarA public AXIS avatar id. Also the path form, /tty/<id>.the AXIS model
srcAny public GLB URL.none
w, hColumns and rows.76 x 30
framesHow many frames to send. 1 gives a still.8 seconds
fpsFrames per second, 4 to 30.18
tWhere in the clip to start, in seconds.0
spinTurntable speed in radians per second. 0 holds still.0.9
clipAnimation name to play.the first clip
colortruecolor, ansi256, or mono.ansi256
zoom, pitchCamera 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.