mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
feat: add token cost estimation and improve token formatting
- Format tokens: 1000k → 1m, 1450k → 1.45m, etc. - Estimate cost using Claude Sonnet pricing ($3/M input, $15/M output) - Show estimated cost in respawn banner and header - More accurate than stored cost which doesn't update in interactive mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ When user says "COM": 1) Increment version in BOTH `package.json` AND `CLAUDE.md
|
|||||||
|
|
||||||
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
|
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
|
||||||
|
|
||||||
**Version**: 0.1361 (must match `package.json`)
|
**Version**: 0.1362 (must match `package.json`)
|
||||||
|
|
||||||
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1361",
|
"version": "0.1362",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
+46
-14
@@ -417,6 +417,26 @@ class ClaudemanApp {
|
|||||||
return this._elemCache[id];
|
return this._elemCache[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format token count: 1000k -> 1m, 1450k -> 1.45m, 500 -> 500
|
||||||
|
formatTokens(count) {
|
||||||
|
if (count >= 1000000) {
|
||||||
|
const m = count / 1000000;
|
||||||
|
return m >= 10 ? `${m.toFixed(1)}m` : `${m.toFixed(2)}m`;
|
||||||
|
} else if (count >= 1000) {
|
||||||
|
const k = count / 1000;
|
||||||
|
return k >= 100 ? `${k.toFixed(0)}k` : `${k.toFixed(1)}k`;
|
||||||
|
}
|
||||||
|
return String(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate cost from tokens using Claude Sonnet pricing
|
||||||
|
// Input: $3/M tokens, Output: $15/M tokens
|
||||||
|
estimateCost(inputTokens, outputTokens) {
|
||||||
|
const inputCost = (inputTokens / 1000000) * 3;
|
||||||
|
const outputCost = (outputTokens / 1000000) * 15;
|
||||||
|
return inputCost + outputCost;
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.initTerminal();
|
this.initTerminal();
|
||||||
this.loadFontSize();
|
this.loadFontSize();
|
||||||
@@ -738,7 +758,7 @@ class ClaudemanApp {
|
|||||||
this.updateCost();
|
this.updateCost();
|
||||||
// Update tokens display if this is the active session
|
// Update tokens display if this is the active session
|
||||||
if (session.id === this.activeSessionId && session.tokens) {
|
if (session.id === this.activeSessionId && session.tokens) {
|
||||||
this.updateRespawnTokens(session.tokens.total);
|
this.updateRespawnTokens(session.tokens);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2086,7 +2106,7 @@ class ClaudemanApp {
|
|||||||
// Show tokens if session has token data
|
// Show tokens if session has token data
|
||||||
const session = this.sessions.get(this.activeSessionId);
|
const session = this.sessions.get(this.activeSessionId);
|
||||||
if (session && session.tokens) {
|
if (session && session.tokens) {
|
||||||
this.updateRespawnTokens(session.tokens.total);
|
this.updateRespawnTokens(session.tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2234,11 +2254,19 @@ class ClaudemanApp {
|
|||||||
this.$('respawnTimer').textContent = this.formatTime(remaining);
|
this.$('respawnTimer').textContent = this.formatTime(remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRespawnTokens(totalTokens) {
|
updateRespawnTokens(tokens) {
|
||||||
const tokensEl = this.$('respawnTokens');
|
const tokensEl = this.$('respawnTokens');
|
||||||
if (totalTokens > 0) {
|
// Support both old format (number) and new format (object with input/output/total)
|
||||||
|
const isObject = tokens && typeof tokens === 'object';
|
||||||
|
const total = isObject ? tokens.total : tokens;
|
||||||
|
const input = isObject ? (tokens.input || 0) : Math.round(total * 0.6);
|
||||||
|
const output = isObject ? (tokens.output || 0) : Math.round(total * 0.4);
|
||||||
|
|
||||||
|
if (total > 0) {
|
||||||
tokensEl.style.display = '';
|
tokensEl.style.display = '';
|
||||||
tokensEl.textContent = `${(totalTokens / 1000).toFixed(1)}k tokens`;
|
const tokenStr = this.formatTokens(total);
|
||||||
|
const estimatedCost = this.estimateCost(input, output);
|
||||||
|
tokensEl.textContent = `${tokenStr} tokens · $${estimatedCost.toFixed(2)}`;
|
||||||
} else {
|
} else {
|
||||||
tokensEl.style.display = 'none';
|
tokensEl.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -2547,28 +2575,32 @@ class ClaudemanApp {
|
|||||||
|
|
||||||
updateTokens() {
|
updateTokens() {
|
||||||
// Use global stats if available (includes deleted sessions)
|
// Use global stats if available (includes deleted sessions)
|
||||||
let total = 0;
|
let totalInput = 0;
|
||||||
|
let totalOutput = 0;
|
||||||
if (this.globalStats) {
|
if (this.globalStats) {
|
||||||
total = this.globalStats.totalInputTokens + this.globalStats.totalOutputTokens;
|
totalInput = this.globalStats.totalInputTokens || 0;
|
||||||
|
totalOutput = this.globalStats.totalOutputTokens || 0;
|
||||||
} else {
|
} else {
|
||||||
// Fallback to active sessions only
|
// Fallback to active sessions only
|
||||||
this.sessions.forEach(s => {
|
this.sessions.forEach(s => {
|
||||||
if (s.tokens) {
|
if (s.tokens) {
|
||||||
total += s.tokens.total || 0;
|
totalInput += s.tokens.input || 0;
|
||||||
|
totalOutput += s.tokens.output || 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const total = totalInput + totalOutput;
|
||||||
this.totalTokens = total;
|
this.totalTokens = total;
|
||||||
const display = total >= 1000 ? `${(total / 1000).toFixed(1)}k` : total;
|
const display = this.formatTokens(total);
|
||||||
|
|
||||||
// Show global cost if available
|
// Estimate cost from tokens (more accurate than stored cost in interactive mode)
|
||||||
const costDisplay = this.globalStats ? `$${this.globalStats.totalCost.toFixed(2)}` : '';
|
const estimatedCost = this.estimateCost(totalInput, totalOutput);
|
||||||
const tokenEl = this.$('headerTokens');
|
const tokenEl = this.$('headerTokens');
|
||||||
if (tokenEl) {
|
if (tokenEl) {
|
||||||
tokenEl.textContent = costDisplay ? `${display} tokens · ${costDisplay}` : `${display} tokens`;
|
tokenEl.textContent = total > 0 ? `${display} tokens · $${estimatedCost.toFixed(2)}` : '0 tokens';
|
||||||
tokenEl.title = this.globalStats
|
tokenEl.title = this.globalStats
|
||||||
? `Lifetime: ${this.globalStats.totalSessionsCreated} sessions created`
|
? `Lifetime: ${this.globalStats.totalSessionsCreated} sessions created\nEstimated cost based on Claude Sonnet pricing`
|
||||||
: 'Token usage across active sessions';
|
: 'Token usage across active sessions\nEstimated cost based on Claude Sonnet pricing';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user