fix: enable token tracking for both interactive and one-shot modes

- Add --output-format stream-json to runPrompt() for JSON output with
  token usage data
- Add parseTokensFromStatusLine() to parse tokens from Claude's status
  line display in interactive mode (e.g., "123.4k tokens")
- Token parsing supports numeric, k (thousands), and M (millions) suffixes
- Estimates input/output split (60/40) for interactive mode since only
  total is displayed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-19 04:42:13 +01:00
co-authored by Claude Opus 4.5
parent e4dfee0ea7
commit 40c2ef1491
+41
View File
@@ -361,6 +361,9 @@ export class Session extends EventEmitter {
this.emit('terminal', data);
this.emit('output', data);
// Parse token count from status line (e.g., "123.4k tokens" or "5234 tokens")
this.parseTokensFromStatusLine(data);
// Detect if Claude is working or at prompt
// The prompt line contains "❯" when waiting for input
if (data.includes('❯') || data.includes('\u276f')) {
@@ -524,6 +527,7 @@ export class Session extends EventEmitter {
this.ptyProcess = pty.spawn('claude', [
'-p',
'--dangerously-skip-permissions',
'--output-format', 'stream-json',
prompt
], {
name: 'xterm-256color',
@@ -661,6 +665,43 @@ export class Session extends EventEmitter {
}
}
// Parse token count from Claude's status line in interactive mode
// Matches patterns like "123.4k tokens", "5234 tokens", "1.2M tokens"
private parseTokensFromStatusLine(data: string): void {
// Remove ANSI escape codes for cleaner parsing
const cleanData = data.replace(/\x1b\[[0-9;]*m/g, '');
// Match patterns: "123.4k tokens", "5234 tokens", "1.2M tokens"
// The status line typically shows total tokens like "1.2k tokens" near the prompt
const tokenMatch = cleanData.match(/(\d+(?:\.\d+)?)\s*([kKmM])?\s*tokens/);
if (tokenMatch) {
let tokenCount = parseFloat(tokenMatch[1]);
const suffix = tokenMatch[2]?.toLowerCase();
// Convert k/M suffix to actual number
if (suffix === 'k') {
tokenCount *= 1000;
} else if (suffix === 'm') {
tokenCount *= 1000000;
}
// Only update if the new count is higher (tokens only increase within a session)
// We use total tokens as an estimate - Claude shows combined input+output
const currentTotal = this._totalInputTokens + this._totalOutputTokens;
if (tokenCount > currentTotal) {
// Estimate: split roughly 60% input, 40% output (common ratio)
// This is an approximation since interactive mode doesn't give us the breakdown
const delta = tokenCount - currentTotal;
this._totalInputTokens += Math.round(delta * 0.6);
this._totalOutputTokens += Math.round(delta * 0.4);
// Check if we should auto-clear
this.checkAutoClear();
}
}
}
// Check if we should auto-clear based on token threshold
private checkAutoClear(): void {
if (!this._autoClearEnabled || this._isClearing) return;