agentbbs/internal/plugin/plugin.go
Anthony Ettinger 481e715d89 fix(arcade): give ncurses games a TERM so they launch
The 80s arcade classics (Space Invaders/nInvaders, Pac-Man/pacman4console,
Tetris/tint, Moon Patrol/moon-buggy) are ncurses programs: initscr() fails
with "Error opening terminal" when TERM is unset. Game subprocesses were
built with exec.Command and no Env, so they inherited the agentbbs systemd
daemon's environment — which has no TERM — and every game exited before
drawing a frame. DOOM was unaffected because doom-ascii writes ANSI directly
and never touches terminfo.

Thread the client PTY's TERM through plugin.Context and hand each sandboxed
game a curated environment (TERM, PATH, HOME, LANG=C.UTF-8) instead of the
daemon's. Curating the env also stops leaking operator secrets (e.g.
COINPAY_API_KEY) into third-party game binaries.

Verified live on bbs.profullstack.com: Space Invaders and Pac-Man now render;
previously all four died instantly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 12:01:26 +00:00

53 lines
1.9 KiB
Go

// Package plugin defines the AgentBBS plugin contract (PRD §4.3).
//
// A plugin is one interface implementation plus one registration in the hub.
// Plugins return control to the hub by emitting ExitMsg, never by quitting
// the session.
package plugin
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/profullstack/agentbbs/internal/auth"
"github.com/profullstack/agentbbs/internal/sandbox"
"github.com/profullstack/agentbbs/internal/store"
)
// Context carries the shared services a plugin may use.
type Context struct {
Store store.Store
Sandbox *sandbox.Runner
// DataDir is the per-user persistent directory (members/agents only;
// empty for guests).
DataDir string
// AssetsDir is the read-only platform assets tree (wads, binaries).
AssetsDir string
// Host is the BBS hostname (e.g. bbs.profullstack.com), for building
// member homepage URLs (https://Host/~name) and similar links.
Host string
// Term is the client PTY's terminal type (e.g. xterm-256color). Needed by
// sandboxed ncurses games (Space Invaders, Pac-Man, Tetris, Moon Patrol),
// which call initscr() and fail with "Error opening terminal" if TERM is
// unset — the systemd daemon environment has no TERM to inherit.
Term string
}
// Plugin is the only integration point between a feature and the hub.
type Plugin interface {
// ID is a stable unique identifier, e.g. "arcade".
ID() string
// Title is the hub menu label.
Title() string
// Description is a one-line summary shown in the menu.
Description() string
// RequiresAuth reports whether guests are admitted.
RequiresAuth() bool
// New returns a fresh Bubble Tea model for one session.
New(user auth.User, ctx Context) tea.Model
}
// ExitMsg is emitted by a plugin model to hand the session back to the hub.
type ExitMsg struct{}
// Exit is a convenience command for plugins.
func Exit() tea.Msg { return ExitMsg{} }