Initial SERMO-BBS: a telnet PC that boots SERMO-DOS
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) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
/.bundle/
|
||||||
|
/vendor/bundle/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
+17
@@ -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"]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gem 'bbs', git: 'https://git.teletype.hu/tools/rubbs.git'
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
bundle update bbs
|
||||||
|
exec bundle exec ruby sermo.rb
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Copy to .env (used by docker compose) and adjust as needed.
|
||||||
|
BBS_PORT=2323
|
||||||
|
BBS_IDLE=900
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user