Learning Activity
寝転がりながらClaude Codeを操作するための環境構築。Nintendo Switch Proコントローラー + Superwhisper音声入力の統合セットアップ。
セットアップ手順
Proコン Bluetoothペアリング
- Proコン上部のシンクボタン長押し(LEDが流れる)
- システム設定 → Bluetooth → 「Pro Controller」をペアリング
リマッパー選択
- JoyMapper(推奨・有料): ジャイロマウス対応、macOS Tahoe互換性あり。腱鞘炎対策に有効。5日無料トライアル。
- JoyKeyMapper(代替・無料): ジャイロ非対応。スティック操作のため親指負荷高い。
App Storeからインストール後、システム設定 → プライバシー → アクセシビリティで許可。
Superwhisper設定
- superwhisper.com またはApp Storeからインストール
- システム設定 → プライバシー → アクセシビリティ + マイクで許可
- Toggle Recording:
Ctrl+Option+Shift+Space推奨
キーマッピング(腱鞘炎対策)
ボタン負荷順: ZL/ZR(低) ← L/R ← A/B/X/Y ← スティック ← 常時操作(高)
| ボタン | 割当 | 用途 |
|---|---|---|
| ZL | Ctrl+Option+Shift+Space | 音声入力トグル ★★★★★ |
| ZR | 左クリック | クリック操作 ★★★★ |
| R | Enter | テキスト送信 ★★★ |
| 左スティック上下 | スクロール | 出力確認 ★★★ |
| ジャイロ | マウスカーソル | 低負荷操作 ★★ |
| B | Escape | キャンセル ★★ |
| L | Cmd+Tab | アプリ切替 ★ |
| A | 右クリック | コンテキスト ★ |
| X | Tab | ペイン移動 ★ |
| Y | Cmd+Z | Undo ★ |
操作フロー例
1. ZL押す → Superwhisper起動(音声待機)
2. 話す「〇〇ファイルを修正して」
3. ZL押す → テキスト確定、クリップボード搭載
4. R押す → Enter送信
5. ジャイロ傾け + ZRクリック → カーソル移動・操作
6. 左スティック上下 → 出力スクロール確認
ツール比較
| ツール | 価格 | ジャイロ | アプリ別プロファイル | 2026年互換性 |
|---|---|---|---|---|
| JoyMapper | 有料 | ○ | ○(自動) | ◎ Tahoe対応 |
| JoyKeyMapper | 無料 | × | ○ | ◎ |
| Recontrol | 無料 | × | × | ○ |
注意点
- ジャイロ必須: スティック常時操作は親指負荷が致命的。JoyMapperのジャイロマウス機能は腱鞘炎対策として必須。
- バッテリー: Proコン約40時間で充電必要。
- Superwhisperクリップボード仕様: ターミナルで動作しない場合あり。メモアプリで動作確認後、ターミナル設定調整。
- Superwhisper Pro: サブスク利用時間に注意。
効果測定ポイント
- 1日の肩・腕疲労度(−70%以上が目標)
- 寝転がり入力速度(手入力との比較)
- Proコン + 音声のハイブリッド利用率
問題
Biome v2.4.7 で Vue SFC の <template> 部分が biome format でフォーマットされない。
解決策
biome.json に3つの設定が必須:
{
"files": {
"includes": ["**", ".dotdir/**"]
},
"html": {
"experimentalFullSupportEnabled": true,
"formatter": {
"enabled": true
}
}
}
重要なポイント
experimentalFullSupportEnabled: true— v2.3で追加。Vue/Svelte/Astro の SFC内の全パート(template, script, style)の完全な解析を有効化。これなしでは<script>と<style>のみ処理html.formatter.enabled: true— デフォルトはfalse。biome formatコマンドが HTMLを処理するための明示的な有効化が必要。biome check --writeはlint経由で動くため気づきにくいfiles.includesにドットディレクトリを明示 —.vitepress/**のようなドットで始まるディレクトリはデフォルトスキャン対象外
既知の制限
- 複雑な TypeScript ジェネリクス(
ref<InstanceType<typeof X>>)でパースエラーが発生するケースあり v-for="item of items"のof構文がパースエラーになる(inは問題なし)- パースエラーが出るファイルはフォーマットがスキップされる(ファイルは壊れない)
formatWithErrors: trueは危険 — パースエラー時にSFCタグが消える・コードが壊れるリスクがある
セットアップ手順の順序が重要
Slack の Event Subscriptions で Request URL を入力すると、即座に URL Verification チャレンジが送信される。Worker がデプロイ済み+Secrets設定済みでないと検証が失敗する。
正しい順序: Slack App 作成(URL は仮)→ Install → デプロイ&Secrets設定 → URL を本番に更新
Worker URL のサブドメインは予測できない
wrangler deploy の出力で初めて https://{worker-name}.{subdomain}.workers.dev が確定する。README にプレースホルダーを書くなら、デプロイ後に更新するフローを明記すること。サブドメインはアカウントごとに異なる。
PEM 秘密鍵は対話入力ではなくパイプで渡す
# NG: 対話入力だと改行が壊れる
wrangler secret put GITHUB_APP_PRIVATE_KEY
# OK: パイプ形式
cat /path/to/private-key.pem | wrangler secret put GITHUB_APP_PRIVATE_KEY
AI Gateway ID の確認方法
Cloudflare ダッシュボード → AI → AI Gateway で表示されるゲートウェイ名がそのまま ID。default という名前なら ID も default。Account ID は npx wrangler whoami で確認できる。
GitHub App Installation ID の確認方法
gh api /orgs/{org}/installations \
--jq '.installations[] | select(.app_slug=="your-app") | .id' 概要
社内ドキュメントサイト(VitePress + Cloudflare Pages)に対して、Slackから自然言語でドキュメント追加・修正を指示するとAIがMarkdownを生成しGitHub PRを自動作成するBotを構築した。
アーキテクチャ
Slack (/update-doc コマンド)
→ Cloudflare Workers(署名検証・ルーティング)
→ Claude API(AI Gateway経由、自然言語→Markdown変換)
→ GitHub REST API(ブランチ作成・ファイル追加・PR作成)
→ Slack(response_url にPRリンク返信)
実装プロセスの流れ
- マイルストーン定義 → タスク自動分解(15タスク、5マイルストーン)
- 基盤構築(MS20: 5タスク直列)→ Slack App登録・Workers雛形・署名検証・コマンドハンドラ・デプロイ
- 機能実装(MS21: 3タスク、一部並列)→ Claude API連携・GitHub API連携・フロー統合
- 各タスクをサブエージェントに委譲し、メインコンテキストを軽量に保つオーケストレーターパターン
技術的な知見
Slack Bolt は使わない
Workers環境ではNode.js依存が問題になるため、fetchハンドラに直接実装。Web APIベースで署名検証もHMAC-SHA256をWeb Crypto APIで自前実装。
3秒タイムアウト対策
Slackはコマンド応答を3秒以内に要求する。ctx.waitUntil()でバックグラウンド処理を起動し、即座に「処理中です…」を返す。完了後はresponse_urlにフォローアップ送信。
Workers のサブドメインは予測できない
wrangler deployの出力で初めてURLが確定する。READMEにプレースホルダーを書くと後で修正が必要になる。デプロイ後に確認して設定するフローにすべき。
Event Subscriptions の URL Verification
Slackの設定画面でRequest URLを入力すると即座にchallengeリクエストが飛ぶ。Workerがデプロイ済み+Secrets設定済みでないと検証が失敗する。設定手順の順序が重要。
GitHub App JWT認証(RS256)on Workers
PEM秘密鍵をcrypto.subtle.importKey("pkcs8", ...)でインポートし、crypto.subtle.sign("RSASSA-PKCS1-v1_5", ...)で署名。Installation Tokenはメモリキャッシュで有効期限管理。
設計判断
| 判断 | 理由 |
|---|---|
| Octokit不使用 | 依存を増やさない。fetchベースで軽量実装 |
| Zod不使用 | devDependencyに入っていないため型ガードで手動バリデーション |
| AI Gateway経由 | ログ・レート制限・モニタリングをCloudflareに委譲 |
| PRベース | レビューによる品質担保。誤った内容が直接公開されない |
1. TypeScript設定
tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"types": ["vitepress/client"]
},
"include": [".vitepress/**/*.ts", ".vitepress/**/*.vue"],
"vueCompilerOptions": {
"vitePressExtensions": [".md"]
}
}
ポイント
vitepress/clientが内部でvite/clientを参照し.vueモジュールの型宣言を含むため、env.d.tsやshims-vue.d.tsは不要vueCompilerOptions.vitePressExtensionsでMarkdown内のVueコードもVolarが認識moduleResolution: "bundler"はVite系プロジェクトの必須設定
pnpm strict mode での注意
VitePressの内部依存(vue等)が node_modules/ 直下にhoistされない。Cannot find module vue エラーが出る場合は pnpm add -D vue で明示的にインストールする。
テーマの型付け
EnhanceAppContext を使うと型展開が深すぎて Excessive stack depth comparing types (ts 2321) になる。必要な部分だけ型付けして回避する。
import type { App } from "vue";
export default {
extends: DefaultTheme,
enhanceApp({ app }: { app: App }) {
// ...
},
};
2. サイドバー構築
ネスト構造の設計
VitePressのサイドバーは collapsed フラグでツリーを制御可能。
{
text: 'ドキュメント',
collapsed: false,
items: [
{
text: 'セクションA',
collapsed: true,
items: [
{ text: 'ページ1', link: '/page1' },
{ text: 'ページ2', link: '/page2' }
]
}
]
}
設計指針
- 深すぎるネストは避ける: 3階層(ルート→カテゴリ→ページ)が可読性の限界
- collapsed デフォルト: 関連トピックは折りたたんで関心別アクセスを支援
- Notionのデータベース構造(parent category → pages)をそのままネスト構造に変換すると自然
- ページ移行後、サイドバー生成スクリプトで自動構築可能
3. lefthook + Biome 設定
lefthook.yml
pre-commit:
commands:
biome:
glob: '*.{ts,js,json,jsonc,vue,css}'
run: pnpm biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
stage_fixed: true
stage_fixed: true で自動修正後のファイルを再stageしてくれる。
4. Markdown変換パターン(Notion → VitePress)
ブロックタイプ別変換
| Notion | Markdown |
|---|---|
| Paragraph | 通常段落 |
| Heading 1-3 | #, ##, ### |
| Bulleted List | - item |
| Numbered List | 1. item |
| Code Block | ```code``` |
| Toggle | <details><summary> |
| Callout | > [icon] メッセージ |
| Quote | > 引用文 |
リンク変換
- Notion内部リンク → ページIDから
slugにマッピングして相対パスに変換 - 外部リンク → そのまま保持
- ファイルリンク → CDN URLに変換(必要に応じて)
ビルド時検証
VitePressビルドでデッドリンクが検出される。Notionリンク残存の有無を自動検知可能。
問題
Zedで .vue ファイルを保存すると、CLIの biome format と微妙に異なるフォーマットが適用される。インデントや閉じタグ前の空白など、細かい差分が毎回発生する。
原因
.zed/settings.json で "formatter": "language_server" と指定すると、Vueファイルでは Volar(Vue LSP)が優先的にフォーマッタとして使われる。BiomeのLSPも動いているが、Volarが先に処理してしまう。
解決策
プロジェクトの .zed/settings.json でVue.jsのフォーマッタにBiome LSPを 名前指定 する。
{
"languages": {
"Vue.js": {
"formatter": {
"language_server": {
"name": "biome"
}
}
}
}
}
"formatter": "language_server" (名前なし)ではなく、"name": "biome" で明示することがポイント。
補足
external コマンドで pnpm biome format --stdin-file-path を呼ぶ方法でも動くが、LSP経由の方がZedのエコシステムに沿った正攻法。
症状
pnpm install 実行後、Zedエディタ上で以下のTypeScriptエラーが表示される。
JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. (ts 7026)
エディタを再起動すると解消する。
原因
pnpm install が node_modules を書き換えた際、ZedのTypeScript言語サーバーがモジュール解決キャッシュを正しく更新できず、フレームワーク固有の型定義(JSX.IntrinsicElements)を見失う。
対処法
Zed全体の再起動は不要。コマンドパレット(Cmd+Shift+P)から language server: restart を実行してTSサーバーだけ再起動すれば解消する。
設計判断: Webクロール vs R2同期
Markdownドキュメントサイト(VitePress等)にRAGチャットボット導入時、データソース戦略はR2が正解。
| 観点 | Webクロール | R2 + 同期 |
|---|---|---|
| チャンク品質 | HTMLノイズ混入、低い | Markdownソース直接、高い |
| 横断検索 | ページ単位で弱い | frontmatter活用で強い |
| 導入コスト | URL指定だけ | 同期スクリプト保守が必要 |
判断基準: プロトタイプ以外はR2推奨。20-50ファイルでも品質差が大きく、数百ファイルではWebクロールが不安定化する。
アーキテクチャ
VitePress(チャットUI) → Workers API Proxy → Cloudflare AI Search
↑
R2 Bucket(Markdown)
AI Searchがチャンキング・Embedding・ベクトル検索・LLM回答生成を一気通貫で処理。自前でVectorize + Workers AIを組む必要がない。社内数十人規模なら月$5(Workers有料プラン)で運用可能。
API仕様
検索・回答API
/search- 自然言語検索(関連チャンクの取得)/ai-search- AI回答生成付き検索/ask- NLWeb統合(拡張子からの情報取得も可能)/mcp- MCPサーバー経由のアクセス
内部最適化機能
- 自動インデックス: ファイル変更を検知して再構築(R2ファイル削除時も自動反映)
- クエリ書き換え: ユーザーの曖昧な質問を改善
- リランキング:
@cf/baai/bge-reranker-baseモデルで意味的に再スコア - 類似性キャッシング: 頻出クエリの高速化
- 同期クールダウン: 3分、インデックス速度3-5x高速化
Workers呼び出し
const response = await env.AI.autorag("instance-name").aiSearch({
query: "ユーザーの質問",
model: "external-model-name" // オプション: AI Gateway経由で外部モデル使用可能
});
データソース
- R2: Markdown, docx, odt の自動クローリング
- Website: URLベースの自動クロール
- AI Gateway経由でClaude API等の外部LLMに切り替え可能(設定のみ)
セットアップ
R2同期
# GitHub Actions で定期実行
# docs を R2 にアップロード → AI Search が検索インデックス自動更新
APIトークンの権限
R2操作に「Workers R2 Storage: Edit」だけでは不足。「Edit Cloudflare Workers」テンプレートから作成するのが確実(Account Settings Read等が含まれる)。
wrangler r2の --remote フラグ
- ローカル開発・GitHub Actions ともに
--remoteが必須(デフォルトはlocal R2) Resource location: localと表示されたら--remoteが足りない
Embeddingモデル選定(日本語)
| モデル | 特徴 | コスト |
|---|---|---|
@cf/baai/bge-m3 | 多言語・Dense+Sparse同時対応 | $0.012/MTok |
@cf/preferred-networks/plamo-embedding-1b | 日本語特化 | $0.019/MTok |
小規模ドキュメントならbge-m3のコスト効率が優る。
実装知見・トラブルシューティング
SSEストリーミングの罠
aiSearch({ stream: true }) の戻り値はReadableStreamではなく**Responseオブジェクト**。instanceof Responseでチェックしてbodyを転送する。
if (streamResult instanceof Response) {
const headers = new Headers(streamResult.headers);
return new Response(streamResult.body, { headers });
}
wrangler.tomlの [vars] が secret を上書きする
wrangler secret putで設定した値が、wrangler.tomlの[vars]に同名キーがあると上書きされる。ローカル開発用の値は.dev.varsファイルに分離すること。
CORS設定のセキュアデフォルト
ALLOWED_ORIGINS未設定時は**return false(全拒否)** にする。return true(全許可)だと本番で設定忘れ時に全オリジンからアクセス可能になる。
必須対応
1. permissions の明示設定
未設定だとデフォルト権限がリポジトリ設定依存で過剰になりうる。最小権限を明示する。
permissions:
contents: read
2. ${{ }} インジェクション対策
run: ブロック内で outputs を直接展開するとシェルインジェクションのリスク。環境変数経由で渡す。
# NG: 直接展開
run: echo "$\{\{ steps.changed.outputs.files \}\}"
# OK: 環境変数経由
env:
CHANGED_FILES: $\{\{ steps.changed.outputs.files \}\}
run: echo "$CHANGED_FILES"
3. SHAピンニング
タグ(@v4)は上書き可能。フルコミットSHAで固定する。2025年3月のtj-actions/changed-files事件(CVE-2025-30066)が実例。
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
4. EOFデリミタのランダム化
マルチライン出力で固定文字列EOFを使うと内容に含まれた場合に破壊される。
DELIMITER=$(openssl rand -hex 16)
echo "output<<$DELIMITER" >> "$GITHUB_OUTPUT"
echo "$CONTENT" >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
推奨対応
5. workflow_dispatch の入力パラメータ
手動実行時はHEAD~1比較が意図通り動かないことがある。全件処理モードを用意する。
判断基準:データの機密性が最優先
社外秘・機密データのAI活用では、データが外部企業のサーバーを通るか否かが最大の判断軸。
| 判定 | プラットフォーム | 適用 |
|---|---|---|
| ❌ 不可 | NotebookLM(Google)、SaaS型外部サービス | データがGoogle等外部企業のインフラで処理 |
| ✅ 可 | 自前RAG(自社インフラ)、オンプレLLM | データ流出ゼロ |
重要: NotebookLMは便利だが、Googleのサーバーを経由するため社外秘データは禁止。
ドキュメントRAG用プラットフォーム比較
SaaS型(社外秘データNG)
- Biel.ai
- Markprompt
- DocsBot AI
- Inkeep
特徴: クイック導入・運用負荷低い → 但し、データ送信ステップで機密性リスク。
OSS型(自前デプロイ推奨)
- RAGFlow
- LlamaIndex
- LangChain + LangGraph
- Haystack
- LightRAG
特徴: インフラ費用・運用負荷増 ↔ データ流出ゼロ → 社外秘プロジェクト向け。
意思決定フロー
- データ機密性は高いか? → Yes: 自前RAG(OSS)選択
- 運用負荷を最小化したいか? → Yes: クラウド運用(Workers等)
- 既存ドキュメント基盤がMarkdown+Gitか? → Yes: VitePressベースRAGが相性良好