Menus and the data-driven engine

Every menu shown to telnet / SSH / rlogin users is stored in the
database and editable from the web. There's no source code change
needed to add, remove, rename, or relink anything.

This covers the ANSI menu tree (BbsMenu/BbsMenuItem). PETSCII
(C64/128) sessions use a completely separate, sysop-buildable menu
tree at /admin/petscii-menus/ with a much smaller action-type set —
most ANSI actions (art, sixel, chat, most doors) have no PETSCII
equivalent, so the two trees are deliberately not shared. See
25 — PETSCII.

Where to edit

Admin → BBS Menus (/admin/bbs-menus/).

You'll see:

  • Menu tree — entry-point menus and which submenus they goto
  • All menus table with quick edit/delete

Anatomy of a menu

A menu has:

field meaning
name unique key (e.g. main, games, sysop_tools)
title shown in the auto-rendered banner
prompt text before the input cursor (Choice:)
ansi_screen raw ANSI/CP437 art shown above the menu (optional)
is_default this is the entry point after login
min_access hide menu from users below this access level

Menu items

Each item has:

  • hotkey — single character or short sequence
  • label — visible text
  • action_type — what to do when picked
  • action_args — passed to the action
  • min_access — hide if user is below this
  • sort_order, is_visible

Action types

action args what it does
goto name of another menu jump to another menu
door Game.id launch a registered door game
exec shell cmd or JSON run an external program (see doc 5)
ansi slot name (welcome / weather_intro / ...) display a sysop-defined ANSI screen
boards message boards UI
pm PM inbox
pm_send compose new PM
imsg Inter-BBS Instant Message (MSP) inbox
imsg_send compose new Inter-BBS Instant Message
bulletins bulletins
echo echomail areas
echo_post compose echomail
files file library
who who's online
multinode interactive multinode chat
oneliners last 10 callers + recent one-liners
lastcallers full, paginated Last Callers list
chat chat menu (IRC / shoutbox)
rss RSS reader
guru Ask Anet — searches the wiki (FTS5, not an LLM)
ebooks ebook reader (Gutenberg)
wall graffiti wall
games game center
dialout dial out to other BBSes
page page sysop
profile view profile
edit_prof edit profile
passwd change password
sysop sysop tools (admin only)
logoff end session

A couple of action types in more detail

Most action types are self-explanatory from the table above, but two
are easy to underestimate from a one-line description:

  • wall — the graffiti wall (anetbbs/features/wall.py). A
    shared, paginated bulletin board of short (1-2 line) messages any
    user can post to, rendered as an ANSI box that adapts its width to
    the caller's terminal (79 cols on standard 80-col, up to 131 wide).
    Posts support Synchronet/Mystic-style |XX pipe color codes, with
    an ASCII-only fallback for terminals that can't render CP437
    box-drawing characters. Sysops get an extra [D]el option to remove
    a post by ID. Not networked or moderated beyond that — it's a purely
    local, lightweight "who was here" wall, distinct from boards,
    echomail, and the shoutbox.
  • ebooks — the ebook reader (slug ebooks, anetbbs/web/ebooks.py
    for the web version, show_ebooks() in anetbbs/features/bbs_ui.py
    for the terminal version — both share the same backend). Lets users
    search and read free public-domain books from Project Gutenberg via
    Gutendex (gutendex.com, a community JSON search API over Gutenberg's
    catalog), plus a curated "classics" shelf for browsing without
    searching first. Book text is fetched once per title and cached
    forever (EbookCache); per-user bookmarks and reading history
    persist across sessions. The terminal reader offers the same
    classics/search/history/bookmarks flow as the web version, paginated
    for the terminal. This action is gated per-Game row by a
    terminal_enabled toggle (Admin → Games) independent of the web
    version's own web_enabled toggle, so a sysop can offer one without
    the other.

Adding a sub-menu

Click any menu's edit page → "Add a sub-menu (one click)" card.

Fill in:
- New menu key (games_arcade)
- Title (Arcade Games)
- Hotkey on this menu (A)
- Label (Arcade)

It creates the child menu, auto-adds a Q key on the child to come
back, and links from the parent. You can deep-nest as far as you
like.

Tips

  • The min_access field on items + menus implements
    Mystic/Synchronet-style tiers. Set a user's access_level in
    Admin → Users → Manage.
  • Setting is_default=True on a menu other than main lets you
    override the entry point.
  • The "Add sample items" button on the menu list adds a working
    weather door (curl wttr.in) + an ANSI replay so you can verify
    exec and ansi actions work without writing your own.

Behavior at the prompt

  • Single-key hotkeys. The prompt reads one keystroke and dispatches
    immediately — no Enter required. Bare Enter just redraws the menu.
    Backspace, arrow keys, and Ctrl-C are silently swallowed.
  • Screen is cleared between menus so the new menu doesn't render
    on top of the previous one.
  • Q (logoff) confirms with Y/N so a stray keystroke doesn't
    drop your session.

Display codes inside ansi_screen

The menu's ansi_screen content runs through the same Synchronet
@CODE@ and Mystic |XX substitution as standalone ANSI screens.
See doc 4 — ANSI screens for the supported code
table. Color pipe-codes (|07 etc.) are unaffected.