O objeto ctx

Duas variantes, dependendo de onde ele é passado.

plugin.setup(ctx) — uma vez, na inicialização

export async function setup(ctx) {
  // sem ctx.msg, ctx.chat
  // ctx.send só tem .to(chatId) — não tem chat "atual"
  await ctx.send.to("5511999999999@c.us").text("Bot online!");
}

plugin.default(ctx) — a cada mensagem

export default async function (ctx) {
  // tudo do setup, mais:
  ctx.msg;   // a mensagem que disparou o handler
  ctx.chat;  // o chat de onde ela veio
  ctx.send.text("...");   // atalho pro chat atual (sem precisar de .to())
}

Onde cada API está disponível

API setup runtime
ctx.config
ctx.i18n (e o atalho ctx.t)
ctx.utils
ctx.download
ctx.scheduler
ctx.storage
ctx.plugins
ctx.log
ctx.botId
ctx.contacts
ctx.me
ctx.settings ⚠️ reduzido (só .global) ✅ completo
ctx.admin.add() ⚠️ só com .to(chatId)
ctx.send.to()
ctx.events
ctx.send.text/image/... (chat atual)
ctx.msg
ctx.chat
ctx.admin (demais métodos)
ctx.poll
ctx.wa (escape hatch, socket/store/msg crus)

ctx.events só existe no setup — registrar listener dentro do handler de mensagem criaria um listener novo a cada mensagem.

ctx.admin existe em ambos, mas no setup não há um chat "atual": só ctx.admin.add(ids).to(chatId) funciona ali (é o único método admin encadeável com .to()). Os demais (kick, promote, setSubject, etc.) exigem o contexto de runtime e lançam erro se chamados no setup.

ctx.settings existe em ambos, mas no setup só expõe .global (configurações do bot inteiro, sem chat associado) — o restante (.forChat(), .link(), etc.) só faz sentido em runtime.