/** * Informational commands β€” /start, /help, /ping, /plugins. */ import { type ChatInputCommandInteraction, type Client, MessageFlags, } from "discord.js"; import { formatDuration, renderMeshReport, renderUsageMessage, } from "../helpers.js"; import type { TalonConfig } from "../../../util/config.js"; import { collectPlanUsage } from "../../shared/plan-usage-report.js"; import { getLoadedPlugins } from "../../../core/plugin/index.js"; import { getMeshService } from "../../../core/mesh/index.js"; import type { MeshPingResult } from "../../../core/mesh/service.js"; import { reply } from "./shared.js"; export async function handleStart( i: ChatInputCommandInteraction, ): Promise { await reply( i, [ "**πŸ¦… Talon**", "", "Agentic AI harness for Discord.", "", "Mention me, reply to me, or DM me. Attach files, photos, voice notes β€” I'll read them.", "", "/status /stop /reset /help", ].join("\n"), ); } export async function handleHelp( i: ChatInputCommandInteraction, c: Client, ): Promise { const botMention = c.user ? `<@${c.user.id}>` : "@bot"; await reply( i, [ "**πŸ¦… Talon β€” Help**", "", "**Settings**", " /settings β€” view and change all chat settings", " /model β€” show or change model", " /effort β€” set thinking effort (off, low, medium, high, max)", " /pulse β€” toggle periodic check-ins (on/off)", "", "**Session**", " /status β€” session info, usage, and stats", " /stop β€” stop the current response", " /metrics β€” aggregate performance metrics (admin)", " /dream β€” force memory consolidation now (admin)", " /ping β€” health check with latency", " /reset β€” clear session and start fresh", " /restart β€” restart the bot process (admin)", " /plugins β€” list loaded plugins", " /help β€” this message", "", "**Input**", " Text, photos, documents, voice notes, audio, videos, GIFs, replies, attachments.", "", "**Servers**", ` In a server, mention ${botMention} or reply to me to activate.`, "", "**DM**", " Whitelisted users can DM me directly.", ].join("\n"), true, ); } /** * /usage β€” plan limits across every exposed backend, not just this chat's. * Not admin-gated: it says how close the shared account is to a wall, which * is exactly what a user hitting one needs to know. */ export async function handleUsage( i: ChatInputCommandInteraction, config: TalonConfig, ): Promise { await i.deferReply({ flags: MessageFlags.Ephemeral }); const entries = await collectPlanUsage(config); await i.editReply(renderUsageMessage(entries)); } export async function handleMesh( i: ChatInputCommandInteraction, ): Promise { await i.deferReply({ flags: MessageFlags.Ephemeral }); await i.editReply("Pinging mesh devices…"); let results: MeshPingResult[]; try { results = await getMeshService().pingAll(); } catch { await i.editReply("Could not reach the mesh service."); return; } await i.editReply(renderMeshReport(results)); } export async function handlePing( i: ChatInputCommandInteraction, ): Promise { const start = Date.now(); await i.deferReply({ flags: MessageFlags.Ephemeral }); const apiLatency = Date.now() - start; // client.ws.ping is the WebSocket heartbeat RTT (the real gateway latency). const wsPing = i.client.ws.ping; const uptime = formatDuration(process.uptime() * 1000); await i.editReply( `Pong! WS: ${wsPing}ms Β· REST: ${apiLatency}ms\nGateway: βœ“ | Uptime: ${uptime}`, ); } export async function handlePlugins( i: ChatInputCommandInteraction, ): Promise { const plugins = getLoadedPlugins(); if (plugins.length === 0) { await reply(i, "No plugins loaded.", true); return; } const lines = plugins.map((p) => { const ver = p.plugin.version ? ` v${p.plugin.version}` : ""; const desc = p.plugin.description ? ` β€” ${p.plugin.description}` : ""; const mcp = p.plugin.mcpServerPath ? " [MCP]" : ""; const fe = p.plugin.frontends?.length ? ` (${p.plugin.frontends.join(", ")})` : ""; return `β€’ **${p.plugin.name}**${ver}${mcp}${fe}${desc}`; }); await reply( i, `**Plugins (${plugins.length})**\n\n${lines.join("\n")}`, true, ); }