Skip to content

Add /admin set-level and /admin whois - #33

Merged
NinjaLikesCheez merged 2 commits into
mainfrom
feature/admin-members
Oct 3, 2026
Merged

NinjaLikesCheez merged 2 commits into
mainfrom
feature/admin-members

Conversation

@NinjaLikesCheez

Copy link
Copy Markdown
Contributor

Closes #26. Builds on the access store (#25) and the admin framework (#24).

What changed

  • /admin set-level user: level: [reason:] (admin, private). level is member, friend or guest. One command assigns, upgrades and demotes. The reply shows before and after.
    • guest keeps the person's entry and capabilities (decided in Writable access store: members, friends and capabilities managed by admin commands #25). The reply says the capabilities are kept but inactive.
    • Refuses admins (they come only from admins.yaml) and bots, each with a clear private message.
    • Setting the level someone already has says so and writes nothing, including an unlisted person set to guest.
    • The optional reason (200 characters at most) goes into the audit log only: not the file, not Sentry.
    • Store errors, such as a failed write, reach the admin as private messages and change nothing.
  • /admin whois user: (admin, private). Level, where it comes from (admins file, members file, or not listed), capabilities (marked inactive for a guest), the note, Discord ID and handle. Bots are shown as bots. Each lookup is logged as admin.whois with who looked up whom.
  • Sanitising. Discord can't turn text into a button, since buttons only come from components the bot sends. But free text can still render as formatting, headings, lists, masked links, mentions, custom emoji or timestamps. So the note and handle go through a new inlineCode(), which shows text as a code span: backticks, control, invisible and bidi characters are removed, and it's length-limited. Names are escaped. Mentions stay disabled.
  • AccessChange gains an optional reason. The tier labels move to core/access.ts and are shared with /whoami.
  • The issue's /admin member set with none became /admin set-level with guest (one level of subcommand, and demotion keeps the entry). Docs updated.

Needs a human reviewer

This is the first code that changes who has access (AGENTS.md rule 11): the guard rails (no admins, no bots, no-ops), what is logged, and the sanitising.

Tests

Through the real dispatcher and a real store on a temp folder: denied to member, friend and guest with nothing changed; assign, upgrade, demote, and persistence across a restart; every no-op; admin, bot, unresolved user, bad level and long reason refused; failed write; the audit contents; a demoted person is refused straight away; whois for every source; and hostile notes, names and handles. inlineCode has its own tests. just check passes: lint, types, 604 tests, coverage thresholds.

🤖 Generated with Claude Code

set-level makes someone a member, friend or guest through the access store,
with an optional reason for the audit log. It refuses admins and bots and
says so when nothing would change. whois shows level, source, capabilities
and the note, and logs the lookup. Free text is shown as a code span and
names are escaped so nothing can render as formatting, a link or a mention.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@NinjaLikesCheez
NinjaLikesCheez merged commit d900ea5 into main Oct 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin commands: /admin member set and /admin whois

1 participant