AIの実行時間・トークン・コストをローカルログだけで可視化する「aimet」
対応ツール: Claude Code / Codex CLI / GitHub Copilot (VS Code Chat)
タグ: Claude, ClaudeCode, OpenAI, GitHubCopilot, TypeScript
1. はじめに — AI開発の「実行時間・トークン・コスト」はブラックボックス
Claude Code や Codex、GitHub Copilot でコードを書くのが当たり前になってきました。ですが、いざ「今回の開発にどれだけ時間がかかったのか」「トークンをどれだけ消費したのか」「従量課金だったらいくらだったのか」と聞かれると、正確に答えられる人は少ないはずです。
チーム向けの管理API(組織機能)を使えば集計はできますが、それは管理者権限とチーム契約が前提です。個人開発者や小さなチームには縁遠い。では手元に何も記録が残っていないかというと、そんなことはありません。各ツールはセッションごとにローカルへ詳細なログ(JSONL)を残しています。ここにはモデル名・タイムスタンプ・実測トークン数(キャッシュ内訳つき)まで全部入っています。
この「手元のログ」だけを情報源に、AIエージェント開発の時間・トークン・API換算コストを採取するCLIツールを作りました。それが aimet(AI Metrics)です。まずは完成形の出力を見てください。
| period | tool | sessions | turns | active | wall | input | output | cacheR | cacheW | cost($) |
| ---------- | ------- | -------- | ----- | ------ | ------ | ----- | ------ | ------ | ------ | ------- |
| 2026-07-05 | copilot | 1 | 1 | 0.01h | 0.02h | 31.3k | 1.6k | 0 | 0 | 0.06 |
| 2026-07-04 | codex | 1 | 24 | 2.12h | 14.98h | 2.15M | 158.2k | 26.05M | 0 | 7.52 |
| 2026-06-19 | claude | 1 | 20 | 0.26h | 0.55h | 29 | 3.9k | 292.8k | 17.4k | 0.25 |
日次・ツール別に、セッション数・ターン数・実働時間・トークン内訳・コストが一望できます。採取したデータは、実行時間の把握・案件別のコスト配賦・モデル選定の判断材料といった、プロジェクトマネジメントの数値として使えます。
2. aimet とは
3ツールのローカルセッションログを走査し、共通スキーマに正規化してSQLiteに蓄積、期間集計やセッション詳細を出力するCLIです。対応状況は次の通り。
| ツール | ログの場所 | 取得できるトークン | 状態 |
|---|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl |
実測(in / out / cacheR / cacheW、1h/5mキャッシュ内訳) | ✅ |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl |
実測(in / cached / out / reasoning)+レート制限時系列 | ✅ |
| GitHub Copilot (VS Code Chat) | <userData>/User/workspaceStorage/<hash>/chatSessions/*.jsonl |
実測(prompt / completion)+消費クレジット | ✅ |
設計上こだわった点は4つです。
-
依存パッケージゼロ。Node.js 22.5+ に標準搭載された
node:sqliteを使うので、npm installしてもサードパーティのライブラリは1つも増えません。 - ローカル完結・組織API不要。手元のログしか読まないため、権限もチーム契約も不要。ログがローカルから外に出ることもありません。
-
SQLiteに蓄積。
~/.aimet/metrics.dbに貯まっていくので、過去分もまとめて集計できます。 -
冪等設計。
(tool, session_id)を主キーに、最終イベント時刻が進んだときだけ更新するので、何度実行しても二重計上しません。
Copilotのログの場所(macOS)は
~/Library/Application Support/Code/User/workspaceStorage/です。記録されるのはChat/エージェントモードの対話のみで、インライン補完は残りません。
リポジトリはこちらです。よければスターやフィードバックをいただけると励みになります。
3. インストール
git clone https://github.com/mayochan32/aimet.git && cd aimet
npm install && npm run build
npm link # `aimet` コマンドをグローバルに登録
npm install は TypeScript のビルド用(devDependencies)だけで、実行時の依存はありません。Node.js は 22.5 以上が必要です(node:sqlite を使うため)。
4. 使い方
4.1 最短3コマンド
aimet collect # 全ログを走査して取り込み(冪等・再実行安全)
aimet report # 日次サマリー(テキスト表)
aimet session --tool claude # 直近セッションのサマリ
collect がログをDBへ取り込み、report が集計、session / detail が個別セッションを掘り下げる、という役割分担です。collect は取り込み済みで変化のないセッションをskipするので、こまめに叩いても安全です。
session の出力例(Claude Code の1セッション):
| item | value |
| tool | claude |
| model | claude-sonnet-4-6 |
| active / wall | 0.26h / 0.55h |
| turns | 20 |
| input tokens | 29 |
| output tokens | 3,921 |
| cache read | 292,816 |
| cache write | 17,350 |
| cost (API-equivalent) | $0.2508 |
input tokens が 29 しかないのに cache read が 292,816 もある——この数字の読み方は6章で詳しく解説します(ここが本記事の技術的な山場です)。
4.2 3種類の発動方法
aimet は「いつ記録するか」を3通り選べます。
手動発動 — 上記のように、好きなタイミングで collect / report を叩く。
自動発動(フック) — aimet init <tool> が各開発環境にフックを組み込みます。Claude Code なら ~/.claude/settings.json に SessionEnd フックを登録し、セッションが終わるたびに自動で記録されるようになります。--dry-run で書き込む内容を事前確認できます。
aimet init claude # SessionEnd フックを登録
対話発動 — /metrics コマンドをインストールすると、エージェントとの対話の中から集計を呼び出せます。既存設定はマージされ、登録済みなら重複追加はしません。
4.3 Markdown / JSON 出力
すべての出力は --md <ファイル> で Markdown に、--json で JSON に整形できます。スプレッドシートやBIツールに流し込みたいときに便利です。
aimet report --by project --md report.md
aimet report --by model --json # BI・スプレッドシート連携用
5. レポートの読み方(3レベル)
aimet の出力は、粒度の違う3つのレベルで見られます。
レベル1: aimet report(PM向けサマリ) — 期間×軸(tool / project / model)で集計した表。案件全体のコスト感や実行時間を俯瞰するのに使います。読み方のヒントとして、active/wall の比が低いほど「AIに任せて放置できていた」ことを意味し、cacheR が大きいほどコンテキスト再利用が効いています。cost/turns で1タスクあたりの単価も出せます。
レベル2: aimet session(1セッションのサマリ) — 特定セッションのトークン内訳・時間・コストを1枚で確認。「あの重かったタスク、何トークン使ったんだ?」を調べるときに。
レベル3: aimet detail(ログの全記録) — APIリクエスト単位まで展開した詳細ダンプ。1リクエストごとのモデル・停止理由・content種別(thinking / text / tool_use)・トークン内訳が並びます。--raw を付けると、通常は除外している巨大フィールド(システムプロンプト全文やツールスキーマ定義)まで含めた完全ダンプになります。
6. トークンの数字の読み方
ここが本記事いちばんの技術的な読みどころです。aimet を使うと否応なく in / out / cacheR / cacheW という数字と向き合うことになりますが、初見では in が 1 や 29 と極端に小さく、まるでバグのように見えます。これはプロンプトキャッシュの仕様であって正常です。入力と出力で仕組みがまったく違うので、分けて説明します。
入力側 — in はキャッシュに乗らなかった「残り」だけ
Claude API の usage は、1リクエストの入力トークンを3つに分類して記録します。aimet の各列はその分類そのものです。
| 列 | 元フィールド | 意味 |
|---|---|---|
in |
input_tokens |
キャッシュから読まれもせず、作成にも使われなかった残りの入力だけ |
cacheR |
cache_read_input_tokens |
過去にキャッシュ済みで、今回読み出して再利用した入力(割引単価) |
cacheW |
cache_creation_input_tokens |
今回新しくキャッシュに書き込んだ入力(割増単価。1h/5m内訳あり) |
つまり in は「入力の総量」ではありません。そのリクエストで実際にモデルが読んだ入力の総量はこうです。
実入力トークン = in + cacheR + cacheW
なぜ in が極端に小さくなるのか。エージェント対話では、システムプロンプト・ツール定義・過去の会話履歴という巨大な塊が毎ターンほぼ同じで、そこがキャッシュに固定されます(→ cacheR)。毎ターン増える新規コンテンツ(ユーザーの一言やツール実行結果)にもキャッシュ印が付くので、その大半は cacheW に吸い込まれます。結果、どのキャッシュ区分にも属さず in に残るのは、最後のキャッシュ区切りより後ろにはみ出す、ごく短い末尾の断片だけになります。
したがって
inが小さいのは「キャッシュがよく効いている=コスト効率が良い」健全な状態を意味します。会話が長くなった分はinではなくcacheR/cacheW側に積み上がります。実際、先ほどの session 出力ではinput=29に対してcacheR=292,816でした。ほぼ全部がキャッシュ再利用で回っている、良い状態です。
出力側 — out はキャッシュされず、生成した全てを合算
キャッシュは入力専用です。出力は絶対にキャッシュされません。モデルの生成物は毎回ゼロから作られるので、out に cacheR / cacheW のような分割はなく、他のどの列とも足し引きの関係を持たない独立した数字です。
out(output_tokens)が数えるのは、その応答でモデルが生成した全トークンで、中身は thinking(推論)+ text(本文)+ tool_use(ツール呼び出しのJSON)をすべて合算した1つの値です。3種すべてが出力単価で課金されます(thinking も例外なく出力扱い)。
detail の落とし穴: detail は1つのAI応答を content ブロックごと(thinking / text / tool_use)に複数行へ展開しますが、
in/out/cacheR/cacheWはターン単位の同じ usage を各行にコピー表示しているだけです。thinking行とtext行の両方にout=59とあるのは「思考59+本文59」ではなく「このターンの生成合計が59」の意味。行ごとに足すと二重計上になります。(集計側のreport/sessionは messageId で重複排除するので合計値は正しく出ます。二重に見えるのは detail の生ダンプだけ。)
out と cache をつなぐ「1ターン遅れ」の関係
出力は生成された瞬間はキャッシュされません(→ out に計上)。しかし応答が終わるとそのテキストは会話履歴に追記され、次のリクエストでは「入力」に化けます。すると次ターンで cacheW(新規書き込み)され、それ以降は cacheR(読み出し)で再利用されます。
今ターンの out ──(1ターン後)──▶ 次ターンの cacheW ──(以降)──▶ cacheR
議事録に例えると、out は「今しゃべった言葉」、cacheW は「それを議事録に書き留める」、cacheR は「議事録を割引価格で読み返す」。出力は1ターン遅れて入力キャッシュのパイプラインに合流します。ただし次ターンの cacheW は前ターンの out そのものだけでなく、間に挟まったユーザー入力やツール実行結果も含むため、数値がぴったり一致するわけではありません。あくまで「出力が入力キャッシュに流れ込む」という方向の関係として捉えてください。
7. コスト計算の考え方 — cost($) は「API換算=参考値」
⚠️ コストは参考値です。実際の実行環境に合わせて計算してください。
aimet の cost($) は**すべて「API換算コスト」**です。従量課金(API直叩き)だった場合にいくらになるかを、ログに記録された実測トークン数 × 公開単価で計算した理論値であり、実際の請求額ではありません。Claude の Pro/Max プランや ChatGPT Plus のような定額サブスクリプションで使っているなら、実際の限界コストは0円です。
この値は次の指標として使ってください。
- サブスクでどれだけ得をしているか
- 従量課金に切り替えたらいくらになるか
- タスクあたりの資源消費量(モデル選定の判断材料)
計算式はシンプルに「各トークン区分 × 対応する単価」の合算です。ここで効いてくるのが6章で見たキャッシュの区分で、in(新規入力)は通常単価、cacheR は割引単価、cacheW は割増単価と、それぞれ別の単価が掛かります。
実装上こだわった点が3つあります。
キャッシュTTL別の正確な計算。キャッシュ書き込みはTTLで単価が違う(5分=1.25倍、1時間=2.0倍)ため、ログの cache_creation 内訳からTTL別に計算します。
重複排除。Claude のログは同一APIメッセージが複数レコードに分かれることがあるため、messageId で重複排除して集計します。
単価不明は0円にしない。モデル名はプレフィックス最長一致で内蔵単価表から引きます。一致するものがなければコストは -(null)となり、0円として集計されることはありません。単価がずれると集計もずれるので、重要な数字を出す前には最新の公開単価と照合してください(~/.aimet/pricing.json で上書き可能)。
なお、ツールによってコストの意味が変わる点にも注意が必要です。とくに GitHub Copilot だけは「API換算値」ではなく「実際に消費したクレジット」(copilotCredits × $0.01)で計算します。ログに実費が記録されているためで、claude / codex の「従量課金だったらいくらか」とは意味が異なります。
8. 設計で意識したこと
最後に、ツールとしての方針を3点だけ。
依存ゼロにこだわった理由。メトリクス採取ツールは、動かすためのセットアップが重いと結局使われません。node:sqlite が標準化されたことで、外部ライブラリなしにSQLiteが使えるようになりました。npm install してもサプライチェーンが増えないことは、手元のログを扱うツールとして安心材料でもあります。
冪等性。フックの多重発動や collect の再実行はふつうに起こります。(tool, session_id) を主キーにして最終イベント時刻が進んだときだけ更新する設計にすることで、「とりあえず叩いておく」が常に安全になります。メトリクスは信頼できてこそ意味があるので、二重計上を絶対に出さないことを最優先にしました。
ローカル完結(プライバシー)。読むのは手元のセッションログだけで、どこにも送信しません。集計結果も ~/.aimet/metrics.db にローカル保存されます。会話内容やコードを含みうるログを扱う以上、外に出さないことは機能というより前提だと考えています。
手元のログという、これまで捨てられていた情報から、AI開発の実行時間・トークン・コストという「見えなかった数字」を取り出せます。同じように自分のAI開発を数値で振り返りたい人の役に立てば嬉しいです。