tokentracker-cli 0.51.1 → 0.52.1
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/dashboard/dist/assets/{ActivityHeatmap-CMsa_I8A.js → ActivityHeatmap-DTwq_M41.js} +1 -1
- package/dashboard/dist/assets/{Card-Dfd--KbV.js → Card-BDZMa1ls.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DOQEtJti.js → DashboardPage-i0x8Tcz8.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-CzKxKcPm.js → DevicePage-CmJnNke9.js} +1 -1
- package/dashboard/dist/assets/{DialogTitle-Ca6U7uez.js → DialogTitle-BA9fkoSJ.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-Br0mORBd.js → FadeIn-2ErjlHB5.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BK2k2YcJ.js → HeaderGithubStar-lyzD8_1T.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-C_P3q6vb.js → IpCheckPage-BzgflmcT.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-CYmHfjgg.js → LandingPage-UKFhQpm0.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-DNw_wzr5.js → LeaderboardAvatar-C9qalgJF.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-CDEltK0c.js → LeaderboardPage-4q0iscrk.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfileModal-DoM26CEG.js → LeaderboardProfileModal-B2N0uaMw.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-xNUzUA53.js → LeaderboardProfilePage-CC_pqwvP.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-B_lIlXxi.js → LimitsPage-B6ijZPMu.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-D8RXOilZ.js → LocalOnlyNotice-CRB3x8Hz.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CGA2Q4Mv.js → LoginPage-RAOFjY01.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-CIF-V90o.js → PopoverPopup-CUX-MAcT.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DySyCBKt.js → ResetPasswordPage-XM80Lfm5.js} +1 -1
- package/dashboard/dist/assets/{Select-DC0ZMkgZ.js → Select-CICbXxUX.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-3uL9SYV4.js → SelectItemText-BtA6xtov.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-Bw3pl_ve.js → SettingsPage-Bc2yBZHC.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-Cl03YNHa.js → SkillsPage-NJj02OMI.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-Dz7GR36L.js → WidgetsPage-DGt_B-gc.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-8VPwTH_x.js → WrappedPage-2cZi01wf.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-Ob9TX2iG.js → agent-logos-kTj_4Oz0.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CiXaJqcJ.js → arrow-up-right-DMKgdeZd.js} +1 -1
- package/dashboard/dist/assets/{download-Uv52RLVu.js → download-a1CVElWZ.js} +1 -1
- package/dashboard/dist/assets/{info-t1V6swsH.js → info-CrEBe6Cz.js} +1 -1
- package/dashboard/dist/assets/{main-MuwgnCSV.js → main-DS6JEATh.js} +2 -2
- package/dashboard/dist/assets/{use-limits-display-prefs-CwnyUqYj.js → use-limits-display-prefs-Z59CX_vx.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-Dq_pV6Kb.js → use-native-settings-T25Z4T4q.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-BmbBW9ph.js → use-usage-limits-CSZILsx8.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DSokKrsK.js → useCurrency-BK1iF8AJ.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-CktMaXWj.js → useScrollLock-awP6YM2L.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/lib/opencode-config.js +4 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/usage-limits.js +128 -7
package/src/lib/usage-limits.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const cp = require("node:child_process");
|
|
2
|
+
const crypto = require("node:crypto");
|
|
2
3
|
const fs = require("node:fs");
|
|
3
4
|
const os = require("node:os");
|
|
4
5
|
const path = require("node:path");
|
|
@@ -22,6 +23,7 @@ const {
|
|
|
22
23
|
fetchCursorUsageSummary,
|
|
23
24
|
} = require("./cursor-config");
|
|
24
25
|
const { fetchGrokLimits } = require("./grok-limits");
|
|
26
|
+
const { readSqliteJsonRows } = require("./sqlite-reader");
|
|
25
27
|
|
|
26
28
|
// 2-minute in-memory cache
|
|
27
29
|
let cache = { data: null, fetchedAt: 0 };
|
|
@@ -1157,11 +1159,26 @@ function parseKiroUsageOutput(output, { now = new Date() } = {}) {
|
|
|
1157
1159
|
|
|
1158
1160
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1159
1161
|
// GitHub Copilot — `GET https://api.github.com/copilot_internal/user`
|
|
1160
|
-
// Reuses the OAuth token from the user's existing Copilot install
|
|
1161
|
-
// (`~/.config/github-copilot/{apps,hosts}.json`)
|
|
1162
|
+
// Reuses the OAuth token from the user's existing Copilot install. Older clients
|
|
1163
|
+
// keep it in plaintext (`~/.config/github-copilot/{apps,hosts}.json`); recent
|
|
1164
|
+
// copilot-language-server builds (Zed, copilot.vim) migrate it into an encrypted
|
|
1165
|
+
// SQLite store (`auth.db`) and leave the plaintext files behind as stale legacy
|
|
1166
|
+
// copies. Read the plaintext first, then fall back to the encrypted store. No
|
|
1167
|
+
// device flow needed either way.
|
|
1162
1168
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1163
1169
|
|
|
1164
|
-
|
|
1170
|
+
const MACOS_SECURITY_BIN = "/usr/bin/security";
|
|
1171
|
+
// copilot-language-server stores the OAuth token as AES-256-GCM ciphertext in
|
|
1172
|
+
// auth.db (`oauth_tokens.token_ciphertext`) and the 32-byte key in the macOS
|
|
1173
|
+
// Keychain (service `copilot-language-server`, account `oauth-token-key`,
|
|
1174
|
+
// base64-encoded). Ciphertext layout: iv(12) ‖ ciphertext ‖ authTag(16).
|
|
1175
|
+
const COPILOT_LS_KEYCHAIN_SERVICE = "copilot-language-server";
|
|
1176
|
+
const COPILOT_LS_KEYCHAIN_ACCOUNT = "oauth-token-key";
|
|
1177
|
+
const COPILOT_AUTH_DB_SQL =
|
|
1178
|
+
"SELECT user_login, auth_authority, scopes, hex(token_ciphertext) AS token_hex, "
|
|
1179
|
+
+ "last_used_at, updated_at FROM oauth_tokens ORDER BY last_used_at DESC, updated_at DESC";
|
|
1180
|
+
|
|
1181
|
+
function readPlaintextCopilotOauthToken({ home }) {
|
|
1165
1182
|
const candidates = [
|
|
1166
1183
|
path.join(home, ".config", "github-copilot", "apps.json"),
|
|
1167
1184
|
path.join(home, ".config", "github-copilot", "hosts.json"),
|
|
@@ -1192,6 +1209,96 @@ function readCopilotOauthToken({ home = require("node:os").homedir() } = {}) {
|
|
|
1192
1209
|
return fallback;
|
|
1193
1210
|
}
|
|
1194
1211
|
|
|
1212
|
+
function readMacosKeychainGenericPassword({ service, account, securityRunner } = {}) {
|
|
1213
|
+
const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
|
|
1214
|
+
if (runner === cp.spawnSync && !fs.existsSync(MACOS_SECURITY_BIN)) return null;
|
|
1215
|
+
const args = ["find-generic-password", "-s", service];
|
|
1216
|
+
if (account) args.push("-a", account);
|
|
1217
|
+
args.push("-w");
|
|
1218
|
+
const result = runner(MACOS_SECURITY_BIN, args, {
|
|
1219
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1220
|
+
timeout: 2000,
|
|
1221
|
+
encoding: "utf8",
|
|
1222
|
+
});
|
|
1223
|
+
if (!result || result.error || result.status !== 0) return null;
|
|
1224
|
+
const stdout =
|
|
1225
|
+
typeof result.stdout === "string"
|
|
1226
|
+
? result.stdout
|
|
1227
|
+
: Buffer.isBuffer(result.stdout)
|
|
1228
|
+
? result.stdout.toString("utf8")
|
|
1229
|
+
: "";
|
|
1230
|
+
const trimmed = stdout.trim();
|
|
1231
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function decryptCopilotAuthDbToken(keyBase64, ciphertextHex) {
|
|
1235
|
+
if (typeof keyBase64 !== "string" || typeof ciphertextHex !== "string") return null;
|
|
1236
|
+
let key;
|
|
1237
|
+
let blob;
|
|
1238
|
+
try {
|
|
1239
|
+
key = Buffer.from(keyBase64, "base64");
|
|
1240
|
+
blob = Buffer.from(ciphertextHex, "hex");
|
|
1241
|
+
} catch (_e) {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
if (key.length !== 32) return null; // AES-256 key
|
|
1245
|
+
if (blob.length < 12 + 16 + 1) return null; // iv(12) + authTag(16) + >=1 byte token
|
|
1246
|
+
const iv = blob.subarray(0, 12);
|
|
1247
|
+
const authTag = blob.subarray(blob.length - 16);
|
|
1248
|
+
const data = blob.subarray(12, blob.length - 16);
|
|
1249
|
+
try {
|
|
1250
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
1251
|
+
decipher.setAuthTag(authTag);
|
|
1252
|
+
const out = Buffer.concat([decipher.update(data), decipher.final()]);
|
|
1253
|
+
const token = out.toString("utf8").trim();
|
|
1254
|
+
return token.length > 0 ? token : null;
|
|
1255
|
+
} catch (_e) {
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function readCopilotAuthDbToken({ home, platform = process.platform, securityRunner, sqliteReader } = {}) {
|
|
1261
|
+
// The decryption key lives in the macOS Keychain. On Linux/Windows the
|
|
1262
|
+
// copilot-language-server uses libsecret / Credential Manager instead, which we
|
|
1263
|
+
// don't read yet — callers fall back to the plaintext path there.
|
|
1264
|
+
if (platform !== "darwin") return null;
|
|
1265
|
+
const resolvedHome = home || os.homedir();
|
|
1266
|
+
const dbPath = path.join(resolvedHome, ".config", "github-copilot", "auth.db");
|
|
1267
|
+
const reader = typeof sqliteReader === "function" ? sqliteReader : readSqliteJsonRows;
|
|
1268
|
+
let rows;
|
|
1269
|
+
try {
|
|
1270
|
+
rows = reader(dbPath, COPILOT_AUTH_DB_SQL, { label: "GitHub Copilot" });
|
|
1271
|
+
} catch (_e) {
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
1275
|
+
// Prefer the public github.com host (mirrors the plaintext reader); rows are
|
|
1276
|
+
// already ordered most-recently-used first.
|
|
1277
|
+
const row =
|
|
1278
|
+
rows.find((r) => String(r?.auth_authority || "").split(":")[0] === "github.com") || rows[0];
|
|
1279
|
+
const ciphertextHex = typeof row?.token_hex === "string" ? row.token_hex : null;
|
|
1280
|
+
if (!ciphertextHex) return null;
|
|
1281
|
+
const keyBase64 = readMacosKeychainGenericPassword({
|
|
1282
|
+
service: COPILOT_LS_KEYCHAIN_SERVICE,
|
|
1283
|
+
account: COPILOT_LS_KEYCHAIN_ACCOUNT,
|
|
1284
|
+
securityRunner,
|
|
1285
|
+
});
|
|
1286
|
+
if (!keyBase64) return null;
|
|
1287
|
+
return decryptCopilotAuthDbToken(keyBase64, ciphertextHex);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function readCopilotOauthToken({
|
|
1291
|
+
home = os.homedir(),
|
|
1292
|
+
platform = process.platform,
|
|
1293
|
+
securityRunner,
|
|
1294
|
+
sqliteReader,
|
|
1295
|
+
} = {}) {
|
|
1296
|
+
const plaintext = readPlaintextCopilotOauthToken({ home });
|
|
1297
|
+
if (plaintext) return plaintext;
|
|
1298
|
+
// No plaintext token found — recover the live one from the encrypted auth.db.
|
|
1299
|
+
return readCopilotAuthDbToken({ home, platform, securityRunner, sqliteReader });
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1195
1302
|
function copilotRequestHeaders(token) {
|
|
1196
1303
|
return {
|
|
1197
1304
|
Authorization: `token ${token}`,
|
|
@@ -1232,7 +1339,7 @@ function buildCopilotWindow(snapshot, resetIso) {
|
|
|
1232
1339
|
}
|
|
1233
1340
|
|
|
1234
1341
|
function describeCopilotOtelStatus({ home, env = process.env } = {}) {
|
|
1235
|
-
const resolvedHome = home || env.HOME ||
|
|
1342
|
+
const resolvedHome = home || env.HOME || os.homedir();
|
|
1236
1343
|
const enabled = String(env.COPILOT_OTEL_ENABLED || "").toLowerCase() === "true";
|
|
1237
1344
|
const exporterType = String(env.COPILOT_OTEL_EXPORTER_TYPE || "").toLowerCase();
|
|
1238
1345
|
const explicitPath = typeof env.COPILOT_OTEL_FILE_EXPORTER_PATH === "string"
|
|
@@ -1255,9 +1362,21 @@ function describeCopilotOtelStatus({ home, env = process.env } = {}) {
|
|
|
1255
1362
|
};
|
|
1256
1363
|
}
|
|
1257
1364
|
|
|
1258
|
-
async function fetchCopilotLimits({
|
|
1365
|
+
async function fetchCopilotLimits({
|
|
1366
|
+
home,
|
|
1367
|
+
env = process.env,
|
|
1368
|
+
fetchImpl = fetch,
|
|
1369
|
+
platform = process.platform,
|
|
1370
|
+
securityRunner,
|
|
1371
|
+
sqliteReader,
|
|
1372
|
+
} = {}) {
|
|
1259
1373
|
const otel = describeCopilotOtelStatus({ home, env });
|
|
1260
|
-
const token = readCopilotOauthToken({
|
|
1374
|
+
const token = readCopilotOauthToken({
|
|
1375
|
+
home: home || (env.HOME || os.homedir()),
|
|
1376
|
+
platform,
|
|
1377
|
+
securityRunner,
|
|
1378
|
+
sqliteReader,
|
|
1379
|
+
});
|
|
1261
1380
|
if (!token) return { configured: false, ...otel };
|
|
1262
1381
|
|
|
1263
1382
|
try {
|
|
@@ -2072,7 +2191,7 @@ async function fetchUsageLimitsUncached({
|
|
|
2072
2191
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
2073
2192
|
fetchKiroLimits({ commandRunner, now }),
|
|
2074
2193
|
fetchAntigravityLimits({ home, commandRunner, requestFn, nowMs }),
|
|
2075
|
-
withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch }), "GitHub Copilot", providerTimeoutMs)
|
|
2194
|
+
withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch, platform, securityRunner }), "GitHub Copilot", providerTimeoutMs)
|
|
2076
2195
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
2077
2196
|
withProviderTimeout(fetchGrokLimits({ home, env, fetchImpl: providerFetch }), "Grok Build", providerTimeoutMs)
|
|
2078
2197
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
@@ -2183,6 +2302,8 @@ module.exports = {
|
|
|
2183
2302
|
fetchAntigravityLimits,
|
|
2184
2303
|
fetchCopilotLimits,
|
|
2185
2304
|
readCopilotOauthToken,
|
|
2305
|
+
readCopilotAuthDbToken,
|
|
2306
|
+
decryptCopilotAuthDbToken,
|
|
2186
2307
|
describeCopilotOtelStatus,
|
|
2187
2308
|
fetchGrokLimits,
|
|
2188
2309
|
};
|