@hasna/economy 0.2.34 → 0.2.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ import { Command } from 'commander';
2
+ import type { SqliteAdapter as Database } from '@hasna/cloud';
3
+ type BriefPeriod = 'today' | 'week';
4
+ export interface BriefTotals {
5
+ sessions: number;
6
+ requests: number;
7
+ input_tokens: number;
8
+ output_tokens: number;
9
+ cache_read_tokens: number;
10
+ cache_create_tokens: number;
11
+ cache_create_5m_tokens: number;
12
+ cache_create_1h_tokens: number;
13
+ total_tokens: number;
14
+ cost_usd: number;
15
+ last_active: string | null;
16
+ }
17
+ export interface BriefPeriodSummary extends BriefTotals {
18
+ label: string;
19
+ period: BriefPeriod | 'since';
20
+ since?: string;
21
+ }
22
+ export interface BriefMachineRow extends BriefTotals {
23
+ machine_id: string;
24
+ last_data_at: string | null;
25
+ last_data_age: string;
26
+ }
27
+ export interface BriefAgentRow extends BriefTotals {
28
+ agent: string;
29
+ }
30
+ export interface BriefAccountRow extends BriefTotals {
31
+ account_key: string;
32
+ account_tool: string;
33
+ account_name: string;
34
+ account_email: string | null;
35
+ account_source: string;
36
+ }
37
+ export interface BriefFreshnessMachine {
38
+ machine_id: string;
39
+ max_request_at: string | null;
40
+ request_age: string;
41
+ last_merge_sync_at: string | null;
42
+ merge_sync_age: string;
43
+ }
44
+ export interface EconomyBrief {
45
+ generated_at: string;
46
+ machine: string;
47
+ since: {
48
+ input: string;
49
+ timestamp: string;
50
+ label: string;
51
+ };
52
+ summaries: BriefPeriodSummary[];
53
+ machines: BriefMachineRow[];
54
+ agents: BriefAgentRow[];
55
+ accounts: BriefAccountRow[];
56
+ freshness: {
57
+ machines: BriefFreshnessMachine[];
58
+ max_request_line: string;
59
+ merge_sync_line: string;
60
+ };
61
+ }
62
+ interface BriefOptions {
63
+ since?: string;
64
+ machine?: string;
65
+ now?: Date;
66
+ currentMachineId?: string;
67
+ localSyncAt?: Date;
68
+ }
69
+ interface BriefCommandDeps {
70
+ beforeRead?: () => void | Promise<void>;
71
+ }
72
+ export declare function buildBrief(db: Database, opts?: BriefOptions): EconomyBrief;
73
+ export declare function renderBriefText(brief: EconomyBrief): string;
74
+ export declare function registerBriefCommand(program: Command, deps?: BriefCommandDeps): void;
75
+ export {};
76
+ //# sourceMappingURL=brief.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/brief.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAa7D,KAAK,WAAW,GAAG,OAAO,GAAG,MAAM,CAAA;AAEnC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,GAAG,OAAO,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;IACD,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,SAAS,EAAE;QACT,QAAQ,EAAE,qBAAqB,EAAE,CAAA;QACjC,gBAAgB,EAAE,MAAM,CAAA;QACxB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAcD,UAAU,YAAY;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,IAAI,CAAA;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,WAAW,CAAC,EAAE,IAAI,CAAA;CACnB;AAED,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxC;AAknBD,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,YAAiB,GAAG,YAAY,CAmD9E;AAyDD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAyE3D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAgCxF"}
package/dist/cli/index.js CHANGED
@@ -236,6 +236,7 @@ var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING,
236
236
  var init_pricing = __esm(() => {
237
237
  init_database();
238
238
  DEFAULT_PRICING = {
239
+ "claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
239
240
  "claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
240
241
  "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
241
242
  "claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
@@ -2437,7 +2438,7 @@ var init_paths = () => {};
2437
2438
  function normalizeEmail(email) {
2438
2439
  return (email ?? "").trim().toLowerCase();
2439
2440
  }
2440
- function 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,743 @@ function registerFleetCommands(program) {
6732
6733
  });
6733
6734
  }
6734
6735
 
6736
+ // src/cli/commands/brief.ts
6737
+ init_database();
6738
+ init_pricing();
6739
+ var ZERO_REQUEST_TOTALS = {
6740
+ sessions: 0,
6741
+ request_sessions: 0,
6742
+ requests: 0,
6743
+ input_tokens: 0,
6744
+ output_tokens: 0,
6745
+ cache_read_tokens: 0,
6746
+ cache_create_tokens: 0,
6747
+ cache_create_5m_tokens: 0,
6748
+ cache_create_1h_tokens: 0,
6749
+ total_tokens: 0,
6750
+ cost_usd: 0,
6751
+ last_active: null
6752
+ };
6753
+ var ZERO_SESSION_TOTALS = {
6754
+ sessions: 0,
6755
+ requests: 0,
6756
+ total_tokens: 0,
6757
+ cost_usd: 0,
6758
+ last_active: null
6759
+ };
6760
+ function machineFilter(machine) {
6761
+ if (!machine || machine === "all")
6762
+ return;
6763
+ return machine;
6764
+ }
6765
+ function requestPeriodWhere2(period) {
6766
+ if (period === "today")
6767
+ return `DATE(timestamp) = DATE('now')`;
6768
+ return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
6769
+ }
6770
+ function sessionPeriodWhere2(period) {
6771
+ if (period === "today")
6772
+ return `DATE(started_at) = DATE('now')`;
6773
+ return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
6774
+ }
6775
+ function baseRequestTotals(row) {
6776
+ return {
6777
+ ...ZERO_REQUEST_TOTALS,
6778
+ ...row,
6779
+ sessions: Number(row?.request_sessions ?? row?.sessions ?? 0),
6780
+ request_sessions: Number(row?.request_sessions ?? row?.sessions ?? 0),
6781
+ requests: Number(row?.requests ?? 0),
6782
+ input_tokens: Number(row?.input_tokens ?? 0),
6783
+ output_tokens: Number(row?.output_tokens ?? 0),
6784
+ cache_read_tokens: Number(row?.cache_read_tokens ?? 0),
6785
+ cache_create_tokens: Number(row?.cache_create_tokens ?? 0),
6786
+ cache_create_5m_tokens: Number(row?.cache_create_5m_tokens ?? 0),
6787
+ cache_create_1h_tokens: Number(row?.cache_create_1h_tokens ?? 0),
6788
+ total_tokens: Number(row?.total_tokens ?? 0),
6789
+ cost_usd: Number(row?.cost_usd ?? 0),
6790
+ last_active: row?.last_active ?? null
6791
+ };
6792
+ }
6793
+ function baseSessionTotals(row) {
6794
+ return {
6795
+ sessions: Number(row?.sessions ?? 0),
6796
+ requests: Number(row?.requests ?? 0),
6797
+ total_tokens: Number(row?.total_tokens ?? 0),
6798
+ cost_usd: Number(row?.cost_usd ?? 0),
6799
+ last_active: row?.last_active ?? null
6800
+ };
6801
+ }
6802
+ function latestTimestamp(...values) {
6803
+ return values.filter(Boolean).sort().at(-1) ?? null;
6804
+ }
6805
+ function combineTotals(requests, sessions) {
6806
+ return {
6807
+ sessions: requests.request_sessions + sessions.sessions,
6808
+ requests: requests.requests + sessions.requests,
6809
+ input_tokens: requests.input_tokens,
6810
+ output_tokens: requests.output_tokens,
6811
+ cache_read_tokens: requests.cache_read_tokens,
6812
+ cache_create_tokens: requests.cache_create_tokens,
6813
+ cache_create_5m_tokens: requests.cache_create_5m_tokens,
6814
+ cache_create_1h_tokens: requests.cache_create_1h_tokens,
6815
+ total_tokens: requests.total_tokens + sessions.total_tokens,
6816
+ cost_usd: requests.cost_usd + sessions.cost_usd,
6817
+ last_active: latestTimestamp(requests.last_active, sessions.last_active)
6818
+ };
6819
+ }
6820
+ function emptyTotals() {
6821
+ return combineTotals(ZERO_REQUEST_TOTALS, ZERO_SESSION_TOTALS);
6822
+ }
6823
+ function requestTotalsSql(where, machine) {
6824
+ const params = [];
6825
+ let machineClause = "";
6826
+ if (machine) {
6827
+ machineClause = " AND machine_id = ?";
6828
+ params.push(machine);
6829
+ }
6830
+ return {
6831
+ sql: `
6832
+ SELECT
6833
+ COUNT(DISTINCT session_id) as request_sessions,
6834
+ COUNT(*) as requests,
6835
+ COALESCE(SUM(input_tokens), 0) as input_tokens,
6836
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
6837
+ COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
6838
+ COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
6839
+ COALESCE(SUM(COALESCE(cache_create_5m_tokens, cache_create_tokens)), 0) as cache_create_5m_tokens,
6840
+ COALESCE(SUM(COALESCE(cache_create_1h_tokens, 0)), 0) as cache_create_1h_tokens,
6841
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
6842
+ COALESCE(SUM(cost_usd), 0) as cost_usd,
6843
+ MAX(timestamp) as last_active
6844
+ FROM requests
6845
+ WHERE ${where}${machineClause}
6846
+ `,
6847
+ params
6848
+ };
6849
+ }
6850
+ function sessionOnlyTotalsSql(where, machine) {
6851
+ const params = [];
6852
+ let machineClause = "";
6853
+ if (machine) {
6854
+ machineClause = " AND machine_id = ?";
6855
+ params.push(machine);
6856
+ }
6857
+ return {
6858
+ sql: `
6859
+ SELECT
6860
+ COUNT(*) as sessions,
6861
+ COALESCE(SUM(request_count), 0) as requests,
6862
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
6863
+ COALESCE(SUM(total_cost_usd), 0) as cost_usd,
6864
+ MAX(started_at) as last_active
6865
+ FROM sessions
6866
+ WHERE ${where}${machineClause}
6867
+ AND id NOT IN (SELECT DISTINCT session_id FROM requests)
6868
+ `,
6869
+ params
6870
+ };
6871
+ }
6872
+ function queryRequestTotals(db, where, params, machine) {
6873
+ const q = requestTotalsSql(where, machine);
6874
+ return baseRequestTotals(db.prepare(q.sql).get(...params, ...q.params));
6875
+ }
6876
+ function querySessionOnlyTotals(db, where, params, machine) {
6877
+ const q = sessionOnlyTotalsSql(where, machine);
6878
+ return baseSessionTotals(db.prepare(q.sql).get(...params, ...q.params));
6879
+ }
6880
+ function queryPeriodTotals(db, period, machine) {
6881
+ const summary = querySummary(db, period, machine);
6882
+ const requests = queryRequestTotals(db, requestPeriodWhere2(period), [], machine);
6883
+ const sessions = querySessionOnlyTotals(db, sessionPeriodWhere2(period), [], machine);
6884
+ const totals = combineTotals(requests, sessions);
6885
+ return {
6886
+ ...totals,
6887
+ sessions: summary.sessions,
6888
+ requests: summary.requests,
6889
+ total_tokens: summary.tokens,
6890
+ cost_usd: summary.total_usd
6891
+ };
6892
+ }
6893
+ function querySinceTotals(db, since, machine) {
6894
+ const requests = queryRequestTotals(db, "timestamp >= ?", [since], machine);
6895
+ const sessions = querySessionOnlyTotals(db, "started_at >= ?", [since], machine);
6896
+ return combineTotals(requests, sessions);
6897
+ }
6898
+ function parseSinceDate(input, now) {
6899
+ const value = input.trim();
6900
+ const match = value.match(/^(\d+(?:\.\d+)?)(m|h|d|w)$/i);
6901
+ if (match) {
6902
+ const amount = Number(match[1]);
6903
+ const unit = match[2].toLowerCase();
6904
+ const multiplier = unit === "m" ? 60000 : unit === "h" ? 60 * 60000 : unit === "d" ? 24 * 60 * 60000 : 7 * 24 * 60 * 60000;
6905
+ return {
6906
+ timestamp: new Date(now.getTime() - amount * multiplier).toISOString(),
6907
+ label: value
6908
+ };
6909
+ }
6910
+ const parsed = new Date(value);
6911
+ if (Number.isNaN(parsed.getTime())) {
6912
+ throw new Error("--since must be a duration like 24h, 7d, 30m, 2w, or an ISO date");
6913
+ }
6914
+ return { timestamp: parsed.toISOString(), label: value };
6915
+ }
6916
+ function queryMachineSinceRows(db, since, machine) {
6917
+ const knownMachines = new Set;
6918
+ for (const m of listMachines(db, "all")) {
6919
+ if (m.machine_id)
6920
+ knownMachines.add(m.machine_id);
6921
+ }
6922
+ for (const m of listMachineRegistry(db)) {
6923
+ if (m.machine_id)
6924
+ knownMachines.add(m.machine_id);
6925
+ }
6926
+ if (machine)
6927
+ knownMachines.add(machine);
6928
+ const requestMachineClause = machine ? " AND machine_id = ?" : "";
6929
+ const requestRows = db.prepare(`
6930
+ SELECT
6931
+ machine_id,
6932
+ COUNT(DISTINCT session_id) as request_sessions,
6933
+ COUNT(*) as requests,
6934
+ COALESCE(SUM(input_tokens), 0) as input_tokens,
6935
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
6936
+ COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
6937
+ COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
6938
+ COALESCE(SUM(COALESCE(cache_create_5m_tokens, cache_create_tokens)), 0) as cache_create_5m_tokens,
6939
+ COALESCE(SUM(COALESCE(cache_create_1h_tokens, 0)), 0) as cache_create_1h_tokens,
6940
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
6941
+ COALESCE(SUM(cost_usd), 0) as cost_usd,
6942
+ MAX(timestamp) as last_active
6943
+ FROM requests
6944
+ WHERE timestamp >= ?
6945
+ AND machine_id != ''
6946
+ ${requestMachineClause}
6947
+ GROUP BY machine_id
6948
+ `).all(...machine ? [since, machine] : [since]);
6949
+ const sessionMachineClause = machine ? " AND machine_id = ?" : "";
6950
+ const sessionRows = db.prepare(`
6951
+ SELECT
6952
+ machine_id,
6953
+ COUNT(*) as sessions,
6954
+ COALESCE(SUM(request_count), 0) as requests,
6955
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
6956
+ COALESCE(SUM(total_cost_usd), 0) as cost_usd,
6957
+ MAX(started_at) as last_active
6958
+ FROM sessions
6959
+ WHERE started_at >= ?
6960
+ AND machine_id != ''
6961
+ ${sessionMachineClause}
6962
+ AND id NOT IN (SELECT DISTINCT session_id FROM requests)
6963
+ GROUP BY machine_id
6964
+ `).all(...machine ? [since, machine] : [since]);
6965
+ const groups = new Map;
6966
+ for (const row of requestRows) {
6967
+ knownMachines.add(row.machine_id);
6968
+ groups.set(row.machine_id, combineTotals(baseRequestTotals(row), ZERO_SESSION_TOTALS));
6969
+ }
6970
+ for (const row of sessionRows) {
6971
+ knownMachines.add(row.machine_id);
6972
+ const current = groups.get(row.machine_id) ?? emptyTotals();
6973
+ const sessions = baseSessionTotals(row);
6974
+ groups.set(row.machine_id, {
6975
+ ...current,
6976
+ sessions: current.sessions + sessions.sessions,
6977
+ requests: current.requests + sessions.requests,
6978
+ total_tokens: current.total_tokens + sessions.total_tokens,
6979
+ cost_usd: current.cost_usd + sessions.cost_usd,
6980
+ last_active: latestTimestamp(current.last_active, sessions.last_active)
6981
+ });
6982
+ }
6983
+ const lastData = queryMachineLastData(db, machine);
6984
+ const rows = [...knownMachines].filter((m) => !machine || m === machine).map((machineId) => {
6985
+ const totals = groups.get(machineId) ?? emptyTotals();
6986
+ const lastDataAt = lastData.get(machineId) ?? totals.last_active;
6987
+ return {
6988
+ machine_id: machineId,
6989
+ ...totals,
6990
+ last_data_at: lastDataAt,
6991
+ last_data_age: ""
6992
+ };
6993
+ });
6994
+ return rows.sort((a, b) => b.cost_usd - a.cost_usd || a.machine_id.localeCompare(b.machine_id));
6995
+ }
6996
+ function queryMachineLastData(db, machine) {
6997
+ const filter = machine ? " AND machine_id = ?" : "";
6998
+ const requestRows = db.prepare(`
6999
+ SELECT machine_id, MAX(timestamp) as last_at
7000
+ FROM requests
7001
+ WHERE machine_id != ''${filter}
7002
+ GROUP BY machine_id
7003
+ `).all(...machine ? [machine] : []);
7004
+ const sessionRows = db.prepare(`
7005
+ SELECT machine_id, MAX(started_at) as last_at
7006
+ FROM sessions
7007
+ WHERE machine_id != ''${filter}
7008
+ GROUP BY machine_id
7009
+ `).all(...machine ? [machine] : []);
7010
+ const result = new Map;
7011
+ for (const row of [...requestRows, ...sessionRows]) {
7012
+ result.set(row.machine_id, latestTimestamp(result.get(row.machine_id), row.last_at));
7013
+ }
7014
+ return result;
7015
+ }
7016
+ function queryAgentSinceRows(db, since, machine) {
7017
+ const requestMachineClause = machine ? " AND machine_id = ?" : "";
7018
+ const sessionMachineClause = machine ? " AND machine_id = ?" : "";
7019
+ const rows = db.prepare(`
7020
+ WITH request_rows AS (
7021
+ SELECT
7022
+ agent,
7023
+ session_id,
7024
+ 1 as requests,
7025
+ input_tokens,
7026
+ output_tokens,
7027
+ cache_read_tokens,
7028
+ cache_create_tokens,
7029
+ COALESCE(cache_create_5m_tokens, cache_create_tokens) as cache_create_5m_tokens,
7030
+ COALESCE(cache_create_1h_tokens, 0) as cache_create_1h_tokens,
7031
+ input_tokens + output_tokens + cache_read_tokens + cache_create_tokens as total_tokens,
7032
+ cost_usd,
7033
+ timestamp as last_active
7034
+ FROM requests
7035
+ WHERE timestamp >= ?${requestMachineClause}
7036
+ ),
7037
+ session_only_rows AS (
7038
+ SELECT
7039
+ agent,
7040
+ id as session_id,
7041
+ COALESCE(request_count, 0) as requests,
7042
+ 0 as input_tokens,
7043
+ 0 as output_tokens,
7044
+ 0 as cache_read_tokens,
7045
+ 0 as cache_create_tokens,
7046
+ 0 as cache_create_5m_tokens,
7047
+ 0 as cache_create_1h_tokens,
7048
+ COALESCE(total_tokens, 0) as total_tokens,
7049
+ COALESCE(total_cost_usd, 0) as cost_usd,
7050
+ started_at as last_active
7051
+ FROM sessions
7052
+ WHERE started_at >= ?${sessionMachineClause}
7053
+ AND id NOT IN (SELECT DISTINCT session_id FROM requests)
7054
+ ),
7055
+ combined AS (
7056
+ SELECT * FROM request_rows
7057
+ UNION ALL
7058
+ SELECT * FROM session_only_rows
7059
+ )
7060
+ SELECT
7061
+ agent,
7062
+ COUNT(DISTINCT session_id) as sessions,
7063
+ COALESCE(SUM(requests), 0) as requests,
7064
+ COALESCE(SUM(input_tokens), 0) as input_tokens,
7065
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
7066
+ COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
7067
+ COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
7068
+ COALESCE(SUM(cache_create_5m_tokens), 0) as cache_create_5m_tokens,
7069
+ COALESCE(SUM(cache_create_1h_tokens), 0) as cache_create_1h_tokens,
7070
+ COALESCE(SUM(total_tokens), 0) as total_tokens,
7071
+ COALESCE(SUM(cost_usd), 0) as cost_usd,
7072
+ MAX(last_active) as last_active
7073
+ FROM combined
7074
+ GROUP BY agent
7075
+ ORDER BY cost_usd DESC
7076
+ `).all(...machine ? [since, machine, since, machine] : [since, since]);
7077
+ if (rows.length > 0)
7078
+ return rows;
7079
+ return queryAgentBreakdown(db, "all", machine).filter((row) => row.last_active >= since).map(agentBreakdownToBriefRow);
7080
+ }
7081
+ function agentBreakdownToBriefRow(row) {
7082
+ return {
7083
+ agent: row.agent,
7084
+ sessions: row.sessions,
7085
+ requests: row.requests,
7086
+ input_tokens: 0,
7087
+ output_tokens: 0,
7088
+ cache_read_tokens: 0,
7089
+ cache_create_tokens: 0,
7090
+ cache_create_5m_tokens: 0,
7091
+ cache_create_1h_tokens: 0,
7092
+ total_tokens: row.total_tokens,
7093
+ cost_usd: row.cost_usd,
7094
+ last_active: row.last_active
7095
+ };
7096
+ }
7097
+ function normalizeAccountEmail2(email) {
7098
+ return email.trim().toLowerCase();
7099
+ }
7100
+ function accountKey(row) {
7101
+ const agent = row.agent || row.account_tool || "unknown";
7102
+ const email = normalizeAccountEmail2(row.account_email);
7103
+ if (email)
7104
+ return `${agent}:${email}`;
7105
+ if (row.account_name)
7106
+ return `${agent}:${row.account_name}`;
7107
+ return row.account_key || `${agent}:unknown`;
7108
+ }
7109
+ function addAccountRow(groups, row) {
7110
+ if (!row.account_key && !row.account_tool && !row.account_name && !row.account_email)
7111
+ return;
7112
+ const key = accountKey(row);
7113
+ const email = normalizeAccountEmail2(row.account_email);
7114
+ const current = groups.get(key) ?? {
7115
+ account_key: key,
7116
+ account_tool: row.agent || row.account_tool || "unknown",
7117
+ account_name: row.account_name || email || row.account_key || "unknown",
7118
+ account_email: email || null,
7119
+ account_source: row.account_source || "unknown",
7120
+ sessionIds: new Set,
7121
+ requests: 0,
7122
+ input_tokens: 0,
7123
+ output_tokens: 0,
7124
+ cache_read_tokens: 0,
7125
+ cache_create_tokens: 0,
7126
+ cache_create_5m_tokens: 0,
7127
+ cache_create_1h_tokens: 0,
7128
+ total_tokens: 0,
7129
+ cost_usd: 0,
7130
+ last_active: null
7131
+ };
7132
+ if (row.session_id)
7133
+ current.sessionIds.add(row.session_id);
7134
+ if (!current.account_email && email)
7135
+ current.account_email = email;
7136
+ if ((!current.account_name || current.account_name === "unknown") && (row.account_name || email)) {
7137
+ current.account_name = row.account_name || email;
7138
+ }
7139
+ if ((!current.account_source || current.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
7140
+ current.account_source = row.account_source;
7141
+ }
7142
+ current.requests += Number(row.requests ?? 0);
7143
+ current.input_tokens += Number(row.input_tokens ?? 0);
7144
+ current.output_tokens += Number(row.output_tokens ?? 0);
7145
+ current.cache_read_tokens += Number(row.cache_read_tokens ?? 0);
7146
+ current.cache_create_tokens += Number(row.cache_create_tokens ?? 0);
7147
+ current.cache_create_5m_tokens += Number(row.cache_create_5m_tokens ?? 0);
7148
+ current.cache_create_1h_tokens += Number(row.cache_create_1h_tokens ?? 0);
7149
+ current.total_tokens += Number(row.total_tokens ?? 0);
7150
+ current.cost_usd += Number(row.cost_usd ?? 0);
7151
+ current.last_active = latestTimestamp(current.last_active, row.last_active);
7152
+ groups.set(key, current);
7153
+ }
7154
+ function queryAccountSinceRows(db, since, machine) {
7155
+ const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
7156
+ const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
7157
+ const requestRows = db.prepare(`
7158
+ SELECT
7159
+ r.session_id as session_id,
7160
+ COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
7161
+ COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
7162
+ COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
7163
+ COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
7164
+ COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
7165
+ COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
7166
+ 1 as requests,
7167
+ COALESCE(r.input_tokens, 0) as input_tokens,
7168
+ COALESCE(r.output_tokens, 0) as output_tokens,
7169
+ COALESCE(r.cache_read_tokens, 0) as cache_read_tokens,
7170
+ COALESCE(r.cache_create_tokens, 0) as cache_create_tokens,
7171
+ COALESCE(r.cache_create_5m_tokens, r.cache_create_tokens, 0) as cache_create_5m_tokens,
7172
+ COALESCE(r.cache_create_1h_tokens, 0) as cache_create_1h_tokens,
7173
+ COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
7174
+ COALESCE(r.cost_usd, 0) as cost_usd,
7175
+ r.timestamp as last_active
7176
+ FROM requests r
7177
+ LEFT JOIN sessions s ON s.id = r.session_id
7178
+ WHERE r.timestamp >= ?${requestMachineClause}
7179
+ `).all(...machine ? [since, machine] : [since]);
7180
+ const sessionRows = db.prepare(`
7181
+ SELECT
7182
+ s.id as session_id,
7183
+ s.agent as agent,
7184
+ s.account_key as account_key,
7185
+ s.account_tool as account_tool,
7186
+ s.account_name as account_name,
7187
+ COALESCE(s.account_email, '') as account_email,
7188
+ COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
7189
+ COALESCE(s.request_count, 0) as requests,
7190
+ 0 as input_tokens,
7191
+ 0 as output_tokens,
7192
+ 0 as cache_read_tokens,
7193
+ 0 as cache_create_tokens,
7194
+ 0 as cache_create_5m_tokens,
7195
+ 0 as cache_create_1h_tokens,
7196
+ COALESCE(s.total_tokens, 0) as total_tokens,
7197
+ COALESCE(s.total_cost_usd, 0) as cost_usd,
7198
+ s.started_at as last_active
7199
+ FROM sessions s
7200
+ WHERE s.started_at >= ?${sessionMachineClause}
7201
+ AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
7202
+ `).all(...machine ? [since, machine] : [since]);
7203
+ const groups = new Map;
7204
+ for (const row of requestRows)
7205
+ addAccountRow(groups, row);
7206
+ for (const row of sessionRows)
7207
+ addAccountRow(groups, row);
7208
+ const rows = [...groups.values()].map(({ sessionIds, ...row }) => ({
7209
+ ...row,
7210
+ sessions: sessionIds.size
7211
+ }));
7212
+ rows.sort((a, b) => b.cost_usd - a.cost_usd || a.account_key.localeCompare(b.account_key));
7213
+ if (rows.length > 0)
7214
+ return rows;
7215
+ return queryAccountBreakdown(db, "all", machine).filter((row) => row.last_active >= since).map(accountBreakdownToBriefRow);
7216
+ }
7217
+ function accountBreakdownToBriefRow(row) {
7218
+ return {
7219
+ account_key: row.account_key,
7220
+ account_tool: row.account_tool,
7221
+ account_name: row.account_name,
7222
+ account_email: row.account_email,
7223
+ account_source: row.account_source,
7224
+ sessions: row.sessions,
7225
+ requests: row.requests,
7226
+ input_tokens: 0,
7227
+ output_tokens: 0,
7228
+ cache_read_tokens: 0,
7229
+ cache_create_tokens: 0,
7230
+ cache_create_5m_tokens: 0,
7231
+ cache_create_1h_tokens: 0,
7232
+ total_tokens: row.total_tokens,
7233
+ cost_usd: row.cost_usd,
7234
+ last_active: row.last_active
7235
+ };
7236
+ }
7237
+ function queryFreshness(db, now, machine, localSync) {
7238
+ const requestFilter = machine ? " AND machine_id = ?" : "";
7239
+ const requestRows = db.prepare(`
7240
+ SELECT machine_id, MAX(timestamp) as max_request_at
7241
+ FROM requests
7242
+ WHERE machine_id != ''${requestFilter}
7243
+ GROUP BY machine_id
7244
+ `).all(...machine ? [machine] : []);
7245
+ const registryRows = listMachineRegistry(db).filter((row) => !machine || row.machine_id === machine);
7246
+ const machines = new Map;
7247
+ for (const row of requestRows) {
7248
+ machines.set(row.machine_id, {
7249
+ machine_id: row.machine_id,
7250
+ max_request_at: row.max_request_at,
7251
+ request_age: formatAge(row.max_request_at, now),
7252
+ last_merge_sync_at: null,
7253
+ merge_sync_age: "never"
7254
+ });
7255
+ }
7256
+ for (const row of registryRows) {
7257
+ const current = machines.get(row.machine_id) ?? {
7258
+ machine_id: row.machine_id,
7259
+ max_request_at: null,
7260
+ request_age: "never",
7261
+ last_merge_sync_at: null,
7262
+ merge_sync_age: "never"
7263
+ };
7264
+ const syncAt = latestTimestamp(row.last_pull_at, row.last_push_at, row.updated_at);
7265
+ current.last_merge_sync_at = syncAt;
7266
+ current.merge_sync_age = formatAge(syncAt, now);
7267
+ machines.set(row.machine_id, current);
7268
+ }
7269
+ if (localSync && (!machine || machine === localSync.machineId)) {
7270
+ const current = machines.get(localSync.machineId) ?? {
7271
+ machine_id: localSync.machineId,
7272
+ max_request_at: null,
7273
+ request_age: "never",
7274
+ last_merge_sync_at: null,
7275
+ merge_sync_age: "never"
7276
+ };
7277
+ current.last_merge_sync_at = latestTimestamp(current.last_merge_sync_at, localSync.syncedAt);
7278
+ current.merge_sync_age = formatAge(current.last_merge_sync_at, now);
7279
+ machines.set(localSync.machineId, current);
7280
+ }
7281
+ const rows = [...machines.values()].sort((a, b) => a.machine_id.localeCompare(b.machine_id));
7282
+ return {
7283
+ machines: rows,
7284
+ max_request_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.max_request_at ?? "never"} (${row.request_age})`).join("; ") : "none",
7285
+ merge_sync_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.last_merge_sync_at ?? "never"} (${row.merge_sync_age})`).join("; ") : "none"
7286
+ };
7287
+ }
7288
+ function buildBrief(db, opts = {}) {
7289
+ const now = opts.now ?? new Date;
7290
+ const machine = machineFilter(opts.machine);
7291
+ const sinceInput = opts.since ?? "24h";
7292
+ const since = parseSinceDate(sinceInput, now);
7293
+ const summaries = [
7294
+ {
7295
+ label: "Today",
7296
+ period: "today",
7297
+ ...queryPeriodTotals(db, "today", machine)
7298
+ },
7299
+ {
7300
+ label: "Week",
7301
+ period: "week",
7302
+ ...queryPeriodTotals(db, "week", machine)
7303
+ },
7304
+ {
7305
+ label: `Since ${since.label}`,
7306
+ period: "since",
7307
+ since: since.timestamp,
7308
+ ...querySinceTotals(db, since.timestamp, machine)
7309
+ }
7310
+ ];
7311
+ const machines = queryMachineSinceRows(db, since.timestamp, machine);
7312
+ for (const row of machines) {
7313
+ row.last_data_age = formatAge(row.last_data_at, now);
7314
+ }
7315
+ return {
7316
+ generated_at: now.toISOString(),
7317
+ machine: machine ?? "all",
7318
+ since: {
7319
+ input: sinceInput,
7320
+ timestamp: since.timestamp,
7321
+ label: since.label
7322
+ },
7323
+ summaries,
7324
+ machines,
7325
+ agents: queryAgentSinceRows(db, since.timestamp, machine),
7326
+ accounts: queryAccountSinceRows(db, since.timestamp, machine),
7327
+ freshness: queryFreshness(db, now, machine, opts.currentMachineId && opts.localSyncAt ? { machineId: opts.currentMachineId, syncedAt: opts.localSyncAt.toISOString() } : undefined)
7328
+ };
7329
+ }
7330
+ function formatUsd(usd) {
7331
+ if (usd >= 0.01)
7332
+ return "$" + usd.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
7333
+ if (usd >= 0.0001)
7334
+ return `${(usd * 100).toFixed(2).replace(/\.?0+$/, "")}c`;
7335
+ if (usd > 0)
7336
+ return "<0.01c";
7337
+ return "$0.00";
7338
+ }
7339
+ function formatCount(n) {
7340
+ return Math.round(n).toLocaleString("en-US");
7341
+ }
7342
+ function formatTokens(n) {
7343
+ if (n >= 1e9)
7344
+ return `${(n / 1e9).toFixed(1)}B`;
7345
+ if (n >= 1e6)
7346
+ return `${(n / 1e6).toFixed(1)}M`;
7347
+ if (n >= 1000)
7348
+ return `${(n / 1000).toFixed(1)}k`;
7349
+ return formatCount(n);
7350
+ }
7351
+ function formatAge(timestamp, now) {
7352
+ if (!timestamp)
7353
+ return "never";
7354
+ const t = new Date(timestamp).getTime();
7355
+ if (!Number.isFinite(t))
7356
+ return "unknown";
7357
+ const ageMs = Math.max(0, now.getTime() - t);
7358
+ const minutes = Math.floor(ageMs / 60000);
7359
+ if (minutes < 1)
7360
+ return "<1m";
7361
+ if (minutes < 60)
7362
+ return `${minutes}m`;
7363
+ const hours = Math.floor(minutes / 60);
7364
+ if (hours < 48)
7365
+ return `${hours}h`;
7366
+ return `${Math.floor(hours / 24)}d`;
7367
+ }
7368
+ function accountLabel(row) {
7369
+ return row.account_email || row.account_name || row.account_key || "unknown";
7370
+ }
7371
+ function cacheLabel(row) {
7372
+ const total = row.cache_read_tokens + row.cache_create_tokens;
7373
+ const split = row.cache_create_5m_tokens || row.cache_create_1h_tokens ? `; 5m ${formatTokens(row.cache_create_5m_tokens)} / 1h ${formatTokens(row.cache_create_1h_tokens)}` : "";
7374
+ return `${formatTokens(total)} (r ${formatTokens(row.cache_read_tokens)} / w ${formatTokens(row.cache_create_tokens)}${split})`;
7375
+ }
7376
+ function table(headers, rows) {
7377
+ if (rows.length === 0)
7378
+ return ["(none)"];
7379
+ const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
7380
+ const lines = [];
7381
+ lines.push(headers.map((header, index) => header.padEnd(widths[index])).join(" "));
7382
+ lines.push(widths.map((width) => "-".repeat(width)).join(" "));
7383
+ for (const row of rows) {
7384
+ lines.push(row.map((cell, index) => cell.padEnd(widths[index])).join(" "));
7385
+ }
7386
+ return lines;
7387
+ }
7388
+ function renderBriefText(brief) {
7389
+ const lines = [];
7390
+ lines.push(`Economy Brief - ${brief.machine === "all" ? "fleet" : brief.machine}`);
7391
+ lines.push(`Generated: ${brief.generated_at}`);
7392
+ lines.push(`Since: ${brief.since.label} (${brief.since.timestamp})`);
7393
+ lines.push("");
7394
+ lines.push("SUMMARY");
7395
+ lines.push(...table(["Period", "Sessions", "Requests", "Input", "Output", "Cache", "Tokens", "Cost"], brief.summaries.map((row) => [
7396
+ row.label,
7397
+ formatCount(row.sessions),
7398
+ formatCount(row.requests),
7399
+ formatTokens(row.input_tokens),
7400
+ formatTokens(row.output_tokens),
7401
+ cacheLabel(row),
7402
+ formatTokens(row.total_tokens),
7403
+ formatUsd(row.cost_usd)
7404
+ ])));
7405
+ lines.push("");
7406
+ lines.push(`PER-MACHINE - since ${brief.since.label}`);
7407
+ lines.push(...table(["Machine", "Sessions", "Tokens", "Cache", "Cost", "Last Data Age"], brief.machines.map((row) => [
7408
+ row.machine_id,
7409
+ formatCount(row.sessions),
7410
+ formatTokens(row.total_tokens),
7411
+ cacheLabel(row),
7412
+ formatUsd(row.cost_usd),
7413
+ row.last_data_age
7414
+ ])));
7415
+ lines.push("");
7416
+ lines.push(`PER-AGENT - since ${brief.since.label}`);
7417
+ lines.push(...table(["Agent", "Sessions", "Requests", "Tokens", "Cache", "Cost", "Last Active"], brief.agents.map((row) => [
7418
+ row.agent,
7419
+ formatCount(row.sessions),
7420
+ formatCount(row.requests),
7421
+ formatTokens(row.total_tokens),
7422
+ cacheLabel(row),
7423
+ formatUsd(row.cost_usd),
7424
+ row.last_active?.substring(0, 19) ?? "-"
7425
+ ])));
7426
+ lines.push("");
7427
+ lines.push(`PER-ACCOUNT - since ${brief.since.label}`);
7428
+ lines.push(...table(["Account", "Agent", "Sessions", "Requests", "Tokens", "Cost", "Last Active"], brief.accounts.map((row) => [
7429
+ accountLabel(row),
7430
+ row.account_tool,
7431
+ formatCount(row.sessions),
7432
+ formatCount(row.requests),
7433
+ formatTokens(row.total_tokens),
7434
+ formatUsd(row.cost_usd),
7435
+ row.last_active?.substring(0, 19) ?? "-"
7436
+ ])));
7437
+ lines.push("");
7438
+ lines.push("FRESHNESS");
7439
+ lines.push(`Max request: ${brief.freshness.max_request_line}`);
7440
+ lines.push(`Merge/sync: ${brief.freshness.merge_sync_line}`);
7441
+ lines.push("");
7442
+ return lines.join(`
7443
+ `);
7444
+ }
7445
+ function registerBriefCommand(program, deps = {}) {
7446
+ program.command("brief").description("Fleet-wide usage brief with tokens, cache, cost, breakdowns, and freshness").option("--since <duration-or-date>", "Since window for breakdown tables (24h, 7d, ISO date)", "24h").option("--machine <id|all>", "Filter to one machine, or all machines", "all").option("--json", "Output JSON").action(async (opts) => {
7447
+ try {
7448
+ let localSyncAt;
7449
+ if (deps.beforeRead) {
7450
+ await deps.beforeRead();
7451
+ localSyncAt = new Date;
7452
+ }
7453
+ const db = openDatabase();
7454
+ ensurePricingSeeded(db);
7455
+ const brief = buildBrief(db, {
7456
+ since: opts.since,
7457
+ machine: opts.machine,
7458
+ currentMachineId: localSyncAt ? getMachineId() : undefined,
7459
+ localSyncAt
7460
+ });
7461
+ if (opts.json) {
7462
+ console.log(JSON.stringify(brief, null, 2));
7463
+ return;
7464
+ }
7465
+ console.log(renderBriefText(brief));
7466
+ } catch (error) {
7467
+ console.error(error instanceof Error ? error.message : String(error));
7468
+ process.exit(1);
7469
+ }
7470
+ });
7471
+ }
7472
+
6735
7473
  // src/cli/index.ts
6736
7474
  init_agents();
6737
7475
  init_sync_all();
@@ -6755,38 +7493,38 @@ var GENERIC_PEER_TABLES = [
6755
7493
  function quoteIdent(identifier) {
6756
7494
  return `"${identifier.replace(/"/g, '""')}"`;
6757
7495
  }
6758
- function tableExists(db, table) {
6759
- const row = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(table);
7496
+ function tableExists(db, table2) {
7497
+ const row = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(table2);
6760
7498
  return Boolean(row);
6761
7499
  }
6762
- function tableColumns(db, table) {
6763
- if (!tableExists(db, table))
7500
+ function tableColumns(db, table2) {
7501
+ if (!tableExists(db, table2))
6764
7502
  return [];
6765
- return db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all();
7503
+ return db.prepare(`PRAGMA table_info(${quoteIdent(table2)})`).all();
6766
7504
  }
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));
7505
+ function commonColumns(source, target, table2) {
7506
+ const sourceCols = new Set(tableColumns(source, table2).map((c) => c.name));
7507
+ return tableColumns(target, table2).map((c) => c.name).filter((c) => sourceCols.has(c));
6770
7508
  }
6771
- function primaryKeyColumns(db, table) {
6772
- return tableColumns(db, table).filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
7509
+ function primaryKeyColumns(db, table2) {
7510
+ return tableColumns(db, table2).filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
6773
7511
  }
6774
- function selectRows(source, table, columns) {
7512
+ function selectRows(source, table2, columns) {
6775
7513
  if (columns.length === 0)
6776
7514
  return [];
6777
7515
  const select = columns.map(quoteIdent).join(", ");
6778
- return source.prepare(`SELECT ${select} FROM ${quoteIdent(table)}`).all();
7516
+ return source.prepare(`SELECT ${select} FROM ${quoteIdent(table2)}`).all();
6779
7517
  }
6780
- function rowByKey(target, table, keyColumns, row) {
7518
+ function rowByKey(target, table2, keyColumns, row) {
6781
7519
  if (keyColumns.length === 0)
6782
7520
  return null;
6783
7521
  if (keyColumns.some((c) => row[c] == null))
6784
7522
  return null;
6785
7523
  const where = keyColumns.map((c) => `${quoteIdent(c)} = ?`).join(" AND ");
6786
- return target.prepare(`SELECT * FROM ${quoteIdent(table)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
7524
+ return target.prepare(`SELECT * FROM ${quoteIdent(table2)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
6787
7525
  }
6788
- function hasId(target, table, id) {
6789
- return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(table)} WHERE id = ?`).get(id);
7526
+ function hasId(target, table2, id) {
7527
+ return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(table2)} WHERE id = ?`).get(id);
6790
7528
  }
6791
7529
  function shouldReplace(source, existing) {
6792
7530
  if (!existing)
@@ -6812,30 +7550,30 @@ function normalizeRow(row, columns, sourceMachine, now) {
6812
7550
  next["attribution_tag"] = "";
6813
7551
  return next;
6814
7552
  }
6815
- function insertOrReplace(target, table, columns, row) {
7553
+ function insertOrReplace(target, table2, columns, row) {
6816
7554
  const colSql = columns.map(quoteIdent).join(", ");
6817
7555
  const placeholders = columns.map(() => "?").join(", ");
6818
7556
  target.prepare(`
6819
- INSERT OR REPLACE INTO ${quoteIdent(table)} (${colSql})
7557
+ INSERT OR REPLACE INTO ${quoteIdent(table2)} (${colSql})
6820
7558
  VALUES (${placeholders})
6821
7559
  `).run(...columns.map((c) => row[c] ?? null));
6822
7560
  }
6823
- function collisionId(target, table, machine, originalId) {
7561
+ function collisionId(target, table2, machine, originalId) {
6824
7562
  const base = `${machine || "peer"}:${originalId}`;
6825
- const baseRow = hasId(target, table, base);
7563
+ const baseRow = hasId(target, table2, base);
6826
7564
  if (!baseRow || String(baseRow["machine_id"] ?? "") === machine)
6827
7565
  return base;
6828
7566
  for (let i = 2;; i++) {
6829
7567
  const candidate = `${base}:${i}`;
6830
- const row = hasId(target, table, candidate);
7568
+ const row = hasId(target, table2, candidate);
6831
7569
  if (!row || String(row["machine_id"] ?? "") === machine)
6832
7570
  return candidate;
6833
7571
  }
6834
7572
  }
6835
- function mergeIdentityTable(target, source, 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);
7573
+ function mergeIdentityTable(target, source, table2, sourceMachine, now, sessionIdMap) {
7574
+ const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
7575
+ const columns = commonColumns(source, target, table2);
7576
+ const rows = selectRows(source, table2, columns);
6839
7577
  const idMap = new Map;
6840
7578
  for (const raw of rows) {
6841
7579
  const row = normalizeRow(raw, columns, sourceMachine, now);
@@ -6845,22 +7583,22 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
6845
7583
  continue;
6846
7584
  }
6847
7585
  const machine = String(row["machine_id"] ?? "");
6848
- const directExisting = hasId(target, table, originalId);
7586
+ const directExisting = hasId(target, table2, originalId);
6849
7587
  if (directExisting && String(directExisting["machine_id"] ?? "") !== machine) {
6850
- row["id"] = collisionId(target, table, machine, originalId);
7588
+ row["id"] = collisionId(target, table2, machine, originalId);
6851
7589
  stats.collisions++;
6852
7590
  }
6853
- if (table === "requests" && sessionIdMap) {
7591
+ if (table2 === "requests" && sessionIdMap) {
6854
7592
  const originalSessionId = String(row["session_id"] ?? "");
6855
7593
  row["session_id"] = sessionIdMap.get(originalSessionId) ?? originalSessionId;
6856
7594
  }
6857
- const existing = hasId(target, table, String(row["id"]));
7595
+ const existing = hasId(target, table2, String(row["id"]));
6858
7596
  idMap.set(originalId, String(row["id"]));
6859
7597
  if (existing && !shouldReplace(row, existing)) {
6860
7598
  stats.skipped++;
6861
7599
  continue;
6862
7600
  }
6863
- insertOrReplace(target, table, columns, row);
7601
+ insertOrReplace(target, table2, columns, row);
6864
7602
  if (existing)
6865
7603
  stats.updated++;
6866
7604
  else
@@ -6869,10 +7607,10 @@ function mergeIdentityTable(target, source, table, sourceMachine, now, sessionId
6869
7607
  return { stats, idMap };
6870
7608
  }
6871
7609
  function mergeProjects(target, source) {
6872
- const 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);
7610
+ const table2 = "projects";
7611
+ const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
7612
+ const columns = commonColumns(source, target, table2);
7613
+ const rows = selectRows(source, table2, columns);
6876
7614
  for (const raw of rows) {
6877
7615
  const row = { ...raw };
6878
7616
  const path = String(row["path"] ?? "");
@@ -6884,7 +7622,7 @@ function mergeProjects(target, source) {
6884
7622
  const existingByPath = target.prepare(`SELECT * FROM projects WHERE path = ?`).get(path);
6885
7623
  if (existingByPath) {
6886
7624
  row["id"] = existingByPath["id"] ?? id;
6887
- insertOrReplace(target, table, columns, row);
7625
+ insertOrReplace(target, table2, columns, row);
6888
7626
  stats.updated++;
6889
7627
  continue;
6890
7628
  }
@@ -6896,24 +7634,24 @@ function mergeProjects(target, source) {
6896
7634
  row["id"] = `peer:${String(row["id"])}`;
6897
7635
  }
6898
7636
  }
6899
- insertOrReplace(target, table, columns, row);
7637
+ insertOrReplace(target, table2, columns, row);
6900
7638
  stats.inserted++;
6901
7639
  }
6902
7640
  return stats;
6903
7641
  }
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);
7642
+ function mergeGenericTable(target, source, table2, sourceMachine, now) {
7643
+ const stats = { table: table2, inserted: 0, updated: 0, skipped: 0, collisions: 0 };
7644
+ const columns = commonColumns(source, target, table2);
7645
+ const keyColumns = primaryKeyColumns(target, table2).filter((c) => columns.includes(c));
7646
+ const rows = selectRows(source, table2, columns);
6909
7647
  for (const raw of rows) {
6910
7648
  const row = normalizeRow(raw, columns, sourceMachine, now);
6911
- const existing = rowByKey(target, table, keyColumns, row);
7649
+ const existing = rowByKey(target, table2, keyColumns, row);
6912
7650
  if (existing && !shouldReplace(row, existing)) {
6913
7651
  stats.skipped++;
6914
7652
  continue;
6915
7653
  }
6916
- insertOrReplace(target, table, columns, row);
7654
+ insertOrReplace(target, table2, columns, row);
6917
7655
  if (existing)
6918
7656
  stats.updated++;
6919
7657
  else
@@ -6925,12 +7663,12 @@ function detectSourceMachine(source, fallback) {
6925
7663
  if (fallback && fallback.trim())
6926
7664
  return fallback.trim();
6927
7665
  const counts = new Map;
6928
- for (const table of ["sessions", "requests", "usage_snapshots"]) {
6929
- if (!tableExists(source, table))
7666
+ for (const table2 of ["sessions", "requests", "usage_snapshots"]) {
7667
+ if (!tableExists(source, table2))
6930
7668
  continue;
6931
7669
  const rows = source.prepare(`
6932
7670
  SELECT machine_id, COUNT(*) as cnt
6933
- FROM ${quoteIdent(table)}
7671
+ FROM ${quoteIdent(table2)}
6934
7672
  WHERE machine_id != '' AND machine_id IS NOT NULL
6935
7673
  GROUP BY machine_id
6936
7674
  `).all();
@@ -6987,8 +7725,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
6987
7725
  const sessionMerge = mergeIdentityTable(target, source, "sessions", sourceMachine, now);
6988
7726
  tables.push(sessionMerge.stats);
6989
7727
  tables.push(mergeIdentityTable(target, source, "requests", sourceMachine, now, sessionMerge.idMap).stats);
6990
- for (const table of GENERIC_PEER_TABLES) {
6991
- tables.push(mergeGenericTable(target, source, table, sourceMachine, now));
7728
+ for (const table2 of GENERIC_PEER_TABLES) {
7729
+ tables.push(mergeGenericTable(target, source, table2, sourceMachine, now));
6992
7730
  }
6993
7731
  ensureMachineRegistry(target, sourceMachine, now);
6994
7732
  target.exec("COMMIT");
@@ -7002,8 +7740,8 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
7002
7740
  source.close();
7003
7741
  }
7004
7742
  const deduped = dedupeRequests(target);
7005
- const rowsWritten = tables.reduce((sum, table) => sum + table.inserted + table.updated, 0);
7006
- const collisions = tables.reduce((sum, table) => sum + table.collisions, 0);
7743
+ const rowsWritten = tables.reduce((sum, table2) => sum + table2.inserted + table2.updated, 0);
7744
+ const collisions = tables.reduce((sum, table2) => sum + table2.collisions, 0);
7007
7745
  return {
7008
7746
  source_path: sourcePath,
7009
7747
  source_machine: sourceMachine,
@@ -7167,7 +7905,7 @@ function printAccountBreakdown(rows) {
7167
7905
  fmt4(r.subscription_included_usd)
7168
7906
  ]));
7169
7907
  }
7170
- function parseSinceDate(since) {
7908
+ function parseSinceDate2(since) {
7171
7909
  const relMatch = since.match(/^(\d+)d$/);
7172
7910
  if (relMatch) {
7173
7911
  const days = parseInt(relMatch[1], 10);
@@ -7312,7 +8050,7 @@ program.command("sessions").description("List coding sessions with costs").optio
7312
8050
  const agent = parseOptionalCliAgent(opts.agent);
7313
8051
  await autoSync();
7314
8052
  const db = openDatabase();
7315
- const sinceDate = opts.since ? parseSinceDate(opts.since) : undefined;
8053
+ const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
7316
8054
  let sessions = querySessions(db, {
7317
8055
  agent,
7318
8056
  project: opts.project,
@@ -7359,7 +8097,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
7359
8097
  const count = parsePositiveCliInteger(opts.n ?? "10", "-n");
7360
8098
  const agent = parseOptionalCliAgent(opts.agent);
7361
8099
  const db = openDatabase();
7362
- const sinceDate = opts.since ? parseSinceDate(opts.since) : undefined;
8100
+ const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
7363
8101
  let sessions = queryTopSessions(db, count, agent);
7364
8102
  if (sinceDate)
7365
8103
  sessions = sessions.filter((s) => s.started_at >= sinceDate);
@@ -7380,7 +8118,7 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
7380
8118
  });
7381
8119
  program.command("breakdown").description("Cost breakdown by model, agent, project, or account").option("--by <dimension>", "Dimension: model|agent|project|account", "model").option("--since <date>", "Filter since date or relative (e.g. 2026-03-01, 7d, 30d)").action((opts) => {
7382
8120
  const db = openDatabase();
7383
- const sinceDate = opts.since ? parseSinceDate(opts.since) : undefined;
8121
+ const sinceDate = opts.since ? parseSinceDate2(opts.since) : undefined;
7384
8122
  console.log();
7385
8123
  if (opts.by === "project") {
7386
8124
  const rows = sinceDate ? db.prepare(`
@@ -7962,8 +8700,8 @@ program.command("merge-db <source-db>").description("Merge another Economy SQLit
7962
8700
  console.log();
7963
8701
  console.log(chalk7.bold.cyan(` Merged Economy DB \u2014 ${result.source_machine}`));
7964
8702
  console.log(` Rows written: ${fmtCount(result.rows_written)} \xB7 collisions remapped: ${fmtCount(result.collisions)} \xB7 deduped: ${fmtCount(result.deduped)}`);
7965
- for (const 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)}`);
8703
+ for (const table2 of result.tables) {
8704
+ console.log(` ${chalk7.white(table2.table.padEnd(16))}` + ` inserted ${fmtCount(table2.inserted).padStart(6)}` + ` updated ${fmtCount(table2.updated).padStart(6)}` + ` skipped ${fmtCount(table2.skipped).padStart(6)}` + ` collisions ${fmtCount(table2.collisions).padStart(3)}`);
7967
8705
  }
7968
8706
  console.log();
7969
8707
  });
@@ -8342,6 +9080,7 @@ billingCmd.command("show").description("Show actual billing totals vs our estima
8342
9080
  });
8343
9081
  registerBrainsCommand(program);
8344
9082
  registerTodosCommand(program);
9083
+ registerBriefCommand(program, { beforeRead: autoSync });
8345
9084
  registerExtendedCommands(program);
8346
9085
  registerFleetCommands(program);
8347
9086
  registerEventsCommands(program, { source: "economy" });
package/dist/index.js CHANGED
@@ -235,6 +235,7 @@ var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING,
235
235
  var init_pricing = __esm(() => {
236
236
  init_database();
237
237
  DEFAULT_PRICING = {
238
+ "claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
238
239
  "claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
239
240
  "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
240
241
  "claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
@@ -1 +1 @@
1
- {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/lib/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,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.36",
4
4
  "description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",