From 25b985c7a2ce08ac7a671468a0fa0990ffb423d3 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Fri, 5 Jun 2026 20:08:34 +0200 Subject: [PATCH] Initial SERMO-BBS: a telnet PC that boots SERMO-DOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A rubbs-based telnet server that drops every caller into a simulated PC: it POSTs, boots, loads SERMO-DOS, and lands at a C:\> prompt. Built-in DOS commands (VER/DATE/TIME/ECHO/MEM/DIR/CLS/HELP/EXIT) behave; anything else is routed to Sermo::Oracle as a question — the hook for a future LLM. Built on rubbs' configurable BBS::FakeOS engine; the whole OS lives in lib/sermo/dos.rb as a FakeOS.define block, so new character-mode OSes are added as config files, not engine changes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 ++ Dockerfile | 17 ++++++ Gemfile | 3 + Gemfile.lock | 21 +++++++ README.md | 64 +++++++++++++++++++++ docker-compose.dev.yaml | 13 +++++ docker-compose.yaml | 11 ++++ entrypoint.sh | 4 ++ env-example | 3 + lib/sermo/dos.rb | 119 ++++++++++++++++++++++++++++++++++++++++ lib/sermo/oracle.rb | 32 +++++++++++ sermo.rb | 19 +++++++ 12 files changed, 310 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 docker-compose.dev.yaml create mode 100644 docker-compose.yaml create mode 100755 entrypoint.sh create mode 100644 env-example create mode 100644 lib/sermo/dos.rb create mode 100644 lib/sermo/oracle.rb create mode 100644 sermo.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..464a821 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.bundle/ +/vendor/bundle/ +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2476f61 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM ruby:3.3-alpine + +RUN apk add --no-cache git build-base + +WORKDIR /app + +COPY Gemfile Gemfile.lock ./ +RUN bundle install + +COPY sermo.rb entrypoint.sh ./ +COPY lib/ ./lib/ + +RUN chmod +x entrypoint.sh + +EXPOSE 2323 + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..499b12f --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'bbs', git: 'https://git.teletype.hu/tools/rubbs.git' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..ec450cb --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,21 @@ +GIT + remote: https://git.teletype.hu/tools/rubbs.git + revision: 4d38046c7056e0edbad02f7046ce83522124dccd + specs: + bbs (0.4.0) + artii (~> 2.1) + +GEM + remote: https://rubygems.org/ + specs: + artii (2.1.2) + +PLATFORMS + arm64-darwin-22 + ruby + +DEPENDENCIES + bbs! + +BUNDLED WITH + 2.5.22 diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ca0e6e --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# SERMO-BBS + +A telnet server that drops every caller into a simulated PC. It POSTs, boots, +loads **SERMO-DOS**, and leaves you at a `C:\>` prompt. Built-in DOS commands +behave like the real thing; anything else you type is treated as a question and +answered by SERMO's conversational core. + +Built on the [rubbs](https://git.teletype.hu/tools/rubbs) gem, using its +configurable `BBS::FakeOS` character-mode OS simulator. + +## Running + +**Directly (Ruby 3.x):** + +```bash +bundle install +ruby sermo.rb +``` + +**Docker Compose:** + +```bash +docker compose up --build # exposes telnet on host port 2324 +``` + +Connect: + +```bash +telnet localhost 2323 # or 2324 with docker compose +``` + +## Configuration + +| Variable | Default | Description | +|------------|---------|--------------------------------| +| `BBS_PORT` | `2323` | TCP port to listen on | +| `BBS_IDLE` | `900` | Idle seconds before disconnect | + +## Layout + +``` +sermo.rb entry point — config, flow, BBS.start +lib/sermo/dos.rb the MS-DOS profile (BBS::FakeOS.define): boot sequence, + DOS commands, and the on_unknown → conversation hook +lib/sermo/oracle.rb the conversational core (canned replies for now) +``` + +## Talking to SERMO / wiring an LLM + +At the `C:\>` prompt, `VER`, `DATE`, `TIME`, `ECHO`, `MEM`, `DIR`, `CLS`, +`HELP` and `EXIT` are real commands. Type anything else and it goes to +`Sermo::Oracle.answer`, which currently returns canned, in-character replies. + +To make SERMO actually think, replace the body of `Sermo::Oracle.answer` +(`lib/sermo/oracle.rb`) with a call to your model and return its text — that +method is the only integration point. + +## Adding more operating systems + +A simulated OS is pure configuration: a `BBS::FakeOS.define { … }` block with a +`prompt`, `colors`, a `boot` sequence, a `command` table and an `on_unknown` +fallback. To add another character-mode OS (CP/M, an AmigaDOS CLI, …), drop a +new file in `lib/sermo/` and point the flow at it in `sermo.rb`. No changes to +the rubbs engine are needed. diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000..85f261d --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,13 @@ +services: + sermo: + build: . + container_name: sermo-bbs + ports: + - "2324:2323" + volumes: + - ./:/app/ + environment: + - BBS_PORT=2323 + restart: unless-stopped + stdin_open: true + tty: true diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..6e76520 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +services: + sermo: + build: . + container_name: sermo-bbs + ports: + - "2324:2323" + environment: + - BBS_PORT=2323 + restart: unless-stopped + stdin_open: true + tty: true diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..07d0765 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +bundle update bbs +exec bundle exec ruby sermo.rb diff --git a/env-example b/env-example new file mode 100644 index 0000000..49d2525 --- /dev/null +++ b/env-example @@ -0,0 +1,3 @@ +# Copy to .env (used by docker compose) and adjust as needed. +BBS_PORT=2323 +BBS_IDLE=900 diff --git a/lib/sermo/dos.rb b/lib/sermo/dos.rb new file mode 100644 index 0000000..0f5f6c0 --- /dev/null +++ b/lib/sermo/dos.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'bbs' +require 'time' +require_relative 'oracle' + +module Sermo + # An MS-DOS-flavoured machine for SERMO-BBS: pretend you sat down at an old + # PC, watched it POST and boot, and landed at a C:\> prompt. Built-in DOS + # commands behave; anything else is treated as a question and handed to + # Sermo::Oracle (the conversational core). + # + # This whole OS is data: to add another character-mode OS later, write a new + # BBS::FakeOS.define block in its own file — no engine changes needed. + DOS = BBS::FakeOS.define do + name 'SERMO-DOS' + prompt 'C:\\>' + + # Classic light-grey-on-black console palette. + colors text: "\e[0;37m", + bright: "\e[1;37m", + accent: "\e[1;32m", + error: "\e[1;31m", + dim: "\e[1;30m" + + # ── Power-on self test → boot ────────────────────────────────────────────── + boot do + cls + print 'Award Modular BIOS v4.51PG, An Energy Star Ally', delay: 0.3 + print 'Copyright (C) 1984-98, Award Software, Inc.', newline: 2, delay: 0.3 + print 'Main Processor : Intel 80486DX2 @ 66 MHz', delay: 0.2 + memtest 16_384, step: 4096, delay: 0.05 + print '', delay: 0.2 + print 'Detecting IDE Primary Master ... SERMO HDD 504MB', delay: 0.3 + print 'Detecting IDE Primary Slave ... None', delay: 0.2 + print 'Detecting Floppy Drive A: ... 1.44M, 3.5 in.', newline: 2, delay: 0.3 + print 'Verifying DMI Pool Data ...........', delay: 0.5 + print 'Starting MS-DOS...', newline: 2, delay: 0.8 + + print 'HIMEM is testing extended memory...done.', delay: 0.3 + print 'C:\\> LOADHIGH SMARTDRV.EXE', delay: 0.3 + print 'C:\\> MSCDEX.EXE /D:SERMO0', newline: 2, delay: 0.4 + + cls + big_banner 'SERMO DOS', font: 'standard' + print 'SERMO-DOS Version 6.22 (c) 1994-2026 Teletype Games', delay: 0.1 + print '' + end + + # Shown once, after boot, before the first prompt. + ready do + puts 'Type HELP for a list of commands — or just type a question and ask.' + puts '' + end + + # ── Built-in commands ─────────────────────────────────────────────────────── + command 'ver', help: 'Display the SERMO-DOS version' do |_args, io| + io.puts 'SERMO-DOS Version 6.22' + end + + command 'cls', help: 'Clear the screen' do |_args, io| + io.cls + end + + command 'date', help: 'Display the current date' do |_args, io| + io.puts Time.now.strftime('Current date is %a %m-%d-%Y') + end + + command 'time', help: 'Display the current time' do |_args, io| + io.puts Time.now.strftime('Current time is %H:%M:%S') + end + + command 'echo', help: 'Display a message' do |args, io| + io.puts(args.empty? ? 'ECHO is on.' : args) + end + + command 'mem', help: 'Display memory usage' do |_args, io| + io.puts ' 655360 bytes total conventional memory' + io.puts ' 655360 bytes available to MS-DOS' + io.puts ' 643072 largest executable program size' + io.puts ' 15728640 bytes total contiguous extended memory' + end + + command 'dir', help: 'List the directory' do |_args, io| + io.puts ' Volume in drive C is SERMO' + io.puts ' Directory of C:\\' + io.puts '' + io.puts 'COMMAND COM 54 645 06-05-26 6:22a' + io.puts 'SERMO SYS 12 080 06-05-26 6:22a' + io.puts 'AUTOEXEC BAT 128 06-05-26 6:22a' + io.puts 'CONFIG SYS 256 06-05-26 6:22a' + io.puts ' 4 file(s) 67 109 bytes' + io.puts ' 503 312 384 bytes free' + end + + command 'help', '?', help: 'Show this help' do |_args, io| + io.puts 'Supported commands:' + io.puts '' + io.puts ' VER DATE TIME ECHO MEM' + io.puts ' DIR CLS HELP EXIT' + io.puts '' + io.puts 'Anything else you type is treated as a question and answered.' + end + + command 'exit', 'quit', 'logoff', help: 'Disconnect' do |_args, io| + io.puts 'Goodbye.' + io.halt + end + + # ── Everything else is a conversation ──────────────────────────────────────── + on_unknown do |input, io| + io.newline + Sermo::Oracle.answer(input, io.ctx).each_line do |line| + io.puts " #{line.chomp}", style: :bright + end + io.newline + end + end +end diff --git a/lib/sermo/oracle.rb b/lib/sermo/oracle.rb new file mode 100644 index 0000000..2070d10 --- /dev/null +++ b/lib/sermo/oracle.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Sermo + # The conversational core of SERMO-DOS. + # + # Anything typed at the C:\> prompt that is not a built-in DOS command is + # routed here (via the FakeOS `on_unknown` hook). For now it returns canned, + # in-character replies so the machine feels alive. + # + # THIS IS THE LLM INTEGRATION POINT: to make SERMO actually think, replace + # the body of `answer` with a call to your model (e.g. the Claude API) and + # return its text. Nothing else in the project needs to change. + module Oracle + def self.answer(question, _ctx = {}) + text = question.to_s.strip + case text.downcase + when /\b(hello|hi|hey|szia|hell[oó])\b/ + 'Greetings, caller. SERMO-DOS is listening.' + when /\bwho are you\b/, /\bmi vagy\b/ + 'I am SERMO-DOS — a conversational disk operating system.' + when /\b(thanks|thank you|k[oö]sz)\b/ + 'You are welcome.' + else + [ + 'My conversational core is not yet connected to a brain.', + %(You said: "#{text}"), + '(Wire Sermo::Oracle.answer to an LLM to make me think.)' + ].join("\n") + end + end + end +end diff --git a/sermo.rb b/sermo.rb new file mode 100644 index 0000000..67abffb --- /dev/null +++ b/sermo.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'bbs' +require_relative 'lib/sermo/dos' + +# SERMO-BBS — a telnet server that drops every caller into a simulated PC: +# it boots, loads SERMO-DOS, and leaves you at a C:\> prompt you can talk to. +BBS.configure do |config| + config.port = (ENV['BBS_PORT'] || 2323).to_i + config.mouse = false + config.idle_seconds = (ENV['BBS_IDLE'] || 900).to_i +end + +# A caller's whole session is just the simulated machine. +BBS.config.flow = BBS::Flow.define do + os Sermo::DOS +end + +BBS.start