awarts 0.2.3 → 0.2.5

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/dist/index.js +826 -222
  4. package/package.json +31 -2
package/dist/index.js CHANGED
@@ -5774,6 +5774,89 @@ function divider() {
5774
5774
  console.log(` ${source_default.dim("─".repeat(50))}`);
5775
5775
  }
5776
5776
 
5777
+ // src/lib/daemon.ts
5778
+ import fs8 from "node:fs/promises";
5779
+ import { openSync } from "node:fs";
5780
+ import path4 from "node:path";
5781
+ import os5 from "node:os";
5782
+ import { spawn } from "node:child_process";
5783
+ var AWARTS_DIR2 = path4.join(os5.homedir(), ".awarts");
5784
+ var PID_FILE = path4.join(AWARTS_DIR2, "daemon.pid");
5785
+ var LOG_FILE = path4.join(AWARTS_DIR2, "daemon.log");
5786
+ var DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
5787
+ async function ensureDir2() {
5788
+ await fs8.mkdir(AWARTS_DIR2, { recursive: true });
5789
+ }
5790
+ async function readPid() {
5791
+ try {
5792
+ const raw = await fs8.readFile(PID_FILE, "utf-8");
5793
+ const pid = parseInt(raw.trim(), 10);
5794
+ return isNaN(pid) ? null : pid;
5795
+ } catch {
5796
+ return null;
5797
+ }
5798
+ }
5799
+ async function writePid(pid) {
5800
+ await ensureDir2();
5801
+ await fs8.writeFile(PID_FILE, String(pid), "utf-8");
5802
+ }
5803
+ async function removePid() {
5804
+ try {
5805
+ await fs8.unlink(PID_FILE);
5806
+ } catch {}
5807
+ }
5808
+ function isProcessRunning(pid) {
5809
+ try {
5810
+ process.kill(pid, 0);
5811
+ return true;
5812
+ } catch {
5813
+ return false;
5814
+ }
5815
+ }
5816
+ function killProcess(pid) {
5817
+ try {
5818
+ process.kill(pid, "SIGTERM");
5819
+ return true;
5820
+ } catch {
5821
+ return false;
5822
+ }
5823
+ }
5824
+ async function appendLog(message) {
5825
+ await ensureDir2();
5826
+ const timestamp = new Date().toISOString();
5827
+ const line = `[${timestamp}] ${message}
5828
+ `;
5829
+ await fs8.appendFile(LOG_FILE, line, "utf-8");
5830
+ }
5831
+ async function readLogTail(lines = 50) {
5832
+ try {
5833
+ const content = await fs8.readFile(LOG_FILE, "utf-8");
5834
+ const allLines = content.split(`
5835
+ `).filter(Boolean);
5836
+ return allLines.slice(-lines).join(`
5837
+ `);
5838
+ } catch {
5839
+ return "(no log file found)";
5840
+ }
5841
+ }
5842
+ async function spawnDaemon(intervalMs) {
5843
+ await ensureDir2();
5844
+ const cliScript = process.argv[1];
5845
+ const logFd = openSync(LOG_FILE, "a");
5846
+ const child = spawn(process.execPath, [cliScript, "daemon", "__run", "--interval", String(intervalMs)], {
5847
+ detached: true,
5848
+ stdio: ["ignore", logFd, logFd],
5849
+ env: { ...process.env }
5850
+ });
5851
+ child.unref();
5852
+ const pid = child.pid;
5853
+ if (!pid) {
5854
+ throw new Error("Failed to spawn daemon process");
5855
+ }
5856
+ await writePid(pid);
5857
+ return pid;
5858
+ }
5859
+
5777
5860
  // src/commands/login.ts
5778
5861
  var POLL_INTERVAL_MS = 2000;
5779
5862
  var MAX_POLLS = 300;
@@ -5855,7 +5938,20 @@ async function startDeviceAuth() {
5855
5938
  kv("User ID", user_id);
5856
5939
  success("Token saved to ~/.awarts/auth.json");
5857
5940
  console.log();
5858
- dim("You can now run awarts push or awarts sync");
5941
+ try {
5942
+ const existingPid = await readPid();
5943
+ const isRunning = existingPid ? isProcessRunning(existingPid) : false;
5944
+ if (!isRunning) {
5945
+ const pid = await spawnDaemon(DEFAULT_INTERVAL_MS);
5946
+ success(`Auto-sync daemon started (PID ${pid}, every 5 min)`);
5947
+ dim("Your usage data will sync automatically in the background.");
5948
+ dim("Manage with: awarts daemon status | stop | logs");
5949
+ } else {
5950
+ dim("Auto-sync daemon is already running.");
5951
+ }
5952
+ } catch {
5953
+ dim("Run awarts daemon start to enable auto-sync.");
5954
+ }
5859
5955
  console.log();
5860
5956
  return;
5861
5957
  }
@@ -5875,17 +5971,25 @@ function sleep(ms) {
5875
5971
  }
5876
5972
 
5877
5973
  // src/adapters/claude.ts
5878
- import fs8 from "node:fs/promises";
5879
- import path4 from "node:path";
5880
- import os5 from "node:os";
5881
- var CLAUDE_DIR = path4.join(os5.homedir(), ".claude");
5882
- var STATS_CACHE = path4.join(CLAUDE_DIR, "stats-cache.json");
5974
+ import fs9 from "node:fs/promises";
5975
+ import path5 from "node:path";
5976
+ import os6 from "node:os";
5977
+ var CLAUDE_DIR = path5.join(os6.homedir(), ".claude");
5978
+ var STATS_CACHE = path5.join(CLAUDE_DIR, "stats-cache.json");
5883
5979
  var MODEL_PRICING = {
5884
- "claude-opus-4-6": { input: 15, output: 75, cacheRead: 1.875, cacheWrite: 18.75 },
5885
- "claude-opus-4": { input: 15, output: 75, cacheRead: 1.875, cacheWrite: 18.75 },
5980
+ "claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
5981
+ "claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
5982
+ "claude-opus-4-1": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
5983
+ "claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
5984
+ "claude-opus-3": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
5985
+ "claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5986
+ "claude-sonnet-4-5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5886
5987
  "claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5988
+ "claude-sonnet-3-7": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5887
5989
  "claude-sonnet-3-5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
5888
- "claude-haiku-3-5": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 }
5990
+ "claude-haiku-4-5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
5991
+ "claude-haiku-3-5": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
5992
+ "claude-haiku-3": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 }
5889
5993
  };
5890
5994
  var DEFAULT_PRICING = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
5891
5995
  function getPricing(model) {
@@ -5903,7 +6007,7 @@ function estimateCost(model, inputTokens, outputTokens, cacheReadTokens, cacheCr
5903
6007
  }
5904
6008
  async function fileExists(filePath) {
5905
6009
  try {
5906
- const stat = await fs8.stat(filePath);
6010
+ const stat = await fs9.stat(filePath);
5907
6011
  return stat.isFile();
5908
6012
  } catch {
5909
6013
  return false;
@@ -5911,14 +6015,14 @@ async function fileExists(filePath) {
5911
6015
  }
5912
6016
  async function dirExists(dir) {
5913
6017
  try {
5914
- const stat = await fs8.stat(dir);
6018
+ const stat = await fs9.stat(dir);
5915
6019
  return stat.isDirectory();
5916
6020
  } catch {
5917
6021
  return false;
5918
6022
  }
5919
6023
  }
5920
6024
  async function readStatsCache() {
5921
- const raw = await fs8.readFile(STATS_CACHE, "utf-8");
6025
+ const raw = await fs9.readFile(STATS_CACHE, "utf-8");
5922
6026
  const cache = JSON.parse(raw);
5923
6027
  const dailyTokens = cache.dailyModelTokens;
5924
6028
  if (!dailyTokens || dailyTokens.length === 0)
@@ -5928,6 +6032,7 @@ async function readStatsCache() {
5928
6032
  let totalCacheRead = 0;
5929
6033
  let totalCacheCreation = 0;
5930
6034
  let totalCost = 0;
6035
+ let hasRealCost = false;
5931
6036
  if (cache.modelUsage) {
5932
6037
  for (const [model, usage] of Object.entries(cache.modelUsage)) {
5933
6038
  totalInputTokens += usage.inputTokens || 0;
@@ -5936,6 +6041,7 @@ async function readStatsCache() {
5936
6041
  totalCacheCreation += usage.cacheCreationInputTokens || 0;
5937
6042
  if (usage.costUSD > 0) {
5938
6043
  totalCost += usage.costUSD;
6044
+ hasRealCost = true;
5939
6045
  } else {
5940
6046
  totalCost += estimateCost(model, usage.inputTokens || 0, usage.outputTokens || 0, usage.cacheReadInputTokens || 0, usage.cacheCreationInputTokens || 0);
5941
6047
  }
@@ -5959,7 +6065,8 @@ async function readStatsCache() {
5959
6065
  cache_read_tokens: Math.round(totalCacheRead * share),
5960
6066
  cache_creation_tokens: Math.round(totalCacheCreation * share),
5961
6067
  cost_usd: Number((totalCost * share).toFixed(4)),
5962
- models
6068
+ models,
6069
+ cost_source: hasRealCost ? "real" : "estimated"
5963
6070
  });
5964
6071
  }
5965
6072
  return entries;
@@ -5984,226 +6091,569 @@ var claudeAdapter = {
5984
6091
  };
5985
6092
 
5986
6093
  // src/adapters/codex.ts
5987
- import fs9 from "node:fs/promises";
5988
- import path5 from "node:path";
5989
- import os6 from "node:os";
5990
- var HOME = os6.homedir();
5991
- var CANDIDATE_DIRS = [
5992
- path5.join(HOME, ".codex", "usage"),
5993
- path5.join(HOME, ".openai-codex", "usage"),
5994
- path5.join(HOME, ".codex")
6094
+ import fs11 from "node:fs/promises";
6095
+ import path7 from "node:path";
6096
+ import os8 from "node:os";
6097
+ import { execFileSync as execFileSync2 } from "node:child_process";
6098
+
6099
+ // src/lib/keys.ts
6100
+ import fs10 from "node:fs/promises";
6101
+ import path6 from "node:path";
6102
+ import os7 from "node:os";
6103
+ var KEYS_PATH = path6.join(os7.homedir(), ".awarts", "keys.json");
6104
+ async function ensureDir3() {
6105
+ const dir = path6.dirname(KEYS_PATH);
6106
+ await fs10.mkdir(dir, { recursive: true });
6107
+ }
6108
+ async function loadKeys() {
6109
+ try {
6110
+ const raw = await fs10.readFile(KEYS_PATH, "utf-8");
6111
+ return JSON.parse(raw);
6112
+ } catch {
6113
+ return {};
6114
+ }
6115
+ }
6116
+ async function saveKeys(keys) {
6117
+ await ensureDir3();
6118
+ await fs10.writeFile(KEYS_PATH, JSON.stringify(keys, null, 2), "utf-8");
6119
+ try {
6120
+ await fs10.chmod(KEYS_PATH, 384);
6121
+ } catch {}
6122
+ }
6123
+ async function setKey(provider, key) {
6124
+ const keys = await loadKeys();
6125
+ keys[provider] = key;
6126
+ await saveKeys(keys);
6127
+ }
6128
+ async function getKey(provider) {
6129
+ const keys = await loadKeys();
6130
+ return keys[provider];
6131
+ }
6132
+ async function removeKey(provider) {
6133
+ const keys = await loadKeys();
6134
+ if (!keys[provider])
6135
+ return false;
6136
+ delete keys[provider];
6137
+ await saveKeys(keys);
6138
+ return true;
6139
+ }
6140
+ async function listKeys() {
6141
+ const keys = await loadKeys();
6142
+ const result = [];
6143
+ for (const [provider, key] of Object.entries(keys)) {
6144
+ if (key) {
6145
+ const masked = key.length > 8 ? key.slice(0, 4) + "..." + key.slice(-4) : "****";
6146
+ result.push({ provider, masked });
6147
+ }
6148
+ }
6149
+ return result;
6150
+ }
6151
+
6152
+ // src/adapters/codex.ts
6153
+ var HOME = os8.homedir();
6154
+ var IS_WIN = process.platform === "win32";
6155
+ var LOCALAPPDATA = process.env.LOCALAPPDATA ?? path7.join(HOME, "AppData", "Local");
6156
+ var APPDATA = process.env.APPDATA ?? path7.join(HOME, "AppData", "Roaming");
6157
+ var LOCAL_DIRS = [
6158
+ path7.join(HOME, ".codex", "usage"),
6159
+ path7.join(HOME, ".openai-codex", "usage"),
6160
+ path7.join(HOME, ".codex"),
6161
+ path7.join(HOME, ".openai-codex"),
6162
+ ...IS_WIN ? [
6163
+ path7.join(LOCALAPPDATA, "codex", "usage"),
6164
+ path7.join(LOCALAPPDATA, "openai-codex", "usage"),
6165
+ path7.join(APPDATA, "codex", "usage"),
6166
+ path7.join(APPDATA, "openai-codex", "usage"),
6167
+ path7.join(LOCALAPPDATA, "codex"),
6168
+ path7.join(APPDATA, "codex")
6169
+ ] : []
6170
+ ];
6171
+ var CONFIG_DIRS = [
6172
+ path7.join(HOME, ".codex"),
6173
+ path7.join(HOME, ".openai-codex"),
6174
+ ...IS_WIN ? [
6175
+ path7.join(LOCALAPPDATA, "codex"),
6176
+ path7.join(APPDATA, "codex"),
6177
+ path7.join(LOCALAPPDATA, "openai-codex"),
6178
+ path7.join(APPDATA, "openai-codex")
6179
+ ] : []
5995
6180
  ];
6181
+ var CODEX_PRICING = {
6182
+ "gpt-5.4": { input: 2.5, output: 15 },
6183
+ "gpt-5.4-pro": { input: 30, output: 180 },
6184
+ "gpt-5.2": { input: 1.75, output: 14 },
6185
+ "gpt-5.1": { input: 1.25, output: 10 },
6186
+ "gpt-5": { input: 1.25, output: 10 },
6187
+ "gpt-5-mini": { input: 0.25, output: 2 },
6188
+ "gpt-5-nano": { input: 0.05, output: 0.4 },
6189
+ "gpt-4.1": { input: 2, output: 8 },
6190
+ "gpt-4.1-mini": { input: 0.4, output: 1.6 },
6191
+ "gpt-4.1-nano": { input: 0.1, output: 0.4 },
6192
+ "gpt-4o": { input: 2.5, output: 10 },
6193
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
6194
+ o3: { input: 2, output: 8 },
6195
+ "o3-pro": { input: 20, output: 80 },
6196
+ "o4-mini": { input: 1.1, output: 4.4 },
6197
+ o1: { input: 15, output: 60 },
6198
+ "o1-pro": { input: 150, output: 600 },
6199
+ "codex-mini": { input: 1.5, output: 6 },
6200
+ "openai-codex": { input: 2, output: 8 }
6201
+ };
6202
+ var DEFAULT_PRICING2 = { input: 2, output: 8 };
6203
+ async function fetchOpenAICosts(apiKey) {
6204
+ const entries = [];
6205
+ const now = Math.floor(Date.now() / 1000);
6206
+ const thirtyDaysAgo = now - 30 * 24 * 60 * 60;
6207
+ let url = `https://api.openai.com/v1/organization/costs?start_time=${thirtyDaysAgo}&end_time=${now}&bucket_width=1d&limit=30`;
6208
+ try {
6209
+ while (url) {
6210
+ const resp = await fetch(url, {
6211
+ headers: {
6212
+ Authorization: `Bearer ${apiKey}`,
6213
+ "Content-Type": "application/json"
6214
+ }
6215
+ });
6216
+ if (!resp.ok) {
6217
+ return [];
6218
+ }
6219
+ const data = await resp.json();
6220
+ for (const bucket of data.data) {
6221
+ const date = new Date(bucket.start_time * 1000).toISOString().split("T")[0];
6222
+ const totalCost = bucket.results.reduce((sum, r) => sum + r.amount.value, 0);
6223
+ if (totalCost > 0) {
6224
+ entries.push({
6225
+ date,
6226
+ provider: "codex",
6227
+ cost_usd: totalCost / 100,
6228
+ input_tokens: 0,
6229
+ output_tokens: 0,
6230
+ models: ["openai-codex"],
6231
+ cost_source: "real"
6232
+ });
6233
+ }
6234
+ }
6235
+ url = data.has_more && data.next_page ? data.next_page : "";
6236
+ }
6237
+ } catch {
6238
+ return [];
6239
+ }
6240
+ return entries;
6241
+ }
5996
6242
  function dateFromFilename(filename) {
5997
6243
  const match = filename.match(/^(\d{4}-\d{2}-\d{2})\.json$/);
5998
6244
  return match ? match[1] : null;
5999
6245
  }
6000
6246
  async function dirExists2(dir) {
6001
6247
  try {
6002
- const stat = await fs9.stat(dir);
6248
+ const stat = await fs11.stat(dir);
6003
6249
  return stat.isDirectory();
6004
6250
  } catch {
6005
6251
  return false;
6006
6252
  }
6007
6253
  }
6008
6254
  async function findUsageDir() {
6009
- for (const dir of CANDIDATE_DIRS) {
6255
+ for (const dir of LOCAL_DIRS) {
6010
6256
  if (await dirExists2(dir))
6011
6257
  return dir;
6012
6258
  }
6013
6259
  return null;
6014
6260
  }
6261
+ function commandExists(cmd) {
6262
+ try {
6263
+ execFileSync2(IS_WIN ? "where" : "which", [cmd], { stdio: "ignore" });
6264
+ return true;
6265
+ } catch {
6266
+ return false;
6267
+ }
6268
+ }
6269
+ async function readLocalFiles() {
6270
+ const entries = [];
6271
+ const dir = await findUsageDir();
6272
+ if (!dir)
6273
+ return entries;
6274
+ let files;
6275
+ try {
6276
+ files = await fs11.readdir(dir);
6277
+ } catch {
6278
+ return entries;
6279
+ }
6280
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
6281
+ for (const file of jsonFiles) {
6282
+ try {
6283
+ const filePath = path7.join(dir, file);
6284
+ const raw = await fs11.readFile(filePath, "utf-8");
6285
+ const data = JSON.parse(raw);
6286
+ const date = data.date ?? dateFromFilename(file);
6287
+ if (!date)
6288
+ continue;
6289
+ const costUsd = data.cost_usd ?? data.total_cost ?? 0;
6290
+ const inputTokens = Number(data.input_tokens ?? 0) || 0;
6291
+ const outputTokens = Number(data.output_tokens ?? 0) || 0;
6292
+ const model = data.model ?? data.models?.[0];
6293
+ let finalCost = Number(costUsd) || 0;
6294
+ let costSource = finalCost > 0 ? "real" : "estimated";
6295
+ if (finalCost === 0 && (inputTokens > 0 || outputTokens > 0)) {
6296
+ const pricing = model && CODEX_PRICING[model] || DEFAULT_PRICING2;
6297
+ finalCost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1e6;
6298
+ costSource = "estimated";
6299
+ }
6300
+ entries.push({
6301
+ date,
6302
+ provider: "codex",
6303
+ cost_usd: finalCost,
6304
+ input_tokens: inputTokens,
6305
+ output_tokens: outputTokens,
6306
+ cache_creation_tokens: Number(data.cache_creation_tokens ?? 0) || 0,
6307
+ cache_read_tokens: Number(data.cache_read_tokens ?? 0) || 0,
6308
+ models: data.models ?? (data.model ? [data.model] : []),
6309
+ cost_source: costSource
6310
+ });
6311
+ } catch {}
6312
+ }
6313
+ return entries;
6314
+ }
6315
+ var SQLITE_PATHS = [
6316
+ path7.join(HOME, ".codex", "state_5.sqlite"),
6317
+ ...IS_WIN ? [
6318
+ path7.join(LOCALAPPDATA, "codex", "state_5.sqlite"),
6319
+ path7.join(APPDATA, "codex", "state_5.sqlite")
6320
+ ] : []
6321
+ ];
6322
+ async function findSqliteDb() {
6323
+ for (const p of SQLITE_PATHS) {
6324
+ try {
6325
+ await fs11.access(p);
6326
+ return p;
6327
+ } catch {}
6328
+ }
6329
+ return null;
6330
+ }
6331
+ async function readSqliteThreads() {
6332
+ const dbPath = await findSqliteDb();
6333
+ if (!dbPath)
6334
+ return [];
6335
+ try {
6336
+ const query = `SELECT date(created_at/1000000000, 'unixepoch') as day, tokens_used, model_provider FROM threads WHERE tokens_used > 0 ORDER BY created_at;`;
6337
+ const result = execFileSync2("sqlite3", [dbPath, query], {
6338
+ timeout: 5000,
6339
+ encoding: "utf-8",
6340
+ stdio: ["pipe", "pipe", "pipe"]
6341
+ }).trim();
6342
+ if (!result)
6343
+ return [];
6344
+ const dayMap = new Map;
6345
+ for (const line of result.split(`
6346
+ `)) {
6347
+ const [day, tokensStr, model] = line.split("|");
6348
+ if (!day)
6349
+ continue;
6350
+ const tokens2 = Number(tokensStr) || 0;
6351
+ if (!dayMap.has(day))
6352
+ dayMap.set(day, { tokens: 0, models: new Set });
6353
+ const entry = dayMap.get(day);
6354
+ entry.tokens += tokens2;
6355
+ if (model)
6356
+ entry.models.add(model);
6357
+ }
6358
+ const entries = [];
6359
+ for (const [date, { tokens: tokens2, models }] of dayMap) {
6360
+ const inputTokens = Math.round(tokens2 * 0.4);
6361
+ const outputTokens = Math.round(tokens2 * 0.6);
6362
+ const modelName = [...models][0] ?? "openai-codex";
6363
+ const pricing = CODEX_PRICING[modelName] || DEFAULT_PRICING2;
6364
+ const cost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1e6;
6365
+ entries.push({
6366
+ date,
6367
+ provider: "codex",
6368
+ cost_usd: cost,
6369
+ input_tokens: inputTokens,
6370
+ output_tokens: outputTokens,
6371
+ models: [...models].length > 0 ? [...models] : ["openai-codex"],
6372
+ cost_source: "estimated"
6373
+ });
6374
+ }
6375
+ return entries;
6376
+ } catch {
6377
+ return [];
6378
+ }
6379
+ }
6015
6380
  var codexAdapter = {
6016
6381
  name: "codex",
6017
- displayName: "Codex",
6382
+ displayName: "Codex (OpenAI)",
6018
6383
  async detect() {
6019
- return await findUsageDir() !== null;
6384
+ const apiKey = await getKey("openai");
6385
+ if (apiKey)
6386
+ return true;
6387
+ if (await findUsageDir() !== null)
6388
+ return true;
6389
+ if (await findSqliteDb() !== null)
6390
+ return true;
6391
+ for (const dir of CONFIG_DIRS) {
6392
+ if (await dirExists2(dir))
6393
+ return true;
6394
+ }
6395
+ if (commandExists("codex"))
6396
+ return true;
6397
+ return false;
6020
6398
  },
6021
6399
  async read() {
6022
- const entries = [];
6023
- const dir = await findUsageDir();
6024
- if (!dir)
6025
- return entries;
6026
- let files;
6027
- try {
6028
- files = await fs9.readdir(dir);
6029
- } catch {
6030
- return entries;
6031
- }
6032
- const jsonFiles = files.filter((f) => f.endsWith(".json"));
6033
- for (const file of jsonFiles) {
6034
- try {
6035
- const filePath = path5.join(dir, file);
6036
- const raw = await fs9.readFile(filePath, "utf-8");
6037
- const data = JSON.parse(raw);
6038
- const date = data.date ?? dateFromFilename(file);
6039
- if (!date)
6040
- continue;
6041
- const costUsd = data.cost_usd ?? data.total_cost ?? 0;
6042
- const inputTokens = data.input_tokens ?? 0;
6043
- const outputTokens = data.output_tokens ?? 0;
6044
- const models = data.models ? data.models : data.model ? [data.model] : [];
6045
- entries.push({
6046
- date,
6047
- provider: "codex",
6048
- cost_usd: Number(costUsd) || 0,
6049
- input_tokens: Number(inputTokens) || 0,
6050
- output_tokens: Number(outputTokens) || 0,
6051
- cache_creation_tokens: Number(data.cache_creation_tokens ?? 0) || 0,
6052
- cache_read_tokens: Number(data.cache_read_tokens ?? 0) || 0,
6053
- models
6054
- });
6055
- } catch {}
6400
+ const apiKey = await getKey("openai");
6401
+ if (apiKey) {
6402
+ const apiEntries = await fetchOpenAICosts(apiKey);
6403
+ if (apiEntries.length > 0)
6404
+ return apiEntries;
6056
6405
  }
6057
- return entries;
6406
+ const localEntries = await readLocalFiles();
6407
+ if (localEntries.length > 0)
6408
+ return localEntries;
6409
+ return readSqliteThreads();
6058
6410
  }
6059
6411
  };
6060
6412
 
6061
6413
  // src/adapters/gemini.ts
6062
- import fs10 from "node:fs/promises";
6063
- import path6 from "node:path";
6064
- import os7 from "node:os";
6065
- var HOME2 = os7.homedir();
6066
- var CANDIDATE_DIRS2 = [
6067
- path6.join(HOME2, ".gemini", "usage"),
6068
- path6.join(HOME2, ".config", "gemini", "usage"),
6069
- path6.join(HOME2, ".gemini")
6414
+ import fs12 from "node:fs/promises";
6415
+ import path8 from "node:path";
6416
+ import os9 from "node:os";
6417
+ import { execFileSync as execFileSync3 } from "node:child_process";
6418
+ var HOME2 = os9.homedir();
6419
+ var IS_WIN2 = process.platform === "win32";
6420
+ var LOCALAPPDATA2 = process.env.LOCALAPPDATA ?? path8.join(HOME2, "AppData", "Local");
6421
+ var LOCAL_DIRS2 = [
6422
+ path8.join(HOME2, ".gemini", "usage"),
6423
+ path8.join(HOME2, ".config", "gemini", "usage"),
6424
+ ...IS_WIN2 ? [
6425
+ path8.join(LOCALAPPDATA2, "gemini", "usage")
6426
+ ] : []
6427
+ ];
6428
+ var DETECT_DIRS = [
6429
+ path8.join(HOME2, ".gemini"),
6430
+ path8.join(HOME2, ".config", "gemini"),
6431
+ ...IS_WIN2 ? [
6432
+ path8.join(LOCALAPPDATA2, "gemini")
6433
+ ] : []
6070
6434
  ];
6435
+ var GEMINI_PRICING = {
6436
+ "gemini-2.5-pro": { input: 1.25, output: 10 },
6437
+ "gemini-2.5-flash": { input: 0.3, output: 2.5 },
6438
+ "gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
6439
+ "gemini-3.1-pro": { input: 2, output: 12 },
6440
+ "gemini-3.1-flash-lite": { input: 0.25, output: 1.5 },
6441
+ "gemini-3-flash": { input: 0.5, output: 3 },
6442
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
6443
+ "gemini-2.0-flash-lite": { input: 0.075, output: 0.3 },
6444
+ "gemini-1.5-pro": { input: 1.25, output: 5 },
6445
+ "gemini-1.5-flash": { input: 0.075, output: 0.3 },
6446
+ "gemini-1.5-flash-8b": { input: 0.0375, output: 0.15 }
6447
+ };
6448
+ var DEFAULT_PRICING3 = { input: 0.3, output: 2.5 };
6449
+ function calculateCost(inputTokens, outputTokens, model) {
6450
+ const pricing = model && GEMINI_PRICING[model] || DEFAULT_PRICING3;
6451
+ return (inputTokens * pricing.input + outputTokens * pricing.output) / 1e6;
6452
+ }
6071
6453
  function dateFromFilename2(filename) {
6072
6454
  const match = filename.match(/^(\d{4}-\d{2}-\d{2})\.json$/);
6073
6455
  return match ? match[1] : null;
6074
6456
  }
6075
6457
  async function dirExists3(dir) {
6076
6458
  try {
6077
- const stat = await fs10.stat(dir);
6459
+ const stat = await fs12.stat(dir);
6078
6460
  return stat.isDirectory();
6079
6461
  } catch {
6080
6462
  return false;
6081
6463
  }
6082
6464
  }
6083
6465
  async function findUsageDir2() {
6084
- for (const dir of CANDIDATE_DIRS2) {
6466
+ for (const dir of LOCAL_DIRS2) {
6085
6467
  if (await dirExists3(dir))
6086
6468
  return dir;
6087
6469
  }
6088
6470
  return null;
6089
6471
  }
6472
+ function commandExists2(cmd) {
6473
+ try {
6474
+ execFileSync3(IS_WIN2 ? "where" : "which", [cmd], { stdio: "ignore" });
6475
+ return true;
6476
+ } catch {
6477
+ return false;
6478
+ }
6479
+ }
6480
+ async function readLocalFiles2() {
6481
+ const entries = [];
6482
+ const dir = await findUsageDir2();
6483
+ if (!dir)
6484
+ return entries;
6485
+ let files;
6486
+ try {
6487
+ files = await fs12.readdir(dir);
6488
+ } catch {
6489
+ return entries;
6490
+ }
6491
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
6492
+ for (const file of jsonFiles) {
6493
+ try {
6494
+ const filePath = path8.join(dir, file);
6495
+ const raw = await fs12.readFile(filePath, "utf-8");
6496
+ const data = JSON.parse(raw);
6497
+ const date = data.date ?? dateFromFilename2(file);
6498
+ if (!date)
6499
+ continue;
6500
+ const costUsd = data.cost_usd ?? data.total_cost ?? 0;
6501
+ const inputTokens = Number(data.input_tokens ?? 0) || 0;
6502
+ const outputTokens = Number(data.output_tokens ?? 0) || 0;
6503
+ const model = data.model ?? data.models?.[0];
6504
+ let finalCost = Number(costUsd) || 0;
6505
+ let costSource = finalCost > 0 ? "real" : "estimated";
6506
+ if (finalCost === 0 && (inputTokens > 0 || outputTokens > 0)) {
6507
+ finalCost = calculateCost(inputTokens, outputTokens, model);
6508
+ costSource = "estimated";
6509
+ }
6510
+ entries.push({
6511
+ date,
6512
+ provider: "gemini",
6513
+ cost_usd: finalCost,
6514
+ input_tokens: inputTokens,
6515
+ output_tokens: outputTokens,
6516
+ cache_creation_tokens: Number(data.cache_creation_tokens ?? 0) || 0,
6517
+ cache_read_tokens: Number(data.cache_read_tokens ?? 0) || 0,
6518
+ models: data.models ?? (data.model ? [data.model] : []),
6519
+ cost_source: costSource
6520
+ });
6521
+ } catch {}
6522
+ }
6523
+ return entries;
6524
+ }
6090
6525
  var geminiAdapter = {
6091
6526
  name: "gemini",
6092
6527
  displayName: "Gemini",
6093
6528
  async detect() {
6094
- return await findUsageDir2() !== null;
6529
+ const apiKey = await getKey("google");
6530
+ if (apiKey)
6531
+ return true;
6532
+ for (const dir of DETECT_DIRS) {
6533
+ if (await dirExists3(dir))
6534
+ return true;
6535
+ }
6536
+ if (commandExists2("gemini"))
6537
+ return true;
6538
+ return false;
6095
6539
  },
6096
6540
  async read() {
6097
- const entries = [];
6098
- const dir = await findUsageDir2();
6099
- if (!dir)
6100
- return entries;
6101
- let files;
6102
- try {
6103
- files = await fs10.readdir(dir);
6104
- } catch {
6105
- return entries;
6106
- }
6107
- const jsonFiles = files.filter((f) => f.endsWith(".json"));
6108
- for (const file of jsonFiles) {
6109
- try {
6110
- const filePath = path6.join(dir, file);
6111
- const raw = await fs10.readFile(filePath, "utf-8");
6112
- const data = JSON.parse(raw);
6113
- const date = data.date ?? dateFromFilename2(file);
6114
- if (!date)
6115
- continue;
6116
- const costUsd = data.cost_usd ?? data.total_cost ?? 0;
6117
- const inputTokens = data.input_tokens ?? 0;
6118
- const outputTokens = data.output_tokens ?? 0;
6119
- const models = data.models ? data.models : data.model ? [data.model] : [];
6120
- entries.push({
6121
- date,
6122
- provider: "gemini",
6123
- cost_usd: Number(costUsd) || 0,
6124
- input_tokens: Number(inputTokens) || 0,
6125
- output_tokens: Number(outputTokens) || 0,
6126
- cache_creation_tokens: Number(data.cache_creation_tokens ?? 0) || 0,
6127
- cache_read_tokens: Number(data.cache_read_tokens ?? 0) || 0,
6128
- models
6129
- });
6130
- } catch {}
6131
- }
6132
- return entries;
6541
+ return readLocalFiles2();
6133
6542
  }
6134
6543
  };
6135
6544
 
6136
6545
  // src/adapters/antigravity.ts
6137
- import fs11 from "node:fs/promises";
6138
- import path7 from "node:path";
6139
- import os8 from "node:os";
6140
- var HOME3 = os8.homedir();
6141
- var CANDIDATE_DIRS3 = [
6142
- path7.join(HOME3, ".antigravity", "usage"),
6143
- path7.join(HOME3, ".antigravity")
6546
+ import fs13 from "node:fs/promises";
6547
+ import path9 from "node:path";
6548
+ import os10 from "node:os";
6549
+ var HOME3 = os10.homedir();
6550
+ var IS_WIN3 = process.platform === "win32";
6551
+ var LOCALAPPDATA3 = process.env.LOCALAPPDATA ?? path9.join(HOME3, "AppData", "Local");
6552
+ var LOCAL_DIRS3 = [
6553
+ path9.join(HOME3, ".antigravity", "usage"),
6554
+ path9.join(HOME3, ".antigravity"),
6555
+ path9.join(HOME3, ".gemini", "antigravity", "usage"),
6556
+ ...IS_WIN3 ? [
6557
+ path9.join(LOCALAPPDATA3, "antigravity", "usage"),
6558
+ path9.join(LOCALAPPDATA3, "antigravity")
6559
+ ] : []
6144
6560
  ];
6561
+ var DETECT_DIRS2 = [
6562
+ path9.join(HOME3, ".antigravity"),
6563
+ path9.join(HOME3, ".gemini", "antigravity"),
6564
+ ...IS_WIN3 ? [
6565
+ path9.join(LOCALAPPDATA3, "antigravity")
6566
+ ] : []
6567
+ ];
6568
+ var ANTIGRAVITY_PRICING = {
6569
+ "antigravity-1": { input: 3, output: 15 },
6570
+ "antigravity-1.5": { input: 3, output: 15 },
6571
+ "antigravity-2": { input: 3, output: 15 }
6572
+ };
6573
+ var DEFAULT_PRICING4 = { input: 3, output: 15 };
6574
+ function calculateCost2(inputTokens, outputTokens, model) {
6575
+ const pricing = model && ANTIGRAVITY_PRICING[model] || DEFAULT_PRICING4;
6576
+ return (inputTokens * pricing.input + outputTokens * pricing.output) / 1e6;
6577
+ }
6145
6578
  function dateFromFilename3(filename) {
6146
6579
  const match = filename.match(/^(\d{4}-\d{2}-\d{2})\.json$/);
6147
6580
  return match ? match[1] : null;
6148
6581
  }
6149
6582
  async function dirExists4(dir) {
6150
6583
  try {
6151
- const stat = await fs11.stat(dir);
6584
+ const stat = await fs13.stat(dir);
6152
6585
  return stat.isDirectory();
6153
6586
  } catch {
6154
6587
  return false;
6155
6588
  }
6156
6589
  }
6157
6590
  async function findUsageDir3() {
6158
- for (const dir of CANDIDATE_DIRS3) {
6591
+ for (const dir of LOCAL_DIRS3) {
6159
6592
  if (await dirExists4(dir))
6160
6593
  return dir;
6161
6594
  }
6162
6595
  return null;
6163
6596
  }
6597
+ async function readLocalFiles3() {
6598
+ const entries = [];
6599
+ const dir = await findUsageDir3();
6600
+ if (!dir)
6601
+ return entries;
6602
+ let files;
6603
+ try {
6604
+ files = await fs13.readdir(dir);
6605
+ } catch {
6606
+ return entries;
6607
+ }
6608
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
6609
+ for (const file of jsonFiles) {
6610
+ try {
6611
+ const filePath = path9.join(dir, file);
6612
+ const raw = await fs13.readFile(filePath, "utf-8");
6613
+ const data = JSON.parse(raw);
6614
+ const date = data.date ?? dateFromFilename3(file);
6615
+ if (!date)
6616
+ continue;
6617
+ const costUsd = data.cost_usd ?? data.total_cost ?? 0;
6618
+ const inputTokens = Number(data.input_tokens ?? 0) || 0;
6619
+ const outputTokens = Number(data.output_tokens ?? 0) || 0;
6620
+ const model = data.model ?? data.models?.[0];
6621
+ let finalCost = Number(costUsd) || 0;
6622
+ let costSource = finalCost > 0 ? "real" : "estimated";
6623
+ if (finalCost === 0 && (inputTokens > 0 || outputTokens > 0)) {
6624
+ finalCost = calculateCost2(inputTokens, outputTokens, model);
6625
+ costSource = "estimated";
6626
+ }
6627
+ entries.push({
6628
+ date,
6629
+ provider: "antigravity",
6630
+ cost_usd: finalCost,
6631
+ input_tokens: inputTokens,
6632
+ output_tokens: outputTokens,
6633
+ cache_creation_tokens: Number(data.cache_creation_tokens ?? 0) || 0,
6634
+ cache_read_tokens: Number(data.cache_read_tokens ?? 0) || 0,
6635
+ models: data.models ?? (data.model ? [data.model] : []),
6636
+ cost_source: costSource
6637
+ });
6638
+ } catch {}
6639
+ }
6640
+ return entries;
6641
+ }
6164
6642
  var antigravityAdapter = {
6165
6643
  name: "antigravity",
6166
6644
  displayName: "Antigravity",
6167
6645
  async detect() {
6168
- return await findUsageDir3() !== null;
6646
+ const apiKey = await getKey("antigravity");
6647
+ if (apiKey)
6648
+ return true;
6649
+ for (const dir of DETECT_DIRS2) {
6650
+ if (await dirExists4(dir))
6651
+ return true;
6652
+ }
6653
+ return false;
6169
6654
  },
6170
6655
  async read() {
6171
- const entries = [];
6172
- const dir = await findUsageDir3();
6173
- if (!dir)
6174
- return entries;
6175
- let files;
6176
- try {
6177
- files = await fs11.readdir(dir);
6178
- } catch {
6179
- return entries;
6180
- }
6181
- const jsonFiles = files.filter((f) => f.endsWith(".json"));
6182
- for (const file of jsonFiles) {
6183
- try {
6184
- const filePath = path7.join(dir, file);
6185
- const raw = await fs11.readFile(filePath, "utf-8");
6186
- const data = JSON.parse(raw);
6187
- const date = data.date ?? dateFromFilename3(file);
6188
- if (!date)
6189
- continue;
6190
- const costUsd = data.cost_usd ?? data.total_cost ?? 0;
6191
- const inputTokens = data.input_tokens ?? 0;
6192
- const outputTokens = data.output_tokens ?? 0;
6193
- const models = data.models ? data.models : data.model ? [data.model] : [];
6194
- entries.push({
6195
- date,
6196
- provider: "antigravity",
6197
- cost_usd: Number(costUsd) || 0,
6198
- input_tokens: Number(inputTokens) || 0,
6199
- output_tokens: Number(outputTokens) || 0,
6200
- cache_creation_tokens: Number(data.cache_creation_tokens ?? 0) || 0,
6201
- cache_read_tokens: Number(data.cache_read_tokens ?? 0) || 0,
6202
- models
6203
- });
6204
- } catch {}
6205
- }
6206
- return entries;
6656
+ return readLocalFiles3();
6207
6657
  }
6208
6658
  };
6209
6659
 
@@ -6319,9 +6769,10 @@ async function pushCommand(opts) {
6319
6769
  const hash = hashEntries(allEntries);
6320
6770
  const spin = spinner("Submitting usage data...");
6321
6771
  spin.start();
6772
+ const cleanEntries = allEntries.map(({ cost_source, ...rest }) => rest);
6322
6773
  try {
6323
6774
  const res = await post("/api/usage/submit", {
6324
- entries: allEntries,
6775
+ entries: cleanEntries,
6325
6776
  source: "cli",
6326
6777
  hash
6327
6778
  });
@@ -6456,6 +6907,16 @@ async function syncCommand() {
6456
6907
  const entries = await adapter.read();
6457
6908
  if (entries.length === 0) {
6458
6909
  spin.info(source_default.dim(`${adapter.displayName} -- no usage data found`));
6910
+ if (adapter.name === "codex") {
6911
+ dim(` Set your OpenAI API key: ${source_default.cyan("awarts keys set openai <your-key>")}`);
6912
+ dim(` Or create usage files manually — see ${source_default.cyan("awarts.com/docs")}`);
6913
+ } else if (adapter.name === "gemini") {
6914
+ dim(` Set your Google API key: ${source_default.cyan("awarts keys set google <your-key>")}`);
6915
+ dim(` Or create usage files manually — see ${source_default.cyan("awarts.com/docs")}`);
6916
+ } else if (adapter.name === "antigravity") {
6917
+ dim(` Set your API key: ${source_default.cyan("awarts keys set antigravity <your-key>")}`);
6918
+ dim(` Or create usage files manually — see ${source_default.cyan("awarts.com/docs")}`);
6919
+ }
6459
6920
  } else {
6460
6921
  allEntries.push(...entries);
6461
6922
  const totalCost2 = entries.reduce((s, e) => s + e.cost_usd, 0);
@@ -6470,6 +6931,12 @@ async function syncCommand() {
6470
6931
  if (allEntries.length === 0) {
6471
6932
  warn("No usage data found across any provider.");
6472
6933
  console.log();
6934
+ info(`Set API keys to fetch real billing data:`);
6935
+ dim(` ${source_default.cyan("awarts keys set openai <key>")} — for Codex / OpenAI`);
6936
+ dim(` ${source_default.cyan("awarts keys set google <key>")} — for Gemini`);
6937
+ dim(` ${source_default.cyan("awarts keys set antigravity <key>")} — for Antigravity`);
6938
+ dim(`Or visit ${source_default.cyan("awarts.com/docs")} for manual import instructions.`);
6939
+ console.log();
6473
6940
  return;
6474
6941
  }
6475
6942
  const totalCost = allEntries.reduce((s, e) => s + e.cost_usd, 0);
@@ -6485,9 +6952,10 @@ async function syncCommand() {
6485
6952
  const hash = hashEntries(allEntries);
6486
6953
  const submitSpin = spinner("Syncing with AWARTS...");
6487
6954
  submitSpin.start();
6955
+ const cleanEntries = allEntries.map(({ cost_source, ...rest }) => rest);
6488
6956
  try {
6489
6957
  const res = await post("/api/usage/submit", {
6490
- entries: allEntries,
6958
+ entries: cleanEntries,
6491
6959
  source: "cli",
6492
6960
  hash
6493
6961
  });
@@ -6518,6 +6986,16 @@ async function syncCommand() {
6518
6986
  console.log();
6519
6987
  success("Your usage data is now live on AWARTS.");
6520
6988
  console.log();
6989
+ try {
6990
+ const existingPid = await readPid();
6991
+ const isRunning = existingPid ? isProcessRunning(existingPid) : false;
6992
+ if (!isRunning) {
6993
+ const pid = await spawnDaemon(DEFAULT_INTERVAL_MS);
6994
+ dim(`Auto-sync daemon started (PID ${pid}, every 5 min)`);
6995
+ dim("Manage with: awarts daemon status | stop | logs");
6996
+ console.log();
6997
+ }
6998
+ } catch {}
6521
6999
  } catch (err) {
6522
7000
  submitSpin.fail("Could not reach the AWARTS server.");
6523
7001
  error(err instanceof Error ? err.message : String(err));
@@ -6525,87 +7003,119 @@ async function syncCommand() {
6525
7003
  }
6526
7004
  }
6527
7005
 
6528
- // src/lib/daemon.ts
6529
- import fs12 from "node:fs/promises";
6530
- import { openSync } from "node:fs";
6531
- import path8 from "node:path";
6532
- import os9 from "node:os";
6533
- import { spawn } from "node:child_process";
6534
- var AWARTS_DIR2 = path8.join(os9.homedir(), ".awarts");
6535
- var PID_FILE = path8.join(AWARTS_DIR2, "daemon.pid");
6536
- var LOG_FILE = path8.join(AWARTS_DIR2, "daemon.log");
6537
- var DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
6538
- async function ensureDir2() {
6539
- await fs12.mkdir(AWARTS_DIR2, { recursive: true });
7006
+ // src/commands/seed.ts
7007
+ import fs14 from "node:fs/promises";
7008
+ import path10 from "node:path";
7009
+ import os11 from "node:os";
7010
+ var HOME4 = os11.homedir();
7011
+ var IS_WIN4 = process.platform === "win32";
7012
+ var LOCALAPPDATA4 = process.env.LOCALAPPDATA ?? path10.join(HOME4, "AppData", "Local");
7013
+ var PROVIDER_USAGE_DIRS = {
7014
+ codex: IS_WIN4 ? path10.join(LOCALAPPDATA4, "codex", "usage") : path10.join(HOME4, ".codex", "usage"),
7015
+ gemini: IS_WIN4 ? path10.join(LOCALAPPDATA4, "gemini", "usage") : path10.join(HOME4, ".gemini", "usage"),
7016
+ antigravity: IS_WIN4 ? path10.join(LOCALAPPDATA4, "antigravity", "usage") : path10.join(HOME4, ".antigravity", "usage")
7017
+ };
7018
+ var SAMPLE_MODELS = {
7019
+ codex: ["gpt-4.1", "gpt-4.1-mini", "o4-mini"],
7020
+ gemini: ["gemini-2.5-flash", "gemini-2.5-pro"],
7021
+ antigravity: ["antigravity-1.5"]
7022
+ };
7023
+ var TOKEN_RANGES = {
7024
+ codex: { inputMin: 8000, inputMax: 45000, outputMin: 2000, outputMax: 18000 },
7025
+ gemini: { inputMin: 5000, inputMax: 35000, outputMin: 1500, outputMax: 12000 },
7026
+ antigravity: { inputMin: 1e4, inputMax: 50000, outputMin: 3000, outputMax: 20000 }
7027
+ };
7028
+ function randomInt(min, max) {
7029
+ return Math.floor(Math.random() * (max - min + 1)) + min;
6540
7030
  }
6541
- async function readPid() {
6542
- try {
6543
- const raw = await fs12.readFile(PID_FILE, "utf-8");
6544
- const pid = parseInt(raw.trim(), 10);
6545
- return isNaN(pid) ? null : pid;
6546
- } catch {
6547
- return null;
7031
+ function generateSampleEntries(provider, days) {
7032
+ const models = SAMPLE_MODELS[provider] ?? ["unknown"];
7033
+ const ranges = TOKEN_RANGES[provider] ?? { inputMin: 5000, inputMax: 30000, outputMin: 2000, outputMax: 15000 };
7034
+ const entries = [];
7035
+ const now = new Date;
7036
+ for (let i = 0;i < days; i++) {
7037
+ const date = new Date(now);
7038
+ date.setDate(date.getDate() - i);
7039
+ const dateStr = date.toISOString().split("T")[0];
7040
+ if (i > 0 && Math.random() < 0.3)
7041
+ continue;
7042
+ const model = models[randomInt(0, models.length - 1)];
7043
+ const inputTokens = randomInt(ranges.inputMin, ranges.inputMax);
7044
+ const outputTokens = randomInt(ranges.outputMin, ranges.outputMax);
7045
+ entries.push({
7046
+ date: dateStr,
7047
+ data: {
7048
+ date: dateStr,
7049
+ input_tokens: inputTokens,
7050
+ output_tokens: outputTokens,
7051
+ model,
7052
+ models: [model]
7053
+ }
7054
+ });
6548
7055
  }
7056
+ return entries;
6549
7057
  }
6550
- async function writePid(pid) {
6551
- await ensureDir2();
6552
- await fs12.writeFile(PID_FILE, String(pid), "utf-8");
6553
- }
6554
- async function removePid() {
6555
- try {
6556
- await fs12.unlink(PID_FILE);
6557
- } catch {}
6558
- }
6559
- function isProcessRunning(pid) {
7058
+ async function dirExists5(dir) {
6560
7059
  try {
6561
- process.kill(pid, 0);
6562
- return true;
7060
+ const stat = await fs14.stat(dir);
7061
+ return stat.isDirectory();
6563
7062
  } catch {
6564
7063
  return false;
6565
7064
  }
6566
7065
  }
6567
- function killProcess(pid) {
7066
+ async function hasExistingData(dir) {
7067
+ if (!await dirExists5(dir))
7068
+ return false;
6568
7069
  try {
6569
- process.kill(pid, "SIGTERM");
6570
- return true;
7070
+ const files = await fs14.readdir(dir);
7071
+ return files.some((f) => f.endsWith(".json"));
6571
7072
  } catch {
6572
7073
  return false;
6573
7074
  }
6574
7075
  }
6575
- async function appendLog(message) {
6576
- await ensureDir2();
6577
- const timestamp = new Date().toISOString();
6578
- const line = `[${timestamp}] ${message}
6579
- `;
6580
- await fs12.appendFile(LOG_FILE, line, "utf-8");
6581
- }
6582
- async function readLogTail(lines = 50) {
6583
- try {
6584
- const content = await fs12.readFile(LOG_FILE, "utf-8");
6585
- const allLines = content.split(`
6586
- `).filter(Boolean);
6587
- return allLines.slice(-lines).join(`
6588
- `);
6589
- } catch {
6590
- return "(no log file found)";
7076
+ async function seedCommand(opts) {
7077
+ banner();
7078
+ const days = opts.days ?? 7;
7079
+ const providers = opts.provider ? [opts.provider] : Object.keys(PROVIDER_USAGE_DIRS);
7080
+ let seeded = 0;
7081
+ for (const provider of providers) {
7082
+ const usageDir = PROVIDER_USAGE_DIRS[provider];
7083
+ if (!usageDir) {
7084
+ warn(`Unknown provider: ${provider}`);
7085
+ continue;
7086
+ }
7087
+ if (!opts.force && await hasExistingData(usageDir)) {
7088
+ info(`${source_default.bold(provider)} already has usage data in ${source_default.dim(usageDir)}`);
7089
+ dim(" Use --force to overwrite existing data");
7090
+ continue;
7091
+ }
7092
+ const spin = spinner(`Generating ${provider} sample data...`);
7093
+ spin.start();
7094
+ try {
7095
+ await fs14.mkdir(usageDir, { recursive: true });
7096
+ const entries = generateSampleEntries(provider, days);
7097
+ for (const entry of entries) {
7098
+ const filePath = path10.join(usageDir, `${entry.date}.json`);
7099
+ await fs14.writeFile(filePath, JSON.stringify(entry.data, null, 2), "utf-8");
7100
+ }
7101
+ spin.succeed(`${providerLabel(provider)} ${source_default.bold(String(entries.length))} sample files written to ${source_default.dim(usageDir)}`);
7102
+ seeded++;
7103
+ } catch (err) {
7104
+ spin.fail(`Failed to seed ${provider}`);
7105
+ error(err instanceof Error ? err.message : String(err));
7106
+ }
6591
7107
  }
6592
- }
6593
- async function spawnDaemon(intervalMs) {
6594
- await ensureDir2();
6595
- const cliScript = process.argv[1];
6596
- const logFd = openSync(LOG_FILE, "a");
6597
- const child = spawn(process.execPath, [cliScript, "daemon", "__run", "--interval", String(intervalMs)], {
6598
- detached: true,
6599
- stdio: ["ignore", logFd, logFd],
6600
- env: { ...process.env }
6601
- });
6602
- child.unref();
6603
- const pid = child.pid;
6604
- if (!pid) {
6605
- throw new Error("Failed to spawn daemon process");
7108
+ console.log();
7109
+ if (seeded > 0) {
7110
+ success(`Sample data generated! Run ${source_default.cyan("awarts sync")} to push it to AWARTS.`);
7111
+ console.log();
7112
+ dim("Note: Seed data uses estimated costs based on token counts and model pricing.");
7113
+ dim("For real billing data, set up API keys: awarts keys set <provider> <key>");
7114
+ } else {
7115
+ info("No providers needed seeding. All providers already have data.");
7116
+ dim(`Use ${source_default.cyan("--force")} to regenerate sample data.`);
6606
7117
  }
6607
- await writePid(pid);
6608
- return pid;
7118
+ console.log();
6609
7119
  }
6610
7120
 
6611
7121
  // src/commands/daemon.ts
@@ -6714,9 +7224,41 @@ async function daemonRunLoop(intervalMs) {
6714
7224
  }
6715
7225
  }
6716
7226
 
7227
+ // src/lib/version-check.ts
7228
+ import { readFileSync } from "node:fs";
7229
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
7230
+ import { dirname, join } from "node:path";
7231
+ var __dirname3 = dirname(fileURLToPath2(import.meta.url));
7232
+ function getLocalVersion() {
7233
+ try {
7234
+ const pkg = JSON.parse(readFileSync(join(__dirname3, "..", "package.json"), "utf-8"));
7235
+ return pkg.version ?? "0.0.0";
7236
+ } catch {
7237
+ return "0.0.0";
7238
+ }
7239
+ }
7240
+ async function checkForUpdates() {
7241
+ try {
7242
+ const controller = new AbortController;
7243
+ const timeout = setTimeout(() => controller.abort(), 3000);
7244
+ const res = await fetch("https://registry.npmjs.org/awarts/latest", {
7245
+ signal: controller.signal
7246
+ });
7247
+ clearTimeout(timeout);
7248
+ if (!res.ok)
7249
+ return;
7250
+ const data = await res.json();
7251
+ const latest = data.version;
7252
+ const current = getLocalVersion();
7253
+ if (latest && latest !== current) {
7254
+ info(`Update available: ${current} → ${latest}. Run: npm i -g awarts@latest`);
7255
+ }
7256
+ } catch {}
7257
+ }
7258
+
6717
7259
  // src/index.ts
6718
7260
  var program2 = new Command;
6719
- program2.name("awarts").description("Track your AI coding spend across Claude, Codex, Gemini & Antigravity").version("0.1.0");
7261
+ program2.name("awarts").description("Track your AI coding spend across Claude, Codex, Gemini & Antigravity").version("0.2.5").hook("preAction", () => checkForUpdates());
6720
7262
  program2.command("login").description("Authenticate with your AWARTS account via device auth").option("--force", "Re-authenticate even if already logged in").action(async (opts) => {
6721
7263
  try {
6722
7264
  if (opts.force) {
@@ -6745,6 +7287,18 @@ program2.command("sync").description("Auto-detect all providers and push usage d
6745
7287
  process.exit(1);
6746
7288
  }
6747
7289
  });
7290
+ program2.command("seed").description("Generate sample usage data for providers with no data (Codex, Gemini, Antigravity)").option("-p, --provider <name>", "Only seed a specific provider (codex, gemini, antigravity)").option("-d, --days <count>", "Number of days of sample data to generate", "7").option("--force", "Overwrite existing usage data").action(async (opts) => {
7291
+ try {
7292
+ await seedCommand({
7293
+ provider: opts.provider,
7294
+ days: Number(opts.days) || 7,
7295
+ force: opts.force
7296
+ });
7297
+ } catch (err) {
7298
+ error(err instanceof Error ? err.message : String(err));
7299
+ process.exit(1);
7300
+ }
7301
+ });
6748
7302
  program2.command("status").description("Show auth status, configuration, and detected providers").action(async () => {
6749
7303
  try {
6750
7304
  await statusCommand();
@@ -6807,6 +7361,56 @@ daemon.command("__run", { hidden: true }).option("--interval <ms>", "Interval in
6807
7361
  process.exit(1);
6808
7362
  }
6809
7363
  });
7364
+ var keys = program2.command("keys").description("Manage API keys for provider billing (OpenAI, Google, Antigravity)");
7365
+ keys.command("set <provider> <key>").description("Store an API key for a provider (openai, google, antigravity)").action(async (provider, key) => {
7366
+ try {
7367
+ const valid = ["openai", "google", "antigravity"];
7368
+ if (!valid.includes(provider)) {
7369
+ error(`Invalid provider "${provider}". Must be one of: ${valid.join(", ")}`);
7370
+ process.exit(1);
7371
+ }
7372
+ await setKey(provider, key);
7373
+ success(`API key saved for ${provider} (stored in ~/.awarts/keys.json)`);
7374
+ } catch (err) {
7375
+ error(err instanceof Error ? err.message : String(err));
7376
+ process.exit(1);
7377
+ }
7378
+ });
7379
+ keys.command("list").description("List stored API keys (masked)").action(async () => {
7380
+ try {
7381
+ const stored = await listKeys();
7382
+ if (stored.length === 0) {
7383
+ info("No API keys stored. Use `awarts keys set <provider> <key>` to add one.");
7384
+ return;
7385
+ }
7386
+ console.log();
7387
+ for (const { provider, masked } of stored) {
7388
+ console.log(` ${provider.padEnd(14)} ${masked}`);
7389
+ }
7390
+ console.log();
7391
+ } catch (err) {
7392
+ error(err instanceof Error ? err.message : String(err));
7393
+ process.exit(1);
7394
+ }
7395
+ });
7396
+ keys.command("remove <provider>").description("Remove a stored API key").action(async (provider) => {
7397
+ try {
7398
+ const valid = ["openai", "google", "antigravity"];
7399
+ if (!valid.includes(provider)) {
7400
+ error(`Invalid provider "${provider}". Must be one of: ${valid.join(", ")}`);
7401
+ process.exit(1);
7402
+ }
7403
+ const removed = await removeKey(provider);
7404
+ if (removed) {
7405
+ success(`API key removed for ${provider}`);
7406
+ } else {
7407
+ info(`No key stored for ${provider}`);
7408
+ }
7409
+ } catch (err) {
7410
+ error(err instanceof Error ? err.message : String(err));
7411
+ process.exit(1);
7412
+ }
7413
+ });
6810
7414
  program2.parseAsync(process.argv).catch((err) => {
6811
7415
  error(err instanceof Error ? err.message : String(err));
6812
7416
  process.exit(1);