@hasna/economy 0.2.33 → 0.2.35

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