From 742dc9e794be1bb485f0e0d1d86b8b63f88d5731 Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Sat, 10 Jan 2026 20:21:34 -0500 Subject: [PATCH 1/4] Adding in changes --- README.md | 33 +++++++++++++++++-- docs/commands.md | 3 +- src/commands/github/gh.ts | 62 +++++++++++++++++++++++++++++++---- src/commands/github/status.ts | 51 ++++++++++++++++++++++++++++ src/commands/help/helpText.ts | 2 +- 5 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 src/commands/github/status.ts diff --git a/README.md b/README.md index 70f1c4f1..3fda5281 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,14 @@ OmegaBot is a modular Discord bot designed to support development projects with - Modular slash-command system (auto-loaded from `dist/commands`) - 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 and admin slash commands +- Welcome and onboarding flows triggered on member join (`guildMemberAdd`) +- Optional auto-role assignment for new members (`DISCORD_AUTO_ROLE_ID`) +- GitHub integration: issue and PR lookups plus polling-based announcements + - New PR announcements + - Issue and PR assignee change announcements + - Issue and PR closed announcements +- Configuration and feature gating via environment variables (optional features run only when configured) +- Per-guild configuration backed by persistent storage and admin slash commands ### Core commands @@ -162,6 +168,24 @@ OmegaBot is online │ │ └── OmegaBot.yml │ └── pull_request_template.md ├── .husky +│ ├── _ +│ │ ├── .gitignore +│ │ ├── applypatch-msg +│ │ ├── commit-msg +│ │ ├── h +│ │ ├── husky.sh +│ │ ├── post-applypatch +│ │ ├── post-checkout +│ │ ├── post-commit +│ │ ├── post-merge +│ │ ├── post-rewrite +│ │ ├── pre-applypatch +│ │ ├── pre-auto-gc +│ │ ├── pre-commit +│ │ ├── pre-merge-commit +│ │ ├── pre-push +│ │ ├── pre-rebase +│ │ └── prepare-commit-msg │ ├── pre-commit │ └── pre-push ├── assets @@ -169,6 +193,7 @@ OmegaBot is online │ └── omegabot.png ├── data │ ├── faqs.json +│ ├── github-assignees.json │ ├── guild-config.json │ ├── last-seen.json │ └── timezones.json @@ -206,7 +231,8 @@ OmegaBot is online │ │ │ └── ping.ts │ │ ├── github │ │ │ ├── gh.ts -│ │ │ └── pr.ts +│ │ │ ├── pr.ts +│ │ │ └── status.ts │ │ ├── help │ │ │ ├── help.ts │ │ │ └── helpText.ts @@ -287,6 +313,7 @@ OmegaBot is online ├── README.md ├── tsconfig.json └── vitest.config.ts + ``` diff --git a/docs/commands.md b/docs/commands.md index d233e3e0..4e4654a0 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -16,7 +16,8 @@ - `/gh issue` — Fetch a GitHub issue by number - `/gh issues` — List open GitHub issues - `/gh prs` — List open pull requests -- `/pr` — Fetch a single pull request by number +- `/gh status` — Show GitHub integration status (configuration, polling, and channels) +- `/pr` — Fetch a single pull request by number (legacy shortcut) ## Timezone diff --git a/src/commands/github/gh.ts b/src/commands/github/gh.ts index 8ed1acb7..4b9e991a 100644 --- a/src/commands/github/gh.ts +++ b/src/commands/github/gh.ts @@ -5,6 +5,7 @@ import { SlashCommandBuilder, type ChatInputCommandInteraction, } from "discord.js"; +import { env } from "../../config/env.js"; import { getIssue, listIssues, @@ -21,6 +22,18 @@ export const data = new SlashCommandBuilder() .setName("gh") .setDescription("GitHub helpers") + .addSubcommand((s) => + s + .setName("status") + .setDescription("Show GitHub integration status for this bot") + .addStringOption((o) => + o.setName("owner").setDescription("Org/user (optional; defaults to env)") + ) + .addStringOption((o) => + o.setName("repo").setDescription("Repo (optional; defaults to env)") + ), + ) + .addSubcommand((s) => s .setName("issue") @@ -73,10 +86,49 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise await interaction.deferReply({ flags: MessageFlags.Ephemeral }); - const owner = interaction.options.getString("owner", true); - const repo = interaction.options.getString("repo", true); - try { + if (sub === "status") { + const owner = interaction.options.getString("owner") ?? env.githubOwner ?? "(unset)"; + const repo = interaction.options.getString("repo") ?? env.githubRepo ?? "(unset)"; + + const lines: string[] = []; + lines.push("**GitHub Status**"); + lines.push(""); + + // Config (never print secrets) + lines.push(`Token: ${env.githubToken ? "configured" : "missing"}`); + lines.push(`Default repo: ${owner}/${repo}`); + lines.push(`Poll interval: ${env.githubPollIntervalMs}ms`); + lines.push(""); + + // Polling streams + lines.push("**Polling streams**"); + lines.push( + `- PR announcements: ${env.githubPrPollingEnabled ? "enabled" : "disabled"}`, + ); + lines.push( + ` - Channel: ${env.githubPrAnnounceChannelId ?? "(unset)"}`, + ); + lines.push( + `- Assignee activity: ${env.githubAssigneePollingEnabled ? "enabled" : "disabled"}`, + ); + lines.push( + ` - Channel: ${env.githubAssigneeAnnounceChannelId ?? "(unset)"}`, + ); + + lines.push(""); + lines.push( + "_Tip: If polling is disabled, set GITHUB_TOKEN + GITHUB_OWNER + GITHUB_REPO + the channel env var(s)._", + ); + + await interaction.editReply(lines.join("\n")); + return; + } + + // The remaining subcommands require explicit owner/repo options + const owner = interaction.options.getString("owner", true); + const repo = interaction.options.getString("repo", true); + if (sub === "issue") { const number = interaction.options.getInteger("number", true); const issue = await getIssue(owner, repo, number); @@ -138,7 +190,6 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise } await interaction.editReply("Unknown subcommand."); - return; } catch (err) { const msg = getGitHubUserMessage(err); if (msg) { @@ -146,8 +197,7 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise return; } - logger.warn({ err, owner, repo, sub }, "[gh] GitHub request failed"); + logger.warn({ err, sub }, "[gh] GitHub request failed"); await interaction.editReply("GitHub request failed. Please try again in a bit."); - return; } } diff --git a/src/commands/github/status.ts b/src/commands/github/status.ts new file mode 100644 index 00000000..48c8b3f0 --- /dev/null +++ b/src/commands/github/status.ts @@ -0,0 +1,51 @@ +// src/commands/github/status.ts + +import { SlashCommandBuilder, type ChatInputCommandInteraction } from "discord.js"; +import { env } from "../../config/env.js"; +import { logger } from "../../utils/logger.js"; + +/** + * /github status command + * + * Reports current GitHub-related configuration and which GitHub pollers are enabled. + * This command does not call the GitHub API. + */ +export const data = new SlashCommandBuilder() + .setName("status") + .setDescription("Show GitHub integration status for this bot"); + +export async function execute(interaction: ChatInputCommandInteraction): Promise { + try { + const lines: string[] = []; + + // High level config (do not print secrets) + lines.push("**GitHub Status**"); + lines.push(`Repo: ${env.githubOwner ?? "(unset)"}/${env.githubRepo ?? "(unset)"}`); + lines.push(`Token: ${env.githubToken ? "present" : "missing"}`); + lines.push(`Poll interval: ${env.githubPollIntervalMs} ms`); + lines.push(""); + + // PR polling + lines.push("**PR announcements**"); + lines.push(`Enabled: ${env.githubPrPollingEnabled ? "yes" : "no"}`); + lines.push(`Channel: ${env.githubPrAnnounceChannelId ?? "(unset)"}`); + lines.push(""); + + // Assignee/issue activity polling + lines.push("**Assignee + issue activity**"); + lines.push(`Enabled: ${env.githubAssigneePollingEnabled ? "yes" : "no"}`); + lines.push(`Channel: ${env.githubAssigneeAnnounceChannelId ?? "(unset)"}`); + + await interaction.reply({ content: lines.join("\n"), ephemeral: true }); + } catch (err) { + logger.error({ err }, "[/status] failed"); + + // Best-effort reply (avoid throwing twice) + const msg = "Something went wrong while building GitHub status."; + if (interaction.deferred || interaction.replied) { + await interaction.editReply({ content: msg }); + } else { + await interaction.reply({ content: msg, ephemeral: true }); + } + } +} \ No newline at end of file diff --git a/src/commands/help/helpText.ts b/src/commands/help/helpText.ts index 5708d407..9a01bf1f 100644 --- a/src/commands/help/helpText.ts +++ b/src/commands/help/helpText.ts @@ -94,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 From 2dab739464fb6c293716291b8ec81d45e039f5d8 Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Sat, 10 Jan 2026 20:21:40 -0500 Subject: [PATCH 2/4] style: auto-format with Prettier [skip-precheck] --- src/commands/github/gh.ts | 15 ++++++--------- src/commands/github/status.ts | 2 +- src/commands/help/helpText.ts | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/commands/github/gh.ts b/src/commands/github/gh.ts index 4b9e991a..356d70af 100644 --- a/src/commands/github/gh.ts +++ b/src/commands/github/gh.ts @@ -27,10 +27,10 @@ export const data = new SlashCommandBuilder() .setName("status") .setDescription("Show GitHub integration status for this bot") .addStringOption((o) => - o.setName("owner").setDescription("Org/user (optional; defaults to env)") + o.setName("owner").setDescription("Org/user (optional; defaults to env)"), ) .addStringOption((o) => - o.setName("repo").setDescription("Repo (optional; defaults to env)") + o.setName("repo").setDescription("Repo (optional; defaults to env)"), ), ) @@ -88,7 +88,8 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise try { if (sub === "status") { - const owner = interaction.options.getString("owner") ?? env.githubOwner ?? "(unset)"; + const owner = + interaction.options.getString("owner") ?? env.githubOwner ?? "(unset)"; const repo = interaction.options.getString("repo") ?? env.githubRepo ?? "(unset)"; const lines: string[] = []; @@ -106,15 +107,11 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise lines.push( `- PR announcements: ${env.githubPrPollingEnabled ? "enabled" : "disabled"}`, ); - lines.push( - ` - Channel: ${env.githubPrAnnounceChannelId ?? "(unset)"}`, - ); + lines.push(` - Channel: ${env.githubPrAnnounceChannelId ?? "(unset)"}`); lines.push( `- Assignee activity: ${env.githubAssigneePollingEnabled ? "enabled" : "disabled"}`, ); - lines.push( - ` - Channel: ${env.githubAssigneeAnnounceChannelId ?? "(unset)"}`, - ); + lines.push(` - Channel: ${env.githubAssigneeAnnounceChannelId ?? "(unset)"}`); lines.push(""); lines.push( diff --git a/src/commands/github/status.ts b/src/commands/github/status.ts index 48c8b3f0..0f3d8774 100644 --- a/src/commands/github/status.ts +++ b/src/commands/github/status.ts @@ -48,4 +48,4 @@ export async function execute(interaction: ChatInputCommandInteraction): Promise await interaction.reply({ content: msg, ephemeral: true }); } } -} \ No newline at end of file +} diff --git a/src/commands/help/helpText.ts b/src/commands/help/helpText.ts index 9a01bf1f..5708d407 100644 --- a/src/commands/help/helpText.ts +++ b/src/commands/help/helpText.ts @@ -94,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 +} From a474150060bb05d86433e130cb6c71cfc392e752 Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Sat, 10 Jan 2026 20:23:53 -0500 Subject: [PATCH 3/4] Adding in changes --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3fda5281..ada28679 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ OmegaBot is a modular Discord bot designed to support development projects with - Welcome and onboarding flows triggered on member join (`guildMemberAdd`) - Optional auto-role assignment for new members (`DISCORD_AUTO_ROLE_ID`) - GitHub integration: issue and PR lookups plus polling-based announcements + - Check if Github is working - New PR announcements - Issue and PR assignee change announcements - Issue and PR closed announcements From 71c049060bcdd14cdf7f11f11d42ef14bf4edb9e Mon Sep 17 00:00:00 2001 From: NickTheDevOpsGuy Date: Sat, 10 Jan 2026 20:23:57 -0500 Subject: [PATCH 4/4] style: auto-format with Prettier [skip-precheck] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ada28679..ed93b4ab 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ OmegaBot is a modular Discord bot designed to support development projects with - Welcome and onboarding flows triggered on member join (`guildMemberAdd`) - Optional auto-role assignment for new members (`DISCORD_AUTO_ROLE_ID`) - GitHub integration: issue and PR lookups plus polling-based announcements - - Check if Github is working + - Check if Github is working - New PR announcements - Issue and PR assignee change announcements - Issue and PR closed announcements