@hasna/economy 0.2.34 → 0.2.36
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 +76 -0
- package/dist/cli/commands/brief.d.ts.map +1 -0
- package/dist/cli/index.js +799 -60
- package/dist/index.js +1 -0
- 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 +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
currentMachineId?: string;
|
|
67
|
+
localSyncAt?: Date;
|
|
68
|
+
}
|
|
69
|
+
interface BriefCommandDeps {
|
|
70
|
+
beforeRead?: () => void | Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
export declare function buildBrief(db: Database, opts?: BriefOptions): EconomyBrief;
|
|
73
|
+
export declare function renderBriefText(brief: EconomyBrief): string;
|
|
74
|
+
export declare function registerBriefCommand(program: Command, deps?: BriefCommandDeps): void;
|
|
75
|
+
export {};
|
|
76
|
+
//# 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;AAa7D,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;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,WAAW,CAAC,EAAE,IAAI,CAAA;CACnB;AAED,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxC;AAknBD,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,YAAiB,GAAG,YAAY,CAmD9E;AAyDD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAyE3D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAgCxF"}
|
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 } : {},
|
|
@@ -6732,6 +6733,743 @@ function registerFleetCommands(program) {
|
|
|
6732
6733
|
});
|
|
6733
6734
|
}
|
|
6734
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, localSync) {
|
|
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
|
+
if (localSync && (!machine || machine === localSync.machineId)) {
|
|
7270
|
+
const current = machines.get(localSync.machineId) ?? {
|
|
7271
|
+
machine_id: localSync.machineId,
|
|
7272
|
+
max_request_at: null,
|
|
7273
|
+
request_age: "never",
|
|
7274
|
+
last_merge_sync_at: null,
|
|
7275
|
+
merge_sync_age: "never"
|
|
7276
|
+
};
|
|
7277
|
+
current.last_merge_sync_at = latestTimestamp(current.last_merge_sync_at, localSync.syncedAt);
|
|
7278
|
+
current.merge_sync_age = formatAge(current.last_merge_sync_at, now);
|
|
7279
|
+
machines.set(localSync.machineId, current);
|
|
7280
|
+
}
|
|
7281
|
+
const rows = [...machines.values()].sort((a, b) => a.machine_id.localeCompare(b.machine_id));
|
|
7282
|
+
return {
|
|
7283
|
+
machines: rows,
|
|
7284
|
+
max_request_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.max_request_at ?? "never"} (${row.request_age})`).join("; ") : "none",
|
|
7285
|
+
merge_sync_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.last_merge_sync_at ?? "never"} (${row.merge_sync_age})`).join("; ") : "none"
|
|
7286
|
+
};
|
|
7287
|
+
}
|
|
7288
|
+
function buildBrief(db, opts = {}) {
|
|
7289
|
+
const now = opts.now ?? new Date;
|
|
7290
|
+
const machine = machineFilter(opts.machine);
|
|
7291
|
+
const sinceInput = opts.since ?? "24h";
|
|
7292
|
+
const since = parseSinceDate(sinceInput, now);
|
|
7293
|
+
const summaries = [
|
|
7294
|
+
{
|
|
7295
|
+
label: "Today",
|
|
7296
|
+
period: "today",
|
|
7297
|
+
...queryPeriodTotals(db, "today", machine)
|
|
7298
|
+
},
|
|
7299
|
+
{
|
|
7300
|
+
label: "Week",
|
|
7301
|
+
period: "week",
|
|
7302
|
+
...queryPeriodTotals(db, "week", machine)
|
|
7303
|
+
},
|
|
7304
|
+
{
|
|
7305
|
+
label: `Since ${since.label}`,
|
|
7306
|
+
period: "since",
|
|
7307
|
+
since: since.timestamp,
|
|
7308
|
+
...querySinceTotals(db, since.timestamp, machine)
|
|
7309
|
+
}
|
|
7310
|
+
];
|
|
7311
|
+
const machines = queryMachineSinceRows(db, since.timestamp, machine);
|
|
7312
|
+
for (const row of machines) {
|
|
7313
|
+
row.last_data_age = formatAge(row.last_data_at, now);
|
|
7314
|
+
}
|
|
7315
|
+
return {
|
|
7316
|
+
generated_at: now.toISOString(),
|
|
7317
|
+
machine: machine ?? "all",
|
|
7318
|
+
since: {
|
|
7319
|
+
input: sinceInput,
|
|
7320
|
+
timestamp: since.timestamp,
|
|
7321
|
+
label: since.label
|
|
7322
|
+
},
|
|
7323
|
+
summaries,
|
|
7324
|
+
machines,
|
|
7325
|
+
agents: queryAgentSinceRows(db, since.timestamp, machine),
|
|
7326
|
+
accounts: queryAccountSinceRows(db, since.timestamp, machine),
|
|
7327
|
+
freshness: queryFreshness(db, now, machine, opts.currentMachineId && opts.localSyncAt ? { machineId: opts.currentMachineId, syncedAt: opts.localSyncAt.toISOString() } : undefined)
|
|
7328
|
+
};
|
|
7329
|
+
}
|
|
7330
|
+
function formatUsd(usd) {
|
|
7331
|
+
if (usd >= 0.01)
|
|
7332
|
+
return "$" + usd.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
7333
|
+
if (usd >= 0.0001)
|
|
7334
|
+
return `${(usd * 100).toFixed(2).replace(/\.?0+$/, "")}c`;
|
|
7335
|
+
if (usd > 0)
|
|
7336
|
+
return "<0.01c";
|
|
7337
|
+
return "$0.00";
|
|
7338
|
+
}
|
|
7339
|
+
function formatCount(n) {
|
|
7340
|
+
return Math.round(n).toLocaleString("en-US");
|
|
7341
|
+
}
|
|
7342
|
+
function formatTokens(n) {
|
|
7343
|
+
if (n >= 1e9)
|
|
7344
|
+
return `${(n / 1e9).toFixed(1)}B`;
|
|
7345
|
+
if (n >= 1e6)
|
|
7346
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
7347
|
+
if (n >= 1000)
|
|
7348
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
7349
|
+
return formatCount(n);
|
|
7350
|
+
}
|
|
7351
|
+
function formatAge(timestamp, now) {
|
|
7352
|
+
if (!timestamp)
|
|
7353
|
+
return "never";
|
|
7354
|
+
const t = new Date(timestamp).getTime();
|
|
7355
|
+
if (!Number.isFinite(t))
|
|
7356
|
+
return "unknown";
|
|
7357
|
+
const ageMs = Math.max(0, now.getTime() - t);
|
|
7358
|
+
const minutes = Math.floor(ageMs / 60000);
|
|
7359
|
+
if (minutes < 1)
|
|
7360
|
+
return "<1m";
|
|
7361
|
+
if (minutes < 60)
|
|
7362
|
+
return `${minutes}m`;
|
|
7363
|
+
const hours = Math.floor(minutes / 60);
|
|
7364
|
+
if (hours < 48)
|
|
7365
|
+
return `${hours}h`;
|
|
7366
|
+
return `${Math.floor(hours / 24)}d`;
|
|
7367
|
+
}
|
|
7368
|
+
function accountLabel(row) {
|
|
7369
|
+
return row.account_email || row.account_name || row.account_key || "unknown";
|
|
7370
|
+
}
|
|
7371
|
+
function cacheLabel(row) {
|
|
7372
|
+
const total = row.cache_read_tokens + row.cache_create_tokens;
|
|
7373
|
+
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)}` : "";
|
|
7374
|
+
return `${formatTokens(total)} (r ${formatTokens(row.cache_read_tokens)} / w ${formatTokens(row.cache_create_tokens)}${split})`;
|
|
7375
|
+
}
|
|
7376
|
+
function table(headers, rows) {
|
|
7377
|
+
if (rows.length === 0)
|
|
7378
|
+
return ["(none)"];
|
|
7379
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
|
|
7380
|
+
const lines = [];
|
|
7381
|
+
lines.push(headers.map((header, index) => header.padEnd(widths[index])).join(" "));
|
|
7382
|
+
lines.push(widths.map((width) => "-".repeat(width)).join(" "));
|
|
7383
|
+
for (const row of rows) {
|
|
7384
|
+
lines.push(row.map((cell, index) => cell.padEnd(widths[index])).join(" "));
|
|
7385
|
+
}
|
|
7386
|
+
return lines;
|
|
7387
|
+
}
|
|
7388
|
+
function renderBriefText(brief) {
|
|
7389
|
+
const lines = [];
|
|
7390
|
+
lines.push(`Economy Brief - ${brief.machine === "all" ? "fleet" : brief.machine}`);
|
|
7391
|
+
lines.push(`Generated: ${brief.generated_at}`);
|
|
7392
|
+
lines.push(`Since: ${brief.since.label} (${brief.since.timestamp})`);
|
|
7393
|
+
lines.push("");
|
|
7394
|
+
lines.push("SUMMARY");
|
|
7395
|
+
lines.push(...table(["Period", "Sessions", "Requests", "Input", "Output", "Cache", "Tokens", "Cost"], brief.summaries.map((row) => [
|
|
7396
|
+
row.label,
|
|
7397
|
+
formatCount(row.sessions),
|
|
7398
|
+
formatCount(row.requests),
|
|
7399
|
+
formatTokens(row.input_tokens),
|
|
7400
|
+
formatTokens(row.output_tokens),
|
|
7401
|
+
cacheLabel(row),
|
|
7402
|
+
formatTokens(row.total_tokens),
|
|
7403
|
+
formatUsd(row.cost_usd)
|
|
7404
|
+
])));
|
|
7405
|
+
lines.push("");
|
|
7406
|
+
lines.push(`PER-MACHINE - since ${brief.since.label}`);
|
|
7407
|
+
lines.push(...table(["Machine", "Sessions", "Tokens", "Cache", "Cost", "Last Data Age"], brief.machines.map((row) => [
|
|
7408
|
+
row.machine_id,
|
|
7409
|
+
formatCount(row.sessions),
|
|
7410
|
+
formatTokens(row.total_tokens),
|
|
7411
|
+
cacheLabel(row),
|
|
7412
|
+
formatUsd(row.cost_usd),
|
|
7413
|
+
row.last_data_age
|
|
7414
|
+
])));
|
|
7415
|
+
lines.push("");
|
|
7416
|
+
lines.push(`PER-AGENT - since ${brief.since.label}`);
|
|
7417
|
+
lines.push(...table(["Agent", "Sessions", "Requests", "Tokens", "Cache", "Cost", "Last Active"], brief.agents.map((row) => [
|
|
7418
|
+
row.agent,
|
|
7419
|
+
formatCount(row.sessions),
|
|
7420
|
+
formatCount(row.requests),
|
|
7421
|
+
formatTokens(row.total_tokens),
|
|
7422
|
+
cacheLabel(row),
|
|
7423
|
+
formatUsd(row.cost_usd),
|
|
7424
|
+
row.last_active?.substring(0, 19) ?? "-"
|
|
7425
|
+
])));
|
|
7426
|
+
lines.push("");
|
|
7427
|
+
lines.push(`PER-ACCOUNT - since ${brief.since.label}`);
|
|
7428
|
+
lines.push(...table(["Account", "Agent", "Sessions", "Requests", "Tokens", "Cost", "Last Active"], brief.accounts.map((row) => [
|
|
7429
|
+
accountLabel(row),
|
|
7430
|
+
row.account_tool,
|
|
7431
|
+
formatCount(row.sessions),
|
|
7432
|
+
formatCount(row.requests),
|
|
7433
|
+
formatTokens(row.total_tokens),
|
|
7434
|
+
formatUsd(row.cost_usd),
|
|
7435
|
+
row.last_active?.substring(0, 19) ?? "-"
|
|
7436
|
+
])));
|
|
7437
|
+
lines.push("");
|
|
7438
|
+
lines.push("FRESHNESS");
|
|
7439
|
+
lines.push(`Max request: ${brief.freshness.max_request_line}`);
|
|
7440
|
+
lines.push(`Merge/sync: ${brief.freshness.merge_sync_line}`);
|
|
7441
|
+
lines.push("");
|
|
7442
|
+
return lines.join(`
|
|
7443
|
+
`);
|
|
7444
|
+
}
|
|
7445
|
+
function registerBriefCommand(program, deps = {}) {
|
|
7446
|
+
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(async (opts) => {
|
|
7447
|
+
try {
|
|
7448
|
+
let localSyncAt;
|
|
7449
|
+
if (deps.beforeRead) {
|
|
7450
|
+
await deps.beforeRead();
|
|
7451
|
+
localSyncAt = new Date;
|
|
7452
|
+
}
|
|
7453
|
+
const db = openDatabase();
|
|
7454
|
+
ensurePricingSeeded(db);
|
|
7455
|
+
const brief = buildBrief(db, {
|
|
7456
|
+
since: opts.since,
|
|
7457
|
+
machine: opts.machine,
|
|
7458
|
+
currentMachineId: localSyncAt ? getMachineId() : undefined,
|
|
7459
|
+
localSyncAt
|
|
7460
|
+
});
|
|
7461
|
+
if (opts.json) {
|
|
7462
|
+
console.log(JSON.stringify(brief, null, 2));
|
|
7463
|
+
return;
|
|
7464
|
+
}
|
|
7465
|
+
console.log(renderBriefText(brief));
|
|
7466
|
+
} catch (error) {
|
|
7467
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
7468
|
+
process.exit(1);
|
|
7469
|
+
}
|
|
7470
|
+
});
|
|
7471
|
+
}
|
|
7472
|
+
|
|
6735
7473
|
// src/cli/index.ts
|
|
6736
7474
|
init_agents();
|
|
6737
7475
|
init_sync_all();
|
|
@@ -6755,38 +7493,38 @@ var GENERIC_PEER_TABLES = [
|
|
|
6755
7493
|
function quoteIdent(identifier) {
|
|
6756
7494
|
return `"${identifier.replace(/"/g, '""')}"`;
|
|
6757
7495
|
}
|
|
6758
|
-
function tableExists(db,
|
|
6759
|
-
const row = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(
|
|
7496
|
+
function tableExists(db, table2) {
|
|
7497
|
+
const row = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(table2);
|
|
6760
7498
|
return Boolean(row);
|
|
6761
7499
|
}
|
|
6762
|
-
function tableColumns(db,
|
|
6763
|
-
if (!tableExists(db,
|
|
7500
|
+
function tableColumns(db, table2) {
|
|
7501
|
+
if (!tableExists(db, table2))
|
|
6764
7502
|
return [];
|
|
6765
|
-
return db.prepare(`PRAGMA table_info(${quoteIdent(
|
|
7503
|
+
return db.prepare(`PRAGMA table_info(${quoteIdent(table2)})`).all();
|
|
6766
7504
|
}
|
|
6767
|
-
function commonColumns(source, target,
|
|
6768
|
-
const sourceCols = new Set(tableColumns(source,
|
|
6769
|
-
return tableColumns(target,
|
|
7505
|
+
function commonColumns(source, target, table2) {
|
|
7506
|
+
const sourceCols = new Set(tableColumns(source, table2).map((c) => c.name));
|
|
7507
|
+
return tableColumns(target, table2).map((c) => c.name).filter((c) => sourceCols.has(c));
|
|
6770
7508
|
}
|
|
6771
|
-
function primaryKeyColumns(db,
|
|
6772
|
-
return tableColumns(db,
|
|
7509
|
+
function primaryKeyColumns(db, table2) {
|
|
7510
|
+
return tableColumns(db, table2).filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
|
|
6773
7511
|
}
|
|
6774
|
-
function selectRows(source,
|
|
7512
|
+
function selectRows(source, table2, columns) {
|
|
6775
7513
|
if (columns.length === 0)
|
|
6776
7514
|
return [];
|
|
6777
7515
|
const select = columns.map(quoteIdent).join(", ");
|
|
6778
|
-
return source.prepare(`SELECT ${select} FROM ${quoteIdent(
|
|
7516
|
+
return source.prepare(`SELECT ${select} FROM ${quoteIdent(table2)}`).all();
|
|
6779
7517
|
}
|
|
6780
|
-
function rowByKey(target,
|
|
7518
|
+
function rowByKey(target, table2, keyColumns, row) {
|
|
6781
7519
|
if (keyColumns.length === 0)
|
|
6782
7520
|
return null;
|
|
6783
7521
|
if (keyColumns.some((c) => row[c] == null))
|
|
6784
7522
|
return null;
|
|
6785
7523
|
const where = keyColumns.map((c) => `${quoteIdent(c)} = ?`).join(" AND ");
|
|
6786
|
-
return target.prepare(`SELECT * FROM ${quoteIdent(
|
|
7524
|
+
return target.prepare(`SELECT * FROM ${quoteIdent(table2)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
|
|
6787
7525
|
}
|
|
6788
|
-
function hasId(target,
|
|
6789
|
-
return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(
|
|
7526
|
+
function hasId(target, table2, id) {
|
|
7527
|
+
return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(table2)} WHERE id = ?`).get(id);
|
|
6790
7528
|
}
|
|
6791
7529
|
function shouldReplace(source, existing) {
|
|
6792
7530
|
if (!existing)
|
|
@@ -6812,30 +7550,30 @@ function normalizeRow(row, columns, sourceMachine, now) {
|
|
|
6812
7550
|
next["attribution_tag"] = "";
|
|
6813
7551
|
return next;
|
|
6814
7552
|
}
|
|
6815
|
-
function insertOrReplace(target,
|
|
7553
|
+
function insertOrReplace(target, table2, columns, row) {
|
|
6816
7554
|
const colSql = columns.map(quoteIdent).join(", ");
|
|
6817
7555
|
const placeholders = columns.map(() => "?").join(", ");
|
|
6818
7556
|
target.prepare(`
|
|
6819
|
-
INSERT OR REPLACE INTO ${quoteIdent(
|
|
7557
|
+
INSERT OR REPLACE INTO ${quoteIdent(table2)} (${colSql})
|
|
6820
7558
|
VALUES (${placeholders})
|
|
6821
7559
|
`).run(...columns.map((c) => row[c] ?? null));
|
|
6822
7560
|
}
|
|
6823
|
-
function collisionId(target,
|
|
7561
|
+
function collisionId(target, table2, machine, originalId) {
|
|
6824
7562
|
const base = `${machine || "peer"}:${originalId}`;
|
|
6825
|
-
const baseRow = hasId(target,
|
|
7563
|
+
const baseRow = hasId(target, table2, base);
|
|
6826
7564
|
if (!baseRow || String(baseRow["machine_id"] ?? "") === machine)
|
|
6827
7565
|
return base;
|
|
6828
7566
|
for (let i = 2;; i++) {
|
|
6829
7567
|
const candidate = `${base}:${i}`;
|
|
6830
|
-
const row = hasId(target,
|
|
7568
|
+
const row = hasId(target, table2, candidate);
|
|
6831
7569
|
if (!row || String(row["machine_id"] ?? "") === machine)
|
|
6832
7570
|
return candidate;
|
|
6833
7571
|
}
|
|
6834
7572
|
}
|
|
6835
|
-
function mergeIdentityTable(target, source,
|
|
6836
|
-
const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
6837
|
-
const columns = commonColumns(source, target,
|
|
6838
|
-
const rows = selectRows(source,
|
|
7573
|
+
function mergeIdentityTable(target, source, table2, sourceMachine, now, sessionIdMap) {
|
|
7574
|
+
const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
7575
|
+
const columns = commonColumns(source, target, table2);
|
|
7576
|
+
const rows = selectRows(source, table2, columns);
|
|
6839
7577
|
const idMap = new Map;
|
|
6840
7578
|
for (const raw of rows) {
|
|
6841
7579
|
const row = normalizeRow(raw, columns, sourceMachine, now);
|
|
@@ -6845,22 +7583,22 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
|
|
|
6845
7583
|
continue;
|
|
6846
7584
|
}
|
|
6847
7585
|
const machine = String(row["machine_id"] ?? "");
|
|
6848
|
-
const directExisting = hasId(target,
|
|
7586
|
+
const directExisting = hasId(target, table2, originalId);
|
|
6849
7587
|
if (directExisting && String(directExisting["machine_id"] ?? "") !== machine) {
|
|
6850
|
-
row["id"] = collisionId(target,
|
|
7588
|
+
row["id"] = collisionId(target, table2, machine, originalId);
|
|
6851
7589
|
stats.collisions++;
|
|
6852
7590
|
}
|
|
6853
|
-
if (
|
|
7591
|
+
if (table2 === "requests" && sessionIdMap) {
|
|
6854
7592
|
const originalSessionId = String(row["session_id"] ?? "");
|
|
6855
7593
|
row["session_id"] = sessionIdMap.get(originalSessionId) ?? originalSessionId;
|
|
6856
7594
|
}
|
|
6857
|
-
const existing = hasId(target,
|
|
7595
|
+
const existing = hasId(target, table2, String(row["id"]));
|
|
6858
7596
|
idMap.set(originalId, String(row["id"]));
|
|
6859
7597
|
if (existing && !shouldReplace(row, existing)) {
|
|
6860
7598
|
stats.skipped++;
|
|
6861
7599
|
continue;
|
|
6862
7600
|
}
|
|
6863
|
-
insertOrReplace(target,
|
|
7601
|
+
insertOrReplace(target, table2, columns, row);
|
|
6864
7602
|
if (existing)
|
|
6865
7603
|
stats.updated++;
|
|
6866
7604
|
else
|
|
@@ -6869,10 +7607,10 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
|
|
|
6869
7607
|
return { stats, idMap };
|
|
6870
7608
|
}
|
|
6871
7609
|
function mergeProjects(target, source) {
|
|
6872
|
-
const
|
|
6873
|
-
const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
6874
|
-
const columns = commonColumns(source, target,
|
|
6875
|
-
const rows = selectRows(source,
|
|
7610
|
+
const table2 = "projects";
|
|
7611
|
+
const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
7612
|
+
const columns = commonColumns(source, target, table2);
|
|
7613
|
+
const rows = selectRows(source, table2, columns);
|
|
6876
7614
|
for (const raw of rows) {
|
|
6877
7615
|
const row = { ...raw };
|
|
6878
7616
|
const path = String(row["path"] ?? "");
|
|
@@ -6884,7 +7622,7 @@ function mergeProjects(target, source) {
|
|
|
6884
7622
|
const existingByPath = target.prepare(`SELECT * FROM projects WHERE path = ?`).get(path);
|
|
6885
7623
|
if (existingByPath) {
|
|
6886
7624
|
row["id"] = existingByPath["id"] ?? id;
|
|
6887
|
-
insertOrReplace(target,
|
|
7625
|
+
insertOrReplace(target, table2, columns, row);
|
|
6888
7626
|
stats.updated++;
|
|
6889
7627
|
continue;
|
|
6890
7628
|
}
|
|
@@ -6896,24 +7634,24 @@ function mergeProjects(target, source) {
|
|
|
6896
7634
|
row["id"] = `peer:${String(row["id"])}`;
|
|
6897
7635
|
}
|
|
6898
7636
|
}
|
|
6899
|
-
insertOrReplace(target,
|
|
7637
|
+
insertOrReplace(target, table2, columns, row);
|
|
6900
7638
|
stats.inserted++;
|
|
6901
7639
|
}
|
|
6902
7640
|
return stats;
|
|
6903
7641
|
}
|
|
6904
|
-
function mergeGenericTable(target, source,
|
|
6905
|
-
const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
6906
|
-
const columns = commonColumns(source, target,
|
|
6907
|
-
const keyColumns = primaryKeyColumns(target,
|
|
6908
|
-
const rows = selectRows(source,
|
|
7642
|
+
function mergeGenericTable(target, source, table2, sourceMachine, now) {
|
|
7643
|
+
const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
|
|
7644
|
+
const columns = commonColumns(source, target, table2);
|
|
7645
|
+
const keyColumns = primaryKeyColumns(target, table2).filter((c) => columns.includes(c));
|
|
7646
|
+
const rows = selectRows(source, table2, columns);
|
|
6909
7647
|
for (const raw of rows) {
|
|
6910
7648
|
const row = normalizeRow(raw, columns, sourceMachine, now);
|
|
6911
|
-
const existing = rowByKey(target,
|
|
7649
|
+
const existing = rowByKey(target, table2, keyColumns, row);
|
|
6912
7650
|
if (existing && !shouldReplace(row, existing)) {
|
|
6913
7651
|
stats.skipped++;
|
|
6914
7652
|
continue;
|
|
6915
7653
|
}
|
|
6916
|
-
insertOrReplace(target,
|
|
7654
|
+
insertOrReplace(target, table2, columns, row);
|
|
6917
7655
|
if (existing)
|
|
6918
7656
|
stats.updated++;
|
|
6919
7657
|
else
|
|
@@ -6925,12 +7663,12 @@ function detectSourceMachine(source, fallback) {
|
|
|
6925
7663
|
if (fallback && fallback.trim())
|
|
6926
7664
|
return fallback.trim();
|
|
6927
7665
|
const counts = new Map;
|
|
6928
|
-
for (const
|
|
6929
|
-
if (!tableExists(source,
|
|
7666
|
+
for (const table2 of ["sessions", "requests", "usage_snapshots"]) {
|
|
7667
|
+
if (!tableExists(source, table2))
|
|
6930
7668
|
continue;
|
|
6931
7669
|
const rows = source.prepare(`
|
|
6932
7670
|
SELECT machine_id, COUNT(*) as cnt
|
|
6933
|
-
FROM ${quoteIdent(
|
|
7671
|
+
FROM ${quoteIdent(table2)}
|
|
6934
7672
|
WHERE machine_id != '' AND machine_id IS NOT NULL
|
|
6935
7673
|
GROUP BY machine_id
|
|
6936
7674
|
`).all();
|
|
@@ -6987,8 +7725,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
|
|
|
6987
7725
|
const sessionMerge = mergeIdentityTable(target, source, "sessions", sourceMachine, now);
|
|
6988
7726
|
tables.push(sessionMerge.stats);
|
|
6989
7727
|
tables.push(mergeIdentityTable(target, source, "requests", sourceMachine, now, sessionMerge.idMap).stats);
|
|
6990
|
-
for (const
|
|
6991
|
-
tables.push(mergeGenericTable(target, source,
|
|
7728
|
+
for (const table2 of GENERIC_PEER_TABLES) {
|
|
7729
|
+
tables.push(mergeGenericTable(target, source, table2, sourceMachine, now));
|
|
6992
7730
|
}
|
|
6993
7731
|
ensureMachineRegistry(target, sourceMachine, now);
|
|
6994
7732
|
target.exec("COMMIT");
|
|
@@ -7002,8 +7740,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
|
|
|
7002
7740
|
source.close();
|
|
7003
7741
|
}
|
|
7004
7742
|
const deduped = dedupeRequests(target);
|
|
7005
|
-
const rowsWritten = tables.reduce((sum,
|
|
7006
|
-
const collisions = tables.reduce((sum,
|
|
7743
|
+
const rowsWritten = tables.reduce((sum, table2) => sum + table2.inserted + table2.updated, 0);
|
|
7744
|
+
const collisions = tables.reduce((sum, table2) => sum + table2.collisions, 0);
|
|
7007
7745
|
return {
|
|
7008
7746
|
source_path: sourcePath,
|
|
7009
7747
|
source_machine: sourceMachine,
|
|
@@ -7167,7 +7905,7 @@ function printAccountBreakdown(rows) {
|
|
|
7167
7905
|
fmt4(r.subscription_included_usd)
|
|
7168
7906
|
]));
|
|
7169
7907
|
}
|
|
7170
|
-
function
|
|
7908
|
+
function parseSinceDate2(since) {
|
|
7171
7909
|
const relMatch = since.match(/^(\d+)d$/);
|
|
7172
7910
|
if (relMatch) {
|
|
7173
7911
|
const days = parseInt(relMatch[1], 10);
|
|
@@ -7312,7 +8050,7 @@ program.command("sessions").description("List coding sessions with costs").optio
|
|
|
7312
8050
|
const agent = parseOptionalCliAgent(opts.agent);
|
|
7313
8051
|
await autoSync();
|
|
7314
8052
|
const db = openDatabase();
|
|
7315
|
-
const sinceDate = opts.since ?
|
|
8053
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7316
8054
|
let sessions = querySessions(db, {
|
|
7317
8055
|
agent,
|
|
7318
8056
|
project: opts.project,
|
|
@@ -7359,7 +8097,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
|
|
|
7359
8097
|
const count = parsePositiveCliInteger(opts.n ?? "10", "-n");
|
|
7360
8098
|
const agent = parseOptionalCliAgent(opts.agent);
|
|
7361
8099
|
const db = openDatabase();
|
|
7362
|
-
const sinceDate = opts.since ?
|
|
8100
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7363
8101
|
let sessions = queryTopSessions(db, count, agent);
|
|
7364
8102
|
if (sinceDate)
|
|
7365
8103
|
sessions = sessions.filter((s) => s.started_at >= sinceDate);
|
|
@@ -7380,7 +8118,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
|
|
|
7380
8118
|
});
|
|
7381
8119
|
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) => {
|
|
7382
8120
|
const db = openDatabase();
|
|
7383
|
-
const sinceDate = opts.since ?
|
|
8121
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7384
8122
|
console.log();
|
|
7385
8123
|
if (opts.by === "project") {
|
|
7386
8124
|
const rows = sinceDate ? db.prepare(`
|
|
@@ -7962,8 +8700,8 @@ program.command("merge-db <source-db>").description("Merge another Economy SQLit
|
|
|
7962
8700
|
console.log();
|
|
7963
8701
|
console.log(chalk7.bold.cyan(` Merged Economy DB \u2014 ${result.source_machine}`));
|
|
7964
8702
|
console.log(` Rows written: ${fmtCount(result.rows_written)} \xB7 collisions remapped: ${fmtCount(result.collisions)} \xB7 deduped: ${fmtCount(result.deduped)}`);
|
|
7965
|
-
for (const
|
|
7966
|
-
console.log(` ${chalk7.white(
|
|
8703
|
+
for (const table2 of result.tables) {
|
|
8704
|
+
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)}`);
|
|
7967
8705
|
}
|
|
7968
8706
|
console.log();
|
|
7969
8707
|
});
|
|
@@ -8342,6 +9080,7 @@ billingCmd.command("show").description("Show actual billing totals vs our estima
|
|
|
8342
9080
|
});
|
|
8343
9081
|
registerBrainsCommand(program);
|
|
8344
9082
|
registerTodosCommand(program);
|
|
9083
|
+
registerBriefCommand(program, { beforeRead: autoSync });
|
|
8345
9084
|
registerExtendedCommands(program);
|
|
8346
9085
|
registerFleetCommands(program);
|
|
8347
9086
|
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":"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 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.36",
|
|
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",
|