---
title: RuBBS
url: 'https://wiki.teletypegames.org/engines/rubbs'
markdown: 'https://wiki.teletypegames.org/engines/rubbs.md'
lang: en
date: '2026-06-04'
description: 'rubbs is our Ruby gem for building telnet bulletin boards: a login dialogue in ten lines, a full-screen board with windows, menus and mouse in a few hundred, and the same board on a Commodore 64 on the same port. All eleven boards of our BBS network run on it. This manual describes rubbs 1.0.0. Ev…'
taxonomy:
  tag:
    - engine
    - highlighted
---

# RuBBS

> rubbs is our Ruby gem for building telnet bulletin boards: a login dialogue in ten lines, a full-screen board with windows, menus and mouse in a few hundred, and the same board on a Commodore 64 on the same port. All eleven boards of our [BBS network](https://wiki.teletypegames.org/services/bbs-network) run on it. This manual describes **rubbs 1.0.0**. Every class and method is documented in the source: the **[API reference (RDoc)](https://wiki.teletypegames.org/rdoc/bbs/latest/)** is rebuilt from it on every release tag.

# What you get

- **Three layers in one call** — `Flow` for line dialogues (login, wizards), `Application` for a desktop with windows, menus and widgets, and the PETSCII `Screen` with its `Selector` cursor menus for a line-by-line board on a Commodore 64 or a modern terminal; a call moves between them freely.
- **Turbo Vision on a telnet line** — windows that move, size, zoom and switch, a menubar with hotkeys, a statusbar, forms, master-detail browsers, chat streams, five colour themes and a Markdown renderer, all clickable with the mouse.
- **A Commodore 64 face** — `session.petscii!` turns the line into 40×25 PETSCII while the code keeps writing UTF-8 and ANSI; `BBS::Terminal` asks each caller which terminal is calling and starts the modern or the C64 face.
- **Doors and gateways** — simulated machines (`FakeOS`), flows and any program as doors, telnet gateways that hand the line on to another board (a C64 can even call an ASCII board), ZMODEM and XMODEM downloads.
- **AI characters** — a persona in a YAML file talking through any OpenAI-compatible model, streamed word by word, with canned replies when the model is away.
- **The blocks of a content board** — an SQLite file with migrations, bounded HTTP, a cache, rate limits, a paced Wikipedia client, one log format and background work with retry.
- **Made for the real line** — telnet negotiation, window size, SGR mouse, idle, session and per-address limits, control-free input, a gateway that calls only public addresses, and a door's crash never ends the call.

# The manual

| Page | What you find there |
|---|---|
| [Minimal project](https://wiki.teletypegames.org/engines/rubbs/minimal-project) | The smallest complete project: a question-and-answer board in four files, Dockerfile, compose, running on Windows, Linux and macOS |
| [Getting started](https://wiki.teletypegames.org/engines/rubbs/getting-started) | Installation, a first board, `BBS.configure` and every setting, the server, a real board's project layout, Docker |
| [Flow](https://wiki.teletypegames.org/engines/rubbs/flow) | The line-dialogue DSL step by step: ask, say, menu, confirm, gate, persist, call, screens, banners, sub-flows |
| [Application](https://wiki.teletypegames.org/engines/rubbs/application) | The desktop: the application DSL, menubar, statusbar, helpers and services, the run loop, events and focus |
| [Windows](https://wiki.teletypegames.org/engines/rubbs/windows) | Windows and the window DSL; Info, Form, MasterDetail, Stream; dialogs; zoom, move, size, switch |
| [Widgets](https://wiki.teletypegames.org/engines/rubbs/widgets) | Every widget and container, scenes, and writing your own widget |
| [Drawing](https://wiki.teletypegames.org/engines/rubbs/drawing) | Themes and their colour roles, the palette, the FrameBuffer, the renderer, Markdown |
| [Doors](https://wiki.teletypegames.org/engines/rubbs/doors) | Doors, simulated machines (`FakeOS`), telnet gateways (`Gateway`), ZMODEM and XMODEM |
| [AI characters](https://wiki.teletypegames.org/engines/rubbs/ai) | Personas, the model client, chat with history and fallbacks, the typewriter |
| [Commodore 64](https://wiki.teletypegames.org/engines/rubbs/commodore) | PETSCII, the terminal question, the PETSCII `Screen` and `Selector`, and a board with a modern and a C64 face |
| [Art and graphics screens](https://wiki.teletypegames.org/engines/rubbs/art) | Drawings as YAML for both faces, the C64 graphics canvas and a cursor menu on it, scenes with live hotspots, seasonal layers, `art-preview` |
| [Telnet and the store](https://wiki.teletypegames.org/engines/rubbs/telnet-and-store) | The session: negotiation, keys, mouse, lines, limits; `BBS::Store` on SQLite |
| [Data and network](https://wiki.teletypegames.org/engines/rubbs/data-and-network) | `BBS.log`, `BBS.background`, `BBS::SQLite`, `HTTP`, `Cache`, `RateLimit`, `Wikipedia`, `Text`, `Address`, and where an error is handled |
| [Changelog](https://wiki.teletypegames.org/engines/rubbs/changelog) | What each version brought, and what 1.0 removed |

# A first board

The gem is called `bbs`. It listens on a TCP port, negotiates telnet with every caller, gives each one a `Session`, and runs the flow you configure:

```ruby
require 'bbs'

BBS.configure do |c|
  c.log_tag = 'my-bbs'
  c.flow = BBS::Flow.define do
    big_banner 'My BBS'
    ask :name, prompt: 'Your name'
    say 'Welcome!', style: :success
  end
end

BBS.start
```

`ruby bbs.rb`, then `telnet localhost 2323`. From there, [Getting started](https://wiki.teletypegames.org/engines/rubbs/getting-started) builds it into a board with windows and a C64 face.

# The three layers

| Layer | What it is | Good for |
|---|---|---|
| `BBS::Flow` | a line-by-line dialogue, declared step by step | logins, registration, wizards, the glue of a call |
| `BBS::Application` | a desktop: menubar, windows, widgets, mouse | a whole board on a modern terminal |
| `BBS::Petscii::Screen` + `BBS::Selector` | a line-by-line screen of 40×25 (or a modern terminal's size): titles, pages, cursor menus, prompts, the editor | a board's C64 face, or a whole board that reads line by line |

A call usually starts in a flow (the terminal question, the login) and continues in an application or on a PETSCII screen; doors and gateways step out of it and come back. Until 1.0 there was a fourth layer, `BBS::TUI`, for single full-screen pages; no board used it, and 1.0 removed it (see the [Changelog](https://wiki.teletypegames.org/engines/rubbs/changelog)).

```mermaid
flowchart LR
  S[TCP server] --> SE[Session: telnet, window size]
  SE --> F[Flow]
  F --> A[Application]
  F --> P[PETSCII Screen + Selector]
  A <--> D[Door: FakeOS, Flow, program]
  A <--> G[Gateway: another board]
  P <--> G
  F --> F2[Sub-flow]
```

# Who uses it

| Board | What rubbs gives it |
|---|---|
| [Teletype BBS](https://wiki.teletypegames.org/services/teletype-bbs) `:2323` | the whole desktop and its C64 face; SERMO and Chat of Ra are FakeOS doors with AI characters, the C64 and Plus/4 doors FakeOS machines |
| [Impostor BBS](https://wiki.teletypegames.org/services/impostor-bbs) `:2324` | a flow with its own screens, in a gem, so another board can run it as a door too |
| [TTG Wiki BBS](https://wiki.teletypegames.org/services/wiki-bbs) `:2325` | a two-pane wiki browser over the wiki's JSON API |
| [Alexandria BBS](https://wiki.teletypegames.org/services/alexandria-bbs) `:2326` | Wikipedia (`BBS::Wikipedia`) in master-detail windows, and in PETSCII |
| [Feedia BBS](https://wiki.teletypegames.org/services/feedia-bbs) `:2327` | an RSS reader with forms to the admins, and in PETSCII |
| [TTG Blog BBS](https://wiki.teletypegames.org/services/ttg-blog-bbs) `:2328` | a blog on `BBS::SQLite`, line by line on both faces |
| [WarpEngine BBS](https://wiki.teletypegames.org/services/warp-engine-bbs) `:2329` | the C64 and Plus/4 shelves, ZMODEM and XMODEM downloads |
| [Scriptorium BBS](https://wiki.teletypegames.org/services/scriptorium-bbs) `:2330` | Project Gutenberg's books through `BBS::HTTP` and `BBS::Cache` |
| [Commodex BBS](https://wiki.teletypegames.org/services/commodex-bbs) `:2331` | C64 games from Wikidata and Wikipedia, tips rate-limited with `BBS::RateLimit` |
| [Pixelia BBS](https://wiki.teletypegames.org/services/pixelia-bbs) `:2332` | an ANSI and PETSCII gallery and a shared canvas (`Petscii::Canvas`, `Art`) |
| [TTG Gate BBS](https://wiki.teletypegames.org/services/ttg-gate-bbs) | one port for the network and a directory of the world's boards, all by `BBS::Gateway` (`translate:` for a C64 calling an ASCII board) |

# Building blocks of a line-by-line board

Since 0.29.0 rubbs carries what every line-by-line board of ours used to write for itself. All of them are in the [API reference](https://wiki.teletypegames.org/rdoc/bbs/latest/); the [Commodore 64](https://wiki.teletypegames.org/engines/rubbs/commodore) page shows them at work.

| Class | What it is |
|---|---|
| `BBS::Petscii::Screen` | the 40×25 screen of a C64 call: `title` (reversed bar), `say(text, color:)`, `rule(color:)`, `page` (20 lines a page, RETURN for more, optional `2/5` page numbers), `pick` (a numbered `BBS::Selector` list), `menu`, `actions` (a row of keys), `choose` (one key of a few), `ask` (`BBS::Field`), `edit` (`BBS::Editor`, `nil` when the caller quits), `pause`, `key`, `bell`, `plain`. Subclass it for the board's own methods; `cols:`/`rows:` make it serve a modern terminal too |
| `BBS::Selector` | the one cursor menu: numbered, the cursor keys and the digits light an option, RETURN takes it; a Flow's `menu`, every menu of a Screen and the canvas menu are all Selectors, coloured by `Selector::COLORS` |
| `BBS::Petscii::Colors` | the C64 names of the `BBS::Ansi` palette: `WH YE CY LG GR DG LB BL GN RD PU OR`, `RV`/`RO` for reverse on/off. `Colors.install(self)` in a board's namespace module gives every class nested in it the bare names |
| `BBS::BlockFont` | the 3×5 block font of the logos (A–Z, 0–9, space and a few signs): `rows`, `half_blocks` for a modern face, `petscii` (reversed spaces, a colour a row) for a C64; `NARROW_I` and `WIDE_W` are the other letter shapes |
| `BBS::Goodbye` | the last screen of a call: `modern` ("Thanks for calling …", NO CARRIER) and `petscii` (reversed bar, `+++`, `ATH0`, NO CARRIER a moment apart) |
| `BBS::DiscordWebhook` | posts to a Discord webhook in a background thread, mentions off, a relay limit (20 posts in 10 minutes by default), timeouts, errors logged under the board's log tag; loads alone with `require 'bbs/discord_webhook'`, so a board's domain can use it |

```ruby
module UI
  module Petscii
    BBS::Petscii::Colors.install(self)

    class Screen < BBS::Petscii::Screen
    end

    LOGO = BBS::BlockFont.petscii('WARP', colors: [LB, LB, CY, CY, WH], indent: 10)
  end
end

screen = UI::Petscii::Screen.new(runner.session)
book   = screen.pick('the shelves', books, &:title)
screen.say('Downloading…', color: YE)
BBS::Goodbye.petscii(screen, title: 'warpengine bbs', color: LB)
```

# Upgrading from 0.x

1.0 is the cleanup release: one way to do each thing. What a board usually has to change:

- `screen.say(text, GN)` → `screen.say(text, color: GN)`, `screen.rule(DG)` → `screen.rule(color: DG)`;
- `screen.editor(max, title)` → `screen.edit(title: title, max: max).to_s`;
- `Petscii::Screen::MENU` → `BBS::Selector::COLORS` (or nothing: it is the default);
- a gateway to a neighbour board on a private address → `public_only: false`;
- `BBS::TUI`, `BBS::Color`, `ProgressBar`, `Spinner`, `HBox`, `VBox`, `Panel`, `AnsiArt`, most Flow steps beside `call` and the colour-named Flow styles are gone.

The [Changelog](https://wiki.teletypegames.org/engines/rubbs/changelog) has the list in short; `UPGRADING.md` in the repository lists every removed or renamed API with its replacement and a grep that finds it.

# Installation

```ruby
gem 'bbs', git: 'https://git.teletypegames.org/engines/rubbs.git', tag: 'v1.0.0'
```

Ruby 3.2 or newer (the socket timeouts need `IO#timeout=`), with the `sqlite3` gem. Boards pin rubbs by tag or by git revision in their `Gemfile.lock`.

# Source layout

| File | Responsibility |
|---|---|
| `lib/bbs.rb` | the public surface: `BBS.configure`, `BBS.config`, `BBS.start`, `BBS.log`, `BBS.background` |
| `bbs/server.rb`, `bbs/session.rb`, `bbs/telnet.rb`, `bbs/telnet/` | accept loop, one thread per caller; telnet (the one IAC `Parser`), keys, mouse, lines |
| `bbs/petscii.rb`, `bbs/petscii/`, `bbs/terminal.rb` | the C64 line, its one glyph table and encoder, the Screen, the canvas; the modern-or-C64 question |
| `bbs/flow.rb`, `bbs/flow/`, `bbs/flow_runner.rb`, `bbs/banner.rb` | the Flow DSL, its step objects and their runner; banners |
| `bbs/selector.rb`, `bbs/selector/` | the cursor menu |
| `bbs/application.rb`, `bbs/menu.rb`, `bbs/statusbar.rb`, `bbs/focus_manager.rb`, `bbs/event.rb` | the Application layer |
| `bbs/widget.rb`, `bbs/container.rb`, `bbs/widgets/` | widgets, one a file |
| `bbs/window.rb`, `bbs/window_spec.rb`, `bbs/windows/*`, `bbs/dialogs.rb` | windows, the window DSL, the ready-made windows, dialogs |
| `bbs/ansi.rb`, `bbs/theme.rb`, `bbs/theme/presets.rb`, `bbs/frame_buffer.rb`, `bbs/renderer.rb`, `bbs/markdown.rb` | the palette and drawing |
| `bbs/art.rb`, `bbs/season.rb`, `bin/art-preview` | drawings for both faces |
| `bbs/door.rb`, `bbs/fake_os.rb`, `bbs/gateway.rb`, `bbs/gateway/`, `bbs/zmodem.rb`, `bbs/xmodem.rb` | doors, simulated machines, gateways, downloads |
| `bbs/ai.rb` | AI characters |
| `bbs/block_font.rb`, `bbs/goodbye.rb`, `bbs/discord_webhook.rb` | logo font, goodbye, Discord webhook posts |
| `bbs/sqlite.rb`, `bbs/store.rb`, `bbs/store/` | SQLite with migrations; the upsert store on it |
| `bbs/http.rb`, `bbs/cache.rb`, `bbs/rate_limit.rb`, `bbs/wikipedia.rb`, `bbs/background.rb`, `bbs/text.rb`, `bbs/address.rb` | the shared blocks of a content board |

# Repository

[engines/rubbs](https://git.teletypegames.org/engines/rubbs) — MIT licensed. Its `README.md` is the developer reference in one file, `UPGRADING.md` the way from 0.x to 1.0; this manual is the guided version.

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Engines](https://wiki.teletypegames.org/engines.md)
- Previous: [Inkwell](https://wiki.teletypegames.org/engines/inkwell.md)
- Next: [WarpEngine](https://wiki.teletypegames.org/engines/warp-engine.md)
- Children:
  - [AI characters](https://wiki.teletypegames.org/engines/rubbs/ai.md)
  - [Application](https://wiki.teletypegames.org/engines/rubbs/application.md)
  - [Art and graphics screens](https://wiki.teletypegames.org/engines/rubbs/art.md)
  - [Changelog](https://wiki.teletypegames.org/engines/rubbs/changelog.md)
  - [Commodore 64](https://wiki.teletypegames.org/engines/rubbs/commodore.md)
  - [Data and network](https://wiki.teletypegames.org/engines/rubbs/data-and-network.md)
  - [Doors](https://wiki.teletypegames.org/engines/rubbs/doors.md)
  - [Drawing and theming](https://wiki.teletypegames.org/engines/rubbs/drawing.md)
  - [Flow](https://wiki.teletypegames.org/engines/rubbs/flow.md)
  - [Getting started](https://wiki.teletypegames.org/engines/rubbs/getting-started.md)
  - [Minimal project](https://wiki.teletypegames.org/engines/rubbs/minimal-project.md)
  - [Telnet, session and store](https://wiki.teletypegames.org/engines/rubbs/telnet-and-store.md)
  - [Widgets](https://wiki.teletypegames.org/engines/rubbs/widgets.md)
  - [Windows](https://wiki.teletypegames.org/engines/rubbs/windows.md)
