Rabbot code conventions

Navigation for AI and new contributors: AGENTS.md, LIB_INDEX.md, CONTRACTS.md.

Keep plugins small and DRY โ€” reuse lib/ helpers instead of copying code.

Shared libraries

Library Use for
lib/irc_format.rb color, decorate, bold, muted, tag, bracket_label, menu_heading, divider, nick, underline, report_header, report_footer
lib/irc_reply.rb IrcReply.card โ€” compose Ibis-style reply cards
lib/flood_safe.rb Long/multi-line IRC replies (flood_safe_reply)
lib/normalize.rb Normalize.nick, Normalize.user_key, Normalize.channel
lib/rabbot_http.rb RabbotHttp.fetch, RabbotHttp.fetch_json + User-Agent
lib/list_command.rb ListCommand.parse_add_rem for add/rem/list plugins
lib/rabbot_db.rb PostgreSQL persistence (see RABBOT_DB.md)
lib/game.rb Game plugins: credits, cards, leaderboard, IRC game styles
lib/ai_summary.rb AI reply footers and build-success formatting
lib/ai_guard.rb Prompt-injection / untrusted-input checks for AI plugins
lib/text_util.rb squish, truncate, title_words, format_count
lib/search_query.rb SearchQuery.parse โ€” normalize catalogue search queries
lib/search_report.rb SearchReport card builders + SearchLookup.format dispatch
lib/cursor_agent_questions.rb Parse askQuestion events from cursor-agent stream-json
lib/directchat_session_codec.rb stringify/symbolize session payloads, sanitize_line
lib/directchat_mirc_parse.rb popup_item_line?, safe_read for mIRC .ibis scripts

Do not duplicate

IRC presentation

Rabbot targets Undernet with mIRC-class clients. Aim for award-winning ASCII art on the frame around a reply โ€” not on the content itself.

Clean body text (important)

Keep main text plain. Colour and ASCII art belong on:

The readable payload (forecast text, joke punchline, wiki summary, list items) should be uncoloured plain text unless one word truly needs emphasis (e.g. a nick). Think fserve layout: flashy header, plain file list, small grey footer.

ยซBOMยป                                   โ† bracket label (IrcFormat.bracket_label / tag)
-=// Brisbane ยท issued 14:30 \\=-       โ† menu heading (IrcFormat.menu_heading)
Fine. Partly cloudy. Max 28.             โ† plain body
(forecast ยท Bureau of Meteorology)       โ† accent_paren footer

Reference implementations: plugins/bom.rb, plugins/news.rb, lib/car_report.rb, lib/irc_reply.rb, plugins/silence.rb, plugins/cocktail.rb, plugins/fart.rb, plugins/cursorusage.rb.

Not: every line wrapped in \003 codes.

Prefer IrcReply.card or IrcFormat.report_header / report_footer for reply frames; use IrcFormat.divider for section breaks. Leave body lines unstyled.

Character set (ASCII-first)

Many IRC clients mishandle UTF-8 in channel text. Prefer widely compatible characters for art and borders:

Safe (use freely) Use with care
Aโ€“Z aโ€“z 0โ€“9 Box drawing โ•ญโ”€โ•ฎโ”‚โ”” (pretty in mIRC/HexChat; may break elsewhere)
= - _ | / \ [ ] ( ) < > Emoji and symbols outside Latin-1
# * @ . , : ; ' " ! ? + & % ^ ~ Fancy Unicode bullets, arrows, block elements

When in doubt, build banners from +, -, and | (fserve-style) rather than Unicode box glyphs. A +---+ frame beats mojibake on a phone client.

Colour contrast

Always pair foreground and background so text stays readable:

Reset with IRC_RESET after every coloured segment.

Extended mIRC colours (16โ€“98)

Beyond the 16 theme colours (0โ€“15), mIRC defines 83 fixed RGB colours (16โ€“98) that look the same for all users. IrcFormat.color accepts any index 0โ€“98:

STYLE_ACCENT = IrcFormat.color(52)        # bright red foreground
STYLE_PANEL  = IrcFormat.color(15, 88)    # light grey on near-black
STYLE_LINK   = IrcFormat.color(59, 98)    # blue on white

Highlights: 52โ€“63 vivid primaries, 64โ€“75 pastels, 76โ€“87 light tints, 88โ€“98 greyscale. See IRC formatting docs for the full table. Prefer extended colours for accents and gradients; keep body text on 0โ€“15 for broad client support.

Use IrcFormat (require_relative "../lib/irc_format"):

IRC_CTRL = IrcFormat::CTRL
IRC_RESET = IrcFormat::RESET
STYLE_TITLE = IrcFormat.named_style(:title)   # extended 52,88
STYLE_CLASSIC = IrcFormat.named_style(:classic_title)  # theme-dependent 0,4

def self.decorate(text, style)
  IrcFormat.decorate(text, style)
end

Channel opt-in: !lolcat on pipes themed replies through installed lolcat (ANSIโ†’IRC via AnsiToIrc). Body rainbow is intentional when enabled.

mIRC colour palette (0โ€“15)

Code Name Typical use in Rabbot
0 white foreground on coloured backgrounds
1 black dark text on yellow highlights
3 green plain success text
4 red titles, errors, card suits
5 maroon loss / negative
7 olive push / neutral game result
8 yellow hints
10 aqua (teal) nicks, regions, highlighted values
12 blue links, conditions, titles
13 fuchsia nicks (games), duration accents
14 grey separators (โ€”), empty bar slots
15 light grey body text, labels

Background colours use the same index range (0โ€“15 theme, 16โ€“98 fixed). Format: \003fg,bg via IrcFormat.color(fg, bg) โ€” codes are zero-padded to two digits.

Undernet style sheet

Element Convention
Tag IrcFormat.bracket_label("BOM") or IrcFormat.tag("BOM") โ†’ ยซBOMยป on line 1
Banner IrcFormat.menu_heading("Brisbane") โ†’ -=// Brisbane \=- on line 2
Card IrcReply.card(tag:, title:, body_lines:, footer_parts:) โ€” preferred composer
Divider IrcFormat.divider("Weather") โ†’ ======== Weather ========
Inline meta ยท between fields: Queensland ยท 2 headlines ยท 14:05 (header/footer only)
Label โ€” value In headers only: Duration โ€” 5 minutes with muted label
Nick IrcFormat.nick("MeatAnt", op: true) โ†’ @MeatAnt in aqua
Footer IrcFormat.report_footer(...) โ†’ accent_paren strip, last line
Reply size 3โ€“6 lines: tag + heading + plain body + optional footer
Extended accents 52โ€“63 brights on 88โ€“92 dark for headings; body stays plain

Avoid on Undernet: emoji-heavy output, rainbow per-word colouring, Markdown, colouring entire paragraphs, Unicode art in body lines. Some channels use +c (no colours) โ€” plain body still works.

Classic IRC aesthetics (what to emulate)

Source Look and feel
Channel topics Coloured title band only; topic text often plain after the header
Fserve announcers Styled welcome box + trigger; plain file/command list; grey credit line
XDCC bots Coloured #12 and headers; plain filenames; status footer
Stats / sysinfo ASCII bar in margin; numbers plain (btop.rb)

Patterns used across plugins

Pattern Example plugins Notes
Tag + banner bom.rb (reference) IrcReply.card or report_header
Box banner silence.rb, animal_news.rb menu_heading with title colour on heading line
Label โ€” value bom.rb, silence.rb, rabbot_user.rb Label (muted) โ€” (grey sep) value (highlight)
Bracketed label fserve/XDCC style [Section] with bg colour; body text reset to plain white
Inline nick highlight autoop.rb, rabbot_user.rb, game.rb nick colour + IRC_RESET + body colour
Footer / meta line ai_summary.rb, ai.rb inline coloured tokens separated by ยท
ASCII bars / charts btop.rb, wheel.rb [###.....] blocks; keep width modest for IRC
List lines autoop.rb, news.rb comma-separated coloured items, reset between entries
Role Pair Used in
Title / banner (0, 4) red bom, silence, cursorusage
Win / success (0, 3) green game, animal_news banner
Loss (0, 5) maroon game
Nick / place (0, 10) aqua autoop, rabbot_user, bom
Count / number (1, 8) black on yellow autoop
Body (15) or (12) descriptions, links
Muted meta (15, 14) white on grey timestamps, issued dates
Separator char (14) grey em dash between fields

Always IRC_RESET after each coloured segment so later text is not tinted. Name styles STYLE_TITLE, STYLE_NICK, STYLE_SEP, etc. at class level โ€” see plugins/bom.rb and plugins/autoop.rb.

Plugin checklist

  1. # Help: description at top for !help (see plugins/help.rb). If the plugin is loaded on multiple bots, add โ€” responders: <scope> (see Responder scope below).

  2. require_relative "../lib/flood_safe" + include FloodSafe for long output; add flood_safe_theme + FloodSafeTheme and themed_flood_safe_reply for theme-ready plugins

  3. Extract testable logic into self. methods; keep execute thin

  4. Prefer RabbotDb over in-memory hashes for data that should survive restarts

  5. Check lib/ before adding a helper โ€” extend an existing module if it fits

  6. Style headings, footers, separators, and doodles only โ€” use IrcReply.card or IrcFormat.report_header; keep body text plain; see IRC presentation

  7. Theme-ready plugins โ€” register PaintSamples, wire FloodSafeTheme + themed_flood_safe_reply; see docs/THEME_MIGRATION.md

Theme migration (IRC frames)

Channel admins use !theme use <name> to recolour plugin output via PaintStore overrides. Migrated plugins call ThemeOutput.apply before sending (via FloodSafeTheme).

require_relative "../lib/flood_safe_theme"

include FloodSafeTheme

def execute(m, ...)
  themed_flood_safe_reply(m, format_reply(...))
end

Register sample output in lib/paint_samples/register_*.rb. Sprint backlog: docs/THEME_MIGRATION.md.

Progress gutter (multi-line replies)

FloodSafe defaults to with_progress: :auto โ€” prefixes each queued line/chunk with a 4-character gutter (01% โ€ฆ 100 ) when there are 2+ logical lines or 2+ flood chunks. Gutter text uses uniform aqua on dark blue (IrcFormat.color(11, 2)); channel themes may override via progress_percent paint rules.

themed_flood_safe_reply(m, format_reply(...))              # auto progress
flood_safe_reply(m, "error", with_progress: false)         # one-liner / errors
flood_safe_reply(m, header_and_body, skip_prefix_lines: 1) # e.g. !sing header

Body text after the gutter aligns at column 5 โ€” see ReplyLayout::PROGRESS_GUTTER_WIDTH. Use TextAlign.format_colon_rows / ReplyLayout.align_colon_body for label:value blocks.

Responder scope

Several bots share the same channels. If a plugin is included on more than one bot, declare who should reply and gate in execute with BotIdentity (silent early return โ€” no channel spam).

Scope Meaning Example
all_loaded Every bot that loads the plugin !ai <question>
owner Primary Rabbot bot only (bot_level: owner) !ai models
room_reader Steve (bot_role: room_reader) !chat control
nick_targeted Bot whose nick matches the command prefix !Dr_Weird get ai_model
character Profile-driven mention replies CharacterResponder
require_relative "../lib/bot_identity"

def execute(m, prompt)
  return if admin_command?(prompt) && !BotIdentity.owner_bot?(bot)
  # ...
end

Help line examples:

AI access

User-initiated AI commands (!ai, etc.) must check AiAccess.user_allowed? before calling agents:

Use lib/ai_access.rb. Internal system paths (room_reader refine, !chat autonomous mode, !rabbot plugin agent) are exempt but must not auto-execute builds from bots โ€” see ChannelTrust.

require_relative "../lib/ai_access"

return flood_safe_reply(m, AiAccess.denial_message) unless AiAccess.user_allowed?(
  channel: m.channel, user: m.user, network: bot.config.server, nick: m.user.nick
)

Tests

New plugin generation

rabbot_meta build/enhance prompts include IRC presentation, theme migration (FloodSafeTheme, PaintSamples, !theme), and column layout (ReplyLayout, progress gutter, colon alignment). Follow them when writing plugins by hand too โ€” see Theme migration and Progress gutter above.