Turn an old Android phone into a glass terminal for your Linux machine.
No keyboard. No logic. No state. Just pixels, streamed as ANSI.
Русский · Quick start · Widgets · Docs
That drawer with three dead phones finally has a purpose. Put one on a stand, point it
at your desk, and let it show whatever your computer is doing: a real shell, a
fastfetch, an aquarium, or a wall of widgets that updates itself.
The phone app is deliberately stupid. It opens a WebSocket, reports its grid size, and draws the bytes that arrive. Everything else — PTYs, layouts, widget scheduling, rendering — lives in a single Python daemon on the computer.
Terminal mode runs a real PTY at the phone's exact size and mirrors it:
ansiko run fastfetch
ansiko run asciiquarium
ansiko run tty-clock -cType on the computer, watch it appear on the phone, exit when you are done.
Board mode composes a grid of widgets into a single frame and streams it through the same channel:
- clock, calendar, system stats, now playing;
- exchange rates, countdowns, build status, weather;
- anything a script can print.
The phone does not know which mode it is in. It never sends a keystroke.
Web editor / CLI (on your computer)
│ HTTP + WebSocket
▼
ansiko daemon PTY · layouts · widget scheduler · board frames
│ WebSocket: raw ANSI bytes
▼
Android app read-only terminal view · screen always on
The phone reports cols × rows once, and the daemon builds the PTY or the board at
exactly that size. Resize the window or rotate the screen and it re-lays out.
1. Server — Python 3.11+ on Linux:
uv tool install ./server # or: pipx install ./server
cp ansiko.example.toml ansiko.local.toml
$EDITOR ansiko.local.toml # set a long token and your LAN address
ansiko serve --host 192.168.1.103The daemon prints the dashboard URL on startup. To keep it running as a user service:
ansiko install-systemdIt writes ~/.config/systemd/user/ansiko.service, enables it and starts it. Set a
stable token in ansiko.local.toml first; pass --config PATH to use another
file, or --no-start to only enable it.
2. Phone — build and install the APK:
cd android
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apkA signed release build reads android/keystore.properties (ignored by git); see
release notes. Release APKs are attached to GitHub Releases when a
release is published.
3. Pair — open http://192.168.1.103:8765/ on the computer, log in with the token,
then in the app long-press the screen and hit Scan pairing QR. Or type the host,
port and token by hand in the settings screen.
4. Push something:
ansiko run asciiquarium
ansiko push desk
ansiko statusEverything is driven from the browser at http://<host>:<port>/ — no TUI, no build step.
| Board library | every board in boards/, with a live ANSI preview and one-click push |
| Visual editor | drag, eight resize handles, 5% snapping, collision checks, undo/redo, keyboard nudge |
| Presets | automatic packing, equal columns, grid, vertical stack |
| Raw TOML | edit the board source with line/column validation |
| Widget catalog | sizes, intervals, timeouts, editable settings with sliders and toggles, single test run |
| Live monitor | the exact byte stream the phone receives, with a full-screen mode |
| Pairing | a QR code containing host, port and token |
The interface is English and Russian, dark-only, and uses self-hosted Archivo + IBM Plex Mono, so it works with no internet access.
A widget is a directory with a manifest and any executable. Python, shell, Node, Go, a compiled binary — the daemon only reads stdout.
widgets/clock/
├── widget.toml
└── run.py
# widget.toml
name = "clock"
description = "Local time"
command = "./run.py"
interval = 1 # seconds between runs
width = 20 # suggested size in cells
height = 5
timeout = 5 # kill the script after N seconds#!/usr/bin/env python3
import time
print(time.strftime("%H:%M:%S"))The script gets its allotted size in ANSIKO_COLS and ANSIKO_ROWS. If it crashes or
times out, only its own cell shows an error — the rest of the board keeps running.
Bundled examples: banner, calendar, clock, date, music, music-art,
sysinfo, ttyclock, weather. Put your own in custom-widgets/ (ignored by git); a
custom widget with the same name overrides the bundled one.
Some examples use optional host tools: music needs playerctl, music-art needs
playerctl and Pillow, and ttyclock needs tty-clock plus tmux. The default boards
use the dependency-free clock, so a fresh install works without them. weather uses
Open-Meteo and needs outbound HTTPS; coordinates and the city live in its
widget.toml.
Tip:
figlet,toilet,richandplotextmake excellent widget output — big digits, boxes and charts.
A board is a directory under boards/ or custom-boards/ with a board.toml. The
simplest form lets the server pack widgets for you:
name = "Desk"
frame = true
widgets = ["clock", "calendar", "sysinfo"]For a fixed composition, place cells with fractional coordinates — they survive rotation and different screen sizes:
name = "Desk"
frame = true
[[cells]]
widget = "clock"
x = 0.0
y = 0.0
width = 0.55
height = 0.3
[[cells]]
widget = "calendar"
x = 0.55
y = 0.0
width = 0.45
height = 0.62frame = true asks the server to draw a titled box around each cell and subtract the
border from the widget's area. Bundled boards: desk, demo, focus, minimal.
Put your own boards in custom-boards/ (ignored by git). A custom board with the same
folder name overrides the bundled one, new boards from the dashboard are created there,
and editing a bundled board writes a local override instead of modifying boards/.
Bundled boards are read-only in the dashboard: use Duplicate before renaming or
deleting one. Bundled boards: board, default, demo, desk, focus, minimal,
switch.
The phone is meant to hang on a wall for weeks, so the client takes care of the panel:
- Pixel shift — the whole grid drifts around a small ring every minute, so no glyph stays in the same physical pixel forever.
- Night dimming — between 22:00 and 08:00 the screen drops to a chosen brightness (20–100 %), then returns to normal in the morning.
- Immersive, always-on display — system bars hidden, the activity is shown over the lock screen and turns the screen on. An optional kiosk (screen pinning) setting keeps accidental swipes from leaving the app. The app also starts after a reboot when it has a saved server configuration; Android 10+ may still require the app to be opened once or to be exempted from background-launch restrictions.
Both burn-in options and kiosk mode live in the settings screen and can be turned off.
ansiko serve start the daemon
ansiko run <command...> run a program on the phone until exit / Ctrl+C
ansiko push <board> switch the phone to a saved board
ansiko status show the connected device and current mode
This is effectively a remote shell on your network, so:
- a token is always required — a long random one, not a PIN;
- bind to your LAN interface, never to the public internet;
- keep secrets in
ansiko.local.toml, which is ignored by git; - if you need access from outside, put it behind Tailscale or WireGuard.
The daemon serves plain HTTP/ws://, not TLS. Use it on a trusted LAN, or wrap it in
Tailscale/WireGuard when the network is not trusted. The token is the only
authentication; do not expose port 8765 directly to the internet.
- Widget guide — manifest, environment, ANSI, errors, testing.
- Board format — auto and free layouts, frames, the visual editor.
- Web dashboard — library, editor, monitor, widgets, pairing, shortcuts.
- Protocol — display, session and preview WebSockets; HTTP API.
- Release checklist — versions, signing, APK verification and CI.
- Daemon: WebSocket + PTY, a live shell on the phone
- Android client: read-only terminal, auto-reconnect, keep screen on
- Terminal mode (
ansiko run) - Board mode: auto packing, fractional cells, frames, scheduler
- Web UI: token login, board editor, push, QR pairing
- Visual editor: drag, resize, snapping, presets, undo/redo, live preview
- Widget catalog, bundled examples and documentation
- OLED burn-in protection: pixel shift, night dimming, lock-screen display
- Optional kiosk mode, boot receiver and version display in settings
- Page rotation and tap zones
- Configurable widgets
- Several devices support
Widgets are the easiest way in: drop a directory under widgets/ with a manifest and a
script, keep it small and dependency-free, and test it with ANSIKO_COLS and
ANSIKO_ROWS set. Read the widget guide first.
GPL-3.0-or-later.
