claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
107 lines (94 loc) • 3.97 kB
JavaScript
// cost-conversation — list all session records in the cost-tracking namespace
// and show cost-per-conversation. Until now reports were per-agent / per-model;
// this gives a per-conversation timeline.
//
// Usage:
// node scripts/conversation.mjs # markdown table
// CONV_FORMAT=json node scripts/conversation.mjs # JSON
// CONV_LIMIT=20 node scripts/conversation.mjs # most recent N
// CONV_NAMESPACE=cost-tracking (default)
import { spawnSync } from 'node:child_process';
// ADR-100 / #1748 Issue 3 — CLI_CORE=1 routes to lite cli-core (~2s cold-cache).
// Conversation listing is list+retrieve only; no search. JSON backend is fine.
const CLI_PKG = process.env.CLI_CORE === '1'
? '@claude-flow/cli-core@alpha'
: '@claude-flow/cli@latest';
const NS = process.env.CONV_NAMESPACE || 'cost-tracking';
function memoryListSessionKeys() {
const r = spawnSync('npx', [
CLI_PKG, 'memory', 'list',
'--namespace', NS, '--format', 'json',
], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
if (r.status !== 0) return [];
const m = /\[[\s\S]*\]/.exec(r.stdout || '');
if (!m) return [];
let entries;
try { entries = JSON.parse(m[0]); } catch { return []; }
return entries
.map((e) => e.key)
.filter((k) => typeof k === 'string' && k.startsWith('session-'));
}
function memoryRetrieve(key) {
const r = spawnSync('npx', [
CLI_PKG, 'memory', 'retrieve',
'--namespace', NS, '--key', key,
], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
if (r.status !== 0) return null;
const m = /\{[\s\S]*\}/.exec(r.stdout || '');
if (!m) return null;
try { return JSON.parse(m[0]); } catch { return null; }
}
function fmtUsd(n) { return `$${(n || 0).toFixed(4)}`; }
function fmtTs(s) { return s ? new Date(s).toISOString().replace('T', ' ').replace(/\.\d+Z/, '') : ''; }
function topModel(byModel) {
const entries = Object.entries(byModel || {}).sort((a, b) => (b[1].cost_usd || 0) - (a[1].cost_usd || 0));
return entries[0]?.[0] || '—';
}
function main() {
const keys = memoryListSessionKeys();
if (!keys.length) {
console.log(`No sessions in '${NS}'. Run \`cost track\` first.`);
return;
}
const records = keys.map((k) => memoryRetrieve(k)).filter(Boolean);
records.sort((a, b) => (a.startedAt || '').localeCompare(b.startedAt || ''));
const limit = parseInt(process.env.CONV_LIMIT || '50', 10);
const recent = records.slice(-limit);
const total = records.reduce((s, r) => s + (r.total_cost_usd || 0), 0);
const byTier = { haiku: 0, sonnet: 0, opus: 0, unknown: 0 };
for (const r of records) {
if (r.byTier) {
for (const [t, v] of Object.entries(r.byTier)) byTier[t] = (byTier[t] || 0) + v;
}
}
if (process.env.CONV_FORMAT === 'json') {
console.log(JSON.stringify({
conversationCount: records.length,
shownCount: recent.length,
total_cost_usd: total,
byTier,
conversations: recent,
}, null, 2));
return;
}
console.log(`# cost-per-conversation — ${records.length} conversations in '${NS}'`);
console.log('');
console.log(`| Metric | Value |`);
console.log(`|---|---:|`);
console.log(`| Conversations tracked | ${records.length} |`);
console.log(`| **Total cost (all)** | **${fmtUsd(total)}** |`);
for (const t of ['haiku', 'sonnet', 'opus', 'unknown']) {
if (byTier[t] > 0) console.log(`| Tier ${t} | ${fmtUsd(byTier[t])} |`);
}
console.log('');
console.log(`## ${recent.length === records.length ? 'All' : 'Most recent ' + recent.length} conversations`);
console.log('');
console.log('| Started | Session | Messages | Top model | Cost |');
console.log('|---|---|---:|---|---:|');
for (const r of recent) {
const sid = (r.sessionId || '').slice(0, 8);
console.log(`| ${fmtTs(r.startedAt)} | \`${sid}\` | ${r.messageCount || 0} | \`${topModel(r.byModel)}\` | ${fmtUsd(r.total_cost_usd)} |`);
}
}
main();