@yaebal/prompt
send a question and run a handler on the next message — no suspended promise, safe under the sequential update loop. pending handlers are in-memory.
install
pnpm add @yaebal/promptusage
install the plugin with .install(prompt()). it adds ctx.prompt on
every update. call it with a question string and a handler; the handler receives the full context
of the reply message.
import { Bot } from "@yaebal/core";
import { prompt } from "@yaebal/prompt";
import type { PromptControl } from "@yaebal/prompt";
const bot = new Bot(process.env.BOT_TOKEN!).install(prompt());
// cast once at the handler boundary — ctx.prompt is fully typed
bot.command("ask", (ctx) =>
(ctx as typeof ctx & PromptControl).prompt("name?", (c) =>
c.reply(`Hello, ${c.text}!`),
),
);
bot.start();api
| export | signature | description |
|---|---|---|
prompt | (options?: PromptOptions) => Plugin<Context, PromptControl> | creates the prompt plugin |
PromptControl | interface | what the plugin adds to the context — the prompt method |
PromptOptions | interface | options passed to prompt() |
PromptHandler | (ctx: Context) => unknown | Promise<unknown> | the callback that handles the answer message |
PromptControl (ctx.prompt)
| parameter | type | required | description |
|---|---|---|---|
question | string | FormatResult | yes | text sent to the user; accepts a plain string or a @yaebal/core format result |
handler | PromptHandler | yes | runs when the next message arrives from the same chat |
extra | Record<string, unknown> | no | extra parameters forwarded to sendMessage (e.g. reply_markup) |
ctx.prompt returns Promise<Message> — the sent question message.
PromptOptions
| field | type | required | description |
|---|---|---|---|
getKey | (ctx: Context) => string | undefined | no | identifies which chat a pending handler belongs to. defaults to ctx.chat?.id?.toString() |
chaining prompts
a handler can call ctx.prompt again to collect a second answer, and so on. each
call registers a new one-shot handler for the next message.
// handlers can call ctx.prompt again to collect multiple values in sequence
bot.command("survey", (ctx) =>
(ctx as typeof ctx & PromptControl).prompt("first name?", (c1) => {
const first = c1.text ?? "";
return (c1 as typeof c1 & PromptControl).prompt("last name?", (c2) => {
const last = c2.text ?? "";
return c2.reply(`Full name: ${first} ${last}`);
});
}),
);extra send parameters
// pass extra sendMessage parameters as the third argument
await (ctx as typeof ctx & PromptControl).prompt(
"Choose an option:",
async (c) => { /* handle reply */ },
{
reply_markup: {
force_reply: true,
input_field_placeholder: "type here",
},
},
);the answer message is consumed. the reply is handled by the pending handler and
never reaches other handlers (e.g.
pending handlers are in-memory. they are stored in a plain
one pending handler per key. calling
bot.on("message:text", ...)). if the user types
a command like /cancel instead of an answer, the pending handler receives it — check
for that in the handler if you want an escape hatch. pending handlers are in-memory. they are stored in a plain
Map and
are lost on process restart. there is no built-in persistent storage option for prompt — use @yaebal/scenes if you need persistence. one pending handler per key. calling
ctx.prompt twice before the
user answers replaces the first handler with the second.