211 109[tech](https://zenn.dev/tech-or-idea) Claude Code の [v1.0.71](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#1071) で、TUI の最下部に表示する status line をカスタマイズできるようになりました。 コンテキストサイズや auto-compact までの残量を常に可視化しておくために便利です。 ![result](https://res.cloudinary.com/zenn/image/fetch/s--ylg3zFyb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://storage.googleapis.com/zenn-user-upload/deployed-images/328495dd190305b6726c9d3a.png%3Fsha%3D46bc964662e860464be872434f089a3402f9d4db) ## モチベーション Claude のコンテキスト幅 (モデルに一度に入力できる上限トークン数) は 200K トークンです。 Claude Code はこのコンテキスト幅に到達する前に、大体 80% ぐらいのタイミングで自動的に `/compact` を実行します。 `/compact` はコンテキストを圧縮するために、過去の会話履歴の情報を大幅に落とした要約を作ります。 その結果、必要なコンテキストが足りずに Claude Code が迷走することが非常によくあります。 そのため、auto-compact まで後どれくらいトークンを使えるかを常に把握しておきたいわけですが、 Claude Code は auto-compact の直前まで情報を表示しません。 この不便さを解消するためにちょうど良い機能がリリースされたので、早速利用しましょう。 ## 設定方法 まず、下記の設定を `.claude/settings.json` に追加します。 ```json { "statusLine": { "type": "command", "command": "~/.claude/statusline.js" } } ``` `statusline.js` の中身は以下の通りです。 ```javascript #!/usr/bin/env node const fs = require('fs'); const path = require('path'); const readline = require('readline'); // Constants const COMPACTION_THRESHOLD = 200000 * 0.8 // Read JSON from stdin let input = ''; process.stdin.on('data', chunk => input += chunk); process.stdin.on('end', async () => { try { const data = JSON.parse(input); // Extract values const model = data.model?.display_name || 'Unknown'; const currentDir = path.basename(data.workspace?.current_dir || data.cwd || '.'); const sessionId = data.session_id; // Calculate token usage for current session let totalTokens = 0; if (sessionId) { // Find all transcript files const projectsDir = path.join(process.env.HOME, '.claude', 'projects'); if (fs.existsSync(projectsDir)) { // Get all project directories const projectDirs = fs.readdirSync(projectsDir) .map(dir => path.join(projectsDir, dir)) .filter(dir => fs.statSync(dir).isDirectory()); // Search for the current session's transcript file for (const projectDir of projectDirs) { const transcriptFile = path.join(projectDir, \`${sessionId}.jsonl\`); if (fs.existsSync(transcriptFile)) { totalTokens = await calculateTokensFromTranscript(transcriptFile); break; } } } } // Calculate percentage const percentage = Math.min(100, Math.round((totalTokens / COMPACTION_THRESHOLD) * 100)); // Format token display const tokenDisplay = formatTokenCount(totalTokens); // Color coding for percentage let percentageColor = '\x1b[32m'; // Green if (percentage >= 70) percentageColor = '\x1b[33m'; // Yellow if (percentage >= 90) percentageColor = '\x1b[31m'; // Red // Build status line const statusLine = \`[${model}] 📁 ${currentDir} | 🪙 ${tokenDisplay} | ${percentageColor}${percentage}%\x1b[0m\`; console.log(statusLine); } catch (error) { // Fallback status line on error console.log('[Error] 📁 . | 🪙 0 | 0%'); } }); async function calculateTokensFromTranscript(filePath) { return new Promise((resolve, reject) => { let lastUsage = null; const fileStream = fs.createReadStream(filePath); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); rl.on('line', (line) => { try { const entry = JSON.parse(line); // Check if this is an assistant message with usage data if (entry.type === 'assistant' && entry.message?.usage) { lastUsage = entry.message.usage; } } catch (e) { // Skip invalid JSON lines } }); rl.on('close', () => { if (lastUsage) { // The last usage entry contains cumulative tokens const totalTokens = (lastUsage.input_tokens || 0) + (lastUsage.output_tokens || 0) + (lastUsage.cache_creation_input_tokens || 0) + (lastUsage.cache_read_input_tokens || 0); resolve(totalTokens); } else { resolve(0); } }); rl.on('error', (err) => { reject(err); }); }); } function formatTokenCount(tokens) { if (tokens >= 1000000) { return \`${(tokens / 1000000).toFixed(1)}M\`; } else if (tokens >= 1000) { return \`${(tokens / 1000).toFixed(1)}K\`; } return tokens.toString(); } ``` ## 仕組み 上の `statusline.js` はトークン数を分析するために transcript と呼ばれるファイルをパースしています。transcript はセッションごとに自動生成されるファイルで、完全な履歴が保存されています。 現在のセッションに対応する transcript のパスは、標準入力経由で status line のコマンドに渡されます。 transcript の各行は下記のような形式になっており、今回利用するのは `message.usage` だけです。 211 109 この記事に贈られたバッジ ![Thank you](https://static.zenn.studio/images/badges/paid/badge-frame-5.svg)