@hasna/economy 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -107,12 +107,32 @@ var init_pricing = __esm(() => {
107
107
  });
108
108
 
109
109
  // src/db/database.ts
110
- import { Database } from "bun:sqlite";
111
- import { existsSync, mkdirSync } from "fs";
110
+ import { SqliteAdapter as Database } from "@hasna/cloud";
111
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
112
112
  import { homedir } from "os";
113
113
  import { join } from "path";
114
+ function getDataDir() {
115
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
116
+ const newDir = join(home, ".hasna", "economy");
117
+ const oldDir = join(home, ".economy");
118
+ if (existsSync(oldDir) && !existsSync(newDir)) {
119
+ mkdirSync(newDir, { recursive: true });
120
+ for (const file of readdirSync(oldDir)) {
121
+ const oldPath = join(oldDir, file);
122
+ if (statSync(oldPath).isFile()) {
123
+ copyFileSync(oldPath, join(newDir, file));
124
+ }
125
+ }
126
+ }
127
+ mkdirSync(newDir, { recursive: true });
128
+ return newDir;
129
+ }
114
130
  function getDbPath() {
115
- return process.env["ECONOMY_DB"] ?? join(homedir(), ".economy", "economy.db");
131
+ if (process.env["HASNA_ECONOMY_DB_PATH"])
132
+ return process.env["HASNA_ECONOMY_DB_PATH"];
133
+ if (process.env["ECONOMY_DB"])
134
+ return process.env["ECONOMY_DB"];
135
+ return join(getDataDir(), "economy.db");
116
136
  }
117
137
  function openDatabase(dbPath, skipSeed = false) {
118
138
  const path = dbPath ?? getDbPath();
@@ -211,12 +231,24 @@ function initSchema(db) {
211
231
  cache_write_per_1m REAL NOT NULL DEFAULT 0,
212
232
  updated_at TEXT NOT NULL
213
233
  );
234
+
235
+ CREATE TABLE IF NOT EXISTS feedback (
236
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
237
+ message TEXT NOT NULL,
238
+ email TEXT,
239
+ category TEXT DEFAULT 'general',
240
+ version TEXT,
241
+ machine_id TEXT,
242
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
243
+ );
214
244
  `);
215
245
  }
216
246
  function periodWhere(period) {
217
247
  switch (period) {
218
248
  case "today":
219
249
  return `DATE(timestamp) = DATE('now')`;
250
+ case "yesterday":
251
+ return `DATE(timestamp) = DATE('now', '-1 day')`;
220
252
  case "week":
221
253
  return `timestamp >= DATE('now', '-7 days')`;
222
254
  case "month":
@@ -231,6 +263,8 @@ function sessionPeriodWhere(period) {
231
263
  switch (period) {
232
264
  case "today":
233
265
  return `DATE(started_at) = DATE('now')`;
266
+ case "yesterday":
267
+ return `DATE(started_at) = DATE('now', '-1 day')`;
234
268
  case "week":
235
269
  return `started_at >= DATE('now', '-7 days')`;
236
270
  case "month":
@@ -510,9 +544,9 @@ function seedModelPricing(db, defaults) {
510
544
  var init_database = () => {};
511
545
 
512
546
  // src/ingest/claude.ts
513
- import { readdirSync, readFileSync, existsSync as existsSync2, statSync } from "fs";
547
+ import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync3, statSync as statSync2 } from "fs";
514
548
  import { homedir as homedir2 } from "os";
515
- import { join as join2, basename } from "path";
549
+ import { join as join4, basename } from "path";
516
550
  function autoDetectProject(cwd, projects) {
517
551
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
518
552
  }
@@ -523,11 +557,11 @@ function collectJsonlFiles(projectDir) {
523
557
  const files = [];
524
558
  function walk(dir) {
525
559
  try {
526
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
560
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
527
561
  if (entry.isDirectory())
528
- walk(join2(dir, entry.name));
562
+ walk(join4(dir, entry.name));
529
563
  else if (entry.name.endsWith(".jsonl"))
530
- files.push(join2(dir, entry.name));
564
+ files.push(join4(dir, entry.name));
531
565
  }
532
566
  } catch {}
533
567
  }
@@ -535,7 +569,7 @@ function collectJsonlFiles(projectDir) {
535
569
  return files;
536
570
  }
537
571
  async function ingestClaude(db, verbose = false, _telemetryDir) {
538
- if (!existsSync2(PROJECTS_DIR)) {
572
+ if (!existsSync3(PROJECTS_DIR)) {
539
573
  if (verbose)
540
574
  console.log("Claude projects dir not found:", PROJECTS_DIR);
541
575
  return { files: 0, requests: 0, sessions: 0 };
@@ -544,16 +578,16 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
544
578
  let totalRequests = 0;
545
579
  const touchedSessions = new Set;
546
580
  const registeredProjects = db.prepare(`SELECT path, name FROM projects ORDER BY LENGTH(path) DESC`).all();
547
- const projectDirs = readdirSync(PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
581
+ const projectDirs = readdirSync2(PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
548
582
  for (const projectDirEntry of projectDirs) {
549
- const projectDirPath = join2(PROJECTS_DIR, projectDirEntry.name);
583
+ const projectDirPath = join4(PROJECTS_DIR, projectDirEntry.name);
550
584
  const projectPath = dirNameToPath(projectDirEntry.name);
551
585
  const jsonlFiles = collectJsonlFiles(projectDirPath);
552
586
  for (const filePath of jsonlFiles) {
553
587
  const stateKey = filePath.replace(PROJECTS_DIR, "");
554
588
  let fileMtime = "0";
555
589
  try {
556
- fileMtime = statSync(filePath).mtimeMs.toString();
590
+ fileMtime = statSync2(filePath).mtimeMs.toString();
557
591
  } catch {
558
592
  continue;
559
593
  }
@@ -562,7 +596,7 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
562
596
  continue;
563
597
  let lines;
564
598
  try {
565
- lines = readFileSync(filePath, "utf-8").split(`
599
+ lines = readFileSync2(filePath, "utf-8").split(`
566
600
  `).filter((l) => l.trim());
567
601
  } catch {
568
602
  continue;
@@ -648,16 +682,16 @@ var PROJECTS_DIR;
648
682
  var init_claude = __esm(() => {
649
683
  init_database();
650
684
  init_pricing();
651
- PROJECTS_DIR = join2(homedir2(), ".claude", "projects");
685
+ PROJECTS_DIR = join4(homedir2(), ".claude", "projects");
652
686
  });
653
687
 
654
688
  // src/ingest/codex.ts
655
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
689
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
656
690
  import { homedir as homedir3 } from "os";
657
- import { join as join3, basename as basename2 } from "path";
691
+ import { join as join5, basename as basename2 } from "path";
658
692
  import { Database as Database2 } from "bun:sqlite";
659
693
  async function ingestCodex(db, verbose = false) {
660
- if (!existsSync3(CODEX_DB_PATH)) {
694
+ if (!existsSync4(CODEX_DB_PATH)) {
661
695
  if (verbose)
662
696
  console.log("Codex DB not found:", CODEX_DB_PATH);
663
697
  return { sessions: 0 };
@@ -701,43 +735,42 @@ async function ingestCodex(db, verbose = false) {
701
735
  var CODEX_DB_PATH, CODEX_CONFIG_PATH;
702
736
  var init_codex = __esm(() => {
703
737
  init_database();
704
- CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
705
- CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
738
+ CODEX_DB_PATH = join5(homedir3(), ".codex", "state_5.sqlite");
739
+ CODEX_CONFIG_PATH = join5(homedir3(), ".codex", "config.toml");
706
740
  });
707
741
 
708
742
  // src/lib/config.ts
709
743
  var exports_config = {};
710
744
  __export(exports_config, {
711
745
  setConfigValue: () => setConfigValue,
712
- saveConfig: () => saveConfig,
713
- loadConfig: () => loadConfig,
746
+ saveConfig: () => saveConfig2,
747
+ loadConfig: () => loadConfig2,
714
748
  getConfigValue: () => getConfigValue
715
749
  });
716
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
717
- import { homedir as homedir5 } from "os";
718
- import { join as join5 } from "path";
719
- function loadConfig() {
750
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
751
+ import { join as join7 } from "path";
752
+ function loadConfig2() {
720
753
  try {
721
- if (existsSync5(CONFIG_PATH)) {
722
- const raw = readFileSync4(CONFIG_PATH, "utf-8");
754
+ if (existsSync6(CONFIG_PATH2)) {
755
+ const raw = readFileSync5(CONFIG_PATH2, "utf-8");
723
756
  return { ...DEFAULTS, ...JSON.parse(raw) };
724
757
  }
725
758
  } catch {}
726
759
  return { ...DEFAULTS };
727
760
  }
728
- function saveConfig(config) {
729
- const dir = CONFIG_PATH.substring(0, CONFIG_PATH.lastIndexOf("/"));
730
- if (!existsSync5(dir))
731
- mkdirSync2(dir, { recursive: true });
732
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
761
+ function saveConfig2(config) {
762
+ const dir = CONFIG_PATH2.substring(0, CONFIG_PATH2.lastIndexOf("/"));
763
+ if (!existsSync6(dir))
764
+ mkdirSync3(dir, { recursive: true });
765
+ writeFileSync2(CONFIG_PATH2, JSON.stringify(config, null, 2) + `
733
766
  `);
734
767
  }
735
768
  function getConfigValue(key) {
736
- const config = loadConfig();
769
+ const config = loadConfig2();
737
770
  return config[key] ?? null;
738
771
  }
739
772
  function setConfigValue(key, value) {
740
- const config = loadConfig();
773
+ const config = loadConfig2();
741
774
  let parsed = value;
742
775
  if (value === "true")
743
776
  parsed = true;
@@ -753,11 +786,12 @@ function setConfigValue(key, value) {
753
786
  } catch {}
754
787
  }
755
788
  config[key] = parsed;
756
- saveConfig(config);
789
+ saveConfig2(config);
757
790
  }
758
- var CONFIG_PATH, DEFAULTS;
791
+ var CONFIG_PATH2, DEFAULTS;
759
792
  var init_config = __esm(() => {
760
- CONFIG_PATH = join5(homedir5(), ".economy", "config.json");
793
+ init_database();
794
+ CONFIG_PATH2 = join7(getDataDir(), "config.json");
761
795
  DEFAULTS = {
762
796
  port: 3456,
763
797
  "default-period": "today",
@@ -774,7 +808,7 @@ __export(exports_webhooks, {
774
808
  checkAndFireWebhooks: () => checkAndFireWebhooks
775
809
  });
776
810
  async function checkAndFireWebhooks(db) {
777
- const config = loadConfig();
811
+ const config = loadConfig2();
778
812
  const url = config["webhook-url"];
779
813
  if (!url)
780
814
  return;
@@ -819,9 +853,9 @@ var exports_watch = {};
819
853
  __export(exports_watch, {
820
854
  watchCosts: () => watchCosts
821
855
  });
822
- import chalk from "chalk";
856
+ import chalk2 from "chalk";
823
857
  function fmt(usd) {
824
- return chalk.green(`$${usd.toFixed(4)}`);
858
+ return chalk2.green(`$${usd.toFixed(4)}`);
825
859
  }
826
860
  function notify(title, body) {
827
861
  try {
@@ -831,12 +865,12 @@ function notify(title, body) {
831
865
  }
832
866
  function renderHeader(todayUsd, weekUsd) {
833
867
  process.stdout.write("\x1B[H\x1B[2J");
834
- console.log(chalk.bold.cyan(" economy watch") + chalk.dim(" \u2014 live cost stream"));
835
- console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
868
+ console.log(chalk2.bold.cyan(" economy watch") + chalk2.dim(" \u2014 live cost stream"));
869
+ console.log(chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
836
870
  console.log(` Today: ${fmt(todayUsd)} Week: ${fmt(weekUsd)}`);
837
- console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
838
- console.log(chalk.dim(" [agent] cost model tokens project"));
839
- console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
871
+ console.log(chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
872
+ console.log(chalk2.dim(" [agent] cost model tokens project"));
873
+ console.log(chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
840
874
  }
841
875
  async function watchCosts(opts) {
842
876
  const db = openDatabase();
@@ -848,7 +882,7 @@ async function watchCosts(opts) {
848
882
  const initialSummaryToday = querySummary(db, "today");
849
883
  const initialSummaryWeek = querySummary(db, "week");
850
884
  renderHeader(initialSummaryToday.total_usd, initialSummaryWeek.total_usd);
851
- console.log(chalk.dim(`
885
+ console.log(chalk2.dim(`
852
886
  Polling every ${opts.interval}s \u2014 Ctrl+C to exit
853
887
  `));
854
888
  async function poll() {
@@ -860,7 +894,7 @@ async function watchCosts(opts) {
860
894
  for (const req of newRequests) {
861
895
  if (opts.agent && req.agent !== opts.agent)
862
896
  continue;
863
- const agentLabel = req.agent === "claude" ? chalk.blue("[claude]") : chalk.yellow("[codex] ");
897
+ const agentLabel = req.agent === "claude" ? chalk2.blue("[claude]") : chalk2.yellow("[codex] ");
864
898
  const tokens = req.input_tokens + req.output_tokens;
865
899
  const tokStr = tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
866
900
  const line = ` ${agentLabel} ${fmt(req.cost_usd).padEnd(14)}${req.model.substring(0, 24).padEnd(26)}${tokStr.padEnd(10)}${req.session_id.substring(0, 12)}`;
@@ -888,15 +922,15 @@ async function watchCosts(opts) {
888
922
  for (const line of lines)
889
923
  console.log(line);
890
924
  if (lines.length === 0)
891
- console.log(chalk.dim(" Waiting for new requests..."));
892
- console.log(chalk.dim(`
925
+ console.log(chalk2.dim(" Waiting for new requests..."));
926
+ console.log(chalk2.dim(`
893
927
  Last updated: ${new Date().toLocaleTimeString()} \u2014 polling every ${opts.interval}s \u2014 Ctrl+C to exit`));
894
928
  }
895
929
  await poll();
896
930
  const timer = setInterval(poll, opts.interval * 1000);
897
931
  process.on("SIGINT", () => {
898
932
  clearInterval(timer);
899
- console.log(chalk.dim(`
933
+ console.log(chalk2.dim(`
900
934
 
901
935
  Stopped watching.`));
902
936
  process.exit(0);
@@ -1098,15 +1132,15 @@ function startServer(port = 3456) {
1098
1132
  return apiHandler(req);
1099
1133
  }
1100
1134
  try {
1101
- const { existsSync: existsSync6 } = await import("fs");
1102
- if (existsSync6(dashboardDir)) {
1135
+ const { existsSync: existsSync7 } = await import("fs");
1136
+ if (existsSync7(dashboardDir)) {
1103
1137
  let filePath = url.pathname === "/" ? "/index.html" : url.pathname;
1104
1138
  const fullPath = dashboardDir + filePath;
1105
- if (existsSync6(fullPath)) {
1139
+ if (existsSync7(fullPath)) {
1106
1140
  return new Response(Bun.file(fullPath));
1107
1141
  }
1108
1142
  const indexPath = dashboardDir + "/index.html";
1109
- if (existsSync6(indexPath)) {
1143
+ if (existsSync7(indexPath)) {
1110
1144
  return new Response(Bun.file(indexPath));
1111
1145
  }
1112
1146
  }
@@ -1137,16 +1171,16 @@ __export(exports_menubar, {
1137
1171
  menubarStart: () => menubarStart,
1138
1172
  menubarInstall: () => menubarInstall
1139
1173
  });
1140
- import chalk2 from "chalk";
1174
+ import chalk3 from "chalk";
1141
1175
  import { execSync } from "child_process";
1142
- import { existsSync as existsSync6, writeFileSync as writeFileSync2 } from "fs";
1176
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "fs";
1143
1177
  import { tmpdir, arch } from "os";
1144
- import { join as join6 } from "path";
1178
+ import { join as join8 } from "path";
1145
1179
  function getArch() {
1146
1180
  return arch() === "arm64" ? "arm64" : "x86_64";
1147
1181
  }
1148
1182
  function isInstalled() {
1149
- return existsSync6(APP_PATH);
1183
+ return existsSync7(APP_PATH);
1150
1184
  }
1151
1185
  function isRunning() {
1152
1186
  try {
@@ -1158,13 +1192,13 @@ function isRunning() {
1158
1192
  }
1159
1193
  async function menubarInstall(opts) {
1160
1194
  if (isInstalled() && !opts.force) {
1161
- console.log(chalk2.yellow("Economy Bar is already installed. Use --force to reinstall."));
1162
- console.log(chalk2.dim(` Location: ${APP_PATH}`));
1195
+ console.log(chalk3.yellow("Economy Bar is already installed. Use --force to reinstall."));
1196
+ console.log(chalk3.dim(` Location: ${APP_PATH}`));
1163
1197
  return;
1164
1198
  }
1165
1199
  const cpuArch = getArch();
1166
- console.log(chalk2.cyan(`\u2192 Detecting architecture: ${cpuArch}`));
1167
- console.log(chalk2.cyan("\u2192 Fetching latest release info..."));
1200
+ console.log(chalk3.cyan(`\u2192 Detecting architecture: ${cpuArch}`));
1201
+ console.log(chalk3.cyan("\u2192 Fetching latest release info..."));
1168
1202
  let assetUrl;
1169
1203
  try {
1170
1204
  const res = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
@@ -1180,24 +1214,24 @@ async function menubarInstall(opts) {
1180
1214
  throw new Error(`No asset found for ${assetName}. Check releases at https://github.com/${REPO}/releases`);
1181
1215
  assetUrl = asset.browser_download_url;
1182
1216
  } catch (e) {
1183
- console.error(chalk2.red(`\u2717 Failed to fetch release info: ${e instanceof Error ? e.message : String(e)}`));
1217
+ console.error(chalk3.red(`\u2717 Failed to fetch release info: ${e instanceof Error ? e.message : String(e)}`));
1184
1218
  process.exit(1);
1185
1219
  }
1186
- const zipPath = join6(tmpdir(), `economy-bar-${cpuArch}.zip`);
1187
- const extractDir = join6(tmpdir(), "economy-bar-extracted");
1188
- console.log(chalk2.cyan(`\u2192 Downloading ${assetUrl}...`));
1220
+ const zipPath = join8(tmpdir(), `economy-bar-${cpuArch}.zip`);
1221
+ const extractDir = join8(tmpdir(), "economy-bar-extracted");
1222
+ console.log(chalk3.cyan(`\u2192 Downloading ${assetUrl}...`));
1189
1223
  try {
1190
1224
  const res = await fetch(assetUrl, { signal: AbortSignal.timeout(60000) });
1191
1225
  if (!res.ok)
1192
1226
  throw new Error(`Download failed: ${res.status}`);
1193
1227
  const buffer = await res.arrayBuffer();
1194
- writeFileSync2(zipPath, Buffer.from(buffer));
1195
- console.log(chalk2.green(`\u2713 Downloaded (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB)`));
1228
+ writeFileSync3(zipPath, Buffer.from(buffer));
1229
+ console.log(chalk3.green(`\u2713 Downloaded (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB)`));
1196
1230
  } catch (e) {
1197
- console.error(chalk2.red(`\u2717 Download failed: ${e instanceof Error ? e.message : String(e)}`));
1231
+ console.error(chalk3.red(`\u2717 Download failed: ${e instanceof Error ? e.message : String(e)}`));
1198
1232
  process.exit(1);
1199
1233
  }
1200
- console.log(chalk2.cyan("\u2192 Installing to /Applications..."));
1234
+ console.log(chalk3.cyan("\u2192 Installing to /Applications..."));
1201
1235
  try {
1202
1236
  execSync(`rm -rf "${extractDir}" && mkdir -p "${extractDir}"`, { stdio: "ignore" });
1203
1237
  execSync(`unzip -q "${zipPath}" -d "${extractDir}"`, { stdio: "ignore" });
@@ -1206,24 +1240,24 @@ async function menubarInstall(opts) {
1206
1240
  execSync(`cp -R "${extractDir}/Economy Bar.app" /Applications/`, { stdio: "ignore" });
1207
1241
  execSync(`xattr -rd com.apple.quarantine "${APP_PATH}" 2>/dev/null || true`, { stdio: "ignore" });
1208
1242
  execSync(`rm -rf "${zipPath}" "${extractDir}"`, { stdio: "ignore" });
1209
- console.log(chalk2.green(`\u2713 Installed to ${APP_PATH}`));
1243
+ console.log(chalk3.green(`\u2713 Installed to ${APP_PATH}`));
1210
1244
  } catch (e) {
1211
- console.error(chalk2.red(`\u2717 Install failed: ${e instanceof Error ? e.message : String(e)}`));
1245
+ console.error(chalk3.red(`\u2717 Install failed: ${e instanceof Error ? e.message : String(e)}`));
1212
1246
  process.exit(1);
1213
1247
  }
1214
- console.log(chalk2.cyan("\u2192 Launching Economy Bar..."));
1248
+ console.log(chalk3.cyan("\u2192 Launching Economy Bar..."));
1215
1249
  try {
1216
1250
  execSync(`open "${APP_PATH}"`, { stdio: "ignore" });
1217
- console.log(chalk2.bold.green(`
1251
+ console.log(chalk3.bold.green(`
1218
1252
  \u2713 Economy Bar is running in your menu bar!`));
1219
- console.log(chalk2.dim(" Make sure economy serve is running: economy serve"));
1253
+ console.log(chalk3.dim(" Make sure economy serve is running: economy serve"));
1220
1254
  } catch (e) {
1221
- console.log(chalk2.yellow("\u26A0 Installed but could not auto-launch. Open from /Applications manually."));
1255
+ console.log(chalk3.yellow("\u26A0 Installed but could not auto-launch. Open from /Applications manually."));
1222
1256
  }
1223
1257
  }
1224
1258
  function menubarUninstall() {
1225
1259
  if (!isInstalled()) {
1226
- console.log(chalk2.yellow("Economy Bar is not installed."));
1260
+ console.log(chalk3.yellow("Economy Bar is not installed."));
1227
1261
  return;
1228
1262
  }
1229
1263
  if (isRunning()) {
@@ -1233,46 +1267,398 @@ function menubarUninstall() {
1233
1267
  } catch {}
1234
1268
  }
1235
1269
  execSync(`rm -rf "${APP_PATH}"`, { stdio: "ignore" });
1236
- console.log(chalk2.green("\u2713 Economy Bar uninstalled"));
1270
+ console.log(chalk3.green("\u2713 Economy Bar uninstalled"));
1237
1271
  }
1238
1272
  function menubarStart() {
1239
1273
  if (!isInstalled()) {
1240
- console.error(chalk2.red("Economy Bar is not installed. Run: economy menubar install"));
1274
+ console.error(chalk3.red("Economy Bar is not installed. Run: economy menubar install"));
1241
1275
  process.exit(1);
1242
1276
  }
1243
1277
  execSync(`open "${APP_PATH}"`, { stdio: "ignore" });
1244
- console.log(chalk2.green("\u2713 Economy Bar launched"));
1278
+ console.log(chalk3.green("\u2713 Economy Bar launched"));
1245
1279
  }
1246
1280
  function menubarStop() {
1247
1281
  if (!isRunning()) {
1248
- console.log(chalk2.yellow("Economy Bar is not running."));
1282
+ console.log(chalk3.yellow("Economy Bar is not running."));
1249
1283
  return;
1250
1284
  }
1251
1285
  try {
1252
1286
  execSync(`osascript -e 'quit app "Economy Bar"'`, { stdio: "ignore" });
1253
- console.log(chalk2.green("\u2713 Economy Bar stopped"));
1287
+ console.log(chalk3.green("\u2713 Economy Bar stopped"));
1254
1288
  } catch {
1255
- console.log(chalk2.yellow("Could not quit Economy Bar gracefully"));
1289
+ console.log(chalk3.yellow("Could not quit Economy Bar gracefully"));
1256
1290
  }
1257
1291
  }
1258
1292
  var APP_PATH = "/Applications/Economy Bar.app", REPO = "hasna/open-economy";
1259
1293
  var init_menubar = () => {};
1260
1294
 
1295
+ // src/cli/index.ts
1296
+ import { Command } from "commander";
1297
+ import chalk4 from "chalk";
1298
+
1299
+ // src/cli/brains.ts
1300
+ import { writeFile, mkdir } from "fs/promises";
1301
+ import { join as join3 } from "path";
1302
+ import chalk from "chalk";
1303
+
1304
+ // src/lib/gatherer.ts
1305
+ init_database();
1306
+ var SYSTEM_PROMPT = "You are a cost-aware AI assistant that tracks API usage, identifies expensive patterns, and helps optimize spending.";
1307
+ var gatherTrainingData = async (options = {}) => {
1308
+ const limit = options.limit ?? 500;
1309
+ const examples = [];
1310
+ try {
1311
+ const db = openDatabase();
1312
+ const periods = ["today", "week", "month", "all"];
1313
+ for (const period of periods) {
1314
+ try {
1315
+ const s = querySummary(db, period);
1316
+ examples.push({
1317
+ messages: [
1318
+ { role: "system", content: SYSTEM_PROMPT },
1319
+ { role: "user", content: `What did I spend on AI ${period === "all" ? "in total" : period}?` },
1320
+ {
1321
+ role: "assistant",
1322
+ content: `${period === "all" ? "Total" : period.charAt(0).toUpperCase() + period.slice(1)} AI spending: $${s.total_usd.toFixed(4)} across ${s.sessions} session(s), ${s.requests} request(s), ${s.tokens.toLocaleString()} tokens.`
1323
+ }
1324
+ ]
1325
+ });
1326
+ } catch {}
1327
+ }
1328
+ const sessions = querySessions(db, {
1329
+ limit: Math.min(Math.floor(limit / 4), 50),
1330
+ since: options.since?.toISOString().substring(0, 10)
1331
+ });
1332
+ for (const s of sessions) {
1333
+ examples.push({
1334
+ messages: [
1335
+ { role: "system", content: SYSTEM_PROMPT },
1336
+ {
1337
+ role: "user",
1338
+ content: `How much did the session "${s.id.substring(0, 12)}" cost?`
1339
+ },
1340
+ {
1341
+ role: "assistant",
1342
+ content: `Session ${s.id.substring(0, 12)} (${s.agent}, project: ${s.project_name || "unknown"}): $${s.total_cost_usd.toFixed(4)}, ${s.total_tokens.toLocaleString()} tokens, ${s.request_count} requests. Started: ${s.started_at.substring(0, 16)}.`
1343
+ }
1344
+ ]
1345
+ });
1346
+ examples.push({
1347
+ messages: [
1348
+ { role: "system", content: SYSTEM_PROMPT },
1349
+ {
1350
+ role: "user",
1351
+ content: `What was the token usage for session ${s.id.substring(0, 12)}?`
1352
+ },
1353
+ {
1354
+ role: "assistant",
1355
+ content: `Session ${s.id.substring(0, 12)} used ${s.total_tokens.toLocaleString()} tokens across ${s.request_count} requests on project "${s.project_name || "unknown"}" (${s.agent}).`
1356
+ }
1357
+ ]
1358
+ });
1359
+ if (examples.length >= limit)
1360
+ break;
1361
+ }
1362
+ const modelBreakdown = queryModelBreakdown(db);
1363
+ if (modelBreakdown.length > 0) {
1364
+ const topModels = modelBreakdown.slice(0, 5);
1365
+ examples.push({
1366
+ messages: [
1367
+ { role: "system", content: SYSTEM_PROMPT },
1368
+ { role: "user", content: "Which AI models have I spent the most on?" },
1369
+ {
1370
+ role: "assistant",
1371
+ content: `Model cost breakdown (top ${topModels.length}):
1372
+ ${topModels.map((m) => `- ${m.model} (${m.agent}): $${m.cost_usd.toFixed(4)}, ${m.requests} requests, ${m.total_tokens.toLocaleString()} tokens`).join(`
1373
+ `)}`
1374
+ }
1375
+ ]
1376
+ });
1377
+ for (const m of topModels) {
1378
+ examples.push({
1379
+ messages: [
1380
+ { role: "system", content: SYSTEM_PROMPT },
1381
+ { role: "user", content: `How much have I spent on ${m.model}?` },
1382
+ {
1383
+ role: "assistant",
1384
+ content: `${m.model} (${m.agent}): $${m.cost_usd.toFixed(4)} total across ${m.requests.toLocaleString()} requests and ${m.total_tokens.toLocaleString()} tokens.`
1385
+ }
1386
+ ]
1387
+ });
1388
+ }
1389
+ }
1390
+ const projectBreakdown = queryProjectBreakdown(db);
1391
+ if (projectBreakdown.length > 0) {
1392
+ const topProjects = projectBreakdown.slice(0, 5);
1393
+ examples.push({
1394
+ messages: [
1395
+ { role: "system", content: SYSTEM_PROMPT },
1396
+ { role: "user", content: "Which projects are costing the most?" },
1397
+ {
1398
+ role: "assistant",
1399
+ content: `Project cost breakdown (top ${topProjects.length}):
1400
+ ${topProjects.map((p) => `- ${p.project_name || "unknown"}: $${p.cost_usd.toFixed(4)}, ${p.sessions} sessions`).join(`
1401
+ `)}`
1402
+ }
1403
+ ]
1404
+ });
1405
+ for (const p of topProjects.slice(0, 3)) {
1406
+ examples.push({
1407
+ messages: [
1408
+ { role: "system", content: SYSTEM_PROMPT },
1409
+ { role: "user", content: `What is the AI spend for project "${p.project_name}"?` },
1410
+ {
1411
+ role: "assistant",
1412
+ content: `Project "${p.project_name}": $${p.cost_usd.toFixed(4)} across ${p.sessions} session(s) and ${p.requests.toLocaleString()} requests. Last active: ${p.last_active?.substring(0, 10) ?? "unknown"}.`
1413
+ }
1414
+ ]
1415
+ });
1416
+ }
1417
+ }
1418
+ try {
1419
+ const budgets = getBudgetStatuses(db);
1420
+ if (budgets.length > 0) {
1421
+ examples.push({
1422
+ messages: [
1423
+ { role: "system", content: SYSTEM_PROMPT },
1424
+ { role: "user", content: "How am I tracking against my AI spending budgets?" },
1425
+ {
1426
+ role: "assistant",
1427
+ content: `Budget status:
1428
+ ${budgets.map((b) => `- ${b.project_path ?? "global"} (${b.period}): $${b.current_spend_usd.toFixed(4)} / $${b.limit_usd.toFixed(2)} (${b.percent_used.toFixed(1)}%) \u2014 ${b.is_over_limit ? "OVER LIMIT" : b.is_over_alert ? "ALERT" : "OK"}`).join(`
1429
+ `)}`
1430
+ }
1431
+ ]
1432
+ });
1433
+ }
1434
+ } catch {}
1435
+ try {
1436
+ const goals = getGoalStatuses(db);
1437
+ if (goals.length > 0) {
1438
+ examples.push({
1439
+ messages: [
1440
+ { role: "system", content: SYSTEM_PROMPT },
1441
+ { role: "user", content: "Am I on track with my AI cost reduction goals?" },
1442
+ {
1443
+ role: "assistant",
1444
+ content: `Goal progress:
1445
+ ${goals.map((g) => `- ${g.period} goal (${g.project_path ?? g.agent ?? "global"}): $${g.current_spend_usd.toFixed(4)} / $${g.limit_usd.toFixed(2)} (${g.percent_used.toFixed(1)}%) \u2014 ${g.is_over ? "OVER" : g.is_at_risk ? "AT RISK" : "ON TRACK"}`).join(`
1446
+ `)}`
1447
+ }
1448
+ ]
1449
+ });
1450
+ }
1451
+ } catch {}
1452
+ const weekSummary = querySummary(db, "week");
1453
+ const monthSummary = querySummary(db, "month");
1454
+ if (weekSummary.total_usd > 0) {
1455
+ const projectedMonthly = weekSummary.total_usd / 7 * 30;
1456
+ examples.push({
1457
+ messages: [
1458
+ { role: "system", content: SYSTEM_PROMPT },
1459
+ { role: "user", content: "Based on my usage, what will I spend this month?" },
1460
+ {
1461
+ role: "assistant",
1462
+ content: `Based on your last 7 days ($${weekSummary.total_usd.toFixed(4)}), projected monthly spend is approximately $${projectedMonthly.toFixed(2)}. Current month-to-date: $${monthSummary.total_usd.toFixed(4)}.`
1463
+ }
1464
+ ]
1465
+ });
1466
+ }
1467
+ if (modelBreakdown.length > 1) {
1468
+ const expensive = modelBreakdown[0];
1469
+ const cheaper = modelBreakdown[modelBreakdown.length - 1];
1470
+ examples.push({
1471
+ messages: [
1472
+ { role: "system", content: SYSTEM_PROMPT },
1473
+ { role: "user", content: "How can I reduce my AI API costs?" },
1474
+ {
1475
+ role: "assistant",
1476
+ content: `Your most expensive model is ${expensive.model} at $${expensive.cost_usd.toFixed(4)}. Consider switching some workloads to ${cheaper.model} ($${cheaper.cost_usd.toFixed(4)}) for cost savings. Cache frequently repeated prompts to reduce cache-miss costs.`
1477
+ }
1478
+ ]
1479
+ });
1480
+ }
1481
+ } catch {}
1482
+ const finalExamples = examples.slice(0, limit);
1483
+ return { source: "economy", examples: finalExamples, count: finalExamples.length };
1484
+ };
1485
+
1486
+ // src/lib/model-config.ts
1487
+ init_database();
1488
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
1489
+ import { join as join2 } from "path";
1490
+ var DEFAULT_MODEL = "gpt-4o-mini";
1491
+ var CONFIG_PATH = join2(getDataDir(), "config.json");
1492
+ function loadConfig() {
1493
+ try {
1494
+ if (existsSync2(CONFIG_PATH)) {
1495
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1496
+ }
1497
+ } catch {}
1498
+ return {};
1499
+ }
1500
+ function saveConfig(config) {
1501
+ const dir = getDataDir();
1502
+ if (!existsSync2(dir))
1503
+ mkdirSync2(dir, { recursive: true });
1504
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
1505
+ `);
1506
+ }
1507
+ function getActiveModel() {
1508
+ return loadConfig().activeModel ?? DEFAULT_MODEL;
1509
+ }
1510
+ function setActiveModel(id) {
1511
+ const config = loadConfig();
1512
+ config.activeModel = id;
1513
+ saveConfig(config);
1514
+ }
1515
+ function clearActiveModel() {
1516
+ const config = loadConfig();
1517
+ delete config.activeModel;
1518
+ saveConfig(config);
1519
+ }
1520
+
1521
+ // src/cli/brains.ts
1522
+ init_database();
1523
+ function registerBrainsCommand(program) {
1524
+ const brainsCmd = program.command("brains").description("Fine-tune an AI model on your economy cost data");
1525
+ brainsCmd.command("gather").description("Gather training data from economy cost data and write to ~/.hasna/economy/training/").option("--limit <n>", "Maximum number of training examples", "500").option("--output <path>", "Output file path (default: ~/.hasna/economy/training/training-<timestamp>.jsonl)").action(async (opts) => {
1526
+ const limit = opts.limit ? parseInt(opts.limit, 10) : 500;
1527
+ console.log(chalk.cyan(`Gathering up to ${limit} training examples from economy data...`));
1528
+ try {
1529
+ const result = await gatherTrainingData({ limit });
1530
+ if (result.count === 0) {
1531
+ console.log(chalk.yellow("No training examples found. Make sure you have cost data synced."));
1532
+ console.log(chalk.dim("Run: economy sync"));
1533
+ return;
1534
+ }
1535
+ const defaultDir = join3(getDataDir(), "training");
1536
+ await mkdir(defaultDir, { recursive: true });
1537
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1538
+ const outputPath = opts.output ?? join3(defaultDir, `training-${timestamp}.jsonl`);
1539
+ const jsonl = result.examples.map((ex) => JSON.stringify(ex)).join(`
1540
+ `);
1541
+ await writeFile(outputPath, jsonl, "utf-8");
1542
+ console.log(chalk.green(`\u2713 Gathered ${result.count} training examples`));
1543
+ console.log(chalk.dim(` Written to: ${outputPath}`));
1544
+ console.log(`
1545
+ ${chalk.dim("Next step:")} economy brains train --base-model gpt-4o-mini`);
1546
+ } catch (e) {
1547
+ console.error(chalk.red(`Error: ${e instanceof Error ? e.message : String(e)}`));
1548
+ process.exit(1);
1549
+ }
1550
+ });
1551
+ brainsCmd.command("train").description("Start a fine-tuning job using gathered training data").option("--base-model <model>", "Base model to fine-tune", "gpt-4o-mini").option("--name <name>", "Name for the fine-tuned model", "economy-assistant").option("--dataset <path>", "Path to JSONL training file (default: latest in ~/.hasna/economy/training/)").action(async (opts) => {
1552
+ const baseModel = opts.baseModel ?? "gpt-4o-mini";
1553
+ const name = opts.name ?? "economy-assistant";
1554
+ console.log(chalk.cyan("Starting fine-tuning job..."));
1555
+ console.log(chalk.dim(` Base model: ${baseModel}`));
1556
+ console.log(chalk.dim(` Name: ${name}`));
1557
+ let datasetPath = opts.dataset;
1558
+ if (!datasetPath) {
1559
+ const { readdirSync: readdirSync2 } = await import("fs");
1560
+ const trainingDir = join3(getDataDir(), "training");
1561
+ try {
1562
+ const files = readdirSync2(trainingDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
1563
+ if (files.length === 0) {
1564
+ console.error(chalk.red("No training data found. Run: economy brains gather"));
1565
+ process.exit(1);
1566
+ }
1567
+ datasetPath = join3(trainingDir, files[0]);
1568
+ console.log(chalk.dim(` Dataset: ${datasetPath}`));
1569
+ } catch {
1570
+ console.error(chalk.red("Training directory not found. Run: economy brains gather"));
1571
+ process.exit(1);
1572
+ }
1573
+ }
1574
+ try {
1575
+ const brains = await import("@hasna/brains");
1576
+ const startFinetune = brains["startFinetune"] ?? brains["start_finetune"];
1577
+ if (typeof startFinetune !== "function") {
1578
+ console.error(chalk.red("@hasna/brains not found or startFinetune not exported."));
1579
+ console.error(chalk.dim("Install with: bun add @hasna/brains"));
1580
+ process.exit(1);
1581
+ }
1582
+ const job = await startFinetune({
1583
+ provider: "openai",
1584
+ baseModel,
1585
+ name,
1586
+ dataset: datasetPath
1587
+ });
1588
+ const jobId = job["id"] ?? job["fine_tune_job_id"] ?? job["jobId"];
1589
+ console.log(chalk.green(`\u2713 Fine-tuning job started: ${String(jobId ?? "unknown")}`));
1590
+ console.log(`
1591
+ ${chalk.dim("Check status:")} economy brains status ${String(jobId ?? "")}`);
1592
+ console.log(`${chalk.dim("When complete, set model:")} economy brains model set <model-id>`);
1593
+ } catch (e) {
1594
+ console.error(chalk.red(`Error starting fine-tune: ${e instanceof Error ? e.message : String(e)}`));
1595
+ process.exit(1);
1596
+ }
1597
+ });
1598
+ const modelCmd = brainsCmd.command("model").description("View or set the active fine-tuned model").action(() => {
1599
+ const active = getActiveModel();
1600
+ const isDefault = active === DEFAULT_MODEL;
1601
+ console.log(`Active model: ${chalk.cyan(active)}${isDefault ? chalk.dim(" (default)") : chalk.green(" (fine-tuned)")}`);
1602
+ if (isDefault) {
1603
+ console.log(chalk.dim(`
1604
+ To set a fine-tuned model: economy brains model set <model-id>`));
1605
+ }
1606
+ });
1607
+ modelCmd.command("set <id>").description("Set the active fine-tuned model ID").action((id) => {
1608
+ setActiveModel(id);
1609
+ console.log(chalk.green(`\u2713 Active model set to: ${id}`));
1610
+ console.log(chalk.dim(" Economy AI analysis will now use this model."));
1611
+ });
1612
+ modelCmd.command("clear").description(`Reset to default model (${DEFAULT_MODEL})`).action(() => {
1613
+ clearActiveModel();
1614
+ console.log(chalk.green(`\u2713 Active model cleared, using default: ${DEFAULT_MODEL}`));
1615
+ });
1616
+ brainsCmd.command("status [job-id]").description("Check the status of a fine-tuning job").option("--provider <provider>", "Provider: openai|thinker-labs", "openai").action(async (jobId, opts) => {
1617
+ if (!jobId) {
1618
+ console.error(chalk.red("Usage: economy brains status <job-id>"));
1619
+ process.exit(1);
1620
+ }
1621
+ try {
1622
+ const brains = await import("@hasna/brains");
1623
+ const getFinetuneStatus = brains["getFinetuneStatus"] ?? brains["get_finetune_status"];
1624
+ if (typeof getFinetuneStatus !== "function") {
1625
+ console.error(chalk.red("@hasna/brains not installed. Run: bun add @hasna/brains"));
1626
+ process.exit(1);
1627
+ }
1628
+ const status = await getFinetuneStatus({
1629
+ jobId,
1630
+ provider: opts.provider ?? "openai"
1631
+ });
1632
+ console.log(`Job ${chalk.cyan(jobId)}:`);
1633
+ console.log(` Status: ${String(status["status"] ?? "unknown")}`);
1634
+ if (status["fine_tuned_model"]) {
1635
+ console.log(` Fine-tuned model: ${chalk.green(String(status["fine_tuned_model"]))}`);
1636
+ console.log(`
1637
+ ${chalk.dim("Set it active:")} economy brains model set ${String(status["fine_tuned_model"])}`);
1638
+ }
1639
+ if (status["error"]) {
1640
+ console.log(chalk.red(` Error: ${String(status["error"])}`));
1641
+ }
1642
+ } catch (e) {
1643
+ console.error(chalk.red(`Error: ${e instanceof Error ? e.message : String(e)}`));
1644
+ process.exit(1);
1645
+ }
1646
+ });
1647
+ }
1648
+
1261
1649
  // src/cli/index.ts
1262
1650
  init_database();
1263
1651
  init_claude();
1264
1652
  init_codex();
1265
- import { Command } from "commander";
1266
- import chalk3 from "chalk";
1267
1653
 
1268
1654
  // src/ingest/gemini.ts
1269
1655
  init_database();
1270
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync2 } from "fs";
1656
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync3 } from "fs";
1271
1657
  import { homedir as homedir4 } from "os";
1272
- import { join as join4 } from "path";
1273
- var GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
1658
+ import { join as join6 } from "path";
1659
+ var GEMINI_TMP_DIR = join6(homedir4(), ".gemini", "tmp");
1274
1660
  async function ingestGemini(db, verbose) {
1275
- if (!existsSync4(GEMINI_TMP_DIR)) {
1661
+ if (!existsSync5(GEMINI_TMP_DIR)) {
1276
1662
  if (verbose)
1277
1663
  console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
1278
1664
  return { sessions: 0 };
@@ -1281,17 +1667,17 @@ async function ingestGemini(db, verbose) {
1281
1667
  const touchedSessions = new Set;
1282
1668
  let projectHashDirs = [];
1283
1669
  try {
1284
- projectHashDirs = readdirSync2(GEMINI_TMP_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && /^[0-9a-f]{64}$/.test(d.name)).map((d) => join4(GEMINI_TMP_DIR, d.name));
1670
+ projectHashDirs = readdirSync3(GEMINI_TMP_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && /^[0-9a-f]{64}$/.test(d.name)).map((d) => join6(GEMINI_TMP_DIR, d.name));
1285
1671
  } catch {
1286
1672
  return { sessions: 0 };
1287
1673
  }
1288
1674
  for (const projectDir of projectHashDirs) {
1289
- const chatsDir = join4(projectDir, "chats");
1290
- if (!existsSync4(chatsDir))
1675
+ const chatsDir = join6(projectDir, "chats");
1676
+ if (!existsSync5(chatsDir))
1291
1677
  continue;
1292
1678
  let chatFiles = [];
1293
1679
  try {
1294
- chatFiles = readdirSync2(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join4(chatsDir, f));
1680
+ chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join6(chatsDir, f));
1295
1681
  } catch {
1296
1682
  continue;
1297
1683
  }
@@ -1299,7 +1685,7 @@ async function ingestGemini(db, verbose) {
1299
1685
  const stateKey = filePath.replace(homedir4(), "~");
1300
1686
  let fileMtime = "0";
1301
1687
  try {
1302
- fileMtime = statSync2(filePath).mtimeMs.toString();
1688
+ fileMtime = statSync3(filePath).mtimeMs.toString();
1303
1689
  } catch {
1304
1690
  continue;
1305
1691
  }
@@ -1308,7 +1694,7 @@ async function ingestGemini(db, verbose) {
1308
1694
  continue;
1309
1695
  let chatData;
1310
1696
  try {
1311
- chatData = JSON.parse(readFileSync3(filePath, "utf-8"));
1697
+ chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
1312
1698
  } catch {
1313
1699
  continue;
1314
1700
  }
@@ -1376,7 +1762,7 @@ function fmt2(usd) {
1376
1762
  } else {
1377
1763
  formatted = "$0.00";
1378
1764
  }
1379
- return chalk3.green(formatted);
1765
+ return chalk4.green(formatted);
1380
1766
  }
1381
1767
  function fmtTokens(n) {
1382
1768
  if (n >= 1e9)
@@ -1421,13 +1807,13 @@ function printSummary(label, period) {
1421
1807
  ensurePricingSeeded(db);
1422
1808
  const s = querySummary(db, period);
1423
1809
  console.log();
1424
- console.log(chalk3.bold.cyan(` ${label}`));
1810
+ console.log(chalk4.bold.cyan(` ${label}`));
1425
1811
  console.log();
1426
1812
  printTable(["Metric", "Value"], [
1427
1813
  ["Total cost", fmt2(s.total_usd)],
1428
- ["Sessions", chalk3.yellow(fmtCount(s.sessions))],
1429
- ["Requests", chalk3.yellow(fmtCount(s.requests))],
1430
- ["Tokens", chalk3.yellow(fmtTokens(s.tokens))]
1814
+ ["Sessions", chalk4.yellow(fmtCount(s.sessions))],
1815
+ ["Requests", chalk4.yellow(fmtCount(s.requests))],
1816
+ ["Tokens", chalk4.yellow(fmtTokens(s.tokens))]
1431
1817
  ]);
1432
1818
  console.log();
1433
1819
  }
@@ -1444,7 +1830,7 @@ program.action(async () => {
1444
1830
  }, {});
1445
1831
  const dailyValues = Object.values(daily);
1446
1832
  console.log();
1447
- console.log(chalk3.bold.cyan(" Economy"));
1833
+ console.log(chalk4.bold.cyan(" Economy"));
1448
1834
  console.log();
1449
1835
  printTable(["Period", "Cost", "Sessions", "Requests", "Tokens"], [
1450
1836
  ["Today", fmt2(t.total_usd), fmtCount(t.sessions), fmtCount(t.requests), fmtTokens(t.tokens)],
@@ -1453,13 +1839,13 @@ program.action(async () => {
1453
1839
  ]);
1454
1840
  if (dailyValues.length > 0) {
1455
1841
  console.log(`
1456
- ${chalk3.dim("14-day trend:")} ${sparkline(dailyValues)}`);
1842
+ ${chalk4.dim("14-day trend:")} ${sparkline(dailyValues)}`);
1457
1843
  }
1458
1844
  if (projects.length > 0) {
1459
1845
  console.log(`
1460
- ${chalk3.dim("Top projects:")}`);
1846
+ ${chalk4.dim("Top projects:")}`);
1461
1847
  for (const p of projects) {
1462
- console.log(` ${chalk3.white(p.project_name.padEnd(25))} ${fmt2(p.cost_usd)}`);
1848
+ console.log(` ${chalk4.white(p.project_name.padEnd(25))} ${fmt2(p.cost_usd)}`);
1463
1849
  }
1464
1850
  }
1465
1851
  console.log();
@@ -1470,32 +1856,32 @@ program.command("sync").description("Ingest cost data from Claude Code, Codex, a
1470
1856
  if (opts.force) {
1471
1857
  db.exec(`DELETE FROM ingest_state WHERE source = 'claude'`);
1472
1858
  if (opts.verbose)
1473
- console.log(chalk3.dim("Cleared ingest cache"));
1859
+ console.log(chalk4.dim("Cleared ingest cache"));
1474
1860
  }
1475
1861
  const anySpecific = opts.claude || opts.codex || opts.gemini;
1476
1862
  const doClaude = opts.claude || !anySpecific;
1477
1863
  const doCodex = opts.codex || !anySpecific;
1478
1864
  const doGemini = opts.gemini || !anySpecific;
1479
1865
  if (doClaude) {
1480
- process.stdout.write(chalk3.cyan("\u2192 Ingesting Claude Code telemetry... "));
1866
+ process.stdout.write(chalk4.cyan("\u2192 Ingesting Claude Code telemetry... "));
1481
1867
  const r = await ingestClaude(db, opts.verbose);
1482
- console.log(chalk3.green(`\u2713 ${r.files} files, ${r.requests} requests, ${r.sessions} sessions`));
1868
+ console.log(chalk4.green(`\u2713 ${r.files} files, ${r.requests} requests, ${r.sessions} sessions`));
1483
1869
  }
1484
1870
  if (doCodex) {
1485
- process.stdout.write(chalk3.cyan("\u2192 Ingesting Codex sessions... "));
1871
+ process.stdout.write(chalk4.cyan("\u2192 Ingesting Codex sessions... "));
1486
1872
  const r = await ingestCodex(db, opts.verbose);
1487
- console.log(chalk3.green(`\u2713 ${r.sessions} sessions`));
1873
+ console.log(chalk4.green(`\u2713 ${r.sessions} sessions`));
1488
1874
  }
1489
1875
  if (doGemini) {
1490
- process.stdout.write(chalk3.cyan("\u2192 Ingesting Gemini CLI sessions... "));
1876
+ process.stdout.write(chalk4.cyan("\u2192 Ingesting Gemini CLI sessions... "));
1491
1877
  const r = await ingestGemini(db, opts.verbose);
1492
- console.log(chalk3.green(`\u2713 ${r.sessions} sessions`));
1878
+ console.log(chalk4.green(`\u2713 ${r.sessions} sessions`));
1493
1879
  }
1494
1880
  try {
1495
1881
  const { checkAndFireWebhooks: checkAndFireWebhooks2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
1496
1882
  await checkAndFireWebhooks2(db);
1497
1883
  } catch {}
1498
- console.log(chalk3.bold.green(`
1884
+ console.log(chalk4.bold.green(`
1499
1885
  \u2713 Sync complete`));
1500
1886
  });
1501
1887
  program.command("today").description("Cost summary for today").action(async () => {
@@ -1522,7 +1908,7 @@ program.command("sessions").description("List coding sessions with costs").optio
1522
1908
  search: opts.search
1523
1909
  });
1524
1910
  if (sessions.length === 0) {
1525
- console.log(chalk3.yellow("No sessions found."));
1911
+ console.log(chalk4.yellow("No sessions found."));
1526
1912
  return;
1527
1913
  }
1528
1914
  const f = opts.format ?? "table";
@@ -1544,13 +1930,13 @@ program.command("sessions").description("List coding sessions with costs").optio
1544
1930
  }
1545
1931
  console.log();
1546
1932
  printTable(["Session ID", "Agent", "Project", "Cost", "Tokens", "Requests", "Started"], sessions.map((s) => [
1547
- chalk3.dim(s.id.substring(0, 12)),
1548
- s.agent === "claude" ? chalk3.blue("claude") : chalk3.yellow("codex"),
1549
- chalk3.white(s.project_name || chalk3.dim("unknown")),
1933
+ chalk4.dim(s.id.substring(0, 12)),
1934
+ s.agent === "claude" ? chalk4.blue("claude") : chalk4.yellow("codex"),
1935
+ chalk4.white(s.project_name || chalk4.dim("unknown")),
1550
1936
  fmt2(s.total_cost_usd),
1551
- chalk3.cyan(fmtTokens(s.total_tokens)),
1937
+ chalk4.cyan(fmtTokens(s.total_tokens)),
1552
1938
  fmtCount(s.request_count),
1553
- chalk3.dim(s.started_at.substring(0, 16))
1939
+ chalk4.dim(s.started_at.substring(0, 16))
1554
1940
  ]));
1555
1941
  console.log();
1556
1942
  });
@@ -1561,17 +1947,17 @@ program.command("top").description("Most expensive sessions").option("-n <n>", "
1561
1947
  if (sinceDate)
1562
1948
  sessions = sessions.filter((s) => s.started_at >= sinceDate);
1563
1949
  if (sessions.length === 0) {
1564
- console.log(chalk3.yellow("No sessions found. Run `economy sync` first."));
1950
+ console.log(chalk4.yellow("No sessions found. Run `economy sync` first."));
1565
1951
  return;
1566
1952
  }
1567
1953
  console.log();
1568
1954
  printTable(["#", "Project", "Agent", "Cost", "Tokens", "Started"], sessions.map((s, i) => [
1569
- chalk3.dim(String(i + 1)),
1570
- chalk3.white(s.project_name || chalk3.dim("unknown")),
1571
- s.agent === "claude" ? chalk3.blue("claude") : chalk3.yellow("codex"),
1955
+ chalk4.dim(String(i + 1)),
1956
+ chalk4.white(s.project_name || chalk4.dim("unknown")),
1957
+ s.agent === "claude" ? chalk4.blue("claude") : chalk4.yellow("codex"),
1572
1958
  fmt2(s.total_cost_usd),
1573
- chalk3.cyan(fmtTokens(s.total_tokens)),
1574
- chalk3.dim(s.started_at.substring(0, 16))
1959
+ chalk4.cyan(fmtTokens(s.total_tokens)),
1960
+ chalk4.dim(s.started_at.substring(0, 16))
1575
1961
  ]));
1576
1962
  console.log();
1577
1963
  });
@@ -1591,10 +1977,10 @@ program.command("breakdown").description("Cost breakdown by model, agent, or pro
1591
1977
  GROUP BY project_path ORDER BY cost_usd DESC
1592
1978
  `).all(sinceDate) : queryProjectBreakdown(db);
1593
1979
  printTable(["Project", "Sessions", "Requests", "Tokens", "Cost"], rows.map((r) => [
1594
- chalk3.white(r.project_name || chalk3.dim("unknown")),
1980
+ chalk4.white(r.project_name || chalk4.dim("unknown")),
1595
1981
  String(r.sessions),
1596
1982
  String(r.requests),
1597
- chalk3.cyan(fmtTokens(r.total_tokens)),
1983
+ chalk4.cyan(fmtTokens(r.total_tokens)),
1598
1984
  fmt2(r.cost_usd)
1599
1985
  ]));
1600
1986
  } else {
@@ -1609,10 +1995,10 @@ program.command("breakdown").description("Cost breakdown by model, agent, or pro
1609
1995
  GROUP BY model, agent ORDER BY cost_usd DESC
1610
1996
  `).all(sinceDate) : queryModelBreakdown(db);
1611
1997
  printTable(["Model", "Agent", "Requests", "Tokens", "Cost"], rows.map((r) => [
1612
- chalk3.white(r.model),
1613
- r.agent === "claude" ? chalk3.blue("claude") : chalk3.yellow("codex"),
1998
+ chalk4.white(r.model),
1999
+ r.agent === "claude" ? chalk4.blue("claude") : chalk4.yellow("codex"),
1614
2000
  String(r.requests),
1615
- chalk3.cyan(fmtTokens(r.total_tokens)),
2001
+ chalk4.cyan(fmtTokens(r.total_tokens)),
1616
2002
  fmt2(r.cost_usd)
1617
2003
  ]));
1618
2004
  }
@@ -1625,7 +2011,7 @@ program.command("watch").description("Live stream of incoming costs").option("--
1625
2011
  var budgetCmd = program.command("budget").description("Manage spending budgets");
1626
2012
  budgetCmd.command("set").description("Set a budget").option("--project <path>", "Project path (omit for global)").option("--period <period>", "Period: daily|weekly|monthly", "monthly").option("--limit <usd>", "Budget limit in USD").option("--alert <percent>", "Alert threshold %", "80").option("--agent <agent>", "Limit to agent (claude|codex)").action((opts) => {
1627
2013
  if (!opts.limit) {
1628
- console.error(chalk3.red("--limit is required"));
2014
+ console.error(chalk4.red("--limit is required"));
1629
2015
  process.exit(1);
1630
2016
  }
1631
2017
  const db = openDatabase();
@@ -1640,22 +2026,22 @@ budgetCmd.command("set").description("Set a budget").option("--project <path>",
1640
2026
  created_at: now,
1641
2027
  updated_at: now
1642
2028
  });
1643
- console.log(chalk3.green(`\u2713 Budget set: ${opts.project ?? "global"} \u2014 ${opts.period} $${opts.limit}`));
2029
+ console.log(chalk4.green(`\u2713 Budget set: ${opts.project ?? "global"} \u2014 ${opts.period} $${opts.limit}`));
1644
2030
  });
1645
2031
  budgetCmd.command("list").description("List all budgets").action(() => {
1646
2032
  const db = openDatabase();
1647
2033
  const statuses = getBudgetStatuses(db);
1648
2034
  if (statuses.length === 0) {
1649
- console.log(chalk3.yellow("No budgets set."));
2035
+ console.log(chalk4.yellow("No budgets set."));
1650
2036
  return;
1651
2037
  }
1652
2038
  console.log();
1653
2039
  printTable(["Scope", "Period", "Limit", "Spent", "Used%", "Status"], statuses.map((b) => {
1654
2040
  const pct = b.percent_used.toFixed(1);
1655
- const status = b.is_over_limit ? chalk3.red("OVER") : b.is_over_alert ? chalk3.yellow("ALERT") : chalk3.green("OK");
1656
- const pctColor = b.is_over_limit ? chalk3.red(pct + "%") : b.is_over_alert ? chalk3.yellow(pct + "%") : chalk3.green(pct + "%");
2041
+ const status = b.is_over_limit ? chalk4.red("OVER") : b.is_over_alert ? chalk4.yellow("ALERT") : chalk4.green("OK");
2042
+ const pctColor = b.is_over_limit ? chalk4.red(pct + "%") : b.is_over_alert ? chalk4.yellow(pct + "%") : chalk4.green(pct + "%");
1657
2043
  return [
1658
- chalk3.white(b.project_path ?? "global"),
2044
+ chalk4.white(b.project_path ?? "global"),
1659
2045
  b.period,
1660
2046
  fmt2(b.limit_usd),
1661
2047
  fmt2(b.current_spend_usd),
@@ -1668,7 +2054,7 @@ budgetCmd.command("list").description("List all budgets").action(() => {
1668
2054
  budgetCmd.command("remove <id>").description("Remove a budget by ID").action((id) => {
1669
2055
  const db = openDatabase();
1670
2056
  deleteBudget(db, id);
1671
- console.log(chalk3.green(`\u2713 Budget removed`));
2057
+ console.log(chalk4.green(`\u2713 Budget removed`));
1672
2058
  });
1673
2059
  var projectCmd = program.command("project").description("Manage tracked projects");
1674
2060
  projectCmd.command("add <path>").description("Add a project").option("--name <name>", "Human-readable name").action((path, opts) => {
@@ -1682,46 +2068,46 @@ projectCmd.command("add <path>").description("Add a project").option("--name <na
1682
2068
  tags: [],
1683
2069
  created_at: new Date().toISOString()
1684
2070
  });
1685
- console.log(chalk3.green(`\u2713 Project added: ${path}`));
2071
+ console.log(chalk4.green(`\u2713 Project added: ${path}`));
1686
2072
  });
1687
2073
  projectCmd.command("list").description("List all projects with costs").action(() => {
1688
2074
  const db = openDatabase();
1689
2075
  const projects = queryProjectBreakdown(db);
1690
2076
  if (projects.length === 0) {
1691
- console.log(chalk3.yellow("No projects tracked yet."));
2077
+ console.log(chalk4.yellow("No projects tracked yet."));
1692
2078
  return;
1693
2079
  }
1694
2080
  console.log();
1695
2081
  printTable(["Project", "Path", "Sessions", "Cost", "Last Active"], projects.map((p) => [
1696
- chalk3.white(p.project_name || chalk3.dim("unknown")),
1697
- chalk3.dim(p.project_path.substring(0, 40)),
2082
+ chalk4.white(p.project_name || chalk4.dim("unknown")),
2083
+ chalk4.dim(p.project_path.substring(0, 40)),
1698
2084
  String(p.sessions),
1699
2085
  fmt2(p.cost_usd),
1700
- chalk3.dim(p.last_active?.substring(0, 16) ?? "\u2014")
2086
+ chalk4.dim(p.last_active?.substring(0, 16) ?? "\u2014")
1701
2087
  ]));
1702
2088
  console.log();
1703
2089
  });
1704
2090
  projectCmd.command("remove <path>").description("Remove a project (keeps historical data)").action((path) => {
1705
2091
  const db = openDatabase();
1706
2092
  deleteProject(db, path);
1707
- console.log(chalk3.green(`\u2713 Project removed`));
2093
+ console.log(chalk4.green(`\u2713 Project removed`));
1708
2094
  });
1709
2095
  projectCmd.command("rename <path> <name>").description("Rename a project").action((path, name) => {
1710
2096
  const db = openDatabase();
1711
2097
  const existing = getProject(db, path);
1712
2098
  if (!existing) {
1713
- console.error(chalk3.red("Project not found"));
2099
+ console.error(chalk4.red("Project not found"));
1714
2100
  process.exit(1);
1715
2101
  }
1716
2102
  upsertProject(db, { ...existing, name });
1717
- console.log(chalk3.green(`\u2713 Renamed to: ${name}`));
2103
+ console.log(chalk4.green(`\u2713 Renamed to: ${name}`));
1718
2104
  });
1719
2105
  projectCmd.command("show <nameOrPath>").description("Detailed project breakdown with sparkline").action(async (nameOrPath) => {
1720
2106
  await autoSync();
1721
2107
  const db = openDatabase();
1722
2108
  const sessions = db.prepare(`SELECT * FROM sessions WHERE project_name LIKE ? OR project_path LIKE ? ORDER BY started_at DESC`).all(`%${nameOrPath}%`, `%${nameOrPath}%`);
1723
2109
  if (sessions.length === 0) {
1724
- console.log(chalk3.yellow(`No sessions found for: ${nameOrPath}`));
2110
+ console.log(chalk4.yellow(`No sessions found for: ${nameOrPath}`));
1725
2111
  return;
1726
2112
  }
1727
2113
  const projectName = sessions[0]["project_name"] || nameOrPath;
@@ -1743,8 +2129,8 @@ projectCmd.command("show <nameOrPath>").description("Detailed project breakdown
1743
2129
  GROUP BY r.model ORDER BY cost DESC LIMIT 5
1744
2130
  `).all(`%${nameOrPath}%`, `%${nameOrPath}%`);
1745
2131
  console.log();
1746
- console.log(chalk3.bold.cyan(` ${projectName}`));
1747
- console.log(chalk3.dim(` ${projectPath}`));
2132
+ console.log(chalk4.bold.cyan(` ${projectName}`));
2133
+ console.log(chalk4.dim(` ${projectPath}`));
1748
2134
  console.log();
1749
2135
  printTable(["Metric", "Value"], [
1750
2136
  ["Total cost", fmt2(totalCost)],
@@ -1753,21 +2139,21 @@ projectCmd.command("show <nameOrPath>").description("Detailed project breakdown
1753
2139
  ]);
1754
2140
  if (dailyValues.length > 0) {
1755
2141
  console.log(`
1756
- ${chalk3.dim("14-day trend:")} ${sparkline(dailyValues)}`);
2142
+ ${chalk4.dim("14-day trend:")} ${sparkline(dailyValues)}`);
1757
2143
  }
1758
2144
  if (models.length > 0) {
1759
2145
  console.log(`
1760
- ${chalk3.dim("Model breakdown:")}`);
2146
+ ${chalk4.dim("Model breakdown:")}`);
1761
2147
  for (const m of models) {
1762
- console.log(` ${chalk3.white(m.model.padEnd(30))} ${fmt2(m.cost)} (${fmtCount(m.reqs)} reqs)`);
2148
+ console.log(` ${chalk4.white(m.model.padEnd(30))} ${fmt2(m.cost)} (${fmtCount(m.reqs)} reqs)`);
1763
2149
  }
1764
2150
  }
1765
2151
  const topSessions = sessions.sort((a, b) => b["total_cost_usd"] - a["total_cost_usd"]).slice(0, 5);
1766
2152
  if (topSessions.length > 0) {
1767
2153
  console.log(`
1768
- ${chalk3.dim("Top sessions:")}`);
2154
+ ${chalk4.dim("Top sessions:")}`);
1769
2155
  for (const s of topSessions) {
1770
- console.log(` ${chalk3.dim(s["id"].substring(0, 12))} ${fmt2(s["total_cost_usd"])} ${chalk3.dim(String(s["started_at"]).substring(0, 16))}`);
2156
+ console.log(` ${chalk4.dim(s["id"].substring(0, 12))} ${fmt2(s["total_cost_usd"])} ${chalk4.dim(String(s["started_at"]).substring(0, 16))}`);
1771
2157
  }
1772
2158
  }
1773
2159
  console.log();
@@ -1776,18 +2162,18 @@ var configCmd = program.command("config").description("Manage economy configurat
1776
2162
  configCmd.command("set <key> <value>").description("Set a config value").action(async (_key, _value) => {
1777
2163
  const { setConfigValue: setConfigValue2 } = await Promise.resolve().then(() => (init_config(), exports_config));
1778
2164
  setConfigValue2(_key, _value);
1779
- console.log(chalk3.green(`\u2713 ${_key} = ${_value}`));
2165
+ console.log(chalk4.green(`\u2713 ${_key} = ${_value}`));
1780
2166
  });
1781
2167
  configCmd.command("get <key>").description("Get a config value").action(async (key) => {
1782
2168
  const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_config(), exports_config));
1783
- console.log(getConfigValue2(key) ?? chalk3.dim("(not set)"));
2169
+ console.log(getConfigValue2(key) ?? chalk4.dim("(not set)"));
1784
2170
  });
1785
2171
  configCmd.command("webhook-test").description("Send a test payload to the configured webhook URL").action(async () => {
1786
- const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
1787
- const config = loadConfig2();
2172
+ const { loadConfig: loadConfig3 } = await Promise.resolve().then(() => (init_config(), exports_config));
2173
+ const config = loadConfig3();
1788
2174
  const url = config["webhook-url"];
1789
2175
  if (!url) {
1790
- console.log(chalk3.yellow("No webhook-url configured. Run: economy config set webhook-url <url>"));
2176
+ console.log(chalk4.yellow("No webhook-url configured. Run: economy config set webhook-url <url>"));
1791
2177
  return;
1792
2178
  }
1793
2179
  const payload = {
@@ -1804,21 +2190,21 @@ configCmd.command("webhook-test").description("Send a test payload to the config
1804
2190
  });
1805
2191
  const text = await res.text().catch(() => "");
1806
2192
  if (res.ok) {
1807
- console.log(chalk3.green(`\u2713 Webhook responded: HTTP ${res.status}`));
2193
+ console.log(chalk4.green(`\u2713 Webhook responded: HTTP ${res.status}`));
1808
2194
  if (text)
1809
- console.log(chalk3.dim(text.slice(0, 200)));
2195
+ console.log(chalk4.dim(text.slice(0, 200)));
1810
2196
  } else {
1811
- console.log(chalk3.red(`\u2717 Webhook failed: HTTP ${res.status}`));
2197
+ console.log(chalk4.red(`\u2717 Webhook failed: HTTP ${res.status}`));
1812
2198
  if (text)
1813
- console.log(chalk3.dim(text.slice(0, 200)));
2199
+ console.log(chalk4.dim(text.slice(0, 200)));
1814
2200
  }
1815
2201
  } catch (e) {
1816
- console.log(chalk3.red(`\u2717 Request failed: ${e instanceof Error ? e.message : String(e)}`));
2202
+ console.log(chalk4.red(`\u2717 Request failed: ${e instanceof Error ? e.message : String(e)}`));
1817
2203
  }
1818
2204
  });
1819
2205
  configCmd.action(async () => {
1820
- const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
1821
- const config = loadConfig2();
2206
+ const { loadConfig: loadConfig3 } = await Promise.resolve().then(() => (init_config(), exports_config));
2207
+ const config = loadConfig3();
1822
2208
  console.log();
1823
2209
  printTable(["Key", "Value"], Object.entries(config).map(([k, v]) => [k, String(v)]));
1824
2210
  console.log();
@@ -1830,18 +2216,18 @@ pricingCmd.command("list").description("List all model prices").action(() => {
1830
2216
  const rows = listModelPricing(db);
1831
2217
  console.log();
1832
2218
  printTable(["Model", "Input/1M", "Output/1M", "CacheR/1M", "CacheW/1M", "Out/1k"], rows.map((r) => [
1833
- chalk3.white(r.model),
2219
+ chalk4.white(r.model),
1834
2220
  fmt2(r.input_per_1m),
1835
2221
  fmt2(r.output_per_1m),
1836
2222
  fmt2(r.cache_read_per_1m),
1837
2223
  fmt2(r.cache_write_per_1m),
1838
- chalk3.dim(fmt2(r.output_per_1m / 1000))
2224
+ chalk4.dim(fmt2(r.output_per_1m / 1000))
1839
2225
  ]));
1840
2226
  console.log();
1841
2227
  });
1842
2228
  pricingCmd.command("set <model>").description("Set pricing for a model").option("--input <usd>", "Input price per 1M tokens").option("--output <usd>", "Output price per 1M tokens").option("--cache-read <usd>", "Cache read price per 1M tokens", "0").option("--cache-write <usd>", "Cache write price per 1M tokens", "0").action((model, opts) => {
1843
2229
  if (!opts.input || !opts.output) {
1844
- console.error(chalk3.red("--input and --output are required"));
2230
+ console.error(chalk4.red("--input and --output are required"));
1845
2231
  process.exit(1);
1846
2232
  }
1847
2233
  const db = openDatabase();
@@ -1854,12 +2240,12 @@ pricingCmd.command("set <model>").description("Set pricing for a model").option(
1854
2240
  cache_write_per_1m: Number(opts.cacheWrite ?? 0),
1855
2241
  updated_at: new Date().toISOString()
1856
2242
  });
1857
- console.log(chalk3.green(`\u2713 Pricing updated for ${model}`));
2243
+ console.log(chalk4.green(`\u2713 Pricing updated for ${model}`));
1858
2244
  });
1859
2245
  pricingCmd.command("remove <model>").description("Remove pricing for a model").action((model) => {
1860
2246
  const db = openDatabase();
1861
2247
  deleteModelPricing(db, model);
1862
- console.log(chalk3.green(`\u2713 Pricing removed for ${model}`));
2248
+ console.log(chalk4.green(`\u2713 Pricing removed for ${model}`));
1863
2249
  });
1864
2250
  program.command("serve").description("Start the REST API server").option("-p, --port <port>", "Port", "3456").action(async (opts) => {
1865
2251
  const port = Number(opts.port ?? 3456);
@@ -1875,7 +2261,7 @@ program.command("dashboard").description("Open the web dashboard (auto-starts se
1875
2261
  serverRunning = res.ok;
1876
2262
  } catch {}
1877
2263
  if (!serverRunning) {
1878
- console.log(chalk3.cyan(`\u2192 Starting economy server on port ${port}...`));
2264
+ console.log(chalk4.cyan(`\u2192 Starting economy server on port ${port}...`));
1879
2265
  const { spawn } = await import("child_process");
1880
2266
  const { resolve, dirname } = await import("path");
1881
2267
  const serveScript = resolve(dirname(process.argv[1]), "..", "server", "index.js");
@@ -1898,29 +2284,29 @@ program.command("dashboard").description("Open the web dashboard (auto-starts se
1898
2284
  attempts++;
1899
2285
  }
1900
2286
  if (serverRunning) {
1901
- console.log(chalk3.green(`\u2713 Server started`));
2287
+ console.log(chalk4.green(`\u2713 Server started`));
1902
2288
  } else {
1903
- console.log(chalk3.yellow(`\u26A0 Server didn't respond \u2014 open ${url} manually after running \`economy serve\``));
2289
+ console.log(chalk4.yellow(`\u26A0 Server didn't respond \u2014 open ${url} manually after running \`economy serve\``));
1904
2290
  }
1905
2291
  }
1906
- console.log(chalk3.cyan(`Opening ${url}`));
2292
+ console.log(chalk4.cyan(`Opening ${url}`));
1907
2293
  try {
1908
2294
  execSync2(`open ${url}`);
1909
2295
  } catch {
1910
- console.log(chalk3.yellow(`Open your browser at ${url}`));
2296
+ console.log(chalk4.yellow(`Open your browser at ${url}`));
1911
2297
  }
1912
2298
  });
1913
2299
  program.command("mcp").description("Show MCP server install commands").option("--claude", "Install into Claude Code").option("--codex", "Install into Codex").option("--all", "Install into all agents").action(async (opts) => {
1914
2300
  const doAll = opts.all || !opts.claude && !opts.codex;
1915
2301
  if (opts.claude || doAll) {
1916
- console.log(chalk3.bold.cyan(`
2302
+ console.log(chalk4.bold.cyan(`
1917
2303
  Claude Code:`));
1918
- console.log(chalk3.white(" claude mcp add --transport stdio --scope user economy -- economy-mcp"));
2304
+ console.log(chalk4.white(" claude mcp add --transport stdio --scope user economy -- economy-mcp"));
1919
2305
  }
1920
2306
  if (opts.codex || doAll) {
1921
- console.log(chalk3.bold.yellow(`
2307
+ console.log(chalk4.bold.yellow(`
1922
2308
  Codex (~/.codex/config.toml):`));
1923
- console.log(chalk3.white(` [mcp_servers.economy]
2309
+ console.log(chalk4.white(` [mcp_servers.economy]
1924
2310
  command = "economy-mcp"
1925
2311
  args = []`));
1926
2312
  }
@@ -1931,12 +2317,12 @@ program.command("session <id>").description("Show detailed breakdown of a single
1931
2317
  const db = openDatabase();
1932
2318
  const session = db.prepare(`SELECT * FROM sessions WHERE id = ? OR id LIKE ?`).get(id, `%${id}%`);
1933
2319
  if (!session) {
1934
- console.log(chalk3.red(`Session not found: ${id}`));
2320
+ console.log(chalk4.red(`Session not found: ${id}`));
1935
2321
  process.exit(1);
1936
2322
  }
1937
2323
  const requests = db.prepare(`SELECT * FROM requests WHERE session_id = ? ORDER BY timestamp ASC`).all(session["id"]);
1938
2324
  console.log();
1939
- console.log(chalk3.bold.cyan(` Session: ${session["id"].substring(0, 16)}...`));
2325
+ console.log(chalk4.bold.cyan(` Session: ${session["id"].substring(0, 16)}...`));
1940
2326
  console.log();
1941
2327
  printTable(["Field", "Value"], [
1942
2328
  ["Agent", String(session["agent"])],
@@ -1948,12 +2334,12 @@ program.command("session <id>").description("Show detailed breakdown of a single
1948
2334
  ["Requests", fmtCount(session["request_count"])]
1949
2335
  ]);
1950
2336
  if (requests.length > 0) {
1951
- console.log(chalk3.dim(`
2337
+ console.log(chalk4.dim(`
1952
2338
  Requests (${requests.length}):
1953
2339
  `));
1954
2340
  printTable(["Time", "Model", "Input", "Output", "Cache R", "Cache W", "Cost"], requests.slice(0, 50).map((r) => [
1955
- chalk3.dim(String(r["timestamp"]).substring(11, 19)),
1956
- chalk3.white(String(r["model"]).substring(0, 22)),
2341
+ chalk4.dim(String(r["timestamp"]).substring(11, 19)),
2342
+ chalk4.white(String(r["model"]).substring(0, 22)),
1957
2343
  fmtTokens(r["input_tokens"]),
1958
2344
  fmtTokens(r["output_tokens"]),
1959
2345
  fmtTokens(r["cache_read_tokens"]),
@@ -1961,7 +2347,7 @@ program.command("session <id>").description("Show detailed breakdown of a single
1961
2347
  fmt2(r["cost_usd"])
1962
2348
  ]));
1963
2349
  if (requests.length > 50)
1964
- console.log(chalk3.dim(` ... and ${requests.length - 50} more requests`));
2350
+ console.log(chalk4.dim(` ... and ${requests.length - 50} more requests`));
1965
2351
  }
1966
2352
  console.log();
1967
2353
  });
@@ -1989,9 +2375,9 @@ program.command("export").description("Export data as CSV").option("--type <type
1989
2375
  }
1990
2376
  }
1991
2377
  if (opts.output) {
1992
- const { writeFileSync: writeFileSync3 } = await import("fs");
1993
- writeFileSync3(opts.output, csv);
1994
- console.log(chalk3.green(`\u2713 Exported to ${opts.output}`));
2378
+ const { writeFileSync: writeFileSync4 } = await import("fs");
2379
+ writeFileSync4(opts.output, csv);
2380
+ console.log(chalk4.green(`\u2713 Exported to ${opts.output}`));
1995
2381
  } else {
1996
2382
  process.stdout.write(csv);
1997
2383
  }
@@ -2052,11 +2438,11 @@ program.command("compare <period1> <period2>").description("Compare two periods
2052
2438
  const d = v1 - v2;
2053
2439
  const pct = v2 > 0 ? (d / v2 * 100).toFixed(1) : "\u2014";
2054
2440
  const sign = d >= 0 ? "+" : "";
2055
- const color = d > 0 ? chalk3.red : d < 0 ? chalk3.green : chalk3.dim;
2441
+ const color = d > 0 ? chalk4.red : d < 0 ? chalk4.green : chalk4.dim;
2056
2442
  return color(`${sign}${pct}%`);
2057
2443
  }
2058
2444
  console.log();
2059
- console.log(chalk3.bold.cyan(` ${p1} vs ${p2}`));
2445
+ console.log(chalk4.bold.cyan(` ${p1} vs ${p2}`));
2060
2446
  console.log();
2061
2447
  printTable(["Metric", p1, p2, "Change"], [
2062
2448
  ["Cost", fmt2(a.cost), fmt2(b.cost), delta(a.cost, b.cost)],
@@ -2085,12 +2471,12 @@ program.command("forecast").description("Project end-of-month cost based on curr
2085
2471
  const cheapest = dailyCosts[0];
2086
2472
  const mostExpensive = dailyCosts[dailyCosts.length - 1];
2087
2473
  console.log();
2088
- console.log(chalk3.bold.cyan(` Forecast (${dayOfMonth} of ${daysInMonth} days)`));
2474
+ console.log(chalk4.bold.cyan(` Forecast (${dayOfMonth} of ${daysInMonth} days)`));
2089
2475
  console.log();
2090
2476
  printTable(["Metric", "Value"], [
2091
2477
  ["Spent so far", fmt2(monthSoFar.cost)],
2092
2478
  ["Daily average", fmt2(dailyAvg)],
2093
- [chalk3.bold("Projected total"), chalk3.bold(fmt2(projected).replace(chalk3.green(""), ""))],
2479
+ [chalk4.bold("Projected total"), chalk4.bold(fmt2(projected).replace(chalk4.green(""), ""))],
2094
2480
  ["Last 7-day rate", `${fmt2(last7DailyAvg)}/day \u2192 ${fmt2(last7Projected)}`],
2095
2481
  ["Cheapest day", cheapest ? `${fmt2(cheapest.cost)} (${cheapest.d})` : "\u2014"],
2096
2482
  ["Most expensive", mostExpensive ? `${fmt2(mostExpensive.cost)} (${mostExpensive.d})` : "\u2014"]
@@ -2107,14 +2493,14 @@ program.command("efficiency").description("Show output/input token ratio per mod
2107
2493
  FROM requests GROUP BY model ORDER BY cost DESC
2108
2494
  `).all();
2109
2495
  console.log();
2110
- console.log(chalk3.bold.cyan(" Token Efficiency"));
2496
+ console.log(chalk4.bold.cyan(" Token Efficiency"));
2111
2497
  console.log();
2112
2498
  printTable(["Model", "Output/Input", "Cache Hit%", "Cost/1k Output", "Requests"], models.map((m) => {
2113
2499
  const ratio = m.input > 0 ? (m.output / m.input).toFixed(2) : "\u2014";
2114
2500
  const totalInput = m.input + m.cache_read + m.cache_write;
2115
2501
  const cacheHit = totalInput > 0 ? (m.cache_read / totalInput * 100).toFixed(1) + "%" : "\u2014";
2116
2502
  const costPer1kOutput = m.output > 0 ? fmt2(m.cost / m.output * 1000) : "\u2014";
2117
- return [chalk3.white(m.model), ratio, cacheHit, costPer1kOutput, fmtCount(m.requests)];
2503
+ return [chalk4.white(m.model), ratio, cacheHit, costPer1kOutput, fmtCount(m.requests)];
2118
2504
  }));
2119
2505
  console.log();
2120
2506
  });
@@ -2138,7 +2524,7 @@ menubarCmd.command("stop").description("Quit Economy Bar").action(async () => {
2138
2524
  var goalCmd = program.command("goal").description("Manage spending goals");
2139
2525
  goalCmd.command("set").description("Set a spending goal").option("--period <period>", "Period: day|week|month|year", "month").option("--limit <usd>", "Goal limit in USD").option("--project <path>", "Scope to project path").option("--agent <agent>", "Scope to agent").action((opts) => {
2140
2526
  if (!opts.limit) {
2141
- console.error(chalk3.red("--limit is required"));
2527
+ console.error(chalk4.red("--limit is required"));
2142
2528
  process.exit(1);
2143
2529
  }
2144
2530
  const db = openDatabase();
@@ -2152,24 +2538,24 @@ goalCmd.command("set").description("Set a spending goal").option("--period <peri
2152
2538
  created_at: now,
2153
2539
  updated_at: now
2154
2540
  });
2155
- console.log(chalk3.green(`\u2713 Goal set: ${opts.period ?? "month"} $${opts.limit}${opts.project ? ` (${opts.project})` : ""}`));
2541
+ console.log(chalk4.green(`\u2713 Goal set: ${opts.period ?? "month"} $${opts.limit}${opts.project ? ` (${opts.project})` : ""}`));
2156
2542
  });
2157
2543
  goalCmd.command("list").description("List all goals with progress").action(() => {
2158
2544
  const db = openDatabase();
2159
2545
  const statuses = getGoalStatuses(db);
2160
2546
  if (statuses.length === 0) {
2161
- console.log(chalk3.yellow("No goals set."));
2547
+ console.log(chalk4.yellow("No goals set."));
2162
2548
  return;
2163
2549
  }
2164
2550
  console.log();
2165
2551
  printTable(["Period", "Scope", "Limit", "Spent", "Used%", "Status"], statuses.map((g) => {
2166
2552
  const pct = g.percent_used.toFixed(1);
2167
2553
  const scope = g.project_path ?? g.agent ?? "global";
2168
- const status = g.is_over ? chalk3.red("OVER") : g.is_at_risk ? chalk3.yellow("AT RISK") : chalk3.green("ON TRACK");
2169
- const pctColor = g.is_over ? chalk3.red(pct + "%") : g.is_at_risk ? chalk3.yellow(pct + "%") : chalk3.green(pct + "%");
2554
+ const status = g.is_over ? chalk4.red("OVER") : g.is_at_risk ? chalk4.yellow("AT RISK") : chalk4.green("ON TRACK");
2555
+ const pctColor = g.is_over ? chalk4.red(pct + "%") : g.is_at_risk ? chalk4.yellow(pct + "%") : chalk4.green(pct + "%");
2170
2556
  return [
2171
2557
  g.period,
2172
- chalk3.white(scope),
2558
+ chalk4.white(scope),
2173
2559
  fmt2(g.limit_usd),
2174
2560
  fmt2(g.current_spend_usd),
2175
2561
  pctColor,
@@ -2181,13 +2567,13 @@ goalCmd.command("list").description("List all goals with progress").action(() =>
2181
2567
  goalCmd.command("remove <id>").description("Remove a goal").action((id) => {
2182
2568
  const db = openDatabase();
2183
2569
  deleteGoal(db, id);
2184
- console.log(chalk3.green(`\u2713 Goal removed`));
2570
+ console.log(chalk4.green(`\u2713 Goal removed`));
2185
2571
  });
2186
2572
  goalCmd.command("status").description("Quick goal progress summary").action(() => {
2187
2573
  const db = openDatabase();
2188
2574
  const statuses = getGoalStatuses(db);
2189
2575
  if (statuses.length === 0) {
2190
- console.log(chalk3.yellow("No goals set."));
2576
+ console.log(chalk4.yellow("No goals set."));
2191
2577
  return;
2192
2578
  }
2193
2579
  console.log();
@@ -2197,7 +2583,7 @@ goalCmd.command("status").description("Quick goal progress summary").action(() =
2197
2583
  const barFilled = Math.round(pct / 10);
2198
2584
  const barEmpty = 10 - barFilled;
2199
2585
  const bar = "\u2588".repeat(barFilled) + "\u2591".repeat(barEmpty);
2200
- const statusStr = g.is_over ? chalk3.red("\u2717 OVER") : g.is_at_risk ? chalk3.yellow("\u26A0 AT RISK") : chalk3.green("\u2713 ON TRACK");
2586
+ const statusStr = g.is_over ? chalk4.red("\u2717 OVER") : g.is_at_risk ? chalk4.yellow("\u26A0 AT RISK") : chalk4.green("\u2713 ON TRACK");
2201
2587
  const label = `${g.period} (${scope})`.padEnd(20);
2202
2588
  console.log(` ${label} ${bar} ${fmt2(g.current_spend_usd)} / ${fmt2(g.limit_usd)} (${g.percent_used.toFixed(0)}%) ${statusStr}`);
2203
2589
  }
@@ -2209,27 +2595,28 @@ program.command("remove <type> <id>").alias("rm").description("Remove a record.
2209
2595
  switch (type.toLowerCase()) {
2210
2596
  case "budget":
2211
2597
  deleteBudget(db, id);
2212
- console.log(chalk3.green(`\u2713 Budget ${id} removed`));
2598
+ console.log(chalk4.green(`\u2713 Budget ${id} removed`));
2213
2599
  break;
2214
2600
  case "project":
2215
2601
  deleteProject(db, id);
2216
- console.log(chalk3.green(`\u2713 Project ${id} removed`));
2602
+ console.log(chalk4.green(`\u2713 Project ${id} removed`));
2217
2603
  break;
2218
2604
  case "goal":
2219
2605
  deleteGoal(db, id);
2220
- console.log(chalk3.green(`\u2713 Goal ${id} removed`));
2606
+ console.log(chalk4.green(`\u2713 Goal ${id} removed`));
2221
2607
  break;
2222
2608
  case "pricing":
2223
2609
  deleteModelPricing(db, id);
2224
- console.log(chalk3.green(`\u2713 Pricing entry ${id} removed`));
2610
+ console.log(chalk4.green(`\u2713 Pricing entry ${id} removed`));
2225
2611
  break;
2226
2612
  default:
2227
- console.error(chalk3.red(`Unknown type: ${type}. Use: budget | project | goal | pricing`));
2613
+ console.error(chalk4.red(`Unknown type: ${type}. Use: budget | project | goal | pricing`));
2228
2614
  process.exit(1);
2229
2615
  }
2230
2616
  } catch (e) {
2231
- console.error(chalk3.red(`Failed: ${e instanceof Error ? e.message : String(e)}`));
2617
+ console.error(chalk4.red(`Failed: ${e instanceof Error ? e.message : String(e)}`));
2232
2618
  process.exit(1);
2233
2619
  }
2234
2620
  });
2621
+ registerBrainsCommand(program);
2235
2622
  program.parse();