@hasna/economy 0.2.34 → 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 +777 -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,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 } : {},
|
|
@@ -6732,6 +6733,721 @@ 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) {
|
|
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
|
+
|
|
6735
7451
|
// src/cli/index.ts
|
|
6736
7452
|
init_agents();
|
|
6737
7453
|
init_sync_all();
|
|
@@ -6755,38 +7471,38 @@ var GENERIC_PEER_TABLES = [
|
|
|
6755
7471
|
function quoteIdent(identifier) {
|
|
6756
7472
|
return `"${identifier.replace(/"/g, '""')}"`;
|
|
6757
7473
|
}
|
|
6758
|
-
function tableExists(db,
|
|
6759
|
-
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);
|
|
6760
7476
|
return Boolean(row);
|
|
6761
7477
|
}
|
|
6762
|
-
function tableColumns(db,
|
|
6763
|
-
if (!tableExists(db,
|
|
7478
|
+
function tableColumns(db, table2) {
|
|
7479
|
+
if (!tableExists(db, table2))
|
|
6764
7480
|
return [];
|
|
6765
|
-
return db.prepare(`PRAGMA table_info(${quoteIdent(
|
|
7481
|
+
return db.prepare(`PRAGMA table_info(${quoteIdent(table2)})`).all();
|
|
6766
7482
|
}
|
|
6767
|
-
function commonColumns(source, target,
|
|
6768
|
-
const sourceCols = new Set(tableColumns(source,
|
|
6769
|
-
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));
|
|
6770
7486
|
}
|
|
6771
|
-
function primaryKeyColumns(db,
|
|
6772
|
-
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);
|
|
6773
7489
|
}
|
|
6774
|
-
function selectRows(source,
|
|
7490
|
+
function selectRows(source, table2, columns) {
|
|
6775
7491
|
if (columns.length === 0)
|
|
6776
7492
|
return [];
|
|
6777
7493
|
const select = columns.map(quoteIdent).join(", ");
|
|
6778
|
-
return source.prepare(`SELECT ${select} FROM ${quoteIdent(
|
|
7494
|
+
return source.prepare(`SELECT ${select} FROM ${quoteIdent(table2)}`).all();
|
|
6779
7495
|
}
|
|
6780
|
-
function rowByKey(target,
|
|
7496
|
+
function rowByKey(target, table2, keyColumns, row) {
|
|
6781
7497
|
if (keyColumns.length === 0)
|
|
6782
7498
|
return null;
|
|
6783
7499
|
if (keyColumns.some((c) => row[c] == null))
|
|
6784
7500
|
return null;
|
|
6785
7501
|
const where = keyColumns.map((c) => `${quoteIdent(c)} = ?`).join(" AND ");
|
|
6786
|
-
return target.prepare(`SELECT * FROM ${quoteIdent(
|
|
7502
|
+
return target.prepare(`SELECT * FROM ${quoteIdent(table2)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
|
|
6787
7503
|
}
|
|
6788
|
-
function hasId(target,
|
|
6789
|
-
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);
|
|
6790
7506
|
}
|
|
6791
7507
|
function shouldReplace(source, existing) {
|
|
6792
7508
|
if (!existing)
|
|
@@ -6812,30 +7528,30 @@ function normalizeRow(row, columns, sourceMachine, now) {
|
|
|
6812
7528
|
next["attribution_tag"] = "";
|
|
6813
7529
|
return next;
|
|
6814
7530
|
}
|
|
6815
|
-
function insertOrReplace(target,
|
|
7531
|
+
function insertOrReplace(target, table2, columns, row) {
|
|
6816
7532
|
const colSql = columns.map(quoteIdent).join(", ");
|
|
6817
7533
|
const placeholders = columns.map(() => "?").join(", ");
|
|
6818
7534
|
target.prepare(`
|
|
6819
|
-
INSERT OR REPLACE INTO ${quoteIdent(
|
|
7535
|
+
INSERT OR REPLACE INTO ${quoteIdent(table2)} (${colSql})
|
|
6820
7536
|
VALUES (${placeholders})
|
|
6821
7537
|
`).run(...columns.map((c) => row[c] ?? null));
|
|
6822
7538
|
}
|
|
6823
|
-
function collisionId(target,
|
|
7539
|
+
function collisionId(target, table2, machine, originalId) {
|
|
6824
7540
|
const base = `${machine || "peer"}:${originalId}`;
|
|
6825
|
-
const baseRow = hasId(target,
|
|
7541
|
+
const baseRow = hasId(target, table2, base);
|
|
6826
7542
|
if (!baseRow || String(baseRow["machine_id"] ?? "") === machine)
|
|
6827
7543
|
return base;
|
|
6828
7544
|
for (let i = 2;; i++) {
|
|
6829
7545
|
const candidate = `${base}:${i}`;
|
|
6830
|
-
const row = hasId(target,
|
|
7546
|
+
const row = hasId(target, table2, candidate);
|
|
6831
7547
|
if (!row || String(row["machine_id"] ?? "") === machine)
|
|
6832
7548
|
return candidate;
|
|
6833
7549
|
}
|
|
6834
7550
|
}
|
|
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,
|
|
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);
|
|
6839
7555
|
const idMap = new Map;
|
|
6840
7556
|
for (const raw of rows) {
|
|
6841
7557
|
const row = normalizeRow(raw, columns, sourceMachine, now);
|
|
@@ -6845,22 +7561,22 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
|
|
|
6845
7561
|
continue;
|
|
6846
7562
|
}
|
|
6847
7563
|
const machine = String(row["machine_id"] ?? "");
|
|
6848
|
-
const directExisting = hasId(target,
|
|
7564
|
+
const directExisting = hasId(target, table2, originalId);
|
|
6849
7565
|
if (directExisting && String(directExisting["machine_id"] ?? "") !== machine) {
|
|
6850
|
-
row["id"] = collisionId(target,
|
|
7566
|
+
row["id"] = collisionId(target, table2, machine, originalId);
|
|
6851
7567
|
stats.collisions++;
|
|
6852
7568
|
}
|
|
6853
|
-
if (
|
|
7569
|
+
if (table2 === "requests" && sessionIdMap) {
|
|
6854
7570
|
const originalSessionId = String(row["session_id"] ?? "");
|
|
6855
7571
|
row["session_id"] = sessionIdMap.get(originalSessionId) ?? originalSessionId;
|
|
6856
7572
|
}
|
|
6857
|
-
const existing = hasId(target,
|
|
7573
|
+
const existing = hasId(target, table2, String(row["id"]));
|
|
6858
7574
|
idMap.set(originalId, String(row["id"]));
|
|
6859
7575
|
if (existing && !shouldReplace(row, existing)) {
|
|
6860
7576
|
stats.skipped++;
|
|
6861
7577
|
continue;
|
|
6862
7578
|
}
|
|
6863
|
-
insertOrReplace(target,
|
|
7579
|
+
insertOrReplace(target, table2, columns, row);
|
|
6864
7580
|
if (existing)
|
|
6865
7581
|
stats.updated++;
|
|
6866
7582
|
else
|
|
@@ -6869,10 +7585,10 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
|
|
|
6869
7585
|
return { stats, idMap };
|
|
6870
7586
|
}
|
|
6871
7587
|
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,
|
|
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);
|
|
6876
7592
|
for (const raw of rows) {
|
|
6877
7593
|
const row = { ...raw };
|
|
6878
7594
|
const path = String(row["path"] ?? "");
|
|
@@ -6884,7 +7600,7 @@ function mergeProjects(target, source) {
|
|
|
6884
7600
|
const existingByPath = target.prepare(`SELECT * FROM projects WHERE path = ?`).get(path);
|
|
6885
7601
|
if (existingByPath) {
|
|
6886
7602
|
row["id"] = existingByPath["id"] ?? id;
|
|
6887
|
-
insertOrReplace(target,
|
|
7603
|
+
insertOrReplace(target, table2, columns, row);
|
|
6888
7604
|
stats.updated++;
|
|
6889
7605
|
continue;
|
|
6890
7606
|
}
|
|
@@ -6896,24 +7612,24 @@ function mergeProjects(target, source) {
|
|
|
6896
7612
|
row["id"] = `peer:${String(row["id"])}`;
|
|
6897
7613
|
}
|
|
6898
7614
|
}
|
|
6899
|
-
insertOrReplace(target,
|
|
7615
|
+
insertOrReplace(target, table2, columns, row);
|
|
6900
7616
|
stats.inserted++;
|
|
6901
7617
|
}
|
|
6902
7618
|
return stats;
|
|
6903
7619
|
}
|
|
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,
|
|
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);
|
|
6909
7625
|
for (const raw of rows) {
|
|
6910
7626
|
const row = normalizeRow(raw, columns, sourceMachine, now);
|
|
6911
|
-
const existing = rowByKey(target,
|
|
7627
|
+
const existing = rowByKey(target, table2, keyColumns, row);
|
|
6912
7628
|
if (existing && !shouldReplace(row, existing)) {
|
|
6913
7629
|
stats.skipped++;
|
|
6914
7630
|
continue;
|
|
6915
7631
|
}
|
|
6916
|
-
insertOrReplace(target,
|
|
7632
|
+
insertOrReplace(target, table2, columns, row);
|
|
6917
7633
|
if (existing)
|
|
6918
7634
|
stats.updated++;
|
|
6919
7635
|
else
|
|
@@ -6925,12 +7641,12 @@ function detectSourceMachine(source, fallback) {
|
|
|
6925
7641
|
if (fallback && fallback.trim())
|
|
6926
7642
|
return fallback.trim();
|
|
6927
7643
|
const counts = new Map;
|
|
6928
|
-
for (const
|
|
6929
|
-
if (!tableExists(source,
|
|
7644
|
+
for (const table2 of ["sessions", "requests", "usage_snapshots"]) {
|
|
7645
|
+
if (!tableExists(source, table2))
|
|
6930
7646
|
continue;
|
|
6931
7647
|
const rows = source.prepare(`
|
|
6932
7648
|
SELECT machine_id, COUNT(*) as cnt
|
|
6933
|
-
FROM ${quoteIdent(
|
|
7649
|
+
FROM ${quoteIdent(table2)}
|
|
6934
7650
|
WHERE machine_id != '' AND machine_id IS NOT NULL
|
|
6935
7651
|
GROUP BY machine_id
|
|
6936
7652
|
`).all();
|
|
@@ -6987,8 +7703,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
|
|
|
6987
7703
|
const sessionMerge = mergeIdentityTable(target, source, "sessions", sourceMachine, now);
|
|
6988
7704
|
tables.push(sessionMerge.stats);
|
|
6989
7705
|
tables.push(mergeIdentityTable(target, source, "requests", sourceMachine, now, sessionMerge.idMap).stats);
|
|
6990
|
-
for (const
|
|
6991
|
-
tables.push(mergeGenericTable(target, source,
|
|
7706
|
+
for (const table2 of GENERIC_PEER_TABLES) {
|
|
7707
|
+
tables.push(mergeGenericTable(target, source, table2, sourceMachine, now));
|
|
6992
7708
|
}
|
|
6993
7709
|
ensureMachineRegistry(target, sourceMachine, now);
|
|
6994
7710
|
target.exec("COMMIT");
|
|
@@ -7002,8 +7718,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
|
|
|
7002
7718
|
source.close();
|
|
7003
7719
|
}
|
|
7004
7720
|
const deduped = dedupeRequests(target);
|
|
7005
|
-
const rowsWritten = tables.reduce((sum,
|
|
7006
|
-
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);
|
|
7007
7723
|
return {
|
|
7008
7724
|
source_path: sourcePath,
|
|
7009
7725
|
source_machine: sourceMachine,
|
|
@@ -7167,7 +7883,7 @@ function printAccountBreakdown(rows) {
|
|
|
7167
7883
|
fmt4(r.subscription_included_usd)
|
|
7168
7884
|
]));
|
|
7169
7885
|
}
|
|
7170
|
-
function
|
|
7886
|
+
function parseSinceDate2(since) {
|
|
7171
7887
|
const relMatch = since.match(/^(\d+)d$/);
|
|
7172
7888
|
if (relMatch) {
|
|
7173
7889
|
const days = parseInt(relMatch[1], 10);
|
|
@@ -7312,7 +8028,7 @@ program.command("sessions").description("List coding sessions with costs").optio
|
|
|
7312
8028
|
const agent = parseOptionalCliAgent(opts.agent);
|
|
7313
8029
|
await autoSync();
|
|
7314
8030
|
const db = openDatabase();
|
|
7315
|
-
const sinceDate = opts.since ?
|
|
8031
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7316
8032
|
let sessions = querySessions(db, {
|
|
7317
8033
|
agent,
|
|
7318
8034
|
project: opts.project,
|
|
@@ -7359,7 +8075,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
|
|
|
7359
8075
|
const count = parsePositiveCliInteger(opts.n ?? "10", "-n");
|
|
7360
8076
|
const agent = parseOptionalCliAgent(opts.agent);
|
|
7361
8077
|
const db = openDatabase();
|
|
7362
|
-
const sinceDate = opts.since ?
|
|
8078
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7363
8079
|
let sessions = queryTopSessions(db, count, agent);
|
|
7364
8080
|
if (sinceDate)
|
|
7365
8081
|
sessions = sessions.filter((s) => s.started_at >= sinceDate);
|
|
@@ -7380,7 +8096,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
|
|
|
7380
8096
|
});
|
|
7381
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) => {
|
|
7382
8098
|
const db = openDatabase();
|
|
7383
|
-
const sinceDate = opts.since ?
|
|
8099
|
+
const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
|
|
7384
8100
|
console.log();
|
|
7385
8101
|
if (opts.by === "project") {
|
|
7386
8102
|
const rows = sinceDate ? db.prepare(`
|
|
@@ -7962,8 +8678,8 @@ program.command("merge-db <source-db>").description("Merge another Economy SQLit
|
|
|
7962
8678
|
console.log();
|
|
7963
8679
|
console.log(chalk7.bold.cyan(` Merged Economy DB \u2014 ${result.source_machine}`));
|
|
7964
8680
|
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(
|
|
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)}`);
|
|
7967
8683
|
}
|
|
7968
8684
|
console.log();
|
|
7969
8685
|
});
|
|
@@ -8342,6 +9058,7 @@ billingCmd.command("show").description("Show actual billing totals vs our estima
|
|
|
8342
9058
|
});
|
|
8343
9059
|
registerBrainsCommand(program);
|
|
8344
9060
|
registerTodosCommand(program);
|
|
9061
|
+
registerBriefCommand(program);
|
|
8345
9062
|
registerExtendedCommands(program);
|
|
8346
9063
|
registerFleetCommands(program);
|
|
8347
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":"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.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",
|