/**
* Informational commands β /start, /help, /ping, /plugins.
*/
import type { Bot } from "grammy";
import { isUserClientReady } from "../userbot.js";
import { escapeHtml } from "../formatting.js";
import { formatDuration, renderMeshReport } from "../helpers/index.js";
import { getLoadedPlugins } from "../../../core/plugin/index.js";
import { getMeshService } from "../../../core/mesh/index.js";
import type { MeshPingResult } from "../../../core/mesh/service.js";
export function registerInfoCommands(bot: Bot): void {
bot.command("start", (ctx) =>
ctx.reply(
[
"π¦
Talon",
"",
"Agentic AI harness for Telegram.",
"",
"Send a message, photo, doc, or voice note.",
"In groups, @mention or reply to activate.",
"",
"/status /stop /reset /help",
].join("\n"),
{ parse_mode: "HTML" },
),
);
bot.command("help", (ctx) =>
ctx.reply(
[
"π¦
Talon -- Help",
"",
"π¦
Settings",
" /settings -- view and change all chat settings",
" /model -- show or change model and backend",
" /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)",
" /doctor -- environment and native-module health (admin)",
" /dream -- force memory consolidation now",
" /ping -- health check with latency",
" /mesh -- ping and list companion mesh devices",
" /reset -- clear session and start fresh",
" /restart -- restart the bot process",
" /plugins -- list loaded plugins",
" /help -- this message",
"",
"Input",
" Text, photos, documents, voice notes, audio, videos, GIFs, stickers, video notes, forwarded messages, reply context",
"",
"Messaging",
" Send, reply, edit, delete, forward, copy, pin/unpin messages. Inline keyboards with callback buttons. Scheduled messages.",
"",
"Media",
" Send photos, videos, GIFs, voice notes, stickers, files, polls, locations, contacts, dice.",
"",
"Chat",
" Read history, search messages, list members, get chat info, manage titles and descriptions.",
"",
"Web",
" Ask Talon to read a URL β it can fetch and summarize web pages.",
"",
"Groups",
" Mention @" +
escapeHtml(ctx.me.username ?? "bot") +
" or reply to activate.",
"",
"Files",
" Ask me to create a file and I'll send it as an attachment.",
].join("\n"),
{ parse_mode: "HTML" },
),
);
bot.command("ping", async (ctx) => {
const start = Date.now();
const sent = await ctx.reply("...");
const latency = Date.now() - start;
const bridgeOk = true;
const userbotOk = isUserClientReady();
const uptime = formatDuration(process.uptime() * 1000);
const statusLine = [
`Bridge: ${bridgeOk ? "β" : "β"}`,
`Userbot: ${userbotOk ? "β" : "β"}`,
`Uptime: ${uptime}`,
].join(" | ");
try {
await bot.api.editMessageText(
ctx.chat.id,
sent.message_id,
`Pong! ${latency}ms\n${statusLine}`,
);
} catch {
// ignore edit failure
}
});
bot.command("mesh", async (ctx) => {
const sent = await ctx.reply("Pinging mesh devicesβ¦");
let results: MeshPingResult[];
try {
results = await getMeshService().pingAll();
} catch {
await editOrReply(
bot,
ctx.chat.id,
sent.message_id,
"Could not reach the mesh service.",
);
return;
}
await editOrReply(
bot,
ctx.chat.id,
sent.message_id,
renderMeshReport(results),
);
});
bot.command("plugins", async (ctx) => {
const plugins = getLoadedPlugins();
if (plugins.length === 0) {
await ctx.reply("No plugins loaded.");
return;
}
const lines = plugins.map((p) => {
// Every field here is author-supplied manifest text, not just the
// name. A description like "R&D tools" or "" would otherwise
// reach Telegram as markup and 400 the whole listing, so `/plugins`
// would look dead rather than show one odd line.
const ver = p.plugin.version ? ` v${escapeHtml(p.plugin.version)}` : "";
const desc = p.plugin.description
? ` β ${escapeHtml(p.plugin.description)}`
: "";
const mcp = p.plugin.mcpServerPath ? " [MCP]" : "";
const fe = p.plugin.frontends?.length
? ` (${escapeHtml(p.plugin.frontends.join(", "))})`
: "";
return `β’ ${escapeHtml(p.plugin.name)}${ver}${mcp}${fe}${desc}`;
});
await ctx.reply(
`Plugins (${plugins.length})\n\n${lines.join("\n")}`,
{
parse_mode: "HTML",
},
);
});
}
/** Edit the placeholder in place, falling back to a fresh reply. */
async function editOrReply(
bot: Bot,
chatId: number,
messageId: number,
text: string,
): Promise {
try {
await bot.api.editMessageText(chatId, messageId, text, {
parse_mode: "HTML",
});
} catch {
await bot.api.sendMessage(chatId, text, { parse_mode: "HTML" });
}
}