Sign In

ccusage-fleet

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ccusage-fleet - npm Package Compare versions

Comparing version
0.2.0
to
0.3.0
+65
src/graph.js
function number(value) {
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
}
function compact(value, prefix = '') {
const numeric = number(value);
const absolute = Math.abs(numeric);
if (absolute >= 1_000_000_000) return `${prefix}${(numeric / 1_000_000_000).toFixed(1)}B`;
if (absolute >= 1_000_000) return `${prefix}${(numeric / 1_000_000).toFixed(1)}M`;
if (absolute >= 1_000) return `${prefix}${(numeric / 1_000).toFixed(1)}K`;
return `${prefix}${Math.round(numeric)}`;
}
function bar(value, peak, width) {
if (value <= 0 || peak <= 0) return ' '.repeat(width);
const scaled = Math.min(width, (value / peak) * width);
let full = Math.floor(scaled);
const fraction = scaled - full;
const partials = ['', '▏', '▎', '▍', '▌', '▋', '▊', '▉'];
const partialIndex = Math.round(fraction * 8);
if (partialIndex === 8) {
full += 1;
}
const partial = full < width ? partials[partialIndex] ?? '' : '';
const used = Math.min(width, full + (partial ? 1 : 0));
return `${'█'.repeat(Math.min(full, width))}${partial}${'░'.repeat(width - used)}`;
}
function pad(value, width, right = false) {
const text = String(value);
return right ? text.padStart(width) : text.padEnd(width);
}
function border(left, middle, right, widths, fill = '─') {
return `${left}${widths.map((width) => fill.repeat(width + 2)).join(middle)}${right}`;
}
export function renderFleetGraph(fleet, settings, terminalWidth = process.stdout.columns ?? 120) {
const rows = fleet[fleet.command];
const bucketName = fleet.command === 'weekly' ? 'week' : fleet.command === 'monthly' ? 'month' : 'day';
const metricKey = settings.graphMetric === 'cost' ? 'totalCost' : 'totalTokens';
const values = rows.map((row) => number(row[metricKey]));
const total = values.reduce((sum, value) => sum + value, 0);
const peak = Math.max(0, ...values);
const metricTitle = settings.graphMetric === 'cost' ? 'Cost' : 'Total tokens';
const metricValue = (value) => settings.graphMetric === 'cost' ? compact(value, '$') : compact(value);
const title = `${metricTitle} over time · ${bucketName} buckets · total ${metricValue(total)} · peak ${metricValue(peak)}`;
const widths = [10, 10, 10, Math.max(16, Math.min(60, terminalWidth - 46))];
const output = [title, border('╭', '┬', '╮', widths)];
const headers = ['BUCKET', 'TOKENS', 'COST', 'DISTRIBUTION'];
output.push(`│ ${headers.map((header, index) => pad(header, widths[index])).join(' ┆ ')} │`);
output.push(border('╞', '╪', '╡', widths, '═'));
for (const row of rows) {
const cells = [
pad(row.period, widths[0]),
pad(compact(row.totalTokens), widths[1], true),
pad(settings.noCost ? '—' : compact(row.totalCost, '$'), widths[2], true),
bar(number(row[metricKey]), peak, widths[3]),
];
output.push(`│ ${cells.join(' ┆ ')} │`);
}
output.push(border('╰', '┴', '╯', widths));
return output.join('\n');
}
+2
-0

@@ -29,2 +29,4 @@ {

"groupBy": { "enum": ["agent", "device", "none"] },
"graph": { "type": "boolean" },
"graphMetric": { "enum": ["tokens", "cost"] },
"byHost": { "type": "boolean" },

@@ -31,0 +33,0 @@ "offline": { "type": "boolean" },

+1
-1
{
"name": "ccusage-fleet",
"version": "0.2.0",
"version": "0.3.0",
"description": "Aggregate ccusage reports across local and SSH-connected machines",

@@ -5,0 +5,0 @@ "type": "module",

@@ -41,2 +41,8 @@ # ccusage-fleet

# Append a report-bucket token distribution graph
npx ccusage-fleet daily --hosts localhost,rtzr --graph
# Scale the graph by estimated cost
npx ccusage-fleet daily --hosts localhost,rtzr --graph --graph-metric cost
# One consistent date range and timezone on every device

@@ -70,2 +76,4 @@ npx ccusage-fleet daily \

"groupBy": "agent",
"graph": false,
"graphMetric": "tokens",
"concurrency": 4,

@@ -96,2 +104,8 @@ "timeoutMs": 120000

## Graph
Add `--graph` to append a proportional distribution chart after the table. Bars use total tokens by default; choose `--graph-metric cost` to scale them by estimated cost. Buckets follow the report command: days for `daily`, weeks for `weekly`, and months for `monthly`.
Hour buckets will require a future normalized event export from ccusage; ccusage-fleet does not copy raw transcripts to approximate them.
## Failure handling

@@ -98,0 +112,0 @@

@@ -13,2 +13,3 @@ const VALUE_OPTIONS = new Map([

['--group-by', 'groupBy'],
['--graph-metric', 'graphMetric'],
]);

@@ -26,2 +27,4 @@

['--dry-run', ['dryRun', true]],
['--graph', ['graph', true]],
['--no-graph', ['graph', false]],
['--help', ['help', true]],

@@ -108,2 +111,4 @@ ['-h', ['help', true]],

--by-host / --no-by-host Compatibility aliases for --group-by device/none
--graph / --no-graph Show a report-bucket distribution graph
--graph-metric <metric> Scale graph bars by tokens or cost (default: tokens)
--no-cost Hide costs

@@ -110,0 +115,0 @@ --offline / --online Use bundled or online ccusage pricing (default: offline)

@@ -10,2 +10,3 @@ import { readFileSync } from 'node:fs';

import { renderFleetTable } from './table.js';
import { renderFleetGraph } from './graph.js';

@@ -66,5 +67,9 @@ const here = dirname(fileURLToPath(import.meta.url));

} else {
process.stdout.write(`${renderFleetTable(fleet, settings)}\n`);
const sections = [renderFleetTable(fleet, settings)];
if (settings.graph) {
sections.push(renderFleetGraph(fleet, settings));
}
process.stdout.write(`${sections.join('\n\n')}\n`);
}
return settings.strict && successful.length !== results.length ? 1 : 0;
}

@@ -146,2 +146,10 @@ import { existsSync, readFileSync } from 'node:fs';

}
const graphMetric = parsedOptions.graphMetric ?? config.graphMetric ?? 'tokens';
if (!['tokens', 'cost'].includes(graphMetric)) {
throw new Error(`graph-metric must be tokens or cost (received '${graphMetric}')`);
}
const noCost = parsedOptions.noCost ?? config.noCost ?? false;
if (graphMetric === 'cost' && noCost) {
throw new Error('graph-metric cost cannot be combined with --no-cost');
}

@@ -154,5 +162,7 @@ return {

groupBy,
graph: parsedOptions.graph ?? config.graph ?? false,
graphMetric,
hosts,
json: parsedOptions.json ?? false,
noCost: parsedOptions.noCost ?? config.noCost ?? false,
noCost,
offline: parsedOptions.offline ?? config.offline ?? true,

@@ -159,0 +169,0 @@ since: parsedOptions.since,