漢字練習のためのnpm packageを作成しているのですが、みなさんにお願いがあります

私は小学2年生の子を持つ*1のですが、小学生といえば漢字練習があります。

うちの小学2年生は文字の読み書きがあまり好きではないようで、漢字練習もやはり好きではないようです。

一方で、ちょっとでもゲーム要素が入ると、たとえそれが勉強っぽいものでも、ずっと楽しんでやっているという単純さもあります。

じゃあ、漢字練習にもゲーム要素を加えられたらずっとやるのではないか?と考えました。親も単純です。

おそらく「漢字を練習したらスタンプ1つ」みたいなレベルのものには「やりたくない」が勝ってしまい、見向きもしないので、もっとゲーム要素が必要です。

ところで、私はソフトウェアエンジニアです。

最近のタブレットはペンもついているし、タブレットで実際に漢字を書くタイプのゲームができればいいのではないかと考えました。漢字は書かないと覚えない気がするので。

SPAを作ってどこか(GitHub Pagesとか)でホストしたらタブレットから簡単にやってもらうこともできそうです。

というわけで、最近漢字練習のためのnpm packageをコツコツ作成しています。

kakitori

k1low.github.io

kakitori は「漢字や仮名の書き取り」を組み込むためのライブラリです。

できること

  • 漢字や仮名を、正しい筆順どおりに書けているかを画ごとに判定する
  • そのうえで「とめ」「はね」「はらい」まで判定する
  • 「学校」のような熟語を、マスに並べて1問の問題にする
  • マスにふりがなを振る
  • 「学[ ]」のように一部だけ書かせる穴埋め問題にする
  • 縦書きで複数の問題を並べて、漢字練習帳のページのようなレイアウトを組む
  • 書いた結果を取り出す(採点や記録などを想定)

3つのプリミティブ

kakitori は主に charblockpageの3つのプリミティブで構成されていて用途に応じて使う設計です。

char

char はkakitoriの中の最小単位で1文字の書き取りを実現します。

import { char } from "@k1low/kakitori";

const c = char.create("学");
c.mount(document.getElementById("writer")!, {
  size: 300,
  showGrid: true,
  onCorrectStroke: (data) => console.log("OK", data.strokeNum),
  onMistake: (data) => console.log("NG", data.strokeNum),
  onComplete: ({ totalMistakes }) => console.log("done", totalMistakes),
});
c.start();

これで「学」が表示されて、正しい筆順で書かないと先に進めないというインタラクションができます。

block

block は1問の単位です。マス目とふりがなで構成されています。

import { block } from "@k1low/kakitori/block";

block.create(document.getElementById("host")!, {
  spec: {
    cells: [
      { kind: "guided", char: "学", mode: "write" },
      { kind: "guided", char: "校", mode: "write" },
    ],
    annotations: [
      { cellRange: [0, 1], expected: "がっこう", mode: "write" },
    ],
  },
});

「学」「校」のマスが縦に2つ並んだ問題が組めます。annotations に書いた「がっこう」が、ふりがな用のマスとして自動で寄り添う形で並びます。mode: "show" を指定すれば「お手本として字を見せるだけのマス」、mode: "write" で「書かせるマス」になるので、混ぜれば穴埋めもできます。

page

pageblock を漢字練習帳のページに似た縦書きグリッドに構成することができます。

import { page } from "@k1low/kakitori/page";

page.create(document.getElementById("host")!, {
  writingMode: "vertical-rl",
  columns: 5,
  cellsPerColumn: 8,
  cellSize: 96,
  blocks: [
    { id: "q1", spec: { /* block の spec をそのまま */ } },
    { id: "q2", spec: { /* ... */ } },
    // ...
  ],
});

columns x cellsPerColumn の格子が用意されて、各 block を右上から流し込んでいく感じです。1つのblock が列をまたぐ場合の改行も自動でやってくれます。

「とめ」「はね」「はらい」判定

漢字の運筆判定そのものは Hanzi Writer という素晴らしい既存ライブラリの仕組みに乗っかっています。筆順どおりに線をなぞらせる、というところまではこれだけでできます。

kakitori 独自の機能として、その上に「とめ」「はね」「はらい」の判定を追加できるようにしています。文部科学省および文化庁は、「とめ」「はね」「はらい」について、「骨組みが適切であれば、正誤に影響しない」という見解(常用漢字表の字体・字形に関する指針)を示してはいるものの、字の練習という観点では大事だろうと判断して追加しています。

各画ごとに、ペンを離す直前の運筆の角度や速度を見て、判定しています。

書いた結果を取り出す

書いた結果も取得できます。

const result = p.result();
// {
//   complete: true,
//   matched: true,
//   blocks: [
//     { id: "q1",
//       cells: [
//         { kind: "guided", chars: [
//             { character: "学", complete: true, matched: true,
//               perStroke: [...], mistakes: 1, strokeEndingMistakes: 0 }
//         ]},
//         ...
//       ],
//       annotations: [...]
//     },
//     ...
//   ]
// }

「何文字書いたか」「画ごとの一致度はどれくらいか」「『とめ』を何回間違えたか」みたいな情報がツリー構造で返ってきます。collectCharResults() という補助関数を使うとページ全体の文字結果をフラットに取り出してフィルタすることもできるので、ゲーム的な採点処理にそのまま流し込めるはずです*2

文字データのこと

文字データは @k1low/hanzi-writer-data-jp というforkedパッケージ経由で読み込んでいて、その元データは主に animCJK です。

animCJKは漢字の書き順や運筆情報を持つsvgファイルを作成するという狂気の1人プロジェクトです*3。すごい。感謝しかないです。

ただ animCJK が使っているフォントは小学校で習う字形とちょっと違うところがあります。たとえば「日」や「田」の中の横画が、フォントだと隣の縦画とちょっと離れているのですが、書き取り練習だとくっつけて書きます。「糸偏」や「竹冠」のように、筆順や形が日本の教科書と違う部首もあります。

どちらも正しいのですが、目的は「書き取り」なので、これらを日本の小学校で習う字形に直す subAnimJ というプロジェクトをはじめて animCJK と併用しています。あと、animCJKには数字(0〜9)が含まれていないので、数字用に animNumber というのも作って、これも取り込んでいます。

subAnimJやanimNumberをやりはじめて、さらにanimCJKの偉大さがわかります。subAnimJなどはまだまだ十分ではありません*4。引き続きやっていこうと思っています。

使うときのライセンス上の注意事項

kakitori 自体は実行時に文字データを fetch しているだけなのですが、kakitori を使ったWebアプリを公開する場合、デフォルト設定だと裏で @k1low/hanzi-writer-data-jp を経由して animCJK / subAnimJ / animNumber / Unihan由来のデータを利用することになるので、各上流プロジェクトのライセンスへの帰属表記をどこかに記載しておくのが良いと思います。

例となるHTMLスニペットを kakitori のREADMEに置いてあるので、よろしければ参考にしてください。

「とめ」「はね」「はらい」データのこと

「とめ」「はね」「はらい」を判定するためには「ここはとめる」「ここははねる」「ここははらう」というラベルを、各文字の各画につけたデータが必要になります。これがいい感じのオープンデータとして見つからなかったので、自分で作ることにしました。

それが @k1low/kakitori-data というパッケージです。

たとえば「あ」のデータはこんな感じになっています。

{
  "character": "",
  "strokeEndings": [
    { "types": ["tome"] },
    { "types": ["tome", "hane"] },
    { "types": ["tome"] }
  ]
}

strokeEndings[i] が i 画目の終わり方を表しています((types を配列にしているのは、字によっては「とめ」も「はね」も許容されるケースがあるためです))。

現在の進捗としては、ひらがなと数字(0〜9、全角・半角)はだいたい埋まったのですが、漢字はまだほぼ手付かずです*5。これもコツコツやっていこうと思っています。

このデータは正解がない(というか正解は「とめ」でも「はらい」でも「はね」でもいいっぽい)ので、他の人からの貢献を受け付けにくいなあと悩んでいます。

「正解がないのはわかっているが、それでも文字を綺麗に書くためにとめはねはらいをしっかり意識して欲しい」という「思い」からの機能なのです。

なお、@k1low/kakitori-data にデータがなければ判定をしないし、そもそも判定を外すこともできるので、安心してください。

ところで、根本的な問題にぶつかっている

ここまで一見すると順調なプロジェクトなのですが、実は、今、根本的な問題にぶつかっています。結構前から気づいていたのですが、いまだ解決の糸口が見つかっていない問題です。

それは、 私にゲームを作るセンスどころか能力が全くないこと です。

「kakitori でレイアウトもふりがなも結果取得もできるんだから、あとはそれっぽく組み合わせればゲームになるだろう」と最初は思っていたのですが、全く進みません。

何をどうしたらゲームが作れるのか、全く手が出ないのです。

「ゲーム要素を加えればやってくれるはず」という発想で kakitori を作り始めたのに、その「ゲーム要素」が私からは生み出せない、というのが今です。厳しい。

みなさんにお願い

「漢字書き取り」に同じような課題を持つみなさん。

kakitori は頑張って作っていきます(すみません。まだAPIもガンガン変えると思います)。

なので、もし、みなさんが kakitori を使って漢字ゲームを作ったら私にも教えていただけないでしょうか。子にやらせたいです。

どうかよろしくお願いいたします。私も引き続きめげずにゲームを考えてみます。

*1:2人目が今月生まれました👶

*2:まだ、BREAKING CHANGE はあると思います

*3:Issueのコメントをみる限り、1人プロジェクトに見えます

*4:主に調整したのが小学校3年生の漢字までです

*5:執筆時点で 76 / 2270 文字

待つツールを作って活用している ( gh-wait / gh-copilot-review )

最近はCoding Agentを使って開発をしています。複数のCoding Agentを立ち上げて、それらと複数のタスクを並行して進めるようになりました。

一方で、感覚として「待つ」ことが多くなった気がします。

PRを出してCIの完了を待つ、レビューを待つ、マージを待つ。IssueやDiscussionでの回答を待つ。並行して進めていると「あれどうなったかな」とターミナルだけではなくブラウザのウィンドウを行ったり来たりする回数が増えています。

そして、Coding Agentは「待つ」のが苦手っぽい?気がしています*1

Coding Agentの作業フローに「状態の変化を待つ」を組み込みにくいんですよね。待ちが入る作業をあまりうまくやってもらえていない。ここに改善ポイントがありそうです。

最近は「待つ」ツールを作って活用しています。

gh-wait

github.com

gh-wait はGitHub CLIのエクステンションで、Pull Request、Issue、Discussion、Workflow Runを監視して、指定した条件が満たされたときにアクション(ブラウザを開く、デスクトップ通知など)を実行するツールです。

基本的な使い方は、GitHubのURLをそのまま渡すだけです。対象の種類(PR、Issue、Workflow Runなど)は自動で判別してくれます。

# PRが承認されたらブラウザで開く
$ gh wait https://github.com/owner/repo/pull/123 --approved --open

# CIが完了したらデスクトップ通知
$ gh wait https://github.com/owner/repo/pull/123 --ci-completed --notify

# Workflow Runの完了を待つ
$ gh wait https://github.com/owner/repo/actions/runs/23424874935 --completed --notify

本当は gh wait prgh wait workflow のようなサブコマンドもあるのですが、大抵はURLを渡して使っています。GitHubの画面からURLをコピーしてそのまま貼るだけなので楽です。

継続監視

デフォルトでは条件が1回成立したらルールが削除されますが、 --until--count で継続的に監視できます。

$ gh wait pr --commented --notify --until merged

この例だと、PRにコメントが付くたびにデスクトップ通知を出して、マージされたら監視を終了します。

アーキテクチャ

クライアント・サーバ方式を採用しています。初回のルール作成時にバックグラウンドサーバが自動起動して、各ルールの設定間隔でGitHub APIをポーリングします。ルールは永続化されるのでサーバを再起動しても大丈夫です。

自分自身のイベント(自分のコメントや承認)は自動的にフィルタされるので、自分でコメントして自分に通知が飛ぶ、みたいなことは起きません(地味に大事)。

gh-copilot-review

github.com

gh-copilot-review もGitHub CLIのエクステンションです。PRに対してGitHub Copilotのコードレビューをリクエストします。

「それって gh pr edit --add-reviewer @copilot でよくない?」と思うかもしれませんが。実際にはCopilotレビューを連続的に活用しようとすると、いくつか面倒なことがあります。

  • 古いコメントが残る
    • コミットを積むたびにCopilotにレビューをリクエストすると、前回のレビューコメントがPR上に残って見づらくなる
  • レビュー完了のタイミングがわからない
    • Copilotのレビューには時間がかかるので、完了を知りたい。しかし(意外に)完了を判断するのにコツがいる(失敗するとCopilotのレビューに対応している間にCopilotからのレビューが追加される)

gh-copilot-review はこれらをコード化しています。

古いコメント(インラインのレビューコメントを除く)を自動的に非表示(minimize)にしてから、新しいレビューをリクエストします。すでにレビュー済み・レビュー中の場合は不要なリクエストをスキップします。

基本的にはPull requestを作業中のブランチで実行するだけです。

$ gh copilot-review
Minimized 3 outdated Copilot review(s)
Copilot review requested on PR #42

そして、 --wait オプションでCopilotのレビュー完了を待機できます。

$ gh copilot-review --wait
Minimized 1 outdated Copilot review(s)
Copilot review requested on PR #42
Waiting for Copilot review... (30s elapsed)
Waiting for Copilot review... (1m0s elapsed)
Copilot review completed on PR #42

タイムアウトとポーリング間隔もカスタマイズできます。

$ gh copilot-review --wait --wait-timeout 5min --wait-interval 10sec

私の場合、Copilotからのレビューがなくなるまで何度もレビューを依頼したりします*2

今のところは人間のためのツール

gh-waitgh-copilot-review も、今のところは人間が状態の変化に気づくために使っています。

Coding Agentの作業フローに直接組み込めたらもっと良くなるとは思っていて、エージェントに「Copilotにレビューをリクエストして、完了を待って、レビューがあればそれぞれ対応を提案して*3人間の判断をもとに対応して。それを繰り返して。」という指示を出せるようになるのが理想です*4

例えば、「待つ」部分とCoding Agentに頑張ってもらう部分を明確に分けると、うまくいくのかもしれない。そう考えて、実は実験的に runnAgent runnerを追加していて、runnのシナリオとして「待つ」と「エージェントに作業させる」を組み合わせて、開発のルーティーンを記述できないかなあと模索中です。

# 現時点でのAgent runnerのインターフェイス
runners:
  claude:
    agent: claude
    model: claude-sonnet-4-20250514
    system: "You are a helpful assistant."
    permissions:
      - "allow:*"
steps:
  -
    claude:
      prompt: "What is Go?"
    test: current.res.content != ''

gh-wait は汎用的な「待つ」ツール、 gh-copilot-review はCopilotレビューに特化した「レビューを依頼して待つ」ツール。どちらもGitHub CLIのエクステンションなので、インストールは同じです。

$ gh extension install k1LoW/gh-wait
$ gh extension install k1LoW/gh-copilot-review

よければ使ってみてください。

*1:もしかしたら私が知らなくてうまくいっていないだけかもしれない

*2:最近のCopilotのレビューは優秀です

*3:ここは https://github.com/k1LoW/gh-pr-reviews という別のツールを作っています

*4:まだうまく回った試しがない

ブラウザベースのMarkdown viewerとしてmoを作った

OpinionatedなMarkdown viewer

世の中にMarkdown viewerはたくさんありますが、自分がほしかったのは以下の特性を持つものでした。

  • CLIからMarkdownを開きたい
  • Markdownはブラウザで見たい(ローカルWebサーバ方式)
  • Markdownを開くたびに別プロセス/別ポートが使用されないようにしたい
  • グルーピングもしたい

また、コーディングエージェントがMarkdownドキュメントを書くようになり、CLIからファイルを追加できることがキーになってきました。 エージェントの行動を邪魔しないインターフェイスが求められています。

というわけで、OpinionatedなMarkdown viewerを作ってみました。

mo

github.com

moMarkdownファイルをブラウザで openするCLIツールです。Markdownファイルを渡すとブラウザが開いてレンダリングされたドキュメントが表示されます。

(Go + 組み込みReact SPAの)単一バイナリで、依存関係なしにインストールできます。

使い方

基本はファイルを渡すだけです。

$ mo README.md
mo: serving at http://localhost:6275 (pid 12345)

複数ファイルも渡せます。

$ mo README.md CHANGELOG.md docs/*.md

グループ

--target (-t) フラグでファイルをグループに分けられます。グループごとにURLパスとサイドバーが分かれます。

$ mo spec.md --target design      # http://localhost:6275/design
$ mo api.md --target design       # design グループに追加
$ mo notes.md --target plans      # http://localhost:6275/plans

設計ドキュメントは"design"グループ、コーディングエージェントが書いたプランファイルは"plans"グループ、という使い分けもできます。

moの仕組み

mo は結構私のユースケースに合わせて特徴的な動きをします。

mo はデフォルトでバックグラウンドでサーバーを起動します。最初の mo コマンドでサーバーが立ち上がり、シェルはすぐに返ってくるので、そのまま作業を続けられます。

$ mo README.md
mo: serving at http://localhost:6275 (pid 12345)
$ # シェルがすぐ使える

これが個人的にめちゃくちゃ使いやすいです。

コーディングエージェントにも使わせやすいですし、スクリプトにも組み込みやすいです。ブラウザオープンも --open--no-open オプションで制御可能です。

2回目以降の mo コマンドの実行も、同じポートですでにサーバーが動いている場合新しいサーバーは起動せず既存のサーバーにファイルを追加します。

$ mo README.md          # サーバー起動してファイル追加
$ mo CHANGELOG.md       # 既存のサーバーにファイル追加
$ mo docs/*.md          # さらに追加

つまり、mo コマンドを叩くたびにプロセスやポートが増えていくことはありません。1つのサーバーに対してファイルをどんどん追加していく設計です。

Webアプリケーションを1つ起動してそこにファイルを追加していくイメージです。Webアプリケーションを開発した経験のある私には馴染みのある動きです。

なお、完全に別のセッションが必要な場合は別ポートを使えば可能です。

$ mo draft.md -p 6276

サーバーの管理もCLIから行えます。これもWebサーバを管理したことがある人には馴染みがあるのではないでしょうか。

$ mo --status              # 稼働中のmoサーバーを一覧表示
$ mo --shutdown            # デフォルトポートのサーバーをシャットダウン
$ mo --shutdown -p 6276    # 特定ポートのサーバーをシャットダウン

Web UIからサーバーをリスタートすることもできます。

画面右下のリスタートボタンをクリックすると、開いているファイルやグループの構成を保ったままサーバーが再起動します。

mo をバージョンアップした後、ファイルを開き直すことなく新しいバージョンに切り替えられるので便利です。

Viewerとしての機能

Markdownのレンダリングは基本的なものには対応しています。

  • GitHub Flavored Markdown(テーブル、タスクリスト、脚注など)
  • Shiki によるシンタックスハイライト
  • Mermaid の図表レンダリング
  • GitHub Alerts(admonitions)

UI

UI周りの機能としては以下があります。

  • ダーク / ライトテーマ切り替え
  • 目次パネル(右側)
  • Rawマークダウン表示の切り替え
  • コンテンツコピー(Markdown / テキスト / HTML形式を選択可)
  • ドラッグ&ドロップでのファイル並び替え

UIはGitHubのMarkdownレンダリングに寄せたデザインにしています。最終的にGitHubでどう見えるかがわかることが重要ですし、何よりGitHubの見た目が一番慣れているので読みやすいです。

サイドバーのファイル一覧には、フラットビューとツリービューの2つの表示モードを用意しています。

フラットビューはファイル名だけを並べたシンプルな一覧で、ドラッグ&ドロップで自由に並び替えられます。少数のファイルをサッと確認したいときはこれで十分です。

一方、docs/*.md のように同じディレクトリの下に大量のファイルを開いた場合、フラットビューだと一覧が長くなって見づらくなります。ツリービューに切り替えると、ディレクトリ階層で折りたたんで表示できるので、ファイルが多くても整理しやすくなります。

ライブリロード

mo は開いたファイルの変更を監視しています。

ファイルが更新されると、SSE(Server-Sent Events)経由でブラウザに通知が飛び、自動でコンテンツが再取得・再描画されます。

アーキテクチャ

GoのHTTPサーバーにReact SPAを go:embed で組み込んでいます。go generate でフロントエンドをビルドしてから go:embed でバイナリに埋め込むので、最終成果物は単一バイナリになります。

バックエンドはGoで、ファイル管理・API・SSE・ファイル監視を担当しています。フロントエンドはVite + React + TypeScript + Tailwind CSSで、react-markdownを使ってMarkdownをレンダリングしています。

インストール

homebrew tap:

$ brew install k1LoW/tap/mo

manually:

releases page からダウンロードできます。

go install は残念ながらできません。

References

mo のアイデアは yusukebe/gh-markdown-preview をベースにしています。gh-markdown-previewはずっと愛用してきたツールで、CLIからMarkdownをブラウザでプレビューするという体験の原点です。そこに自分がほしかった機能を足していった結果、別のツールとして形になりました。

まとめ

mo を使うことで、複数のMarkdownファイルを持続的にプレビューできるようになりました。

特に気に入っているのは、バックグラウンドでサーバーが動いていて、mo コマンドでファイルをどんどん追加できるところです。シェルを掴まないのも推しポイントです。

是非使ってみてください。

octocov.dev でカバレッジレポートを確認する

octocovはコードメトリクス(カバレッジ、Code to Test Ratio、テスト実行時間)を収集するツールキットです。以前もこのブログで紹介しました。

k1low.hatenablog.com

k1low.hatenablog.com

今回、octocovのサイトとして octocov.dev を作りました。

octocov.dev

ランディングページとしてoctocovの紹介(ほぼリンクだけ)を掲載しつつ、実験的機能としてカバレッジレポートをWebブラウザで閲覧できる機能も載せています。というか、これが作りたくて octocov.dev を作りました。

カバレッジレポート閲覧機能

octocovには artifact:// というdatastore設定があり、GitHub Actionsのアーティファクトにレポートを保存できます。外部サービスのアカウント不要で使えるので、個人的にはこれがお気に入りです。

CI上ではPRコメントやJob Summaryでレポート自体は確認できるので普段は困らないのですが、「任意のタイミングでサッと確認したい」「任意のソースコードのカバレッジを行単位で確認したい」となるとCLIとしての octocov コマンドを使用するしかありませんでした。

octocov.devでGitHubにサインインすると、artifact:// datastoreに保存されたカバレッジレポートをブラウザで閲覧できます。

前提として、リポジトリに以下のような .octocov.yml が設定されている必要があります。

# .octocov.yml
report:
  datastores:
    - artifact://owner/repo

そして octocov-action を使ってGitHub Actionsのアーティファクトにレポートが保存されている必要があります。

github.com

使い方

サインイン後、ヘッダーの検索バーに owner/repo を入力するとレポートページに遷移します。

レポートサマリでは、コードカバレッジ、Code to Test ratio、テスト実行時間、そしてファイル一覧が表示されます。

ファイル一覧からファイルをクリックすると、ソースコードビューに遷移します。行ごとのカバレッジが色分けで表示されます(緑=カバー済み)。シンタックスハイライトも有効なので、コードも読みやすいと思います。

スコープとプライバシー

サインイン時に public_repo(パブリックリポジトリのみ)と repo(プライベートリポジトリも含む)のスコープを選択できます。

データの取り扱いについて明確にしておくと、サーバーが保持するのはセッション(GitHubアクセストークンとユーザー情報)のみです。カバレッジデータはブラウザのIndexedDBに、ソースコードはキャッシュせず毎回GitHub APIから取得します。ログアウト時にはブラウザキャッシュを全削除します。

技術スタック

バックエンドはCloudflare Workers + Hono、フロントエンドはReact + Tailwind CSS v4で、@cloudflare/vite-plugin を使って単一のViteビルドに統合しています。シンタックスハイライトにはShikiを使用しています。

認証はGitHub OAuth + PKCEで、セッション管理にCloudflare KV(8時間TTL)を使っています。

パフォーマンス確保のため、クライアント側ではIndexedDBにカバレッジデータをキャッシュしています。

一方、サーバー側にはカバレッジデータのキャッシュは持たせていません。また、ソースコードはどこにも持たせていません(都度フェッチ)。これはプライベートリポジトリのデータが意図せず漏洩するリスクを避けるためです。

まとめ

octocov CLIはターミナルで、octocov-actionはCIで、そしてoctocov.dev はブラウザで。octocovのカバレッジレポートを確認する手段がまた一つ増えました。

CoverallsやCodecovのような専用カバレッジサービスの代替ではなく、octocovの artifact:// datastoreを使っているユーザー向けの軽量なビューアという位置づけです。

よかったら使ってみてください。

tmuxのウィンドウで動かしているコーディングエージェントのステータスを確認できるtcmuxを作った

普段使っているtmuxにClaude Codeの情報がほしい

私は普段からtmuxを使っています。Emacsを開いているウィンドウ、シェルを開いているウィンドウ、特定の作業をしているブランチのウィンドウ、別のリポジトリのウィンドウといった感じで複数のウィンドウを行き来しながら作業しています。

最近はClaude Codeを使うことが増えてきて、tmuxのウィンドウのいくつかでClaude Codeが動いている状態になりました。あるウィンドウではバグ修正を依頼して、別のウィンドウでは新機能の実装検討を依頼して、さらに別のウィンドウではテストを書かせている、といった具合です。

ここで問題がひとつ。「どのウィンドウのClaude Codeが今どういう状態なのかがわからない」

Claude Codeは、処理中なのか、完了して入力待ちなのか、あるいは許可を求めて止まっているのか、実際にそのウィンドウに切り替えないとわかりません。許可を求めて止まっているのに気づかず放置してしまうこともあります。

Claude Code専用の管理ツールを作るという手もありますが、自分がやりたいのは 普段使っているtmuxにClaude Codeの情報がのっている状態 です。Emacsやシェルといったウィンドウはそのままでいいので、Claude Codeが動いているウィンドウだけステータスが見えればいい。

というわけで、tmuxのウィンドウ一覧にClaude Codeのステータスを表示するツールを作りました。

tcmux

github.com

当初は "terminal と Claude Code の mux viewer" ということで tcmux という名前にしましたが、GitHub Copilot CLIにも対応したので、今は "terminal coding agent mux viewer" ということにしています。tmuxのウィンドウ一覧やセッション一覧にコーディングエージェントのステータスを付加して表示するCLIツールです。

それだけのツールです。

使い方

list-windows (lsw)

tcmux list-windows でClaude Codeが起動しているウィンドウの一覧を表示できます。

$ tcmux list-windows
0: editor (1 panes) ✻ Fix login bug [Idle]
2: server (2 panes) ✻ Add API endpoint [Running (1m 30s)], ✻ Write tests [Idle]
5: docs (1 panes) ✻ Update README [Idle]
7: review (1 panes) ✻ Review PR [Waiting]

各行の末尾にClaude Codeのステータスが表示されます。これで、どのウィンドウが処理中で、どのウィンドウが入力待ちかが一目でわかります。

-A オプションをつけると、Claude Codeが起動していないウィンドウも含めて全て表示されます。

$ tcmux list-windows -A
0: editor (1 panes) ✻ Fix login bug [Idle]
1: shell (1 panes)
2: server (2 panes) ✻ Add API endpoint [Running (1m 30s)], ✻ Write tests [Idle]
3: logs (1 panes)
4: htop (1 panes)
5: docs (1 panes) ✻ Update README [Idle]
7: review (1 panes) ✻ Review PR [Waiting]

-F オプションには tmux list-windows と互換があるので自由にフォーマット変更が可能です。

オプション 説明
-A, --all-windows Claude Codeが起動していないウィンドウも含めて全て表示
-a, --all-sessions 全セッションのウィンドウを表示
-t, --target-session 対象セッションを指定
-F, --format 出力フォーマットを指定(tmux互換+tcmux拡張)

list-sessions (ls)

tcmux list-sessions でtmuxセッションの一覧を、Claude Codeのステータス集計とともに表示できます。

$ tcmux list-sessions
dev: 7 windows (attached) - 3 Idle, 1 Running, 1 Waiting
main: 2 windows - 1 Idle
work: 1 window

複数のセッションを使い分けている場合に便利です。

ステータス検出

tcmuxは以下のステータスを検出します。

ステータス 説明
Idle プロンプト待ち。Claude Codeが次の入力を待っている状態
Running 処理中
Waiting ユーザー入力待ち。許可ダイアログや確認プロンプトが表示されている状態

また、モードも検出して表示します。

モード 説明
plan mode Plan modeがONの状態
accept edits Accept editsがONの状態

例えば、Plan modeがONで処理中の場合は [Running (30s, plan mode)] のように表示されます。

GitHub Copilot CLIにも対応した

tcmuxはClaude Code向けに作り始めましたが、ターミナルで動くコーディングエージェントはClaude Codeだけではありません。私は、主にレビュータスク目的でGitHub Copilot CLIも同様にtmux上で動かすことがあります。

そこで、GitHub Copilot CLIのステータス検出にも対応しました。検出するステータス(Idle / Running / Waiting)はClaude Codeと同じです。エージェントの種類はアイコンで区別できます。

アイコン エージェント
Claude Code
GitHub Copilot CLI

Claude CodeとCopilot CLIを混在させて使っている場合はこんな感じの表示になります。

$ tcmux list-windows
0: editor (1 panes) ✻ Fix login bug [Idle]
2: server (2 panes) ✻ Add API endpoint [Running (1m 30s)], ⬢ Write tests [Idle]
5: docs (1 panes) ⬢ Update README [Running]
7: review (1 panes) ✻ Review PR [Waiting]

レシピ

自分が実際に使っている設定を紹介します。

tmuxのウィンドウ選択で 、.tmux.conftmux list-windows の代わりに tcmux list-windows -A を使うと、ウィンドウ選択時にClaude Codeのステータスが表示されて便利です。

Before:

bind-key w run-shell "tmux list-windows | fzf --tmux | cut -d: -f1 | xargs tmux select-window -t"

After:

bind-key w run-shell "tcmux list-windows -A --color=always | fzf --ansi --tmux | cut -d: -f1 | xargs tmux select-window -t"

基本 tmux list-windows の置き換えのためだけに作ったので実質これが全てかもしれません。

Waitingのウィンドウがあれば一目でわかるので、許可を求められているClaude Codeを放置してしまうことがなくなりました。

自分はもう少し見た目を調整して使っています。

bind -r w run-shell "tcmux lsw -A --color=always | fzf --ansi --layout reverse --tmux 80%,50% --color='pointer:24' | cut -d: -f 1 | xargs tmux select-window -t"

冒頭のポストのイメージのように表示されます。

インストール

homebrew tap:

$ brew install k1LoW/tap/tcmux

go install:

$ go install github.com/k1LoW/tcmux@latest

まとめ

tcmuxを使うことで、tmux上で複数のClaude Codeを並行して動かしているときに、各インスタンスの状態を把握しやすくなりました。

特に「許可待ちで止まっているClaude Codeに気づかない」問題が解消されたのが大きいです。GitHub Copilot CLIにも対応しているので、複数のコーディングエージェントを混在させて使っている場合でも、アイコンで区別しつつ同じインターフェースでステータスを確認できます。

2025年の振り返りと2026年の抱負

明けました

2025年の振り返り

2025年2月からテイラー株式会社で働いています。

note.com

現在はTailor Platformの開発をメインにしていますが、入社エントリで書いたような感想は依然としてそのままで1年弱怒涛の日々でした。

入社エントリの最後に書いていた「プロジェクト」も https://docs.tailor.tech/ の1セクション(サブサブセクションくらい)を担うくらいの機能(サービス)としてリリースできました。 まだ改善の余地はありますが、開発体験の一端をサポートできてよかったです。

2025年はAIエージェントを使った開発もスタンダードになりました。開発能力を押し上げられる感覚がありました。

テイラー株式会社での日々

2025年の後半は私はPlatformの開発を主としていましたが、それ以外にもパッと思いつくだけでも以下のような開発領域があります。

  • Platformを使ったアプリケーションの開発(Professional Service)
  • Platformのコンソール画面の開発
  • PlatformのSDKの開発
  • Platformを使ったアプリケーションのフロントエンドのフレームワークの開発
  • Platformを使ったアプリケーション開発の開発フローの開発
  • Platformを支えるプラットフォームの開発・運用(SRE)

それぞれの領域にプロフェッショナルがいて、日々開発が進んでいます。

ふわっとした開発領域はありますがそれでチームが閉じているという印象はなく、「あらゆるアイデアをデプロイする」を実現するためのエコシステムをそれぞれの視点で成長させていっているイメージです。それぞれの視点のキャリブレーションはCTOがしているイメージ(そういえばどの領域にもCTOがいる気がする)。

Platformの開発は、あくまでイメージなのですが、日々「ある程度規模の大きくなったOSSの開発に参加している感じ」です。

コードにオーナー(オーサー)がいてメンテナがいて、Pull Requestを出してレビューがある。機能要望や提案がIssueやDesign docを通じてあり、適切に取り込まれる。

提案から実装までの流れが気持ち良いので、ついついいろいろ考えて(開発して)しまいます。

メンバーとのコミュニケーションはいつも学びがあります。

提案段階でもらったフィードバックで大きく方針を変えて大きく改善されることも1度や2度ではないですし、Pull Requestに対するレビューは必ずより良いものにつながります*1

なんというかrunnやdeckで体験したことがあるコントリビュータとの濃いめのコミュニケーションみたいなものがずっと続いている感じなんですよね。うーん説明が難しい。

つまり、かなり楽しくやっているということです。

もし興味がありましたら質問などお声がけください。

note.com

OSS

環境が変わったことから、「まずは手元の開発体験を助けるもの」みたいなものが多かった印象です。VSCode Extensionsまである。

deck、今年だったのか。。。個人的に欲しい機能は完全に揃ったので、あとはバグ修正をきっちりやっていきたいですね。

あと、2024年にできなかったrunn v1も無事リリースしました。

発表

今年は6本でした。私は基本的に発表は「いろいろな話題を話す」方針なのですが、いい感じにバラけたかなあと思っています。

2026年は発表は控える方向ですが2027年はまた頑張りたい!!*2

2026年の抱負

今年は「地に足をつける」です。 2026年は個人的特大イベントがあるので、そのイベントに振り回されないようしっかりを踏ん張りたいと思います。

いや、それがなくてもその日暮らし的に生きている気がするので、しっかりと地に足をつけていきたいです。なんか「ルーティーン」みたいなのを手に入れたい。

今年もどうぞよろしくお願いします。

*1:単純なものは全てAIエージェントで事前に終わっているのですが、それでも

*2:Fukuoka.goはできれば参加したい!

PHPカンファレンス福岡の記憶

これは、PHPカンファレンス福岡について語ってほしい! Advent Calendar 2025 の14日目のエントリです。

adventar.org

はじめに

私は性質として過去の記憶が結構消失してしまうようです。脳の問題なのか性格の問題なのかはわかりませんが、昔からそうです。個人的にはしょうがないと諦めています。

なのでここから書く内容は勘違いが混じっている可能性が高いです。人の数だけPHPカンファレンス福岡があるということでご了承ください。

過去のエントリからの記憶のサルベージです。

PHPカンファレンス福岡2015

k1low.hatenablog.com

なんと私、運営スタッフをやっています。エントリにも書いていますが、役に立たない系運営スタッフだった模様。今でもその自覚があります。カンファレンス運営は能力です。

当時「マスクド」は別のPHPer界隈でも存在していたのですが、その「マスクド」が偶然の被りだったことは覚えています。奇跡の被りでテンションが上がりました。

スポンサーの話。

多分10年経っていて隠すことではないし記憶も定かではないので適当に言うと、初年度からはじまっている株式会社Fusicのスポンサードは withelmo が動いた + 会社のGoサインがすぐ出たからだったと思います*1

スポンサードの会社判断が早いと、実は社内にも良い効果があると思っていて、「そういうカンファレンスに行って交流すること知見を得ることが良いことなんだ」と社内に暗黙の理解をさせることができるんですよね。

今年2025年のFusicのスポンサーセッションはとても良かったです。「カンファレンスとスポンサーの良い関係というのがずっと続いていたんだろうなあ」と部外者ながら思いました。

speakerdeck.com

PHPカンファレンス福岡2016

k1low.hatenablog.com

そういえば私運営スタッフでは印刷係でした。ネームカードに貼るステッカーを手作りで作成しはじめたのっていつだったかな。。。覚えていない。A4のシール用紙にいい感じにマークが印刷できるように何度も微調整をしていた気がします。

2016の発表資料で紹介したツールkomaの名前元ネタであるうちの犬、狛次郎は10歳などとうに過ぎていますが今も元気です。

PHPカンファレンス福岡2017

k1low.hatenablog.com

私、Ask the Speakerを早速体験していますね。Ask the Speaker、最初は誰も来ないかもしれないとドキドキするのですが、結局色々話すことができて良い思い出しかないです。

そういえばこのときだったっけな、清家くんがカンファレンスチケット購入忘れてたの。そのseike460くんはPHPカンファレンス福岡2023の実行委員長です。

この年まで私は運営スタッフでした。3年!

PHPカンファレンス福岡2018

tech.pepabo.com

2018はGMOペパボ株式会社のパートナー*2としてスポンサーブースに立ったりなどしていました(写真に証拠として残っていました)。

プロポーザルは落ちて、リジェクトコンで発表させてもらったりしていました。

【非公式】PHPカンファレンス福岡2018前夜祭リジェクトコン #phpconfuk_rej - connpass

リジェクトコンなのに3トラック!!すごい!

たまたまではあるとは思うのですが、Fusicと(GMOペパボの)ロリポップ!マネージドクラウドが同時にスポンサーしていて、とても誇らしかったのを覚えています。

PHPカンファレンス福岡2019

note.com

個人ブログに単独エントリがなかったので、もしかして、、、と思ったらやはり「マネクラからの挑戦状」の年でした。

これの「Webサイトコードゴルフチャレンジ」ですが、思いついちゃったのでしょうがない。仕組みを作るのがちょっと時間がかかりましたがやりました。

「サイトの表示を一切変えずに」って今で言うとビジュアルリグレッションテストですね。

PHPアプリケーション脆弱性修正チャレンジ」も好評でした。mrtc0 さんありがとう!

PHPカンファレンス福岡2023

k1low.hatenablog.com

@nojimage さんがニコニコしているのも「あーPHPカンファレンス福岡だ」と思いました。

PHPカンファレンス福岡を実感するための個人的なルーティーンの1つが「nojimageさんとお話しする」でした。

nojimageさんは(個人的)福岡の2大CakePHP Bakerの1人で*3当時から本当にお世話になっていました。

nojimageさんが時々お話しされる実装や設計のお話は、いつも私は数年遅れて実感していました。やはりレベルの高い人とお話しすると得られるものが多いです(数年遅れるのは私の練度の問題)。

最近のPHPカンファレンス福岡では私がPHPから離れているので軽い近況の話が多かったですが、ずっと尊敬している方です。

PHPカンファレンス福岡が終わってしまったので、何かまたきっかけを作りたいなあと思っています。

PHPカンファレンス福岡2024

k1low.hatenablog.com

2024はプロポーザル落ちて完全に参加者としてウロウロしていた年でした。めちゃくちゃ面白いですね!!!!!それなのにプロポーザル出すのなんでなんだろう。。。?謎です。

全国からお話ししたい人、質問したい人が来てくれるわけで、「東京、いつもズルい!」と思うと共に「PHPカンファレンス福岡ありがとう!!!!!!!!!!!」となりました。PHPカンファレンス福岡ありがとう!

これを本当に実感した年だった気がします。

いつも各所で言っているのですが、PHPカンファレンス福岡とかPHPerKaigiとかPHPカンファレンスとかPHPカンファレンス(いろいろ)とか、「設計」とか「組織」とかそういう方面の練度が高い人がいてめちゃくちゃ勉強になるんですよね。「PHP」というプレフィックスは本当にあの「プログラミング言語PHP」の意味なのかいつも疑問に思っています。

PHPプレフィックス」なカンファレンスの多くはPHPer以外でも知見が得られるカンファレンスだともっと認知されて欲しいですね。

PHPカンファレンス福岡2025

phpcon.fukuoka.jp

最後の年でしたが、普通にPHPカンファレンス福岡を楽しみました。

受付時に、akase244さんに「今からでも(運営スタッフ)間に合う」と言っていただいたのは嬉しかったです。そういえば運営スタッフやっていたんだよなあ。

終わりに

次の週の別のカンファレンスで akase224 / cakephper のお二人に会いましたが、お二人ともとても笑顔でした。

良いPHPカンファレンス福岡の10年だったのではないでしょうか。

私にとっては「地元」で「ホーム」でした。10年、10年かあ。

個人的な話ですが、来年はちょっと家庭の事情がありカンファレンスの遠方参加は控えることになりそうです*4

カンファレンスの楽しさは忘れられないですし、「PHPカンファレンス福岡フォロワーカンファレンス」も多くありますし、また再来年とかに参加できればなあと思っています。

カンファレンスいいですよね。PHPカンファレンス福岡も良かった。良かった。

*1:当時は私Fusicに所属していました

*2:社員のことをパートナーと呼びます

*3:もう1人はryuringさん

*4:ネガティブな話題ではありません。念の為