@hasna/economy 0.2.33 → 0.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/brief.d.ts +71 -0
- package/dist/cli/commands/brief.d.ts.map +1 -0
- package/dist/cli/index.js +812 -62
- package/dist/index.js +1 -0
- package/dist/ingest/billing.d.ts.map +1 -1
- package/dist/lib/pricing.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/otel/index.js +1 -0
- package/dist/server/index.js +36 -2
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { SqliteAdapter as Database } from '@hasna/cloud';
|
|
3
|
+
type BriefPeriod = 'today' | 'week';
|
|
4
|
+
export interface BriefTotals {
|
|
5
|
+
sessions: number;
|
|
6
|
+
requests: number;
|
|
7
|
+
input_tokens: number;
|
|
8
|
+
output_tokens: number;
|
|
9
|
+
cache_read_tokens: number;
|
|
10
|
+
cache_create_tokens: number;
|
|
11
|
+
cache_create_5m_tokens: number;
|
|
12
|
+
cache_create_1h_tokens: number;
|
|
13
|
+
total_tokens: number;
|
|
14
|
+
cost_usd: number;
|
|
15
|
+
last_active: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface BriefPeriodSummary extends BriefTotals {
|
|
18
|
+
label: string;
|
|
19
|
+
period: BriefPeriod | 'since';
|
|
20
|
+
since?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface BriefMachineRow extends BriefTotals {
|
|
23
|
+
machine_id: string;
|
|
24
|
+
last_data_at: string | null;
|
|
25
|
+
last_data_age: string;
|
|
26
|
+
}
|
|
27
|
+
export interface BriefAgentRow extends BriefTotals {
|
|
28
|
+
agent: string;
|
|
29
|
+
}
|
|
30
|
+
export interface BriefAccountRow extends BriefTotals {
|
|
31
|
+
account_key: string;
|
|
32
|
+
account_tool: string;
|
|
33
|
+
account_name: string;
|
|
34
|
+
account_email: string | null;
|
|
35
|
+
account_source: string;
|
|
36
|
+
}
|
|
37
|
+
export interface BriefFreshnessMachine {
|
|
38
|
+
machine_id: string;
|
|
39
|
+
max_request_at: string | null;
|
|
40
|
+
request_age: string;
|
|
41
|
+
last_merge_sync_at: string | null;
|
|
42
|
+
merge_sync_age: string;
|
|
43
|
+
}
|
|
44
|
+
export interface EconomyBrief {
|
|
45
|
+
generated_at: string;
|
|
46
|
+
machine: string;
|
|
47
|
+
since: {
|
|
48
|
+
input: string;
|
|
49
|
+
timestamp: string;
|
|
50
|
+
label: string;
|
|
51
|
+
};
|
|
52
|
+
summaries: BriefPeriodSummary[];
|
|
53
|
+
machines: BriefMachineRow[];
|
|
54
|
+
agents: BriefAgentRow[];
|
|
55
|
+
accounts: BriefAccountRow[];
|
|
56
|
+
freshness: {
|
|
57
|
+
machines: BriefFreshnessMachine[];
|
|
58
|
+
max_request_line: string;
|
|
59
|
+
merge_sync_line: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
interface BriefOptions {
|
|
63
|
+
since?: string;
|
|
64
|
+
machine?: string;
|
|
65
|
+
now?: Date;
|
|
66
|
+
}
|
|
67
|
+
export declare function buildBrief(db: Database, opts?: BriefOptions): EconomyBrief;
|
|
68
|
+
export declare function renderBriefText(brief: EconomyBrief): string;
|
|
69
|
+
export declare function registerBriefCommand(program: Command): void;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=brief.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/brief.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAY7D,KAAK,WAAW,GAAG,OAAO,GAAG,MAAM,CAAA;AAEnC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,GAAG,OAAO,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;IACD,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,SAAS,EAAE;QACT,QAAQ,EAAE,qBAAqB,EAAE,CAAA;QACjC,gBAAgB,EAAE,MAAM,CAAA;QACxB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAcD,UAAU,YAAY;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,IAAI,CAAA;CACX;AAgmBD,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,YAAiB,GAAG,YAAY,CA4C9E;AAyDD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAyE3D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsB3D"}
|
package/dist/cli/index.js
CHANGED
|
@@ -236,6 +236,7 @@ var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING,
|
|
|
236
236
|
var init_pricing = __esm(() => {
|
|
237
237
|
init_database();
|
|
238
238
|
DEFAULT_PRICING = {
|
|
239
|
+
"claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
239
240
|
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
240
241
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
241
242
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
@@ -2437,7 +2438,7 @@ var init_paths = () => {};
|
|
|
2437
2438
|
function normalizeEmail(email) {
|
|
2438
2439
|
return (email ?? "").trim().toLowerCase();
|
|
2439
2440
|
}
|
|
2440
|
-
function
|
|
2441
|
+
function accountKey2(tool, name, email) {
|
|
2441
2442
|
const normalizedEmail = normalizeEmail(email);
|
|
2442
2443
|
return `${tool}:${normalizedEmail || name}`;
|
|
2443
2444
|
}
|
|
@@ -2446,7 +2447,7 @@ function normalizeDir(value) {
|
|
|
2446
2447
|
}
|
|
2447
2448
|
function fromProfile(profile, source) {
|
|
2448
2449
|
return {
|
|
2449
|
-
account_key:
|
|
2450
|
+
account_key: accountKey2(profile.tool, profile.name, profile.email),
|
|
2450
2451
|
account_tool: profile.tool,
|
|
2451
2452
|
account_name: profile.name,
|
|
2452
2453
|
...profile.email ? { account_email: normalizeEmail(profile.email) } : {},
|
|
@@ -2463,7 +2464,7 @@ function fromOverride(raw, agent) {
|
|
|
2463
2464
|
return null;
|
|
2464
2465
|
const email = name.includes("@") ? normalizeEmail(name) : undefined;
|
|
2465
2466
|
return {
|
|
2466
|
-
account_key:
|
|
2467
|
+
account_key: accountKey2(tool, name, email),
|
|
2467
2468
|
account_tool: tool,
|
|
2468
2469
|
account_name: name,
|
|
2469
2470
|
...email ? { account_email: email } : {},
|
|
@@ -2481,7 +2482,7 @@ function envOverride(agent, env) {
|
|
|
2481
2482
|
return null;
|
|
2482
2483
|
const email = normalizeEmail(env[`ECONOMY_${agentPrefix}_ACCOUNT_EMAIL`] ?? env["ECONOMY_ACCOUNT_EMAIL"]);
|
|
2483
2484
|
return {
|
|
2484
|
-
account_key:
|
|
2485
|
+
account_key: accountKey2(tool, name, email),
|
|
2485
2486
|
account_tool: tool,
|
|
2486
2487
|
account_name: name,
|
|
2487
2488
|
...email ? { account_email: email } : {},
|
|
@@ -3924,12 +3925,45 @@ function getGeminiBillingExportPath() {
|
|
|
3924
3925
|
function toISODate(d) {
|
|
3925
3926
|
return d.toISOString().substring(0, 10);
|
|
3926
3927
|
}
|
|
3928
|
+
function isValidDateParts(year, month, day) {
|
|
3929
|
+
const d = new Date(Date.UTC(year, month - 1, day));
|
|
3930
|
+
return d.getUTCFullYear() === year && d.getUTCMonth() === month - 1 && d.getUTCDate() === day;
|
|
3931
|
+
}
|
|
3932
|
+
function formatDateParts(year, month, day) {
|
|
3933
|
+
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
3934
|
+
}
|
|
3927
3935
|
function parseDate(value) {
|
|
3936
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
3937
|
+
return value >= 100001 && value <= 999912 ? parseDate(String(value)) : null;
|
|
3938
|
+
}
|
|
3928
3939
|
if (typeof value !== "string" || !value.trim())
|
|
3929
3940
|
return null;
|
|
3930
|
-
const
|
|
3941
|
+
const trimmed = value.trim();
|
|
3942
|
+
const compactMonth = /^(\d{4})(\d{2})$/.exec(trimmed);
|
|
3943
|
+
if (compactMonth) {
|
|
3944
|
+
const year = Number(compactMonth[1]);
|
|
3945
|
+
const month = Number(compactMonth[2]);
|
|
3946
|
+
return isValidDateParts(year, month, 1) ? formatDateParts(year, month, 1) : null;
|
|
3947
|
+
}
|
|
3948
|
+
const dashedMonth = /^(\d{4})-(\d{2})$/.exec(trimmed);
|
|
3949
|
+
if (dashedMonth) {
|
|
3950
|
+
const year = Number(dashedMonth[1]);
|
|
3951
|
+
const month = Number(dashedMonth[2]);
|
|
3952
|
+
return isValidDateParts(year, month, 1) ? formatDateParts(year, month, 1) : null;
|
|
3953
|
+
}
|
|
3954
|
+
const isoDate = /^(\d{4})-(\d{2})-(\d{2})(?:$|[T\s])/.exec(trimmed);
|
|
3955
|
+
if (isoDate) {
|
|
3956
|
+
const year = Number(isoDate[1]);
|
|
3957
|
+
const month = Number(isoDate[2]);
|
|
3958
|
+
const day = Number(isoDate[3]);
|
|
3959
|
+
if (!isValidDateParts(year, month, day))
|
|
3960
|
+
return null;
|
|
3961
|
+
if (trimmed.length === 10)
|
|
3962
|
+
return formatDateParts(year, month, day);
|
|
3963
|
+
}
|
|
3964
|
+
const d = new Date(trimmed);
|
|
3931
3965
|
if (Number.isNaN(d.getTime()))
|
|
3932
|
-
return
|
|
3966
|
+
return null;
|
|
3933
3967
|
return toISODate(d);
|
|
3934
3968
|
}
|
|
3935
3969
|
function parseCsv(content) {
|
|
@@ -6699,6 +6733,721 @@ function registerFleetCommands(program) {
|
|
|
6699
6733
|
});
|
|
6700
6734
|
}
|
|
6701
6735
|
|
|
6736
|
+
// src/cli/commands/brief.ts
|
|
6737
|
+
init_database();
|
|
6738
|
+
init_pricing();
|
|
6739
|
+
var ZERO_REQUEST_TOTALS = {
|
|
6740
|
+
sessions: 0,
|
|
6741
|
+
request_sessions: 0,
|
|
6742
|
+
requests: 0,
|
|
6743
|
+
input_tokens: 0,
|
|
6744
|
+
output_tokens: 0,
|
|
6745
|
+
cache_read_tokens: 0,
|
|
6746
|
+
cache_create_tokens: 0,
|
|
6747
|
+
cache_create_5m_tokens: 0,
|
|
6748
|
+
cache_create_1h_tokens: 0,
|
|
6749
|
+
total_tokens: 0,
|
|
6750
|
+
cost_usd: 0,
|
|
6751
|
+
last_active: null
|
|
6752
|
+
};
|
|
6753
|
+
var ZERO_SESSION_TOTALS = {
|
|
6754
|
+
sessions: 0,
|
|
6755
|
+
requests: 0,
|
|
6756
|
+
total_tokens: 0,
|
|
6757
|
+
cost_usd: 0,
|
|
6758
|
+
last_active: null
|
|
6759
|
+
};
|
|
6760
|
+
function machineFilter(machine) {
|
|
6761
|
+
if (!machine || machine === "all")
|
|
6762
|
+
return;
|
|
6763
|
+
return machine;
|
|
6764
|
+
}
|
|
6765
|
+
function requestPeriodWhere2(period) {
|
|
6766
|
+
if (period === "today")
|
|
6767
|
+
return `DATE(timestamp) = DATE('now')`;
|
|
6768
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
6769
|
+
}
|
|
6770
|
+
function sessionPeriodWhere2(period) {
|
|
6771
|
+
if (period === "today")
|
|
6772
|
+
return `DATE(started_at) = DATE('now')`;
|
|
6773
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
6774
|
+
}
|
|
6775
|
+
function baseRequestTotals(row) {
|
|
6776
|
+
return {
|
|
6777
|
+
...ZERO_REQUEST_TOTALS,
|
|
6778
|
+
...row,
|
|
6779
|
+
sessions: Number(row?.request_sessions ?? row?.sessions ?? 0),
|
|
6780
|
+
request_sessions: Number(row?.request_sessions ?? row?.sessions ?? 0),
|
|
6781
|
+
requests: Number(row?.requests ?? 0),
|
|
6782
|
+
input_tokens: Number(row?.input_tokens ?? 0),
|
|
6783
|
+
output_tokens: Number(row?.output_tokens ?? 0),
|
|
6784
|
+
cache_read_tokens: Number(row?.cache_read_tokens ?? 0),
|
|
6785
|
+
cache_create_tokens: Number(row?.cache_create_tokens ?? 0),
|
|
6786
|
+
cache_create_5m_tokens: Number(row?.cache_create_5m_tokens ?? 0),
|
|
6787
|
+
cache_create_1h_tokens: Number(row?.cache_create_1h_tokens ?? 0),
|
|
6788
|
+
total_tokens: Number(row?.total_tokens ?? 0),
|
|
6789
|
+
cost_usd: Number(row?.cost_usd ?? 0),
|
|
6790
|
+
last_active: row?.last_active ?? null
|
|
6791
|
+
};
|
|
6792
|
+
}
|
|
6793
|
+
function baseSessionTotals(row) {
|
|
6794
|
+
return {
|
|
6795
|
+
sessions: Number(row?.sessions ?? 0),
|
|
6796
|
+
requests: Number(row?.requests ?? 0),
|
|
6797
|
+
total_tokens: Number(row?.total_tokens ?? 0),
|
|
6798
|
+
cost_usd: Number(row?.cost_usd ?? 0),
|
|
6799
|
+
last_active: row?.last_active ?? null
|
|
6800
|
+
};
|
|
6801
|
+
}
|
|
6802
|
+
function latestTimestamp(...values) {
|
|
6803
|
+
return values.filter(Boolean).sort().at(-1) ?? null;
|
|
6804
|
+
}
|
|
6805
|
+
function combineTotals(requests, sessions) {
|
|
6806
|
+
return {
|
|
6807
|
+
sessions: requests.request_sessions + sessions.sessions,
|
|
6808
|
+
requests: requests.requests + sessions.requests,
|
|
6809
|
+
input_tokens: requests.input_tokens,
|
|
6810
|
+
output_tokens: requests.output_tokens,
|
|
6811
|
+
cache_read_tokens: requests.cache_read_tokens,
|
|
6812
|
+
cache_create_tokens: requests.cache_create_tokens,
|
|
6813
|
+
cache_create_5m_tokens: requests.cache_create_5m_tokens,
|
|
6814
|
+
cache_create_1h_tokens: requests.cache_create_1h_tokens,
|
|
6815
|
+
total_tokens: requests.total_tokens + sessions.total_tokens,
|
|
6816
|
+
cost_usd: requests.cost_usd + sessions.cost_usd,
|
|
6817
|
+
last_active: latestTimestamp(requests.last_active, sessions.last_active)
|
|
6818
|
+
};
|
|
6819
|
+
}
|
|
6820
|
+
function emptyTotals() {
|
|
6821
|
+
return combineTotals(ZERO_REQUEST_TOTALS, ZERO_SESSION_TOTALS);
|
|
6822
|
+
}
|
|
6823
|
+
function requestTotalsSql(where, machine) {
|
|
6824
|
+
const params = [];
|
|
6825
|
+
let machineClause = "";
|
|
6826
|
+
if (machine) {
|
|
6827
|
+
machineClause = " AND machine_id = ?";
|
|
6828
|
+
params.push(machine);
|
|
6829
|
+
}
|
|
6830
|
+
return {
|
|
6831
|
+
sql: `
|
|
6832
|
+
SELECT
|
|
6833
|
+
COUNT(DISTINCT session_id) as request_sessions,
|
|
6834
|
+
COUNT(*) as requests,
|
|
6835
|
+
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
6836
|
+
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
6837
|
+
COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
|
|
6838
|
+
COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
|
|
6839
|
+
COALESCE(SUM(COALESCE(cache_create_5m_tokens, cache_create_tokens)), 0) as cache_create_5m_tokens,
|
|
6840
|
+
COALESCE(SUM(COALESCE(cache_create_1h_tokens, 0)), 0) as cache_create_1h_tokens,
|
|
6841
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
6842
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
6843
|
+
MAX(timestamp) as last_active
|
|
6844
|
+
FROM requests
|
|
6845
|
+
WHERE ${where}${machineClause}
|
|
6846
|
+
`,
|
|
6847
|
+
params
|
|
6848
|
+
};
|
|
6849
|
+
}
|
|
6850
|
+
function sessionOnlyTotalsSql(where, machine) {
|
|
6851
|
+
const params = [];
|
|
6852
|
+
let machineClause = "";
|
|
6853
|
+
if (machine) {
|
|
6854
|
+
machineClause = " AND machine_id = ?";
|
|
6855
|
+
params.push(machine);
|
|
6856
|
+
}
|
|
6857
|
+
return {
|
|
6858
|
+
sql: `
|
|
6859
|
+
SELECT
|
|
6860
|
+
COUNT(*) as sessions,
|
|
6861
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
6862
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
6863
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
6864
|
+
MAX(started_at) as last_active
|
|
6865
|
+
FROM sessions
|
|
6866
|
+
WHERE ${where}${machineClause}
|
|
6867
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
6868
|
+
`,
|
|
6869
|
+
params
|
|
6870
|
+
};
|
|
6871
|
+
}
|
|
6872
|
+
function queryRequestTotals(db, where, params, machine) {
|
|
6873
|
+
const q = requestTotalsSql(where, machine);
|
|
6874
|
+
return baseRequestTotals(db.prepare(q.sql).get(...params, ...q.params));
|
|
6875
|
+
}
|
|
6876
|
+
function querySessionOnlyTotals(db, where, params, machine) {
|
|
6877
|
+
const q = sessionOnlyTotalsSql(where, machine);
|
|
6878
|
+
return baseSessionTotals(db.prepare(q.sql).get(...params, ...q.params));
|
|
6879
|
+
}
|
|
6880
|
+
function queryPeriodTotals(db, period, machine) {
|
|
6881
|
+
const summary = querySummary(db, period, machine);
|
|
6882
|
+
const requests = queryRequestTotals(db, requestPeriodWhere2(period), [], machine);
|
|
6883
|
+
const sessions = querySessionOnlyTotals(db, sessionPeriodWhere2(period), [], machine);
|
|
6884
|
+
const totals = combineTotals(requests, sessions);
|
|
6885
|
+
return {
|
|
6886
|
+
...totals,
|
|
6887
|
+
sessions: summary.sessions,
|
|
6888
|
+
requests: summary.requests,
|
|
6889
|
+
total_tokens: summary.tokens,
|
|
6890
|
+
cost_usd: summary.total_usd
|
|
6891
|
+
};
|
|
6892
|
+
}
|
|
6893
|
+
function querySinceTotals(db, since, machine) {
|
|
6894
|
+
const requests = queryRequestTotals(db, "timestamp >= ?", [since], machine);
|
|
6895
|
+
const sessions = querySessionOnlyTotals(db, "started_at >= ?", [since], machine);
|
|
6896
|
+
return combineTotals(requests, sessions);
|
|
6897
|
+
}
|
|
6898
|
+
function parseSinceDate(input, now) {
|
|
6899
|
+
const value = input.trim();
|
|
6900
|
+
const match = value.match(/^(\d+(?:\.\d+)?)(m|h|d|w)$/i);
|
|
6901
|
+
if (match) {
|
|
6902
|
+
const amount = Number(match[1]);
|
|
6903
|
+
const unit = match[2].toLowerCase();
|
|
6904
|
+
const multiplier = unit === "m" ? 60000 : unit === "h" ? 60 * 60000 : unit === "d" ? 24 * 60 * 60000 : 7 * 24 * 60 * 60000;
|
|
6905
|
+
return {
|
|
6906
|
+
timestamp: new Date(now.getTime() - amount * multiplier).toISOString(),
|
|
6907
|
+
label: value
|
|
6908
|
+
};
|
|
6909
|
+
}
|
|
6910
|
+
const parsed = new Date(value);
|
|
6911
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
6912
|
+
throw new Error("--since must be a duration like 24h, 7d, 30m, 2w, or an ISO date");
|
|
6913
|
+
}
|
|
6914
|
+
return { timestamp: parsed.toISOString(), label: value };
|
|
6915
|
+
}
|
|
6916
|
+
function queryMachineSinceRows(db, since, machine) {
|
|
6917
|
+
const knownMachines = new Set;
|
|
6918
|
+
for (const m of listMachines(db, "all")) {
|
|
6919
|
+
if (m.machine_id)
|
|
6920
|
+
knownMachines.add(m.machine_id);
|
|
6921
|
+
}
|
|
6922
|
+
for (const m of listMachineRegistry(db)) {
|
|
6923
|
+
if (m.machine_id)
|
|
6924
|
+
knownMachines.add(m.machine_id);
|
|
6925
|
+
}
|
|
6926
|
+
if (machine)
|
|
6927
|
+
knownMachines.add(machine);
|
|
6928
|
+
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
6929
|
+
const requestRows = db.prepare(`
|
|
6930
|
+
SELECT
|
|
6931
|
+
machine_id,
|
|
6932
|
+
COUNT(DISTINCT session_id) as request_sessions,
|
|
6933
|
+
COUNT(*) as requests,
|
|
6934
|
+
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
6935
|
+
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
6936
|
+
COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
|
|
6937
|
+
COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
|
|
6938
|
+
COALESCE(SUM(COALESCE(cache_create_5m_tokens, cache_create_tokens)), 0) as cache_create_5m_tokens,
|
|
6939
|
+
COALESCE(SUM(COALESCE(cache_create_1h_tokens, 0)), 0) as cache_create_1h_tokens,
|
|
6940
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
6941
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
6942
|
+
MAX(timestamp) as last_active
|
|
6943
|
+
FROM requests
|
|
6944
|
+
WHERE timestamp >= ?
|
|
6945
|
+
AND machine_id != ''
|
|
6946
|
+
${requestMachineClause}
|
|
6947
|
+
GROUP BY machine_id
|
|
6948
|
+
`).all(...machine ? [since, machine] : [since]);
|
|
6949
|
+
const sessionMachineClause = machine ? " AND machine_id = ?" : "";
|
|
6950
|
+
const sessionRows = db.prepare(`
|
|
6951
|
+
SELECT
|
|
6952
|
+
machine_id,
|
|
6953
|
+
COUNT(*) as sessions,
|
|
6954
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
6955
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
6956
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
6957
|
+
MAX(started_at) as last_active
|
|
6958
|
+
FROM sessions
|
|
6959
|
+
WHERE started_at >= ?
|
|
6960
|
+
AND machine_id != ''
|
|
6961
|
+
${sessionMachineClause}
|
|
6962
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
6963
|
+
GROUP BY machine_id
|
|
6964
|
+
`).all(...machine ? [since, machine] : [since]);
|
|
6965
|
+
const groups = new Map;
|
|
6966
|
+
for (const row of requestRows) {
|
|
6967
|
+
knownMachines.add(row.machine_id);
|
|
6968
|
+
groups.set(row.machine_id, combineTotals(baseRequestTotals(row), ZERO_SESSION_TOTALS));
|
|
6969
|
+
}
|
|
6970
|
+
for (const row of sessionRows) {
|
|
6971
|
+
knownMachines.add(row.machine_id);
|
|
6972
|
+
const current = groups.get(row.machine_id) ?? emptyTotals();
|
|
6973
|
+
const sessions = baseSessionTotals(row);
|
|
6974
|
+
groups.set(row.machine_id, {
|
|
6975
|
+
...current,
|
|
6976
|
+
sessions: current.sessions + sessions.sessions,
|
|
6977
|
+
requests: current.requests + sessions.requests,
|
|
6978
|
+
total_tokens: current.total_tokens + sessions.total_tokens,
|
|
6979
|
+
cost_usd: current.cost_usd + sessions.cost_usd,
|
|
6980
|
+
last_active: latestTimestamp(current.last_active, sessions.last_active)
|
|
6981
|
+
});
|
|
6982
|
+
}
|
|
6983
|
+
const lastData = queryMachineLastData(db, machine);
|
|
6984
|
+
const rows = [...knownMachines].filter((m) => !machine || m === machine).map((machineId) => {
|
|
6985
|
+
const totals = groups.get(machineId) ?? emptyTotals();
|
|
6986
|
+
const lastDataAt = lastData.get(machineId) ?? totals.last_active;
|
|
6987
|
+
return {
|
|
6988
|
+
machine_id: machineId,
|
|
6989
|
+
...totals,
|
|
6990
|
+
last_data_at: lastDataAt,
|
|
6991
|
+
last_data_age: ""
|
|
6992
|
+
};
|
|
6993
|
+
});
|
|
6994
|
+
return rows.sort((a, b) => b.cost_usd - a.cost_usd || a.machine_id.localeCompare(b.machine_id));
|
|
6995
|
+
}
|
|
6996
|
+
function queryMachineLastData(db, machine) {
|
|
6997
|
+
const filter = machine ? " AND machine_id = ?" : "";
|
|
6998
|
+
const requestRows = db.prepare(`
|
|
6999
|
+
SELECT machine_id, MAX(timestamp) as last_at
|
|
7000
|
+
FROM requests
|
|
7001
|
+
WHERE machine_id != ''${filter}
|
|
7002
|
+
GROUP BY machine_id
|
|
7003
|
+
`).all(...machine ? [machine] : []);
|
|
7004
|
+
const sessionRows = db.prepare(`
|
|
7005
|
+
SELECT machine_id, MAX(started_at) as last_at
|
|
7006
|
+
FROM sessions
|
|
7007
|
+
WHERE machine_id != ''${filter}
|
|
7008
|
+
GROUP BY machine_id
|
|
7009
|
+
`).all(...machine ? [machine] : []);
|
|
7010
|
+
const result = new Map;
|
|
7011
|
+
for (const row of [...requestRows, ...sessionRows]) {
|
|
7012
|
+
result.set(row.machine_id, latestTimestamp(result.get(row.machine_id), row.last_at));
|
|
7013
|
+
}
|
|
7014
|
+
return result;
|
|
7015
|
+
}
|
|
7016
|
+
function queryAgentSinceRows(db, since, machine) {
|
|
7017
|
+
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
7018
|
+
const sessionMachineClause = machine ? " AND machine_id = ?" : "";
|
|
7019
|
+
const rows = db.prepare(`
|
|
7020
|
+
WITH request_rows AS (
|
|
7021
|
+
SELECT
|
|
7022
|
+
agent,
|
|
7023
|
+
session_id,
|
|
7024
|
+
1 as requests,
|
|
7025
|
+
input_tokens,
|
|
7026
|
+
output_tokens,
|
|
7027
|
+
cache_read_tokens,
|
|
7028
|
+
cache_create_tokens,
|
|
7029
|
+
COALESCE(cache_create_5m_tokens, cache_create_tokens) as cache_create_5m_tokens,
|
|
7030
|
+
COALESCE(cache_create_1h_tokens, 0) as cache_create_1h_tokens,
|
|
7031
|
+
input_tokens + output_tokens + cache_read_tokens + cache_create_tokens as total_tokens,
|
|
7032
|
+
cost_usd,
|
|
7033
|
+
timestamp as last_active
|
|
7034
|
+
FROM requests
|
|
7035
|
+
WHERE timestamp >= ?${requestMachineClause}
|
|
7036
|
+
),
|
|
7037
|
+
session_only_rows AS (
|
|
7038
|
+
SELECT
|
|
7039
|
+
agent,
|
|
7040
|
+
id as session_id,
|
|
7041
|
+
COALESCE(request_count, 0) as requests,
|
|
7042
|
+
0 as input_tokens,
|
|
7043
|
+
0 as output_tokens,
|
|
7044
|
+
0 as cache_read_tokens,
|
|
7045
|
+
0 as cache_create_tokens,
|
|
7046
|
+
0 as cache_create_5m_tokens,
|
|
7047
|
+
0 as cache_create_1h_tokens,
|
|
7048
|
+
COALESCE(total_tokens, 0) as total_tokens,
|
|
7049
|
+
COALESCE(total_cost_usd, 0) as cost_usd,
|
|
7050
|
+
started_at as last_active
|
|
7051
|
+
FROM sessions
|
|
7052
|
+
WHERE started_at >= ?${sessionMachineClause}
|
|
7053
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
7054
|
+
),
|
|
7055
|
+
combined AS (
|
|
7056
|
+
SELECT * FROM request_rows
|
|
7057
|
+
UNION ALL
|
|
7058
|
+
SELECT * FROM session_only_rows
|
|
7059
|
+
)
|
|
7060
|
+
SELECT
|
|
7061
|
+
agent,
|
|
7062
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
7063
|
+
COALESCE(SUM(requests), 0) as requests,
|
|
7064
|
+
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
7065
|
+
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
7066
|
+
COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
|
|
7067
|
+
COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
|
|
7068
|
+
COALESCE(SUM(cache_create_5m_tokens), 0) as cache_create_5m_tokens,
|
|
7069
|
+
COALESCE(SUM(cache_create_1h_tokens), 0) as cache_create_1h_tokens,
|
|
7070
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
7071
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
7072
|
+
MAX(last_active) as last_active
|
|
7073
|
+
FROM combined
|
|
7074
|
+
GROUP BY agent
|
|
7075
|
+
ORDER BY cost_usd DESC
|
|
7076
|
+
`).all(...machine ? [since, machine, since, machine] : [since, since]);
|
|
7077
|
+
if (rows.length > 0)
|
|
7078
|
+
return rows;
|
|
7079
|
+
return queryAgentBreakdown(db, "all", machine).filter((row) => row.last_active >= since).map(agentBreakdownToBriefRow);
|
|
7080
|
+
}
|
|
7081
|
+
function agentBreakdownToBriefRow(row) {
|
|
7082
|
+
return {
|
|
7083
|
+
agent: row.agent,
|
|
7084
|
+
sessions: row.sessions,
|
|
7085
|
+
requests: row.requests,
|
|
7086
|
+
input_tokens: 0,
|
|
7087
|
+
output_tokens: 0,
|
|
7088
|
+
cache_read_tokens: 0,
|
|
7089
|
+
cache_create_tokens: 0,
|
|
7090
|
+
cache_create_5m_tokens: 0,
|
|
7091
|
+
cache_create_1h_tokens: 0,
|
|
7092
|
+
total_tokens: row.total_tokens,
|
|
7093
|
+
cost_usd: row.cost_usd,
|
|
7094
|
+
last_active: row.last_active
|
|
7095
|
+
};
|
|
7096
|
+
}
|
|
7097
|
+
function normalizeAccountEmail2(email) {
|
|
7098
|
+
return email.trim().toLowerCase();
|
|
7099
|
+
}
|
|
7100
|
+
function accountKey(row) {
|
|
7101
|
+
const agent = row.agent || row.account_tool || "unknown";
|
|
7102
|
+
const email = normalizeAccountEmail2(row.account_email);
|
|
7103
|
+
if (email)
|
|
7104
|
+
return `${agent}:${email}`;
|
|
7105
|
+
if (row.account_name)
|
|
7106
|
+
return `${agent}:${row.account_name}`;
|
|
7107
|
+
return row.account_key || `${agent}:unknown`;
|
|
7108
|
+
}
|
|
7109
|
+
function addAccountRow(groups, row) {
|
|
7110
|
+
if (!row.account_key && !row.account_tool && !row.account_name && !row.account_email)
|
|
7111
|
+
return;
|
|
7112
|
+
const key = accountKey(row);
|
|
7113
|
+
const email = normalizeAccountEmail2(row.account_email);
|
|
7114
|
+
const current = groups.get(key) ?? {
|
|
7115
|
+
account_key: key,
|
|
7116
|
+
account_tool: row.agent || row.account_tool || "unknown",
|
|
7117
|
+
account_name: row.account_name || email || row.account_key || "unknown",
|
|
7118
|
+
account_email: email || null,
|
|
7119
|
+
account_source: row.account_source || "unknown",
|
|
7120
|
+
sessionIds: new Set,
|
|
7121
|
+
requests: 0,
|
|
7122
|
+
input_tokens: 0,
|
|
7123
|
+
output_tokens: 0,
|
|
7124
|
+
cache_read_tokens: 0,
|
|
7125
|
+
cache_create_tokens: 0,
|
|
7126
|
+
cache_create_5m_tokens: 0,
|
|
7127
|
+
cache_create_1h_tokens: 0,
|
|
7128
|
+
total_tokens: 0,
|
|
7129
|
+
cost_usd: 0,
|
|
7130
|
+
last_active: null
|
|
7131
|
+
};
|
|
7132
|
+
if (row.session_id)
|
|
7133
|
+
current.sessionIds.add(row.session_id);
|
|
7134
|
+
if (!current.account_email && email)
|
|
7135
|
+
current.account_email = email;
|
|
7136
|
+
if ((!current.account_name || current.account_name === "unknown") && (row.account_name || email)) {
|
|
7137
|
+
current.account_name = row.account_name || email;
|
|
7138
|
+
}
|
|
7139
|
+
if ((!current.account_source || current.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
|
|
7140
|
+
current.account_source = row.account_source;
|
|
7141
|
+
}
|
|
7142
|
+
current.requests += Number(row.requests ?? 0);
|
|
7143
|
+
current.input_tokens += Number(row.input_tokens ?? 0);
|
|
7144
|
+
current.output_tokens += Number(row.output_tokens ?? 0);
|
|
7145
|
+
current.cache_read_tokens += Number(row.cache_read_tokens ?? 0);
|
|
7146
|
+
current.cache_create_tokens += Number(row.cache_create_tokens ?? 0);
|
|
7147
|
+
current.cache_create_5m_tokens += Number(row.cache_create_5m_tokens ?? 0);
|
|
7148
|
+
current.cache_create_1h_tokens += Number(row.cache_create_1h_tokens ?? 0);
|
|
7149
|
+
current.total_tokens += Number(row.total_tokens ?? 0);
|
|
7150
|
+
current.cost_usd += Number(row.cost_usd ?? 0);
|
|
7151
|
+
current.last_active = latestTimestamp(current.last_active, row.last_active);
|
|
7152
|
+
groups.set(key, current);
|
|
7153
|
+
}
|
|
7154
|
+
function queryAccountSinceRows(db, since, machine) {
|
|
7155
|
+
const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
|
|
7156
|
+
const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
|
|
7157
|
+
const requestRows = db.prepare(`
|
|
7158
|
+
SELECT
|
|
7159
|
+
r.session_id as session_id,
|
|
7160
|
+
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
|
|
7161
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
|
|
7162
|
+
COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
|
|
7163
|
+
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
|
|
7164
|
+
COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
|
|
7165
|
+
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
7166
|
+
1 as requests,
|
|
7167
|
+
COALESCE(r.input_tokens, 0) as input_tokens,
|
|
7168
|
+
COALESCE(r.output_tokens, 0) as output_tokens,
|
|
7169
|
+
COALESCE(r.cache_read_tokens, 0) as cache_read_tokens,
|
|
7170
|
+
COALESCE(r.cache_create_tokens, 0) as cache_create_tokens,
|
|
7171
|
+
COALESCE(r.cache_create_5m_tokens, r.cache_create_tokens, 0) as cache_create_5m_tokens,
|
|
7172
|
+
COALESCE(r.cache_create_1h_tokens, 0) as cache_create_1h_tokens,
|
|
7173
|
+
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
7174
|
+
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
7175
|
+
r.timestamp as last_active
|
|
7176
|
+
FROM requests r
|
|
7177
|
+
LEFT JOIN sessions s ON s.id = r.session_id
|
|
7178
|
+
WHERE r.timestamp >= ?${requestMachineClause}
|
|
7179
|
+
`).all(...machine ? [since, machine] : [since]);
|
|
7180
|
+
const sessionRows = db.prepare(`
|
|
7181
|
+
SELECT
|
|
7182
|
+
s.id as session_id,
|
|
7183
|
+
s.agent as agent,
|
|
7184
|
+
s.account_key as account_key,
|
|
7185
|
+
s.account_tool as account_tool,
|
|
7186
|
+
s.account_name as account_name,
|
|
7187
|
+
COALESCE(s.account_email, '') as account_email,
|
|
7188
|
+
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
7189
|
+
COALESCE(s.request_count, 0) as requests,
|
|
7190
|
+
0 as input_tokens,
|
|
7191
|
+
0 as output_tokens,
|
|
7192
|
+
0 as cache_read_tokens,
|
|
7193
|
+
0 as cache_create_tokens,
|
|
7194
|
+
0 as cache_create_5m_tokens,
|
|
7195
|
+
0 as cache_create_1h_tokens,
|
|
7196
|
+
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
7197
|
+
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
7198
|
+
s.started_at as last_active
|
|
7199
|
+
FROM sessions s
|
|
7200
|
+
WHERE s.started_at >= ?${sessionMachineClause}
|
|
7201
|
+
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
7202
|
+
`).all(...machine ? [since, machine] : [since]);
|
|
7203
|
+
const groups = new Map;
|
|
7204
|
+
for (const row of requestRows)
|
|
7205
|
+
addAccountRow(groups, row);
|
|
7206
|
+
for (const row of sessionRows)
|
|
7207
|
+
addAccountRow(groups, row);
|
|
7208
|
+
const rows = [...groups.values()].map(({ sessionIds, ...row }) => ({
|
|
7209
|
+
...row,
|
|
7210
|
+
sessions: sessionIds.size
|
|
7211
|
+
}));
|
|
7212
|
+
rows.sort((a, b) => b.cost_usd - a.cost_usd || a.account_key.localeCompare(b.account_key));
|
|
7213
|
+
if (rows.length > 0)
|
|
7214
|
+
return rows;
|
|
7215
|
+
return queryAccountBreakdown(db, "all", machine).filter((row) => row.last_active >= since).map(accountBreakdownToBriefRow);
|
|
7216
|
+
}
|
|
7217
|
+
function accountBreakdownToBriefRow(row) {
|
|
7218
|
+
return {
|
|
7219
|
+
account_key: row.account_key,
|
|
7220
|
+
account_tool: row.account_tool,
|
|
7221
|
+
account_name: row.account_name,
|
|
7222
|
+
account_email: row.account_email,
|
|
7223
|
+
account_source: row.account_source,
|
|
7224
|
+
sessions: row.sessions,
|
|
7225
|
+
requests: row.requests,
|
|
7226
|
+
input_tokens: 0,
|
|
7227
|
+
output_tokens: 0,
|
|
7228
|
+
cache_read_tokens: 0,
|
|
7229
|
+
cache_create_tokens: 0,
|
|
7230
|
+
cache_create_5m_tokens: 0,
|
|
7231
|
+
cache_create_1h_tokens: 0,
|
|
7232
|
+
total_tokens: row.total_tokens,
|
|
7233
|
+
cost_usd: row.cost_usd,
|
|
7234
|
+
last_active: row.last_active
|
|
7235
|
+
};
|
|
7236
|
+
}
|
|
7237
|
+
function queryFreshness(db, now, machine) {
|
|
7238
|
+
const requestFilter = machine ? " AND machine_id = ?" : "";
|
|
7239
|
+
const requestRows = db.prepare(`
|
|
7240
|
+
SELECT machine_id, MAX(timestamp) as max_request_at
|
|
7241
|
+
FROM requests
|
|
7242
|
+
WHERE machine_id != ''${requestFilter}
|
|
7243
|
+
GROUP BY machine_id
|
|
7244
|
+
`).all(...machine ? [machine] : []);
|
|
7245
|
+
const registryRows = listMachineRegistry(db).filter((row) => !machine || row.machine_id === machine);
|
|
7246
|
+
const machines = new Map;
|
|
7247
|
+
for (const row of requestRows) {
|
|
7248
|
+
machines.set(row.machine_id, {
|
|
7249
|
+
machine_id: row.machine_id,
|
|
7250
|
+
max_request_at: row.max_request_at,
|
|
7251
|
+
request_age: formatAge(row.max_request_at, now),
|
|
7252
|
+
last_merge_sync_at: null,
|
|
7253
|
+
merge_sync_age: "never"
|
|
7254
|
+
});
|
|
7255
|
+
}
|
|
7256
|
+
for (const row of registryRows) {
|
|
7257
|
+
const current = machines.get(row.machine_id) ?? {
|
|
7258
|
+
machine_id: row.machine_id,
|
|
7259
|
+
max_request_at: null,
|
|
7260
|
+
request_age: "never",
|
|
7261
|
+
last_merge_sync_at: null,
|
|
7262
|
+
merge_sync_age: "never"
|
|
7263
|
+
};
|
|
7264
|
+
const syncAt = latestTimestamp(row.last_pull_at, row.last_push_at, row.updated_at);
|
|
7265
|
+
current.last_merge_sync_at = syncAt;
|
|
7266
|
+
current.merge_sync_age = formatAge(syncAt, now);
|
|
7267
|
+
machines.set(row.machine_id, current);
|
|
7268
|
+
}
|
|
7269
|
+
const rows = [...machines.values()].sort((a, b) => a.machine_id.localeCompare(b.machine_id));
|
|
7270
|
+
return {
|
|
7271
|
+
machines: rows,
|
|
7272
|
+
max_request_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.max_request_at ?? "never"} (${row.request_age})`).join("; ") : "none",
|
|
7273
|
+
merge_sync_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.last_merge_sync_at ?? "never"} (${row.merge_sync_age})`).join("; ") : "none"
|
|
7274
|
+
};
|
|
7275
|
+
}
|
|
7276
|
+
function buildBrief(db, opts = {}) {
|
|
7277
|
+
const now = opts.now ?? new Date;
|
|
7278
|
+
const machine = machineFilter(opts.machine);
|
|
7279
|
+
const sinceInput = opts.since ?? "24h";
|
|
7280
|
+
const since = parseSinceDate(sinceInput, now);
|
|
7281
|
+
const summaries = [
|
|
7282
|
+
{
|
|
7283
|
+
label: "Today",
|
|
7284
|
+
period: "today",
|
|
7285
|
+
...queryPeriodTotals(db, "today", machine)
|
|
7286
|
+
},
|
|
7287
|
+
{
|
|
7288
|
+
label: "Week",
|
|
7289
|
+
period: "week",
|
|
7290
|
+
...queryPeriodTotals(db, "week", machine)
|
|
7291
|
+
},
|
|
7292
|
+
{
|
|
7293
|
+
label: `Since ${since.label}`,
|
|
7294
|
+
period: "since",
|
|
7295
|
+
since: since.timestamp,
|
|
7296
|
+
...querySinceTotals(db, since.timestamp, machine)
|
|
7297
|
+
}
|
|
7298
|
+
];
|
|
7299
|
+
const machines = queryMachineSinceRows(db, since.timestamp, machine);
|
|
7300
|
+
for (const row of machines) {
|
|
7301
|
+
row.last_data_age = formatAge(row.last_data_at, now);
|
|
7302
|
+
}
|
|
7303
|
+
return {
|
|
7304
|
+
generated_at: now.toISOString(),
|
|
7305
|
+
machine: machine ?? "all",
|
|
7306
|
+
since: {
|
|
7307
|
+
input: sinceInput,
|
|
7308
|
+
timestamp: since.timestamp,
|
|
7309
|
+
label: since.label
|
|
7310
|
+
},
|
|
7311
|
+
summaries,
|
|
7312
|
+
machines,
|
|
7313
|
+
agents: queryAgentSinceRows(db, since.timestamp, machine),
|
|
7314
|
+
accounts: queryAccountSinceRows(db, since.timestamp, machine),
|
|
7315
|
+
freshness: queryFreshness(db, now, machine)
|
|
7316
|
+
};
|
|
7317
|
+
}
|
|
7318
|
+
function formatUsd(usd) {
|
|
7319
|
+
if (usd >= 0.01)
|
|
7320
|
+
return "$" + usd.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
7321
|
+
if (usd >= 0.0001)
|
|
7322
|
+
return `${(usd * 100).toFixed(2).replace(/\.?0+$/, "")}c`;
|
|
7323
|
+
if (usd > 0)
|
|
7324
|
+
return "<0.01c";
|
|
7325
|
+
return "$0.00";
|
|
7326
|
+
}
|
|
7327
|
+
function formatCount(n) {
|
|
7328
|
+
return Math.round(n).toLocaleString("en-US");
|
|
7329
|
+
}
|
|
7330
|
+
function formatTokens(n) {
|
|
7331
|
+
if (n >= 1e9)
|
|
7332
|
+
return `${(n / 1e9).toFixed(1)}B`;
|
|
7333
|
+
if (n >= 1e6)
|
|
7334
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
7335
|
+
if (n >= 1000)
|
|
7336
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
7337
|
+
return formatCount(n);
|
|
7338
|
+
}
|
|
7339
|
+
function formatAge(timestamp, now) {
|
|
7340
|
+
if (!timestamp)
|
|
7341
|
+
return "never";
|
|
7342
|
+
const t = new Date(timestamp).getTime();
|
|
7343
|
+
if (!Number.isFinite(t))
|
|
7344
|
+
return "unknown";
|
|
7345
|
+
const ageMs = Math.max(0, now.getTime() - t);
|
|
7346
|
+
const minutes = Math.floor(ageMs / 60000);
|
|
7347
|
+
if (minutes < 1)
|
|
7348
|
+
return "<1m";
|
|
7349
|
+
if (minutes < 60)
|
|
7350
|
+
return `${minutes}m`;
|
|
7351
|
+
const hours = Math.floor(minutes / 60);
|
|
7352
|
+
if (hours < 48)
|
|
7353
|
+
return `${hours}h`;
|
|
7354
|
+
return `${Math.floor(hours / 24)}d`;
|
|
7355
|
+
}
|
|
7356
|
+
function accountLabel(row) {
|
|
7357
|
+
return row.account_email || row.account_name || row.account_key || "unknown";
|
|
7358
|
+
}
|
|
7359
|
+
function cacheLabel(row) {
|
|
7360
|
+
const total = row.cache_read_tokens + row.cache_create_tokens;
|
|
7361
|
+
const split = row.cache_create_5m_tokens || row.cache_create_1h_tokens ? `; 5m ${formatTokens(row.cache_create_5m_tokens)} / 1h ${formatTokens(row.cache_create_1h_tokens)}` : "";
|
|
7362
|
+
return `${formatTokens(total)} (r ${formatTokens(row.cache_read_tokens)} / w ${formatTokens(row.cache_create_tokens)}${split})`;
|
|
7363
|
+
}
|
|
7364
|
+
function table(headers, rows) {
|
|
7365
|
+
if (rows.length === 0)
|
|
7366
|
+
return ["(none)"];
|
|
7367
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
|
|
7368
|
+
const lines = [];
|
|
7369
|
+
lines.push(headers.map((header, index) => header.padEnd(widths[index])).join(" "));
|
|
7370
|
+
lines.push(widths.map((width) => "-".repeat(width)).join(" "));
|
|
7371
|
+
for (const row of rows) {
|
|
7372
|
+
lines.push(row.map((cell, index) => cell.padEnd(widths[index])).join(" "));
|
|
7373
|
+
}
|
|
7374
|
+
return lines;
|
|
7375
|
+
}
|
|
7376
|
+
function renderBriefText(brief) {
|
|
7377
|
+
const lines = [];
|
|
7378
|
+
lines.push(`Economy Brief - ${brief.machine === "all" ? "fleet" : brief.machine}`);
|
|
7379
|
+
lines.push(`Generated: ${brief.generated_at}`);
|
|
7380
|
+
lines.push(`Since: ${brief.since.label} (${brief.since.timestamp})`);
|
|
7381
|
+
lines.push("");
|
|
7382
|
+
lines.push("SUMMARY");
|
|
7383
|
+
lines.push(...table(["Period", "Sessions", "Requests", "Input", "Output", "Cache", "Tokens", "Cost"], brief.summaries.map((row) => [
|
|
7384
|
+
row.label,
|
|
7385
|
+
formatCount(row.sessions),
|
|
7386
|
+
formatCount(row.requests),
|
|
7387
|
+
formatTokens(row.input_tokens),
|
|
7388
|
+
formatTokens(row.output_tokens),
|
|
7389
|
+
cacheLabel(row),
|
|
7390
|
+
formatTokens(row.total_tokens),
|
|
7391
|
+
formatUsd(row.cost_usd)
|
|
7392
|
+
])));
|
|
7393
|
+
lines.push("");
|
|
7394
|
+
lines.push(`PER-MACHINE - since ${brief.since.label}`);
|
|
7395
|
+
lines.push(...table(["Machine", "Sessions", "Tokens", "Cache", "Cost", "Last Data Age"], brief.machines.map((row) => [
|
|
7396
|
+
row.machine_id,
|
|
7397
|
+
formatCount(row.sessions),
|
|
7398
|
+
formatTokens(row.total_tokens),
|
|
7399
|
+
cacheLabel(row),
|
|
7400
|
+
formatUsd(row.cost_usd),
|
|
7401
|
+
row.last_data_age
|
|
7402
|
+
])));
|
|
7403
|
+
lines.push("");
|
|
7404
|
+
lines.push(`PER-AGENT - since ${brief.since.label}`);
|
|
7405
|
+
lines.push(...table(["Agent", "Sessions", "Requests", "Tokens", "Cache", "Cost", "Last Active"], brief.agents.map((row) => [
|
|
7406
|
+
row.agent,
|
|
7407
|
+
formatCount(row.sessions),
|
|
7408
|
+
formatCount(row.requests),
|
|
7409
|
+
formatTokens(row.total_tokens),
|
|
7410
|
+
cacheLabel(row),
|
|
7411
|
+
formatUsd(row.cost_usd),
|
|
7412
|
+
row.last_active?.substring(0, 19) ?? "-"
|
|
7413
|
+
])));
|
|
7414
|
+
lines.push("");
|
|
7415
|
+
lines.push(`PER-ACCOUNT - since ${brief.since.label}`);
|
|
7416
|
+
lines.push(...table(["Account", "Agent", "Sessions", "Requests", "Tokens", "Cost", "Last Active"], brief.accounts.map((row) => [
|
|
7417
|
+
accountLabel(row),
|
|
7418
|
+
row.account_tool,
|
|
7419
|
+
formatCount(row.sessions),
|
|
7420
|
+
formatCount(row.requests),
|
|
7421
|
+
formatTokens(row.total_tokens),
|
|
7422
|
+
formatUsd(row.cost_usd),
|
|
7423
|
+
row.last_active?.substring(0, 19) ?? "-"
|
|
7424
|
+
])));
|
|
7425
|
+
lines.push("");
|
|
7426
|
+
lines.push("FRESHNESS");
|
|
7427
|
+
lines.push(`Max request: ${brief.freshness.max_request_line}`);
|
|
7428
|
+
lines.push(`Merge/sync: ${brief.freshness.merge_sync_line}`);
|
|
7429
|
+
lines.push("");
|
|
7430
|
+
return lines.join(`
|
|
7431
|
+
`);
|
|
7432
|
+
}
|
|
7433
|
+
function registerBriefCommand(program) {
|
|
7434
|
+
program.command("brief").description("Fleet-wide usage brief with tokens, cache, cost, breakdowns, and freshness").option("--since <duration-or-date>", "Since window for breakdown tables (24h, 7d, ISO date)", "24h").option("--machine <id|all>", "Filter to one machine, or all machines", "all").option("--json", "Output JSON").action((opts) => {
|
|
7435
|
+
try {
|
|
7436
|
+
const db = openDatabase();
|
|
7437
|
+
ensurePricingSeeded(db);
|
|
7438
|
+
const brief = buildBrief(db, { since: opts.since, machine: opts.machine });
|
|
7439
|
+
if (opts.json) {
|
|
7440
|
+
console.log(JSON.stringify(brief, null, 2));
|
|
7441
|
+
return;
|
|
7442
|
+
}
|
|
7443
|
+
console.log(renderBriefText(brief));
|
|
7444
|
+
} catch (error) {
|
|
7445
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
7446
|
+
process.exit(1);
|
|
7447
|
+
}
|
|
7448
|
+
});
|
|
7449
|
+
}
|
|
7450
|
+
|
|
6702
7451
|
// src/cli/index.ts
|
|
6703
7452
|
init_agents();
|
|
6704
7453
|
init_sync_all();
|
|
@@ -6722,38 +7471,38 @@ var GENERIC_PEER_TABLES = [
|
|
|
6722
7471
|
function quoteIdent(identifier) {
|
|
6723
7472
|
return `"${identifier.replace(/"/g, '""')}"`;
|
|
6724
7473
|
}
|
|
6725
|
-
function tableExists(db,
|
|
6726
|
-
const row = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(
|
|
7474
|
+
function tableExists(db, table2) {
|
|
7475
|
+
const row = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(table2);
|
|
6727
7476
|
return Boolean(row);
|
|
6728
7477
|
}
|
|
6729
|
-
function tableColumns(db,
|
|
6730
|
-
if (!tableExists(db,
|
|
7478
|
+
function tableColumns(db, table2) {
|
|
7479
|
+
if (!tableExists(db, table2))
|
|
6731
7480
|
return [];
|
|
6732
|
-
return db.prepare(`PRAGMA table_info(${quoteIdent(
|
|
7481
|
+
return db.prepare(`PRAGMA table_info(${quoteIdent(table2)})`).all();
|
|
6733
7482
|
}
|
|
6734
|
-
function commonColumns(source, target,
|
|
6735
|
-
const sourceCols = new Set(tableColumns(source,
|
|
6736
|
-
return tableColumns(target,
|
|
7483
|
+
function commonColumns(source, target, table2) {
|
|
7484
|
+
const sourceCols = new Set(tableColumns(source, table2).map((c) => c.name));
|
|
7485
|
+
return tableColumns(target, table2).map((c) => c.name).filter((c) => sourceCols.has(c));
|
|
6737
7486
|
}
|
|
6738
|
-
function primaryKeyColumns(db,
|
|
6739
|
-
return tableColumns(db,
|
|
7487
|
+
function primaryKeyColumns(db, table2) {
|
|
7488
|
+
return tableColumns(db, table2).filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
|
|
6740
7489
|
}
|
|
6741
|
-
function selectRows(source,
|
|
7490
|
+
function selectRows(source, table2, columns) {
|
|
6742
7491
|
if (columns.length === 0)
|
|
6743
7492
|
return [];
|
|
6744
7493
|
const select = columns.map(quoteIdent).join(", ");
|
|
6745
|
-
return source.prepare(`SELECT ${select} FROM ${quoteIdent(
|
|
7494
|
+
return source.prepare(`SELECT ${select} FROM ${quoteIdent(table2)}`).all();
|
|
6746
7495
|
}
|
|
6747
|
-
function rowByKey(target,
|
|
7496
|
+
function rowByKey(target, table2, keyColumns, row) {
|
|
6748
7497
|
if (keyColumns.length === 0)
|
|
6749
7498
|
return null;
|
|
6750
7499
|
if (keyColumns.some((c) => row[c] == null))
|
|
6751
7500
|
return null;
|
|
6752
7501
|
const where = keyColumns.map((c) => `${quoteIdent(c)} = ?`).join(" AND ");
|
|
6753
|
-
return target.prepare(`SELECT * FROM ${quoteIdent(
|
|
7502
|
+
return target.prepare(`SELECT * FROM ${quoteIdent(table2)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
|
|
6754
7503
|
}
|
|
6755
|
-
function hasId(target,
|
|
6756
|
-
return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(
|
|
7504
|
+
function hasId(target, table2, id) {
|
|
7505
|
+
return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(table2)} WHERE id = ?`).get(id);
|
|
6757
7506
|
}
|
|
6758
7507
|
function shouldReplace(source, existing) {
|
|
6759
7508
|
if (!existing)
|
|
@@ -6779,30 +7528,30 @@ function normalizeRow(row, columns, sourceMachine, now) {
|
|
|
6779
7528
|
next["attribution_tag"] = "";
|
|
6780
7529
|
return next;
|
|
6781
7530
|
}
|
|
6782
|
-
function insertOrReplace(target,
|
|
7531
|
+
function insertOrReplace(target, table2, columns, row) {
|
|
6783
7532
|
const colSql = columns.map(quoteIdent).join(", ");
|
|
6784
7533
|
const placeholders = columns.map(() => "?").join(", ");
|
|
6785
7534
|
target.prepare(`
|
|
6786
|
-
INSERT OR REPLACE INTO ${quoteIdent(
|
|
7535
|
+
INSERT OR REPLACE INTO ${quoteIdent(table2)} (${colSql})
|
|
6787
7536
|
VALUES (${placeholders})
|
|
6788
7537
|
`).run(...columns.map((c) => row[c] ?? null));
|
|
6789
7538
|
}
|
|
6790
|
-
function collisionId(target,
|
|
7539
|
+
function collisionId(target, table2, machine, originalId) {
|
|
6791
7540
|
const base = `${machine || "peer"}:${originalId}`;
|
|
6792
|
-
const baseRow = hasId(target,
|
|
7541
|
+
const baseRow = hasId(target, table2, base);
|
|
6793
7542
|
if (!baseRow || String(baseRow["machine_id"] ?? "") === machine)
|
|
6794
7543
|
return base;
|
|
6795
7544
|
for (let i = 2;; i++) {
|
|
6796
7545
|
const candidate = `${base}:${i}`;
|
|
6797
|
-
const row = hasId(target,
|
|
7546
|
+
const row = hasId(target, table2, candidate);
|
|
6798
7547
|
if (!row || String(row["machine_id"] ?? "") === machine)
|
|
6799
7548
|
return candidate;
|
|
6800
7549
|
}
|
|
6801
7550
|
}
|
|
6802
|
-
function mergeIdentityTable(target, source,
|
|
6803
|
-
const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
6804
|
-
const columns = commonColumns(source, target,
|
|
6805
|
-
const rows = selectRows(source,
|
|
7551
|
+
function mergeIdentityTable(target, source, table2, sourceMachine, now, sessionIdMap) {
|
|
7552
|
+
const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
7553
|
+
const columns = commonColumns(source, target, table2);
|
|
7554
|
+
const rows = selectRows(source, table2, columns);
|
|
6806
7555
|
const idMap = new Map;
|
|
6807
7556
|
for (const raw of rows) {
|
|
6808
7557
|
const row = normalizeRow(raw, columns, sourceMachine, now);
|
|
@@ -6812,22 +7561,22 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
|
|
|
6812
7561
|
continue;
|
|
6813
7562
|
}
|
|
6814
7563
|
const machine = String(row["machine_id"] ?? "");
|
|
6815
|
-
const directExisting = hasId(target,
|
|
7564
|
+
const directExisting = hasId(target, table2, originalId);
|
|
6816
7565
|
if (directExisting && String(directExisting["machine_id"] ?? "") !== machine) {
|
|
6817
|
-
row["id"] = collisionId(target,
|
|
7566
|
+
row["id"] = collisionId(target, table2, machine, originalId);
|
|
6818
7567
|
stats.collisions++;
|
|
6819
7568
|
}
|
|
6820
|
-
if (
|
|
7569
|
+
if (table2 === "requests" && sessionIdMap) {
|
|
6821
7570
|
const originalSessionId = String(row["session_id"] ?? "");
|
|
6822
7571
|
row["session_id"] = sessionIdMap.get(originalSessionId) ?? originalSessionId;
|
|
6823
7572
|
}
|
|
6824
|
-
const existing = hasId(target,
|
|
7573
|
+
const existing = hasId(target, table2, String(row["id"]));
|
|
6825
7574
|
idMap.set(originalId, String(row["id"]));
|
|
6826
7575
|
if (existing && !shouldReplace(row, existing)) {
|
|
6827
7576
|
stats.skipped++;
|
|
6828
7577
|
continue;
|
|
6829
7578
|
}
|
|
6830
|
-
insertOrReplace(target,
|
|
7579
|
+
insertOrReplace(target, table2, columns, row);
|
|
6831
7580
|
if (existing)
|
|
6832
7581
|
stats.updated++;
|
|
6833
7582
|
else
|
|
@@ -6836,10 +7585,10 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
|
|
|
6836
7585
|
return { stats, idMap };
|
|
6837
7586
|
}
|
|
6838
7587
|
function mergeProjects(target, source) {
|
|
6839
|
-
const
|
|
6840
|
-
const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
6841
|
-
const columns = commonColumns(source, target,
|
|
6842
|
-
const rows = selectRows(source,
|
|
7588
|
+
const table2 = "projects";
|
|
7589
|
+
const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
7590
|
+
const columns = commonColumns(source, target, table2);
|
|
7591
|
+
const rows = selectRows(source, table2, columns);
|
|
6843
7592
|
for (const raw of rows) {
|
|
6844
7593
|
const row = { ...raw };
|
|
6845
7594
|
const path = String(row["path"] ?? "");
|
|
@@ -6851,7 +7600,7 @@ function mergeProjects(target, source) {
|
|
|
6851
7600
|
const existingByPath = target.prepare(`SELECT * FROM projects WHERE path = ?`).get(path);
|
|
6852
7601
|
if (existingByPath) {
|
|
6853
7602
|
row["id"] = existingByPath["id"] ?? id;
|
|
6854
|
-
insertOrReplace(target,
|
|
7603
|
+
insertOrReplace(target, table2, columns, row);
|
|
6855
7604
|
stats.updated++;
|
|
6856
7605
|
continue;
|
|
6857
7606
|
}
|
|
@@ -6863,24 +7612,24 @@ function mergeProjects(target, source) {
|
|
|
6863
7612
|
row["id"] = `peer:${String(row["id"])}`;
|
|
6864
7613
|
}
|
|
6865
7614
|
}
|
|
6866
|
-
insertOrReplace(target,
|
|
7615
|
+
insertOrReplace(target, table2, columns, row);
|
|
6867
7616
|
stats.inserted++;
|
|
6868
7617
|
}
|
|
6869
7618
|
return stats;
|
|
6870
7619
|
}
|
|
6871
|
-
function mergeGenericTable(target, source,
|
|
6872
|
-
const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
6873
|
-
const columns = commonColumns(source, target,
|
|
6874
|
-
const keyColumns = primaryKeyColumns(target,
|
|
6875
|
-
const rows = selectRows(source,
|
|
7620
|
+
function mergeGenericTable(target, source, table2, sourceMachine, now) {
|
|
7621
|
+
const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
7622
|
+
const columns = commonColumns(source, target, table2);
|
|
7623
|
+
const keyColumns = primaryKeyColumns(target, table2).filter((c) => columns.includes(c));
|
|
7624
|
+
const rows = selectRows(source, table2, columns);
|
|
6876
7625
|
for (const raw of rows) {
|
|
6877
7626
|
const row = normalizeRow(raw, columns, sourceMachine, now);
|
|
6878
|
-
const existing = rowByKey(target,
|
|
7627
|
+
const existing = rowByKey(target, table2, keyColumns, row);
|
|
6879
7628
|
if (existing && !shouldReplace(row, existing)) {
|
|
6880
7629
|
stats.skipped++;
|
|
6881
7630
|
continue;
|
|
6882
7631
|
}
|
|
6883
|
-
insertOrReplace(target,
|
|
7632
|
+
insertOrReplace(target, table2, columns, row);
|
|
6884
7633
|
if (existing)
|
|
6885
7634
|
stats.updated++;
|
|
6886
7635
|
else
|
|
@@ -6892,12 +7641,12 @@ function detectSourceMachine(source, fallback) {
|
|
|
6892
7641
|
if (fallback && fallback.trim())
|
|
6893
7642
|
return fallback.trim();
|
|
6894
7643
|
const counts = new Map;
|
|
6895
|
-
for (const
|
|
6896
|
-
if (!tableExists(source,
|
|
7644
|
+
for (const table2 of ["sessions", "requests", "usage_snapshots"]) {
|
|
7645
|
+
if (!tableExists(source, table2))
|
|
6897
7646
|
continue;
|
|
6898
7647
|
const rows = source.prepare(`
|
|
6899
7648
|
SELECT machine_id, COUNT(*) as cnt
|
|
6900
|
-
FROM ${quoteIdent(
|
|
7649
|
+
FROM ${quoteIdent(table2)}
|
|
6901
7650
|
WHERE machine_id != '' AND machine_id IS NOT NULL
|
|
6902
7651
|
GROUP BY machine_id
|
|
6903
7652
|
`).all();
|
|
@@ -6954,8 +7703,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
|
|
|
6954
7703
|
const sessionMerge = mergeIdentityTable(target, source, "sessions", sourceMachine, now);
|
|
6955
7704
|
tables.push(sessionMerge.stats);
|
|
6956
7705
|
tables.push(mergeIdentityTable(target, source, "requests", sourceMachine, now, sessionMerge.idMap).stats);
|
|
6957
|
-
for (const
|
|
6958
|
-
tables.push(mergeGenericTable(target, source,
|
|
7706
|
+
for (const table2 of GENERIC_PEER_TABLES) {
|
|
7707
|
+
tables.push(mergeGenericTable(target, source, table2, sourceMachine, now));
|
|
6959
7708
|
}
|
|
6960
7709
|
ensureMachineRegistry(target, sourceMachine, now);
|
|
6961
7710
|
target.exec("COMMIT");
|
|
@@ -6969,8 +7718,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
|
|
|
6969
7718
|
source.close();
|
|
6970
7719
|
}
|
|
6971
7720
|
const deduped = dedupeRequests(target);
|
|
6972
|
-
const rowsWritten = tables.reduce((sum,
|
|
6973
|
-
const collisions = tables.reduce((sum,
|
|
7721
|
+
const rowsWritten = tables.reduce((sum, table2) => sum + table2.inserted + table2.updated, 0);
|
|
7722
|
+
const collisions = tables.reduce((sum, table2) => sum + table2.collisions, 0);
|
|
6974
7723
|
return {
|
|
6975
7724
|
source_path: sourcePath,
|
|
6976
7725
|
source_machine: sourceMachine,
|
|
@@ -7134,7 +7883,7 @@ function printAccountBreakdown(rows) {
|
|
|
7134
7883
|
fmt4(r.subscription_included_usd)
|
|
7135
7884
|
]));
|
|
7136
7885
|
}
|
|
7137
|
-
function
|
|
7886
|
+
function parseSinceDate2(since) {
|
|
7138
7887
|
const relMatch = since.match(/^(\d+)d$/);
|
|
7139
7888
|
if (relMatch) {
|
|
7140
7889
|
const days = parseInt(relMatch[1], 10);
|
|
@@ -7279,7 +8028,7 @@ program.command("sessions").description("List coding sessions with costs").optio
|
|
|
7279
8028
|
const agent = parseOptionalCliAgent(opts.agent);
|
|
7280
8029
|
await autoSync();
|
|
7281
8030
|
const db = openDatabase();
|
|
7282
|
-
const sinceDate = opts.since ?
|
|
8031
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7283
8032
|
let sessions = querySessions(db, {
|
|
7284
8033
|
agent,
|
|
7285
8034
|
project: opts.project,
|
|
@@ -7326,7 +8075,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
|
|
|
7326
8075
|
const count = parsePositiveCliInteger(opts.n ?? "10", "-n");
|
|
7327
8076
|
const agent = parseOptionalCliAgent(opts.agent);
|
|
7328
8077
|
const db = openDatabase();
|
|
7329
|
-
const sinceDate = opts.since ?
|
|
8078
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7330
8079
|
let sessions = queryTopSessions(db, count, agent);
|
|
7331
8080
|
if (sinceDate)
|
|
7332
8081
|
sessions = sessions.filter((s) => s.started_at >= sinceDate);
|
|
@@ -7347,7 +8096,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
|
|
|
7347
8096
|
});
|
|
7348
8097
|
program.command("breakdown").description("Cost breakdown by model, agent, project, or account").option("--by <dimension>", "Dimension: model|agent|project|account", "model").option("--since <date>", "Filter since date or relative (e.g. 2026-03-01, 7d, 30d)").action((opts) => {
|
|
7349
8098
|
const db = openDatabase();
|
|
7350
|
-
const sinceDate = opts.since ?
|
|
8099
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7351
8100
|
console.log();
|
|
7352
8101
|
if (opts.by === "project") {
|
|
7353
8102
|
const rows = sinceDate ? db.prepare(`
|
|
@@ -7929,8 +8678,8 @@ program.command("merge-db <source-db>").description("Merge another Economy SQLit
|
|
|
7929
8678
|
console.log();
|
|
7930
8679
|
console.log(chalk7.bold.cyan(` Merged Economy DB \u2014 ${result.source_machine}`));
|
|
7931
8680
|
console.log(` Rows written: ${fmtCount(result.rows_written)} \xB7 collisions remapped: ${fmtCount(result.collisions)} \xB7 deduped: ${fmtCount(result.deduped)}`);
|
|
7932
|
-
for (const
|
|
7933
|
-
console.log(` ${chalk7.white(
|
|
8681
|
+
for (const table2 of result.tables) {
|
|
8682
|
+
console.log(` ${chalk7.white(table2.table.padEnd(16))}` + ` inserted ${fmtCount(table2.inserted).padStart(6)}` + ` updated ${fmtCount(table2.updated).padStart(6)}` + ` skipped ${fmtCount(table2.skipped).padStart(6)}` + ` collisions ${fmtCount(table2.collisions).padStart(3)}`);
|
|
7934
8683
|
}
|
|
7935
8684
|
console.log();
|
|
7936
8685
|
});
|
|
@@ -8309,6 +9058,7 @@ billingCmd.command("show").description("Show actual billing totals vs our estima
|
|
|
8309
9058
|
});
|
|
8310
9059
|
registerBrainsCommand(program);
|
|
8311
9060
|
registerTodosCommand(program);
|
|
9061
|
+
registerBriefCommand(program);
|
|
8312
9062
|
registerExtendedCommands(program);
|
|
8313
9063
|
registerFleetCommands(program);
|
|
8314
9064
|
registerEventsCommands(program, { source: "economy" });
|
package/dist/index.js
CHANGED
|
@@ -235,6 +235,7 @@ var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING,
|
|
|
235
235
|
var init_pricing = __esm(() => {
|
|
236
236
|
init_database();
|
|
237
237
|
DEFAULT_PRICING = {
|
|
238
|
+
"claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
238
239
|
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
239
240
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
240
241
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../src/ingest/billing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../src/ingest/billing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA+I7D,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuD7C;AAeD,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAqD7C;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwC/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/lib/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/lib/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA+FxD,CAAA;AAqND,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOtD;AA2CD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAMtD;AA+GD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoBjF;AA4BD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAG7D;AAMD,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -236,6 +236,7 @@ var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING,
|
|
|
236
236
|
var init_pricing = __esm(() => {
|
|
237
237
|
init_database();
|
|
238
238
|
DEFAULT_PRICING = {
|
|
239
|
+
"claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
239
240
|
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
240
241
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
241
242
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
package/dist/otel/index.js
CHANGED
|
@@ -236,6 +236,7 @@ var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING,
|
|
|
236
236
|
var init_pricing = __esm(() => {
|
|
237
237
|
init_database();
|
|
238
238
|
DEFAULT_PRICING = {
|
|
239
|
+
"claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
239
240
|
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
240
241
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
241
242
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
package/dist/server/index.js
CHANGED
|
@@ -236,6 +236,7 @@ var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING,
|
|
|
236
236
|
var init_pricing = __esm(() => {
|
|
237
237
|
init_database();
|
|
238
238
|
DEFAULT_PRICING = {
|
|
239
|
+
"claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
239
240
|
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
240
241
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
241
242
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
@@ -1793,12 +1794,45 @@ function getGeminiBillingExportPath() {
|
|
|
1793
1794
|
function toISODate(d) {
|
|
1794
1795
|
return d.toISOString().substring(0, 10);
|
|
1795
1796
|
}
|
|
1797
|
+
function isValidDateParts(year, month, day) {
|
|
1798
|
+
const d = new Date(Date.UTC(year, month - 1, day));
|
|
1799
|
+
return d.getUTCFullYear() === year && d.getUTCMonth() === month - 1 && d.getUTCDate() === day;
|
|
1800
|
+
}
|
|
1801
|
+
function formatDateParts(year, month, day) {
|
|
1802
|
+
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
1803
|
+
}
|
|
1796
1804
|
function parseDate(value) {
|
|
1805
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
1806
|
+
return value >= 100001 && value <= 999912 ? parseDate(String(value)) : null;
|
|
1807
|
+
}
|
|
1797
1808
|
if (typeof value !== "string" || !value.trim())
|
|
1798
1809
|
return null;
|
|
1799
|
-
const
|
|
1810
|
+
const trimmed = value.trim();
|
|
1811
|
+
const compactMonth = /^(\d{4})(\d{2})$/.exec(trimmed);
|
|
1812
|
+
if (compactMonth) {
|
|
1813
|
+
const year = Number(compactMonth[1]);
|
|
1814
|
+
const month = Number(compactMonth[2]);
|
|
1815
|
+
return isValidDateParts(year, month, 1) ? formatDateParts(year, month, 1) : null;
|
|
1816
|
+
}
|
|
1817
|
+
const dashedMonth = /^(\d{4})-(\d{2})$/.exec(trimmed);
|
|
1818
|
+
if (dashedMonth) {
|
|
1819
|
+
const year = Number(dashedMonth[1]);
|
|
1820
|
+
const month = Number(dashedMonth[2]);
|
|
1821
|
+
return isValidDateParts(year, month, 1) ? formatDateParts(year, month, 1) : null;
|
|
1822
|
+
}
|
|
1823
|
+
const isoDate = /^(\d{4})-(\d{2})-(\d{2})(?:$|[T\s])/.exec(trimmed);
|
|
1824
|
+
if (isoDate) {
|
|
1825
|
+
const year = Number(isoDate[1]);
|
|
1826
|
+
const month = Number(isoDate[2]);
|
|
1827
|
+
const day = Number(isoDate[3]);
|
|
1828
|
+
if (!isValidDateParts(year, month, day))
|
|
1829
|
+
return null;
|
|
1830
|
+
if (trimmed.length === 10)
|
|
1831
|
+
return formatDateParts(year, month, day);
|
|
1832
|
+
}
|
|
1833
|
+
const d = new Date(trimmed);
|
|
1800
1834
|
if (Number.isNaN(d.getTime()))
|
|
1801
|
-
return
|
|
1835
|
+
return null;
|
|
1802
1836
|
return toISODate(d);
|
|
1803
1837
|
}
|
|
1804
1838
|
function parseCsv(content) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.35",
|
|
4
4
|
"description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|