mcpField
B
builderbot-code-skill

builderbot-code-skill

github.com/leifermendez開発4184 回インストール32 週間前に更新
リポジトリ

builderbot-code-skill について

BuilderBot Code Skillは、BuilderBotでチャットボットのフローを作る開発者向けの実装ガイドです。フロー制御、状態管理、モジュールのインポート、WhatsAppでの会話UXを扱います。

主な機能

  1. 01フロー制御におけるreturnとawaitの規則
  2. 02状態とコールバック型のリファレンス
  3. 03モジュールのインポートと循環依存の指針
  4. 04会話UXのレビューチェックリスト
  5. 05WhatsAppの書式とメッセージ規則

活用シーン

  1. 01入力を受け取る複数段階のフローを実装
  2. 02フロー遷移や状態更新の問題を調査
  3. 03組み合わせ可能なフローモジュールを構成
  4. 04WhatsAppメッセージの読みやすさを確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/leifermendez/skill-builderbot-code --skill builderbot-code-skill

インストール後、Claude Code で/builderbot-code-skillを入力して読み込まれたか確認します。

同梱ファイルpatterns.mdproviders.md

詳細

BuilderBot Code スキル

操作

  1. スタックを特定する — コードを書く前に、プロバイダーパッケージ、データベースアダプター、フローのエントリーポイントを確認します。
  2. コールバックのシグネチャーを正しく記述する:
    • ctx: BotContext → { body, from, name, host, ...platform fields }
    • methods: BotMethods → { flowDynamic, gotoFlow, endFlow, fallBack, state, globalState, blacklist, provider, database, extensions }
  3. フロー制御のセマンティクスを守る — 次の3つは必ず return します:
    • return gotoFlow(flow) · return endFlow('msg') · return fallBack('msg')
    • 次のものには必ず await を付けます:await flowDynamic(...) · await state.update(...) · await globalState.update(...)
  4. 小さく、組み合わせ可能なフローを優先する — フローファイルごとに責務を1つにします。
  5. シークレットは環境変数に保存する — トークン、キー、認証情報をハードコードしないでください。
  6. フローを作成したら、必ず以下の UX レビューチェックリストを実施する — エンドユーザーとして会話をシミュレートし、フローが完成したと判断する前に、すべての項目を確認します。
  7. 終了前に必ず lint を検証する — eslint . --no-ignore を実行し、コードを提出する前にすべてのエラーを修正します。

重要なルール(よくあるバグ)

バグ誤り正しい例
フロー制御で return がないgotoFlow(flow)return gotoFlow(flow)
state/dynamic で await がないstate.update({...})await state.update({...})
flowDynamic に生の文字列配列を渡すflowDynamic(['a','b','c']) — 3つのメッセージを個別に送信しますflowDynamic([{ body: ['a','b','c'].join('\n'), delay: rnd() }])
同じコールバック内で flowDynamic と endFlow を使うawait flowDynamic(...); return endFlow()addAction を2つに分けて連結します。最初に flowDynamic を送信し、次に return endFlow(...) を呼び出します
不要な動的インポート循環依存がないのに await import('./flow') を使うトップレベルで import { flow } from './flow' を使います — 動的インポートは、フロー A → B かつ B → A の場合にのみ使用します
ESM の循環インポート相互に参照するフロー間でトップレベルの import を使うコールバック内で動的に const { xFlow } = await import('./x.flow') を使います
capture なしで idle を使う{ idle: 5000 }{ capture: true, idle: 5000 }
ESM で require() を使うrequire('./flow')await import('./flow') — プロジェクトは "type": "module" であり、require は使用できません
ボタンを使う{ buttons: [...] }テキスト形式の番号付きメニューと capture: true を使います
idleFallBack を確認するif (ctx?.idleFallBack) { ... }idleFallBack は使わないでください — { capture: true, idle: N } を設定し、フローが自動的に期限切れになるようにします

フローチェーン

addKeyword(keywords, options?)        // ActionPropertiesKeyword: capture, idle, media, delay, regex, sensitive  ← NEVER use `buttons`
  .addAnswer(message, options?, cb?, childFlows?)
  .addAction(cb)
// cb: async (ctx: BotContext, methods: BotMethods) => void

state クイックリファレンス

// Per-user
await state.update({ key: value })
state.get('key')            // dot notation supported: 'user.profile.name'
state.getMyState()
state.clear()

// Global (shared across all users)
await globalState.update({ key: value })
globalState.get('key')
globalState.getAllState()

UX レビューチェックリスト

フローを構築したら、エンドユーザーの立場で会話を頭の中でたどり、以下の各項目を確認してください。コードを納品する前に、問題があれば修正してください。

#質問確認・修正する内容
1すべてのプロンプトで、ユーザーが何を入力すればよいかを正確に伝えていますか?capture: true を指定した各 addAnswer で、期待する入力を明示する必要があります(例:「1、2、または 3 と返信してください」)。
2無効な入力をすべて処理していますか?入力を受け取る各ステップに、無効な入力用の fallBack('...') 分岐が必要です。
3入力待ちが長い場合にアイドルタイムアウトを設定していますか?{ capture: true, idle: 60000 } を追加してください。タイムアウト後、フローは自動的に終了します。
4ユーザーはいつでも終了できますか?キャンセルキーワード(例:「cancel」、「salir」)を用意するか、入力待ちの中で認識して return endFlow('...') を呼び出してください。
5メッセージは短く、モバイル向けで、バブル数が最大3〜4個ですか?長文にも、バブルの乱発にもならないようにしてください。FlowDynamicMessage.body の中で \n を使って行をまとめます。flowDynamic([...]) 配列内の各文字列は個別の WhatsApp メッセージになります。長いリストを分割して送信しないでください。バブルごとにランダムな遅延を追加してください。
6自然な箇所でユーザーの名前を使っていますか?挨拶や確認では、利用可能な場合に ctx.name を参照してください。
7メニューは番号付きのテキストリストですか(buttons は使っていませんか)?['Option 1', '1. Foo', '2. Bar'] と capture: true、fallBack を組み合わせてください。
8複数ステップの各フローで、確定前に確認していますか?取り消せない操作(注文、支払い、削除)の前に、概要を表示して「確認しますか? yes / no」と尋ねてください。
9フローは、明確な締めくくりのメッセージで終わっていますか?最後のステップで、何が起きたかと次に何をすべきかをユーザーに伝えてください(または別れの挨拶をしてください)。
10エラーメッセージは、対処方法がわかる内容ですか?「エラー」とだけ伝えてはいけません。何が問題で、どう修正すればよいかを伝えてください。例:return fallBack('有効なメールアドレスではないようです。もう一度入力してください:')

WhatsApp テキストの書式設定

WhatsApp 独自の Markdown を使用します。メッセージ文字列には必ず適用してください。

スタイル構文例
太字*text**Pepperoni*
斜体_text__Tomate y mozzarella_
取り消し線~text~~$15~
等幅```text``````CODE123```
箇条書き• または -• Opción 1
ルール
  • 商品名、合計、セクション見出し、ユーザーが実行する必要のある操作には *bold* を使います。
  • 説明、ヒント、補足情報には _italic_ を使います。
  • リスト項目には - ではなく • を使います。モバイルで見やすく表示されます。
  • 1つのバブル内でセクションを区切るには、視覚的な区切りとして ━━━━━━━(または ---)を使います。
  • HTML タグ(<b>、<br> など)は使わないでください。 WhatsApp では無視されます。
  • 標準の Markdown(**bold**、## heading)は使わないでください。 サポートされていません。
例 — 書式設定されたバブル
const body = [
    '*🍕 Tu pedido*',
    '━━━━━━━━━━━━━━',
    `• Pizza: *Pepperoni*`,
    `• Tamaño: *Mediana (30 cm)*`,
    `• Cantidad: *2*`,
    '',
    `💰 Total: *$26 USD*`,
    '',
    '_Responde *sí* para confirmar o *no* para cancelar._',
].join('\n')

WhatsApp メッセージの規範

これらのルールはすべてのフローに適用されます。違反すると、ボットがスパムのように感じられます。

重要な違い — flowDynamic と配列を使った addAnswer:

呼び出し配列の動作
flowDynamic(['a','b','c'])⚠️ 各文字列が個別の WhatsApp メッセージになります
addAnswer(['a','b','c'])✅ 改行で結合され、1つのメッセージになります

flowDynamic を呼び出すときは、body 内で .join('\n') を使った FlowDynamicMessage[] を必ず使用してください。生の文字列配列を渡してはいけません。

ルール誤り正しい
1ターンあたり最大3〜4個のバブルflowDynamic(['line1','line2','line3',...]) → 各文字列が1つのバブルになりますflowDynamic([{ body: ['line1','line2','line3'].join('\n'), delay: rnd() }])
常にランダムな遅延を使うバブル間に遅延がありません各バブルに delay: Math.floor(Math.random() * 800) + 500 を設定します
項目リストを分割して送信しないflowDynamic([...items.map(...)])flowDynamic([{ body: items.map(...).join('\n'), delay: rnd() }])
ランダム遅延ヘルパー(ファイルごとに一度定義)
const rnd = () => Math.floor(Math.random() * 800) + 500
正しいパターン — 最大3個のバブル
await flowDynamic([
    {
        body: [
            '*Título*',
            '',
            items.map(i => `• ${i.name} — $${i.price}`).join('\n'),
        ].join('\n'),
        delay: rnd(),
    },
    {
        body: '*Sección 2*\nLínea A\nLínea B',
        delay: rnd(),
    },
])
// addAnswer o addAction siguiente = bubble 3

プレゼンス更新(Baileys / Sherpa のみ)

メッセージを送信する前に「入力中…」または「録音中…」をシミュレートします。ボットが人間らしく感じられます。BaileysProvider と SherpaProvider でのみ利用できます。

type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused'
// composing = typing bubble   recording = audio bubble
パターン — 各メッセージの前に入力インジケーターを表示
const waitT = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

.addAction(async (ctx, { provider, flowDynamic }) => {
    await provider.vendor.sendPresenceUpdate('composing', ctx.key.remoteJid)
    await waitT(1500)
    await flowDynamic([{ body: 'Mensaje que parece escrito por humano', delay: rnd() }])
    await provider.vendor.sendPresenceUpdate('paused', ctx.key.remoteJid)
})
ルール
  • 送信後は必ず sendPresenceUpdate('paused', ...) を呼び出してください。インジケーターがクリアされます。
  • rnd() による遅延と組み合わせてください。プレゼンス更新 → 待機 → flowDynamic → paused の順です。
  • テキストの返信には composing、音声のコンテキストには recording を使用してください。
  • Baileys/Sherpa 以外のプロバイダーでは絶対に使用しないでください。実行時にエラーが発生します。

デバッグチェックリスト

  • フローが切り替わらない → return gotoFlow(...)
  • セッションが終了しない → return endFlow(...)
  • フォールバックが繰り返されない → return fallBack(...)
  • 状態が保存されない → await state.update(...)
  • アイドルイベントが発生しない → idle と併せて capture: true を追加してください(idleFallBack は絶対に確認しないでください)
  • EVENTS フローがトリガーされない → プロバイダーがイベントペイロードを EVENTS.* にマッピングしていることを確認してください
  • 循環インポートでクラッシュする → コールバック内で動的な await import() を使用してください(require() は絶対に使用しないでください)
  • 動的インポートで言語サーバーに「Cannot find module」と表示される → 循環依存が実際に存在するか確認してください。存在しない場合は、静的なトップレベルの import に置き換えてください
  • 変更後に ESLint エラーが発生する → eslint . --no-ignore を実行し、完了前に修正してください
  • builderbot/func-prefix-endflow-flowdynamic エラー → flowDynamic と endFlow が同じコールバック内にあります。2 つの addAction に分けてチェーンしてください:
    .addAction(async (_, { flowDynamic }) => { await flowDynamic(lines) })
    .addAction(async (_, { endFlow }) => { return endFlow('bye') })
    

モジュールシステム

このプロジェクトでは ESM を使用しています(package.json の "type": "module"、tsconfig の "module": "ES2022")。

  • 絶対に require() を使用しないでください。ESM では利用できません。
  • 基本的には静的なトップレベルのインポートを使用してください。 循環依存がない場合、動的インポートを使うと言語サーバーで TypeScript エラーが発生します。
  • await import() を使用する前に、依存関係のグラフを描いてください。動的インポートが正当化されるのは、フロー A がフロー B を呼び出し、かつフロー B がフロー A を呼び出す(実際に循環している)場合のみです。
✅ Static (no cycle):  welcome → menu → order → payment
   import { orderFlow } from './order.flow'   ← top of file

✅ Dynamic (real cycle): welcome ↔ order
   const { orderFlow } = await import('./order.flow')  ← inside callback

フロー同士が相互に参照する(循環する)場合は、コールバック内で 動的な import() を使用してください。

.addAction(async (ctx, { gotoFlow }) => {
    const { targetFlow } = await import('./target.flow')
    return gotoFlow(targetFlow)
})

モジュール構成(推奨)

フローを src/flows/ ディレクトリに整理し、組み立てたフローをエクスポートするバレル index.ts を用意してください。

src/
├── app.ts
├── flows/
│   ├── index.ts            # createFlow([...all flows])
│   ├── welcome.flow.ts
│   └── order.flow.ts
└── services/
// src/flows/index.ts
import { createFlow } from '@builderbot/bot'
import { welcomeFlow } from './welcome.flow'
import { orderFlow } from './order.flow'

export const flow = createFlow([welcomeFlow, orderFlow])
// src/app.ts
import { createBot, createProvider } from '@builderbot/bot'
import { MemoryDB as Database } from '@builderbot/bot'
import { BaileysProvider as Provider } from '@builderbot/provider-baileys'
import { flow } from './flows'

const main = async () => {
    const provider = createProvider(Provider)
    const database = new Database()
    await createBot({ flow, provider, database })
    provider.initHttpServer(+(process.env.PORT ?? 3008))
}
main()

参考資料

  • コードパターン(スキャフォールド、キャプチャ、gotoFlow、メディア、idle、flowDynamic、fallBack、REST、EVENTS、UX パターン):patterns.md
  • プロバイダー設定、データベース設定、TypeScript 型:providers.md

コメント