Commit graph

129 commits

Author SHA1 Message Date
c5489a458e
Merge pull request #49 from profullstack/feat/arcade-hangman
feat(arcade): add Hangman built-in game + leaderboard
2026-06-22 07:59:57 -07:00
93eaef1184 feat(arcade): add Hangman built-in game + leaderboard
Hangman joins Snake as a built-in, leaderboard-backed TUI game (PRD §5.1).
Endless mode: each solved word banks points (longer words and unused guesses
score more) and deals a fresh word with full lives; the run ends when one word
exhausts all six wrong guesses, persisting the total for members under the
"hangman" score key. Guests play without persisting, same as Snake.

Generalize the leaderboard board to take a game name and split the single
"Leaderboard" row into per-game "Leaderboard — Snake" / "Leaderboard — Hangman"
entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:59:06 +00:00
threebeats
a56f3afbca
fix: add GameNames to IsReservedName checks (#42)
GameNames ("game", "games") are reserved SSH route usernames for
AgentGames but IsReservedName did not check GameNames, allowing
members to register accounts that collide with SSH routes.

Fixes #40

Co-authored-by: root <root@vultr.guest>
2026-06-22 05:23:50 -07:00
threebeats
94846b91f3
fix: block shared/reserved IP ranges in SSRF guard (#47)
Adds Carrier-Grade NAT (100.64.0.0/10), benchmarking (198.18.0.0/15),
and documentation/example (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24)
ranges to the isBlockedIP check.

Go's net.IsPrivate() covers RFC1918 but not these shared/reserved ranges.

Fixes #43

Co-authored-by: root <root@vultr.guest>
2026-06-22 05:23:15 -07:00
threebeats
5f9d66e1a7
fix: reject malformed NNTP OVER ranges instead of returning all articles (#48)
parseRange previously returned (0, MaxInt64) for unparseable input,
causing OVER/XOVER to deliver the full article overview instead of
returning an empty result. Now returns (0, 0) for any parse error.

Fixes #45

Co-authored-by: root <root@vultr.guest>
2026-06-22 05:22:58 -07:00
threebeats
c0378ece3c
fix: remove outdated ssh irc@ route from README (#39)
The in-BBS ssh irc@ route no longer exists — internal/auth only reserves
'irc' as a name but has no handler for it. docs/irc.md already documents
this correctly.

- Removed ssh -t irc@ command and 'built-in IRC client' language
- Updated to reflect that members connect with their own client
- Kept correct native TLS and WebSocket connection examples

Co-authored-by: threebeats <threebeats@users.noreply.github.com>
2026-06-19 02:30:06 -07:00
d65c2bcecb
Merge pull request #36 from profullstack/fix/arcade-ncurses-term
fix(arcade): give ncurses games a TERM so they launch
2026-06-18 05:42:41 -07:00
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
Руслан Латыпов
beba7dc388
Fix unused and ineffectual variables (#32) 2026-06-16 03:21:39 -07:00
threebeats
9c2886c9b8
fix: case-sensitive flag comparison in AgentMail bot (#35) 2026-06-16 03:20:17 -07:00
337011fa03
refactor(pods): drop redundant tuneApt apt-sandbox hack (#31)
The rootless-podman default capability set (already on main) lets apt drop
to the _apt user on its own, so disabling the apt download sandbox via
tuneApt is dead code. Removing it; apt now works the proper way.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 08:23:54 -07:00
e0a267e343
Fix/pod public html bind mount (#30)
* feat(pods/admin): root admin alias, default-caps cleanup, pod rebuild script

- auth: add `root` as an admin-console route alias (alongside admin/sysop);
  still gated by $AGENTBBS_ADMINS — the name confers nothing on its own.
- pods: drop the now-redundant tuneApt apt-sandbox hack. Rootless podman keeps
  its default capability set, so apt/chown/su work without disabling the
  download sandbox.
- scripts/rebuild-pods.sh: recreate all member pods (keeps home volumes) so
  they pick up the current container profile on next `ssh pod@`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(arcade,ui): 80s arcade classics + shared menu theme

Arcade games (PRD §5.1), generalizing the sandboxed-PTY DOOM path into an
external-game registry: Space Invaders (nInvaders), Pac-Man (pacman4console),
Tetris (tint/vitetris), Moon Patrol (moon-buggy). Binaries resolve from
assets/bin, PATH, then /usr/games, so a distro install or a hand-built binary
lights each game up; missing games are skipped with a discovery hint. Installed
on the host via `scripts/fetch-assets.sh --arcade` (apt), wired into setup.sh
behind FETCH_ARCADE (default on).

UI: new shared ui.Theme.MenuItem widget (accent cursor + badge, description
shown only for the focused row) adopted by the hub and arcade menus; the hub
groups rows under Features/Sessions headers and the arcade under
DOOM/ARCADE/BUILT-IN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 08:06:32 -07:00
362b47fdde
Feat/members messaging (#29)
* fix(deploy): build Go binaries on the runner, ship them, SKIP_BUILD on box

The deploy SSHed into the ~458MB droplet and ran `go build` there. The Go
linker's peak memory OOM-killed the build — and with it the sshd serving the
deploy session — surfacing as "Connection closed by remote host" (exit 255).
It was flaky because it tracked momentary memory pressure from the co-resident
ergo/forgejo/tor/podman/agentbbs processes (run #25 passed, #26 failed on
near-identical code).

Build both binaries on the 16GB GitHub runner instead (pure-Go, modernc
sqlite, so CGO_ENABLED=0 static cross-build), scp them to the droplet, and run
setup.sh with SKIP_BUILD=1 so the box never compiles. Arch is detected from
the droplet so amd64/arm64 both work. setup.sh now also skips the Go toolchain
download when SKIP_BUILD=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(members): member directory + store-and-forward messaging

A members-only hub plugin (the BBS "who") plus user-to-user messaging:

- internal/store: messages table + SendMessage/Inbox/UnreadCount/MarkRead, and
  OnlineUsers (open sessions) for presence. MarkRead is recipient-scoped so a
  member can only clear their own mail.
- plugins/members: directory with online dots + last-seen, a finger-style
  profile view, a minimal compose box, and an inbox that marks read on open.
- ssh msg@host <user> [text]: scriptable CLI to leave a note (body from args or
  stdin), mirroring the existing finger route; "msg"/"message" are reserved.
- hub: "N unread" badge on login (hubMOTD). plugin.Context gains Host for member
  homepage URLs.

Extends the existing finger@ behavior (ssh <name>@host) rather than replacing it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 07:48:07 -07:00
d763d732a3
fix(deploy): build Go binaries on the runner, ship them, SKIP_BUILD on box (#27)
The deploy SSHed into the ~458MB droplet and ran `go build` there. The Go
linker's peak memory OOM-killed the build — and with it the sshd serving the
deploy session — surfacing as "Connection closed by remote host" (exit 255).
It was flaky because it tracked momentary memory pressure from the co-resident
ergo/forgejo/tor/podman/agentbbs processes (run #25 passed, #26 failed on
near-identical code).

Build both binaries on the 16GB GitHub runner instead (pure-Go, modernc
sqlite, so CGO_ENABLED=0 static cross-build), scp them to the droplet, and run
setup.sh with SKIP_BUILD=1 so the box never compiles. Arch is detected from
the droplet so amd64/arm64 both work. setup.sh now also skips the Go toolchain
download when SKIP_BUILD=1.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 07:09:57 -07:00
68899180a7
Merge pull request #26 from profullstack/fix/pod-public-html-selfheal
fix(pods): self-heal pre-existing pods to pick up the public_html bind
2026-06-15 06:50:22 -07:00
2d6fba5f16 fix(pods): self-heal pre-existing pods to pick up the public_html bind
The box auto-deploys (self-update timer pulls origin/main, rebuilds, restarts
agentbbs), but `systemctl restart agentbbs` only restarts the daemon — it never
touches the long-lived per-user pod containers. ensure() also short-circuits on
any container that already exists, so pods created before the homepage bind
landed would never gain the /home/dev/public_html mount without a manual
`podman rm`. That defeats the "everything happens automatically on push" goal.

Make ensure() self-healing: when a pod exists but lacks the public_html mount,
recreate it so the bind is applied. The named home volume survives `rm`, so the
member's files are kept. Only heal when the pod is idle (attached count 0) to
avoid pulling a running pod out from under an active session — an unbound pod
heals on its next idle attach. New hasMount() inspects the container's mounts.

Net effect: push to main -> self-update redeploys within the timer interval ->
the next `ssh pod@` recreates the pod with the bind. No manual step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:50:06 +00:00
cbe7a44bd5
Merge pull request #25 from profullstack/fix/pod-public-html-bind-mount
fix(pods): bind-mount host public_html into the pod so ~/public_html is served
2026-06-15 06:44:37 -07:00
06bc5631d8 fix(pods): bind-mount host public_html into the pod so ~/public_html is served
A member's pod home (/home/dev) is a named container volume, but Caddy serves
<name>.<host> from the host path <data>/users/<name>/public_html. The two were
disconnected, so editing ~/public_html/index.html in the pod never changed the
served page — contradicting the on-screen "edit ~/public_html to make it yours"
instruction.

Bind-mount <data>/users/<user>/public_html at /home/dev/public_html when pods
start. The host tree is created if absent so the mount source exists, and under
the docker fallback (uid 1000) the one-shot init container now also chowns the
bind path; rootless podman maps container root to the host service user that
already owns the tree, so no chown is needed there.

Detect now takes the host users dir; pass "" to disable the bind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:42:08 +00:00
lazyGPT07
c480e62f39
Fix NNTP IHAVE missing message ID (#24)
Co-authored-by: lazyGPT07 <lazyGPT07@users.noreply.github.com>
2026-06-15 01:34:13 -07:00
phucnguyen1707
36c6e0e275
Handle repeated NNTP command whitespace (#20) 2026-06-14 20:25:04 -07:00
phucnguyen1707
0944fa2cb4
Merge pull request #22 from phucnguyen1707/fix-over-single-article
Fix single article NNTP overview ranges
2026-06-14 20:24:50 -07:00
336ff00fa3
Merge pull request #15 from profullstack/feat/irc-store-auth
feat(irc): gate IRC on the BBS user store; remove ssh irc@; external clients only
2026-06-14 20:23:54 -07:00
7a90c50242
Merge pull request #18 from profullstack/fix/qryptinvite-flaky-tamper-test
Fix flaky TestTamperedTokenFails (qryptinvite) reddening main CI
2026-06-14 10:06:03 -07:00
169a73d180 Fix flaky TestTamperedTokenFails in qryptinvite
flipLastChar toggled only the last base64 char of the signature segment.
For a 64-byte Ed25519 signature that char carries unused trailing bits, so
the toggle could decode back to the same bytes and still verify — making the
"corrupted signature must fail" assertion flaky (it reddened main CI).

Corrupt a decoded signature byte (sig[0] ^= 0xFF) and re-encode instead, so
the signature always differs. Verified deterministic over 50 runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:05:30 +00:00
8113b6d60e
Merge pull request #17 from profullstack/feat/mailu-stack
Self-host Mailu mail stack at mail.profullstack.com
2026-06-14 09:56:34 -07:00
45c46db373
Merge pull request #16 from profullstack/feat/bbs-mail-reader
AgentMail: paid-member mailbox reader (TUI + bot mode) on the BBS
2026-06-14 09:56:28 -07:00
b304922451
Merge pull request #14 from profullstack/feat/bbs-logo-banner
Show the Profullstack </> ASCII logo on join@ and the hub
2026-06-14 09:56:22 -07:00
e165ecee26 Add AgentMail: paid-member mailbox reader (TUI + bot mode) on the BBS
internal/mailbox is a transport-agnostic mail client for Founding Lifetime
members: read, search, compose, send, reply, flag, delete.

- types/transport/client: paid-gated facade returning JSON-serializable
  structs (same shapes as @logicsrc/plugin-agentmail)
- memory.go: in-memory transport (tests/dev/reference) + tests
- imap.go/smtp.go: real backend — go-imap/v2 (Dovecot IMAP, master-user
  login) + net/smtp submission via the co-located relay; go-message parses
  bodies/attachments
- reader_tui.go: Bubble Tea reader for humans (list/read/flag/delete)
- bot.go: JSON in/out mode for agents (ssh mail@host list|read|send|…)
- wired as the paid "Mail" hub entry and the ssh mail@ route + auth.MailNames

Connects to the self-hosted Mailu stack at mail.profullstack.com /
smtp.profullstack.com. Rebased onto main; build + vet + tests clean (Go 1.26).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:49:04 +00:00
6b4db2b934 Use the Profullstack </> ASCII logo as the BBS banner
Replace the figlet "profullstack.com" wordmark with a scaled-down ASCII
rendition of the Profullstack </> mark (derived from the brand image),
shown on the join@ onboarding and the ssh <name>@ hub. Reuses the existing
banner plumbing; the mark is sourced from internal/brand and rendered in
Profullstack red.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:46:59 +00:00
cb09300357 Self-host Mailu mail stack at mail.profullstack.com
Members get a real <name>@mail.profullstack.com mailbox, served by a
co-located Mailu (Postfix+Dovecot+Roundcube+rspamd) Docker stack. Coexists
with the host Caddy: Mailu owns the mail ports; Caddy fronts the loopback
webmail and supplies the TLS cert (TLS_FLAVOR=mail), the same cert-copy
pattern as the Ergo/IRC and NNTP services.

- deploy/mailu/: docker-compose.yml, mailu.env.example, refresh-certs.sh
  (copy Caddy's mail cert into Mailu on renewal), provision-mailbox.sh
  (member mailbox + Dovecot gateway master user), README.
- setup.sh: MAIL flag + mail.${DOMAIN#*.} Caddy site + §9e (cert timer,
  mail-port firewall, conditional compose bring-up, AGENTBBS_MAIL_* env).
- docs/mail.md: architecture, DNS (MX/SPF/DKIM/DMARC/PTR), gateway
  master-user setup, env, provisioning, webmail-only policy.

Apex profullstack.com stays corporate; member mail is only on mail.*.
Infra is inspection-verified (bash -n, YAML lint); deploy pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:33:25 +00:00
243ef58e59 feat(irc): gate on BBS user store (loopback /irc-auth), drop ssh irc@
Rework the IRC access model to the user-level source of truth — the
bbs.profullstack.com user store — per the agreed design:

- Auth via the store, not OS users. New loopback endpoint /irc-auth (on the
  /verify server) answers {member,premium} from the store via UserByName. Ergo's
  auth-script now curls it instead of `getent passwd`; the OS-user (tilde.town)
  reconcile in setup.sh is reverted. require-sasl on, registration off.
- Remove the in-BBS `ssh irc@` route: delete internal/irc (in-process client +
  TUI), handleIRC/runIRC, the hub IRC menu entry, and the dispatch case. Members
  connect with an external client (or web) at irc.profullstack.com:6697 / wss.
  IRCNames kept only to reserve the "irc" name (IsIRCName removed).
- Keep external clients; keep the irc.profullstack.com hostname + cert work.

Premium channel creation: /irc-auth already returns each account's premium
status, but enforcement is NOT yet wired (the old irc@ /create was removed with
the route; Ergo can't gate creation per-account natively). operator-only-creation
stays off for now; server-side premium gating (a ChanServ-style bot) is a
documented follow-up. See docs/irc.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:24:38 +00:00
49db5e103d hub: ASCII banner, MOTD, ctrl+c→main-menu everywhere, Shell option
UX pass on the single-login hub:

- ctrl+c now always returns to the main menu from any in-hub plugin/game
  instead of being forwarded. Fixes the snake game being un-exitable (it never
  handled ctrl+c) and makes "back to menu" uniform across every screen.
- profullstack.com ASCII banner rendered atop the hub menu and the join@
  onboarding flow.
- MOTD box under the title (guest vs member tailored; AGENTBBS_MOTD overrides
  the body).
- New "Shell" hub entry drops straight into `bash -l` in the member's pod; the
  existing "Pod" entry now attaches to the pod's main session.
- Polish: highlighted selection + ❯ cursor, brand colors, clearer footer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:29:18 +00:00
5eb1e96480 feat(irc): irc.profullstack.com host, OS-user (tilde.town) gate, premium channels
Hostname: serve the network as irc.profullstack.com (new IRC_DOMAIN var,
default irc.<root-of-DOMAIN>). Caddy serves an irc.profullstack.com site so it
gets a Let's Encrypt cert; ergo-refresh-certs copies that into Ergo for 6697.
Needs an A record irc.profullstack.com -> the box (self-signed until it resolves).

Members are OS users (tilde.town model): setup.sh reconciles a real OS account
per member dir (root-side, on each deploy + the 15-min timer; nologin shell, so
identity-only — no shell access). The IRC auth-script now gates on
`getent passwd` with uid>=1000 instead of the member dir, so "OS user" == member.

Premium channels: free members may /join; creating channels is a premium perk.
The ssh irc@ client gains /create #name (premium-gated via ensurePremium): it
joins the fresh channel and registers it with ChanServ as the member's founder.
v1 gate is route-level (operator-only-creation left off); external-client
creation hardening is a follow-up. See docs/irc.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:23:44 +00:00
d4ada98b69 One login: pod/IRC/news/Tor as hub menu items + single-login onboarding
The join@ output and hub previously pushed members to ssh into separate
servers (ssh pod@, ssh irc@, ssh news@, ssh tor@). Members now reach
everything from one `ssh <name>@bbs.profullstack.com` login:

- hub: new SessionApp entries (Pod, IRC, News, Tor shell) run as
  terminal-takeover features via tea.Exec, then return to the menu.
  Gated by membership/email-verification/plan; shown locked otherwise.
- main: build the session apps in teaHandler; extract runIRC/runNews so
  the irc@/news@ routes and the hub share one path. The old pod@/irc@/
  news@/tor@ routes stay as aliases (handy for bots).
- onboarding: rewrite the "You're in" and Founding-Member copy to present
  ONE login and stop advertising separate servers.
- email: member mailboxes move to mail.profullstack.com (apex reserved
  for corporate mail); webmail shown at https://mail.profullstack.com.
- username: harden the default handle to a safe /home/<name> token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:22:12 +00:00
579999153e Merge remote-tracking branch 'origin/main' 2026-06-14 14:49:09 +00:00
017722d398 Merge feat/news-nntp-server: v0.1.0 + AgentGit provisioning wiring
Brings the AgentGit provisioning wiring (provisionGit hook in both email-verify
paths + setup.sh Forgejo backend section) and the v0.1.0 stack version onto main.
The internal/forgejo package was already on main but unwired; this connects it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:48:35 +00:00
903286f785 chore: version the agentbbs stack v0.1.0 for bbs.profullstack.com
Add Version const + `agentbbs version` subcommand; log version at startup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:48:19 +00:00
5d56e021cc wire AgentGit provisioning into verify flow + setup.sh
The internal/forgejo package landed without its call sites. Restore them:
- main.go: provisionGit() called after both email-verify paths (web link +
  interactive code), the forgejo.Config app field, and ConfigFromEnv wiring.
  Every verified member (free + paid) gets a git.profullstack.com account;
  failures are logged, never block verification; no-op when unconfigured.
- setup.sh §9d: install + run the Forgejo backend on a loopback port fronted
  by Caddy at https://git.<root>, members-only, with an admin token minted
  into agentbbs.env. Caddy vhost + done-banner + env template entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:38:51 +00:00
115df7418f
Merge pull request #11 from profullstack/feat/agentgit-provisioning
News (Usenet/NNTP) server + AgentGit Forgejo provisioning + $99 Founding Lifetime + join@ terms
2026-06-14 07:38:09 -07:00
f270a80d13 join@: require accepting acceptable-use terms (no illegal activity) before registering
New members must read a short terms notice and type "agree" before an
account is created. The terms state that AgentBBS is for lawful use only,
that illegal activity results in an immediate permanent ban (and may be
reported), and that members are responsible for what they and their agents do.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:35:32 +00:00
7a9e0751b4 chore: version the agentbbs stack (v0.1.0) for bbs.profullstack.com
Add Version const + `agentbbs version` subcommand; log version on startup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:30:52 +00:00
3b6b9a4a78 feat: members-only Usenet (NNTP) + Forgejo git provisioning + founding-lifetime $99
WIP feature branch: NNTPS news server, per-member Forgejo accounts on email
confirm, and founding-lifetime pricing tier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:26:23 +00:00
2a9d841ddb Rebrand paid tier to $99 Founding Lifetime Member
Replace the $10 "Premium" pitch with a $99 Founding Lifetime Member
offer (first 1,000 accounts) and list every bonus feature in the join@
upgrade prompt: personal email + webmail, custom domains, Tor access,
and lifetime locked-in pricing.

- payments: PremiumPriceLabel/default amount $10 -> $99, add FoundingCap
- join@ offer + already-paid welcome + gated-route upsell copy
- README + interface doc comments

Price stays env-overridable via AGENTBBS_PREMIUM_AMOUNT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:46:23 +00:00
52b06d8cfe Merge feat/irc-server: add ssh irc@ built-in IRC client
Brings the in-process IRC client (internal/irc) and the `irc@` SSH route on
top of the already-merged members-only Ergo server, completing the IRC
feature. The other commits on the branch (Ergo server, Tor routes) were
already merged via feat/qrypt-invite-issuer, so this applies only the client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:47:34 +00:00
3d0d483239 deploy: inject AGENTBBS_QRYPT_ISSUER_KEY to enable qrypt invite minting
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:46:45 +00:00
d5135cd1fe payments: accept CoinPay crypto_amount as number or string
The live CoinPay API returns crypto_amount as a bare JSON number (e.g.
0.0031), but the struct decoded it as a string, so /payments/create failed
with "cannot unmarshal number into ... crypto_amount of type string" and
join@ showed "Payment is temporarily unavailable". The unit test had hidden
the bug by sending the value quoted.

Add a flexStr type that unmarshals from either a JSON number or string and
use it for crypto_amount; update the test to send a number and add a direct
flexStr decode test for both forms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:45:41 +00:00
641fa01d9a Merge feat/qrypt-invite-issuer: qrypt.chat invite issuer + IRC + Tor routes 2026-06-14 11:44:46 +00:00
302259f65c feat(irc): add ssh irc@ built-in client for the members-only network
Adds an in-process IRC client (internal/irc) and an `irc@` SSH route that
drops a member straight into the BBS's own Ergo network with no client to
install and no SASL to configure.

- internal/irc/client.go: minimal IRC client (SASL PLAIN, IRCv3 CAP, PING,
  PRIVMSG/JOIN/PART/NICK, event stream). Dials Ergo on the loopback
  127.0.0.1:6667; presents the member's account name (the SSH key already
  proved membership; Ergo's auth-script ignores the passphrase by design).
- internal/irc/tui.go: Bubble Tea TUI over the SSH PTY (mirrors internal/chat)
  with /join /part /msg /me /names /nick /help and a current-channel input.
- cmd/agentbbs: handleIRC resolves the member by key (members-only, free) and
  runs the client; routed via auth.IsIRCName. AGENTBBS_IRC_ADDR overrides the
  target on dev hosts.
- auth: reserve `irc` as a route name.

Unlike copying tor-irc@ (a third-party client in a pod), this runs our own Go
code in-process, so there is no /exec shell-escape surface, and the host
process can reach Ergo's loopback listener directly.

Validated live against a members-only Ergo: non-members are rejected, a member
authenticates via SASL, and channel messages are received.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:35:09 +00:00
97da723c5c feat(qrypt): qrypt.chat anonymous-invite issuer
AgentBBS becomes a trusted Ed25519 issuer for qrypt.chat anonymous
accounts. Members mint a signed, single-use qci1 token (per the shared
invite contract) that qrypt.chat verifies and redeems.

- internal/qryptinvite: Mint / GenerateIssuerKey / ParsePrivateKey +
  Config (AGENTBBS_QRYPT_* env) with unit tests (independent verify,
  payload assertions, jti uniqueness, tamper rejection, seed/full key).
- store: qrypt_invites table + QryptInviteCount / RecordQryptInvite
  (per-member quota, enforced in a tx; ErrQuotaExceeded) + test.
- plugins/qryptinvite: hub plugin (members only) — checks quota, mints,
  records, prints token + redeem URL.
- cmd/agentbbs: `qrypt-invite <user>` and `qrypt-issuer-keygen`
  subcommands wired into dispatch.
- setup.sh env template + docs/qrypt-invites.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:35:05 +00:00
f44f0c9ae8 join@: fix onboarding freeze — read raw PTY input instead of ReadString('\n')
ssh join@host requests a PTY by default, so the client's terminal goes into
raw mode: it sends keystrokes as they're typed (Enter is '\r', not '\n') and
does no local echo. The onboarding prompts (username, email, code) read with
bufio.ReadString('\n'), which never sees a '\n' and blocks forever — the user
sees a dead prompt at "Email:". The earlier "require a PTY" change made this
guaranteed rather than intermittent.

Add a readLine helper that reads byte-by-byte, treats '\r' or '\n' as the
line terminator, handles backspace, and echoes printable bytes back so the
user can see what they type. Route the three onboarding prompts through it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:30:52 +00:00