Annotate an object/class with @WizardHandler. Define nested steps extending WizardStep. KSP generates the WizardActivity; no manual Activity class needed.
import eu.vendeli.tgbot.types.component.MessageUpdate
@WizardHandler(trigger = ["/start"])
object RegistrationWizard {
object NameStep : WizardStep(isInitial = true) {
override suspend fun onEntry(ctx: WizardContext) {
message("What's your name?").send(ctx.user, ctx.bot)
}
override suspend fun validate(ctx: WizardContext): Transition {
val text = (ctx.update as? MessageUpdate)?.message?.text ?: return Transition.Retry()
return if (text.isNotBlank()) Transition.Next else Transition.Retry("Invalid name")
}
override suspend fun store(ctx: WizardContext): String? =
(ctx.update as? MessageUpdate)?.message?.text
}
object AgeStep : WizardStep() {
override suspend fun onEntry(ctx: WizardContext) {
message("How old are you?").send(ctx.user, ctx.bot)
}
override suspend fun validate(ctx: WizardContext): Transition {
val age = (ctx.update as? MessageUpdate)?.message?.text?.toIntOrNull()
return if (age != null && age in 1..150) Transition.Next else Transition.Retry()
}
override suspend fun store(ctx: WizardContext): Int? =
(ctx.update as? MessageUpdate)?.message?.text?.toIntOrNull()
}
}
WizardStep Lifecycle
onEntry(ctx) — Called when entering the step. Send prompts, set keyboards.
validate(ctx) — Return Transition based on input.
store(ctx) — Return value to persist (or null). Type must match a state manager.
onRetry(ctx, reason) — Called when validation fails (optional).
Transitions
Transition
Effect
Transition.Next
Move to next step in sequence
Transition.JumpTo(Step::class)
Jump to specific step
Transition.Retry() or Transition.Retry("reason")
Stay on step, call onRetry
Transition.Finish
End wizard
State Managers
Default: MapStringStateManager, MapIntStateManager, MapLongStateManager. KSP matches store() return type to the manager.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: vendelieu-telegram-bot-build-wizard-flow3description: Build Wizard Flow4---56# Build Wizard Flow78## Quick Start910Annotate an object/class with `@WizardHandler`. Define nested steps extending `WizardStep`. KSP generates the `WizardActivity`; no manual Activity class needed.1112```kotlin13import eu.vendeli.tgbot.types.component.MessageUpdate1415@WizardHandler(trigger = ["/start"])16object RegistrationWizard {17 object NameStep : WizardStep(isInitial = true) {18 override suspend fun onEntry(ctx: WizardContext) {19 message("What's your name?").send(ctx.user, ctx.bot)20 }21 override suspend fun validate(ctx: WizardContext): Transition {22 val text = (ctx.update as? MessageUpdate)?.message?.text ?: return Transition.Retry()23 return if (text.isNotBlank()) Transition.Next else Transition.Retry("Invalid name")24 }25 override suspend fun store(ctx: WizardContext): String? =26 (ctx.update as? MessageUpdate)?.message?.text27 }2829 object AgeStep : WizardStep() {30 override suspend fun onEntry(ctx: WizardContext) {31 message("How old are you?").send(ctx.user, ctx.bot)32 }33 override suspend fun validate(ctx: WizardContext): Transition {34 val age = (ctx.update as? MessageUpdate)?.message?.text?.toIntOrNull()35 return if (age != null && age in 1..150) Transition.Next else Transition.Retry()36 }37 override suspend fun store(ctx: WizardContext): Int? =38 (ctx.update as? MessageUpdate)?.message?.text?.toIntOrNull()39 }40}41```4243## WizardStep Lifecycle4445- **onEntry(ctx)** — Called when entering the step. Send prompts, set keyboards.46- **validate(ctx)** — Return `Transition` based on input.47- **store(ctx)** — Return value to persist (or null). Type must match a state manager.48- **onRetry(ctx, reason)** — Called when validation fails (optional).4950## Transitions5152| Transition | Effect |53|------------|--------|54| `Transition.Next` | Move to next step in sequence |55| `Transition.JumpTo(Step::class)` | Jump to specific step |56| `Transition.Retry()` or `Transition.Retry("reason")` | Stay on step, call onRetry |57| `Transition.Finish` | End wizard |5859## State Managers6061Default: `MapStringStateManager`, `MapIntStateManager`, `MapLongStateManager`. KSP matches `store()` return type to the manager.6263Override per step:6465```kotlin66@WizardHandler.StateManager(CustomStateManager::class)67object CustomStep : WizardStep { ... }68```6970## WizardContext7172- `ctx.user`, `ctx.update`, `ctx.bot`73- `ctx.getState(Step::class)`, `ctx.setState(Step::class, value)`, `ctx.delState(Step::class)` — type-safe accessors generated by KSP for each step7475## Trigger and Scope7677- `trigger = ["/start", "/register"]` — commands that start the wizard78- `scope = [UpdateType.MESSAGE]` — default; use `UpdateType.CALLBACK_QUERY` for button-triggered wizards7980## Related Skills8182- **add-wizard-handler** — Configure @WizardHandler (trigger, scope, state managers)83- **add-wizard-step** — Implement WizardStep (onEntry, validate, store, transitions)8485## Reference8687- [WizardHandler.kt](telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/annotations/WizardHandler.kt)88- [WizardStep.kt](telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/chain/WizardStep.kt)89- [TestWizard.kt](telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/TestWizard.kt)9091---92> Converted and distributed by [TomeVault](https://tomevault.io/claim/vendelieu) — claim your Tome and manage your conversions.93<!-- tomevault:4.0:skill_md:2026-04-11 -->
Run npx skillmds@latest add tomevault-io/vendelieu-telegram-bot-build-wizard-flow in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Build Wizard Flow It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Independent scanners report: SkillSpector: PASS, Skill Scanner: PASS. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
tomevault-io (@tomevault-io) published this skill. Their other Agent Skills are listed on their SkillMD profile.