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.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/index.js +826 -222
- 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
|
-
|
|
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
|
|
5879
|
-
import
|
|
5880
|
-
import
|
|
5881
|
-
var CLAUDE_DIR =
|
|
5882
|
-
var STATS_CACHE =
|
|
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:
|
|
5885
|
-
"claude-opus-4": { input:
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5988
|
-
import
|
|
5989
|
-
import
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
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
|
-
|
|
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
|
|
6063
|
-
import
|
|
6064
|
-
import
|
|
6065
|
-
|
|
6066
|
-
var
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6138
|
-
import
|
|
6139
|
-
import
|
|
6140
|
-
var HOME3 =
|
|
6141
|
-
var
|
|
6142
|
-
|
|
6143
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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/
|
|
6529
|
-
import
|
|
6530
|
-
import
|
|
6531
|
-
import
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
var
|
|
6535
|
-
var
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
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
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
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
|
|
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
|
-
|
|
6562
|
-
return
|
|
7060
|
+
const stat = await fs14.stat(dir);
|
|
7061
|
+
return stat.isDirectory();
|
|
6563
7062
|
} catch {
|
|
6564
7063
|
return false;
|
|
6565
7064
|
}
|
|
6566
7065
|
}
|
|
6567
|
-
function
|
|
7066
|
+
async function hasExistingData(dir) {
|
|
7067
|
+
if (!await dirExists5(dir))
|
|
7068
|
+
return false;
|
|
6568
7069
|
try {
|
|
6569
|
-
|
|
6570
|
-
return
|
|
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
|
|
6576
|
-
|
|
6577
|
-
const
|
|
6578
|
-
const
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
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
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
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
|
-
|
|
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.
|
|
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);
|