From 68745072418c2bfa94bea9c5ca219ed594def1d2 Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Fri, 2 Jan 2026 14:10:14 -0500 Subject: [PATCH 1/3] Trying again --- src/commands/help/help.ts | 44 +++++++++++++ src/commands/help/helpText.ts | 95 +++++++++++++++++++++++++++++ src/services/discord/commandMeta.ts | 94 ++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 src/commands/help/help.ts create mode 100644 src/commands/help/helpText.ts create mode 100644 src/services/discord/commandMeta.ts diff --git a/src/commands/help/help.ts b/src/commands/help/help.ts new file mode 100644 index 00000000..de66ff88 --- /dev/null +++ b/src/commands/help/help.ts @@ -0,0 +1,44 @@ +// src/commands/help/help.ts + +import { + SlashCommandBuilder, + PermissionFlagsBits, + type ChatInputCommandInteraction, +} from "discord.js"; +import { buildHelpText } from "./helpText.js"; +import { extractCommandList, type CommandListItem } from "../../services/discord/commandMeta.js"; + +/** + * /help + * + * MVP goal: + * - Give users a quick “what can this bot do?” + * - Point admins to config commands + * - Keep output short and readable + * + * Enhancement: + * - If commands are loaded on the client, we also list them (grouped). + */ +export const data = new SlashCommandBuilder() + .setName("help") + .setDescription("Show what OmegaBot can do and how to get started"); + +export async function execute(interaction: ChatInputCommandInteraction) { + const isAdmin = + interaction.inGuild() && + Boolean(interaction.memberPermissions?.has(PermissionFlagsBits.ManageGuild)); + + // Try to extract a command list from the runtime-loaded command map. + // This is optional: if your loader uses a different shape, help still works. + const commands: CommandListItem[] = extractCommandList(interaction.client); + + const text = buildHelpText({ + isAdmin, + commands, + }); + + await interaction.reply({ + content: text, + ephemeral: true, + }); +} \ No newline at end of file diff --git a/src/commands/help/helpText.ts b/src/commands/help/helpText.ts new file mode 100644 index 00000000..10c98063 --- /dev/null +++ b/src/commands/help/helpText.ts @@ -0,0 +1,95 @@ +// src/commands/help/helpText.ts + +import type { CommandListItem } from "../../services/discord/commandMeta.js"; + +/** + * Help text builder for /help. + * + * Keep this file focused on presentation so the command handler stays tiny. + */ +export function buildHelpText(args: { + isAdmin: boolean; + commands: CommandListItem[]; +}): string { + const { isAdmin, commands } = args; + + const lines: string[] = []; + + lines.push("**OmegaBot Help**"); + lines.push(""); + lines.push("**Start here**"); + lines.push("- Try `/help` any time you forget what I can do"); + lines.push(""); + + lines.push("**Onboarding**"); + lines.push("- Welcome messages are posted when someone joins"); + lines.push(""); + + if (isAdmin) { + lines.push("**Admin config** (Manage Server)"); + lines.push("- `/config welcome-channel set channel:#your-channel`"); + lines.push("- `/config welcome-channel clear`"); + lines.push(""); + } else { + lines.push("**Admin config**"); + lines.push("- Ask a server admin to run `/config welcome-channel set` if needed"); + lines.push(""); + } + + // Optional dynamic section: list known commands if we can extract them. + const pretty = formatCommandList(commands, { isAdmin }); + + if (pretty.length) { + lines.push("**Commands**"); + lines.push(...pretty); + lines.push(""); + } + + lines.push("_Tip: If new commands don’t show up, admins may need to run the register script._"); + + return lines.join("\n"); +} + +function formatCommandList( + commands: CommandListItem[], + opts: { isAdmin: boolean }, +): string[] { + if (!commands.length) return []; + + // Group by category and hide admin-only commands from non-admin users. + const visible = commands.filter((c) => (opts.isAdmin ? true : !c.adminOnly)); + + // Keep stable ordering: group then name. + visible.sort((a, b) => { + const g = a.group.localeCompare(b.group); + if (g !== 0) return g; + return a.name.localeCompare(b.name); + }); + + const out: string[] = []; + let currentGroup: string | null = null; + + for (const cmd of visible) { + const groupLabel = titleCase(cmd.group); + + if (currentGroup !== groupLabel) { + currentGroup = groupLabel; + out.push(`- **${currentGroup}**`); + } + + // Indent commands under group header + const desc = cmd.description ? `: ${cmd.description}` : ""; + out.push(` - \`/${cmd.name}\`${desc}`); + } + + return out; +} + +function titleCase(s: string): string { + if (!s) return "Other"; + return s + .split(/[-_\s]+/g) + .filter(Boolean) + .map((w) => w[0].toUpperCase() + w.slice(1)) + .join(" "); +} \ No newline at end of file diff --git a/src/services/discord/commandMeta.ts b/src/services/discord/commandMeta.ts new file mode 100644 index 00000000..9d0883a8 --- /dev/null +++ b/src/services/discord/commandMeta.ts @@ -0,0 +1,94 @@ +// src/services/discord/commandMeta.ts + +/** + * Minimal metadata shape used by /help. + * + * This is intentionally defensive: it tries to extract command names and + * descriptions from whatever your loader stores in `client.commands`. + */ + +export type CommandListItem = { + name: string; + description: string; + group: string; + adminOnly: boolean; +}; + +type MaybeCommandModule = { + data?: { + name?: string; + description?: string; + toJSON?: () => { name?: string; description?: string }; + default_member_permissions?: string | null; + }; + meta?: Partial>; +}; + +type MaybeHasCommandsMap = { + commands?: Map; +}; + +/** + * Extract a list of commands from a Discord client object. + * + * Supports common shapes: + * - client.commands = Map + * - client.commands = Map + * + * If nothing matches, returns an empty array and /help remains static. + */ +export function extractCommandList(client: unknown): CommandListItem[] { + const c = client as MaybeHasCommandsMap; + + if (!c?.commands || !(c.commands instanceof Map)) return []; + + const items: CommandListItem[] = []; + + for (const [fallbackName, modUnknown] of c.commands.entries()) { + const mod = modUnknown as MaybeCommandModule; + + const fromJson = safeToJson(mod?.data); + const name = (mod?.data?.name ?? fromJson?.name ?? fallbackName)?.trim(); + if (!name) continue; + + const description = (mod?.data?.description ?? fromJson?.description ?? "").trim(); + + // Best-effort group: use explicit meta.group if provided, else infer. + // (You can improve this later by having the loader attach folder name.) + const group = (mod?.meta?.group ?? inferGroupFromName(name)).trim(); + + // Best-effort adminOnly: + // - If command module sets meta.adminOnly, trust it + // - Else if default_member_permissions is set on command data, consider it adminOnly + const adminOnly = + Boolean(mod?.meta?.adminOnly) || Boolean(mod?.data?.default_member_permissions); + + items.push({ + name, + description, + group, + adminOnly, + }); + } + + return items; +} + +function safeToJson(data: unknown): { name?: string; description?: string } | null { + try { + const d = data as { toJSON?: () => unknown }; + if (typeof d?.toJSON !== "function") return null; + const j = d.toJSON() as { name?: string; description?: string }; + return j ?? null; + } catch { + return null; + } +} + +function inferGroupFromName(name: string): string { + // Minimal inference, keeps output readable until the loader tags groups. + if (name === "help") return "general"; + if (name === "config") return "admin"; + if (name.includes("github")) return "github"; + return "other"; +} \ No newline at end of file From 04168cff22da215fdc6e358da34a494ad9a3dd43 Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Fri, 2 Jan 2026 14:10:19 -0500 Subject: [PATCH 2/3] style: auto-format with Prettier [skip-precheck] --- src/commands/help/help.ts | 7 +++++-- src/commands/help/helpText.ts | 6 ++++-- src/services/discord/commandMeta.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/commands/help/help.ts b/src/commands/help/help.ts index de66ff88..2453086f 100644 --- a/src/commands/help/help.ts +++ b/src/commands/help/help.ts @@ -6,7 +6,10 @@ import { type ChatInputCommandInteraction, } from "discord.js"; import { buildHelpText } from "./helpText.js"; -import { extractCommandList, type CommandListItem } from "../../services/discord/commandMeta.js"; +import { + extractCommandList, + type CommandListItem, +} from "../../services/discord/commandMeta.js"; /** * /help @@ -41,4 +44,4 @@ export async function execute(interaction: ChatInputCommandInteraction) { content: text, ephemeral: true, }); -} \ No newline at end of file +} diff --git a/src/commands/help/helpText.ts b/src/commands/help/helpText.ts index 10c98063..5708d407 100644 --- a/src/commands/help/helpText.ts +++ b/src/commands/help/helpText.ts @@ -45,7 +45,9 @@ export function buildHelpText(args: { lines.push(""); } - lines.push("_Tip: If new commands don’t show up, admins may need to run the register script._"); + lines.push( + "_Tip: If new commands don’t show up, admins may need to run the register script._", + ); return lines.join("\n"); } @@ -92,4 +94,4 @@ function titleCase(s: string): string { .filter(Boolean) .map((w) => w[0].toUpperCase() + w.slice(1)) .join(" "); -} \ No newline at end of file +} diff --git a/src/services/discord/commandMeta.ts b/src/services/discord/commandMeta.ts index 9d0883a8..3c23224e 100644 --- a/src/services/discord/commandMeta.ts +++ b/src/services/discord/commandMeta.ts @@ -91,4 +91,4 @@ function inferGroupFromName(name: string): string { if (name === "config") return "admin"; if (name.includes("github")) return "github"; return "other"; -} \ No newline at end of file +} From ac9abb2c8d89f045540df7f2dd205e2a09b8936d Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Fri, 2 Jan 2026 14:14:35 -0500 Subject: [PATCH 3/3] Adding in help slash command changes --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 94b50b7a..0851a3b0 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,11 @@ OmegaBot is a modular Discord bot designed to support development projects with - Centralized interaction routing with safe error handling - Structured logging (pino) - Welcome / onboarding system triggered on member join (`guildMemberAdd`) -- Per-guild configuration system backed by persistent storage +- Per-guild configuration system backed by persistent storage and admin slash commands ### Core commands +- /help – command discovery and getting started guide - /ping – health check - /summary – conversation summaries (local + LLM mode) - /history – conversation history (DM + file fallback) @@ -65,7 +66,6 @@ OmegaBot is a modular Discord bot designed to support development projects with ## Planned features -- /help command for command discovery - /docs command for documentation lookups - GitHub issues and pull request lookups - Pull request announcements @@ -152,6 +152,7 @@ OmegaBot is online │ │ └── OmegaBot.yml │ └── pull_request_template.md ├── .husky +│ ├── pre-commit │ └── pre-push ├── assets │ ├── banner.png @@ -196,6 +197,9 @@ OmegaBot is online │ │ ├── github │ │ │ ├── gh.ts │ │ │ └── pr.ts +│ │ ├── help +│ │ │ ├── help.ts +│ │ │ └── helpText.ts │ │ ├── history │ │ │ └── history.ts │ │ ├── pagination @@ -213,6 +217,7 @@ OmegaBot is online │ │ │ └── types.ts │ │ ├── discord │ │ │ ├── commandLoader.ts +│ │ │ ├── commandMeta.ts │ │ │ ├── fetchChannelMessages.ts │ │ │ └── interactionHandler.ts │ │ ├── faq