Files
amari/src/commands/forwardToOwner.ts
T
hikari 1ebe240475
Node.js CI / CI (push) Successful in 28s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 2m41s
feat: add slash commands and context menu command (#16)
## Summary

This PR adds a suite of slash commands and a context menu command to Amari, along with shared utilities and quality improvements across the board.

### New Commands
- **`/create-issue`** — generates a GitHub issue on a specified repo using AI-drafted content (title, description, acceptance criteria)
- **`/create-task`** — creates a task in Naomi's Leantime instance with an AI-drafted description and configurable priority
- **`/onboard-mentee`** — automates the mentorship onboarding flow (GitHub invite, forum thread, role assignment)
- **Forward to Owner** (context menu, message command) — forwards any message to Naomi with action buttons (contributed by @teklu)

### Shared Utilities
- **`src/utils/makeAiRequest.ts`** — a single wrapper around the Anthropic SDK for all AI calls, with Amari's personality prompt baked in and full error handling
- **`src/events/handleInteractionCreate.ts`** — extracted interaction handler (was inline in `index.ts`) to keep complexity under control

### Quality Improvements
- `ephemeral: true` → `flags: [ MessageFlags.Ephemeral ]` (deprecated API removed)
- Full `try/catch` + `logger.error` audit across all modules (`logMenteeJoin`, `checkAchievements`, `processMentorshipRole`, `processGitHubEvent`)
- `deployGlobal.ts` replaced with a static `commands.json` payload for manual registration
- Amari's personality prompt updated to reflect her actual character — warm, observant, and relentlessly caring

### Notes
- `CLIENT_ID` is needed in 1Password at `op://Environment Variables - Naomi/Amari/client id` for the `commands.json` registration call
- The forward-to-owner command (PR #13, contributed by @teklu) is fully preserved with original commit authorship

 This PR was created with help from Hikari~ 🌸

Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-authored-by: Teklu <tekluabayneh@gmail.com>
Reviewed-on: #16
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
2026-03-03 15:05:09 -08:00

63 lines
1.8 KiB
TypeScript

/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Teklu Abayneh
*/
import {
DiscordAPIError,
MessageFlags,
type MessageContextMenuCommandInteraction,
} from "discord.js";
import { ids } from "../config/ids.js";
import { getComponentsForNaomi } from "../utils/getComponentsForNaomi.js";
import { logger } from "../utils/logger.js";
/**
* Forwards a message to Naomi via DM using a context menu command.
* @param interaction -- The message context menu interaction.
*/
const forwardToOwner = async(
interaction: MessageContextMenuCommandInteraction,
): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
if (interaction.user.id !== ids.users.naomi) {
await interaction.editReply("❌ Only Naomi can use this command.");
return;
}
const message = interaction.targetMessage;
if (message.author.id === ids.users.naomi) {
await interaction.editReply(
"No need to forward your own message to yourself 😄",
);
return;
}
try {
const naomi = await interaction.client.users.fetch(ids.users.naomi);
await naomi.send({
components: getComponentsForNaomi(
message.author,
message.content,
message.url,
),
flags: [ MessageFlags.IsComponentsV2 ],
});
await logger.metric("forwarded_message", 1, { user: message.author.id });
await interaction.editReply({ content: "✅ Forwarded to your DMs!" });
} catch (error) {
let replyText = "❌ Failed to forward message.";
if (error instanceof DiscordAPIError && error.code === 50_007) {
replyText = `${replyText} (Naomi's DMs might be closed)`;
}
if (error instanceof Error) {
await logger.error("forwardToOwner command", error);
}
await interaction.editReply(replyText);
}
};
export { forwardToOwner };