UNPKG

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
#!/usr/bin/env node // 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();