自作エージェントに喋りかけるだけでアプリ開発|MastraとClaude Code Actionで音声バイブコーディング
今回はGitHub ActionsのClaude Code連携を試します。
また、音声入力でIssue作成とかできれば、今一番アツいClaude CodeでどこでもラフにVibe Codingができるんじゃないかと思ったのでやってみます。
発端
Claude Codeの勉強会でこんなこと👇を言ったので、言ったらやるの精神でやります。
実際はGitHubのMCPサーバ設定したClaude Desktopに、なんかのツールで音声入力するだけでも実現できると思うのですが、以前からMastraを触ってみたかったのでこの機会に自前でAIエージェントアプリを作ることにしました。
音声入力ツールはすでにLLMを活用したちゃんとしたのがあるっぽいので気になる方はそちらもチェック。
まずは成果物から
アプリはこちら
実際のアプリケーションはこちらから使えます。
動画もあるよ
デモ動画を作ったので大体何やろうとしているのかがわかるかと
スマホでも
要件・設計の整理
要件整理
まず簡単に要件を整理すると以下の通りです。
- アプリ
- 音声入力をテキストに変換する(STT)
- そのまんまのテキストだと話し言葉すぎるので、LLMにテキストを投げてIssueの原稿を作ってもらう
- 作ってもらった原稿でIssueを作成する
- なお、LLMのアカウントはユーザー自身のものを使う。
- GitHub
- Issueを検知してClaude Codeが実装する
- Pull Requestが作成されたらプレビューデプロイして実装結果をチェックできるようにする
技術スタック
主要なものだけ
- フロントエンド
- Next.js 15.3.3
- TypeScript 5
- Tailwind CSS 4
- Headless UI 2.2.4
- エージェント・ワークフロー
- Mastra 0.10.3
- Anthropic Claude SDK 0.53.0
- API
- Octokit REST 22.0.0
- Web Speech API
- デプロイ
- Vercel
- コーディングエージェント
- Claude Code Actions
- 実装に使ったのはCursor(Claude Sonnet 4)
シーケンス図
今回のシーケンス図、つまりこういうことになります。
WebアプリによるIssue作成と、Claude Codeによる実装は非同期になります。
Issue案作成LLMはなんでもよかったんですが、課金先を統一したかったのでClaude一本に絞ってます。
実装する
Webアプリの実装
ソースコードはこちら
以下の主要な部分だけ簡単な説明を書きます。
- 音声入力
- Mastra Workflow
- GitHub APIでのIssue作成
音声入力部分
音声入力はWeb Speech APIを使ってブラウザの音声認識機能を利用しています。
初期化部分
const SpeechRecognition = window.webkitSpeechRecognition || window.SpeechRecognition;
const recognition = new SpeechRecognition();
recognition.lang = 'ja-JP';
recognition.continuous = true; // 連続認識
recognition.interimResults = true; // 中間結果も取得
recognition.maxAlternatives = 1;
認識結果取得部分
recognition.onresult = (event) => {
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcriptText = event.results[i][0].transcript;
if (event.results[i].isFinal) {
sessionFinalTranscript += transcriptText;
} else {
sessionInterimTranscript += transcriptText;
}
}
};
Mastra Workflow
MastraはAIエージェントやワークフローを構築するためのTypeScript向けフレームワークです。
Next.jsはもちろん他のフレームワークのサポートもありますし、メモリやRAG、MCPなどエージェント開発に必要な機能が盛り込まれています。
今回はMastraのWorkflowを作成しています。
大してステップはありませんが、LLMでIssue案作成→GitHub API実行、っていう順番はあるのでWorkflowだと開発しやすいかなと思ったためです。
また、エージェントにリモートMCPサーバを統合できればGitHubの操作もLLMに投げられるじゃん、と思ったものの、GitHubのMCPサーバはDockerを使ったりとちょっと癖がありそうで断念しました(ローカルで動かす分なら全然問題ないけど、自前のアプリケーションに統合してホスティングサービスにデプロイするのを考えるとどうすればいいのかわからなかった)
というわけでコードの話ですが、
まずMastraの初期化はこんな感じ。
export const mastra = new Mastra({
agents: {},
workflows: { voiceToIssueWorkflow },
storage: new LibSQLStore({
url: ":memory:",
}),
});
音声入力結果を分析してIssueの案を考えるステップは以下の部分
const analyzeVoiceInput = createStep({
id: "analyze-voice-input",
execute: async ({ inputData }) => {
const anthropic = new Anthropic({
apiKey: inputData.anthropicApiKey,
});
const prompt = `
音声入力: "${inputData.voiceInput}"
音声入力内容を分析して、GitHub Issueの情報を生成してください。
// ... (中略)
`;
const result = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1000,
messages: [{ role: "user", content: prompt }]
});
},
});
でその分析結果を使ってGitHub Issueを作成するステップはこう
const createGitHubIssue = createStep({
id: "create-github-issue",
execute: async ({ inputData }) => {
const octokit = new Octokit({ auth: token });
const fullBody = `
${inputData.body}
---
@claude
このIssueの実装をお願いします。
実装タスクを分割し、ステップバイステップで実装してください。
---
*この依頼は Voice2Issue アプリケーションによって自動生成されました*`;
const issueResponse = await octokit.rest.issues.create({
owner,
repo,
title: inputData.title,
body: fullBody,
labels: allLabels,
});
},
});
上記2つのステップをWorkflowとして以下のように定義します。
export const voiceToIssueWorkflow = createWorkflow({
id: "voice-to-issue",
// スキーマ定義...
})
.then(analyzeVoiceInput) // 音声認識ステップ
.then(createGitHubIssue); // GitHub Issue作成ステップ
で、作ったWorkflowはAPI経由で呼び出す必要があります。
※下記リンクは日本語だと表示が不十分の可能性があります
import { NextRequest, NextResponse } from "next/server";
import { mastra } from "../../../../../mastra";
export async function POST(request: NextRequest) {
try {
// (中略)
// Mastraワークフローを実行
let result;
try {
const workflow = mastra.getWorkflow("voiceToIssueWorkflow");
// Mastraワークフローの実行 - createRun()からstart()を使用
const run = workflow.createRun();
result = await run.start({
inputData: {
voiceInput,
repository,
githubToken,
anthropicApiKey,
}
});
// Mastraワークフロー結果の構造に合わせて処理
if (result.status === 'success' && result.result) {
return NextResponse.json({});
} else {
throw new Error(`Workflow execution failed with status: ${result.status}`);
}
} catch (workflowError) {
//
}
} catch (error) {
//
}
}
GitHub APIでのIssue作成
Issueの作成はOctokitライブラリを使用しています。
クライアントの初期化部分
const token = inputData.githubToken;
const octokit = new Octokit({ auth: token });
Issue本文の定義部分
inputData.body
にはLLMに生成してもらった整理された要件が入ります。
@claudeをIssueの本文に入れることでClaude Codeの実行をフックします
const fullBody = `${inputData.body}
---
@claude
このIssueの実装をお願いします。
実装タスクを分割し、ステップバイステップで実装してください。
なお、Issueへのコメントは日本語を使用してください。
---
*この依頼は Voice2Issue アプリケーションによって自動生成されました*`;
Issueの作成部分
const issueResponse = await octokit.rest.issues.create({
owner,
repo,
title: inputData.title,
body: fullBody,
labels: allLabels,
assignees: [],
});
GitHub ActionsのClaude Code設定
GitHub ActionsでClaude Codeを設定する方法は大きく分けて二つあります。
1つがまずローカルにClaude Codeをインストールし、ターミナルから/install-github-app
でセットアップを開始する方法。
もう1つがGitHub AppのClaudeを画面からインストールし、手動でAPI Keyの登録やワークフローの作成をする方法です。
前者の場合、まずClaude Codeがインストールできるのが以下のOSとなっています。
- macOS 10.15+
- Ubuntu 20.04+/Debian 10+
- WSL経由のWindows
今回私はWindowsで作業しているのでWSL経由でインストールしてもいいのですが、なんとなくで後者の方法でやっていきます。
Claude GitHub Appのインストール
Appがアクセスできるリポジトリを選択
Anthropic API Keyの登録
まずAnthropicのAPI Keyを払い出します
対象のリポジトリのSetting > Secrets and variables > Actions
から払いだしたAPI Keyを登録
GitHub Actions用ワークフローの作成
下記のリンクからサンプルのymlをコピーして、リポジトリの.github/workflows/
に設定
ただし上記は与えているpermissionsがreadなので、要件に応じて編集します
私は今回以下のように設定しています
(タスク完了後にPRを自動作成できないかと試行錯誤していた名残があるので、適宜変えてください)
name: Claude Assistant
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude-response:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code Action
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
trigger_phrase: "@claude"
timeout_minutes: "60"
Claude Codeの設定はこれだけです。
Vercelの設定
今回Claude Codeをワークフローに入れたリポジトリ(個人のポートフォリオサイト)は元々Vercelで自動デプロイするように設定済みです。
設定自体はこの辺りが参考になります。
あとVercelの公式
動かしてみる
では動かしてみます(やることは冒頭の動画と同じです)
現状確認
現在の個人ポートフォリオは以下のようなセクションに分かれています。
これにProductsとして個人の成果物を紹介するセクションを作ります。
音声入力でIssue作成
今回作ったWebアプリで音声を入力し、Issue作成ボタンを押します
Issueの作成確認
こんな感じでちゃんとしたIssueが作成されました
Claude Codeの実装確認
無事Claude Codeが実装を完了してくれました
PR作成からプレビューデプロイの確認
PRを作成するとVercelがプレビュー環境のURLをコメントしてくれます。
プレビュー環境を見ると、ヘッダにProductsが追加されています。
Productsセクションの中身もちゃんと作られています。
(Voice2IssueでWhisper APIは使ってないけど)
おしまい
まずWebアプリについての感想
この記事を書いている途中で思いましたが、Issue投げる前にどんなIssue作ろうとしてるか表示したほうがよかったですね。
思ったのと違うIssueが作成されて、「あっ…待って…そういう意味じゃないの…」ってなってる間にClaude Codeが実装しちゃったりするので。
また、今回音声入力はWeb Speech APIを使いましたが制御がちょっと難しかったので、Mastraの音声認識とかも使ってみたいですね。
あとMastraに関して言うと、Workflowとかは作っちゃえば意外とすっきりした実装になるんですが、ドキュメントを読み解くのが結構大変でした。
やっぱりドキュメント丸ごとLLMに投げて必要なところだけ聞くのが楽です。
Claude Code Actionについての感想
Claude Code Actionは一度走り始めるとこちらからの介入ができないので、
実装の経過を見たり、適宜指示を出したり、日常遣いをするにはやっぱりターミナルやエディタで動かすClaude Codeがいいですね。
ただ、出先や散歩中やお風呂の中で思いついたアイデアとかをとりあえず実装させてみる、とか単発のリクエストをするにはClaude Code Actionを使うのも結構いいんじゃないかと思います。
多少雑な実装でも実際の画面とかで動作確認したりとかは早くしたいですしね。
Discussion
これを導入すると週休3日になりますか?