@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.
@@ -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 accountKey(tool, name, email) {
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: accountKey(profile.tool, profile.name, profile.email),
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: accountKey(tool, name, email),
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: accountKey(tool, name, email),
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, table) {
6759
- const row = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(table);
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, table) {
6763
- if (!tableExists(db, table))
7478
+ function tableColumns(db, table2) {
7479
+ if (!tableExists(db, table2))
6764
7480
  return [];
6765
- return db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all();
7481
+ return db.prepare(`PRAGMA table_info(${quoteIdent(table2)})`).all();
6766
7482
  }
6767
- function commonColumns(source, target, table) {
6768
- const sourceCols = new Set(tableColumns(source, table).map((c) => c.name));
6769
- return tableColumns(target, table).map((c) => c.name).filter((c) => sourceCols.has(c));
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, table) {
6772
- return tableColumns(db, table).filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
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, table, columns) {
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(table)}`).all();
7494
+ return source.prepare(`SELECT ${select} FROM ${quoteIdent(table2)}`).all();
6779
7495
  }
6780
- function rowByKey(target, table, keyColumns, row) {
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(table)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
7502
+ return target.prepare(`SELECT * FROM ${quoteIdent(table2)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
6787
7503
  }
6788
- function hasId(target, table, id) {
6789
- return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(table)} WHERE id = ?`).get(id);
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, table, columns, row) {
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(table)} (${colSql})
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, table, machine, originalId) {
7539
+ function collisionId(target, table2, machine, originalId) {
6824
7540
  const base = `${machine || "peer"}:${originalId}`;
6825
- const baseRow = hasId(target, table, base);
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, table, candidate);
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, table, sourceMachine, now, sessionIdMap) {
6836
- const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
6837
- const columns = commonColumns(source, target, table);
6838
- const rows = selectRows(source, table, columns);
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, table, originalId);
7564
+ const directExisting = hasId(target, table2, originalId);
6849
7565
  if (directExisting && String(directExisting["machine_id"] ?? "") !== machine) {
6850
- row["id"] = collisionId(target, table, machine, originalId);
7566
+ row["id"] = collisionId(target, table2, machine, originalId);
6851
7567
  stats.collisions++;
6852
7568
  }
6853
- if (table === "requests" && sessionIdMap) {
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, table, String(row["id"]));
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, table, columns, row);
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 table = "projects";
6873
- const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
6874
- const columns = commonColumns(source, target, table);
6875
- const rows = selectRows(source, table, columns);
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, table, columns, row);
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, table, columns, row);
7615
+ insertOrReplace(target, table2, columns, row);
6900
7616
  stats.inserted++;
6901
7617
  }
6902
7618
  return stats;
6903
7619
  }
6904
- function mergeGenericTable(target, source, table, sourceMachine, now) {
6905
- const stats = { table, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
6906
- const columns = commonColumns(source, target, table);
6907
- const keyColumns = primaryKeyColumns(target, table).filter((c) => columns.includes(c));
6908
- const rows = selectRows(source, table, columns);
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, table, keyColumns, row);
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, table, columns, row);
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 table of ["sessions", "requests", "usage_snapshots"]) {
6929
- if (!tableExists(source, table))
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(table)}
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 table of GENERIC_PEER_TABLES) {
6991
- tables.push(mergeGenericTable(target, source, table, sourceMachine, now));
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, table) => sum + table.inserted + table.updated, 0);
7006
- const collisions = tables.reduce((sum, table) => sum + table.collisions, 0);
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 parseSinceDate(since) {
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 ? parseSinceDate(opts.since) : undefined;
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 ? parseSinceDate(opts.since) : undefined;
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 ? parseSinceDate(opts.since) : undefined;
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 table of result.tables) {
7966
- console.log(` ${chalk7.white(table.table.padEnd(16))}` + ` inserted ${fmtCount(table.inserted).padStart(6)}` + ` updated ${fmtCount(table.updated).padStart(6)}` + ` skipped ${fmtCount(table.skipped).padStart(6)}` + ` collisions ${fmtCount(table.collisions).padStart(3)}`);
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,CA8FxD,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"}
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 },
@@ -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 },
@@ -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.34",
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",