just-usage 0.0.5 → 0.0.6
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/CHANGELOG.md +6 -0
- package/dist/cli.js +340 -78
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.0.6
|
|
4
|
+
|
|
5
|
+
## What's Changed
|
|
6
|
+
Detect newly installed CLIs on refresh, keep provider tabs out of the URL, and write action logs. in [#3b3f92c](https://github.com/spheceo/just-usage/commit/3b3f92c086e55533da48ebac2023b41ce32d21a3)
|
|
7
|
+
Remove obsolete architecture plan in [#23d08e9](https://github.com/spheceo/just-usage/commit/23d08e96a8a485692062802d3e01886857573af9)
|
|
8
|
+
|
|
3
9
|
## v0.0.5
|
|
4
10
|
|
|
5
11
|
## What's Changed
|
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { mkdirSync } from "node:fs";
|
|
|
15
15
|
// package.json
|
|
16
16
|
var package_default = {
|
|
17
17
|
name: "just-usage",
|
|
18
|
-
version: "0.0.
|
|
18
|
+
version: "0.0.6",
|
|
19
19
|
description: "One local page for your coding-CLI subscription quotas: Claude, Codex, Cursor, Antigravity, Grok, OpenCode Go.",
|
|
20
20
|
type: "module",
|
|
21
21
|
license: "MIT",
|
|
@@ -481,12 +481,15 @@ async function codexLogin(codexHome, onAuthUrl, timeoutMs = 5 * 60000) {
|
|
|
481
481
|
|
|
482
482
|
// src/adapters/claude.ts
|
|
483
483
|
import { createHash } from "node:crypto";
|
|
484
|
-
import { existsSync as
|
|
485
|
-
import { homedir as
|
|
486
|
-
import { join as
|
|
484
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
485
|
+
import { homedir as homedir3 } from "node:os";
|
|
486
|
+
import { join as join3 } from "node:path";
|
|
487
487
|
|
|
488
488
|
// src/proc.ts
|
|
489
489
|
import { spawn as spawn2 } from "node:child_process";
|
|
490
|
+
import { existsSync } from "node:fs";
|
|
491
|
+
import { homedir as homedir2 } from "node:os";
|
|
492
|
+
import { delimiter, join as join2 } from "node:path";
|
|
490
493
|
function run(cmd, args, opts = {}) {
|
|
491
494
|
return new Promise((resolve) => {
|
|
492
495
|
let stdout = "";
|
|
@@ -500,7 +503,7 @@ function run(cmd, args, opts = {}) {
|
|
|
500
503
|
resolve({ code, stdout, stderr });
|
|
501
504
|
};
|
|
502
505
|
const child = spawn2(cmd, args, {
|
|
503
|
-
env:
|
|
506
|
+
env: spawnEnv(opts.env),
|
|
504
507
|
stdio: ["pipe", "pipe", "pipe"]
|
|
505
508
|
});
|
|
506
509
|
const timer = setTimeout(() => {
|
|
@@ -523,23 +526,70 @@ function run(cmd, args, opts = {}) {
|
|
|
523
526
|
}
|
|
524
527
|
function runInteractive(cmd, args, env) {
|
|
525
528
|
return new Promise((resolve, reject) => {
|
|
526
|
-
const child = spawn2(cmd, args, { env:
|
|
529
|
+
const child = spawn2(cmd, args, { env: spawnEnv(env), stdio: "inherit" });
|
|
527
530
|
child.on("error", reject);
|
|
528
531
|
child.on("close", (code) => resolve(code));
|
|
529
532
|
});
|
|
530
533
|
}
|
|
531
534
|
var whichCache = new Map;
|
|
532
|
-
function
|
|
535
|
+
function extraBinDirs() {
|
|
536
|
+
const home = homedir2();
|
|
537
|
+
const dirs = [
|
|
538
|
+
join2(home, ".local", "bin"),
|
|
539
|
+
join2(home, "bin"),
|
|
540
|
+
"/opt/homebrew/bin",
|
|
541
|
+
"/usr/local/bin",
|
|
542
|
+
"/home/linuxbrew/.linuxbrew/bin",
|
|
543
|
+
join2(home, ".npm-global", "bin")
|
|
544
|
+
];
|
|
545
|
+
if (process.platform === "win32") {
|
|
546
|
+
const local = process.env.LOCALAPPDATA;
|
|
547
|
+
if (local)
|
|
548
|
+
dirs.push(join2(local, "agy", "bin"));
|
|
549
|
+
}
|
|
550
|
+
return dirs.filter((d) => existsSync(d));
|
|
551
|
+
}
|
|
552
|
+
function spawnEnv(extra) {
|
|
553
|
+
const pathKey = process.platform === "win32" && process.env.Path && !process.env.PATH ? "Path" : "PATH";
|
|
554
|
+
const current = extra?.[pathKey] ?? extra?.PATH ?? process.env[pathKey] ?? process.env.PATH ?? "";
|
|
555
|
+
const seen = new Set;
|
|
556
|
+
const parts = [];
|
|
557
|
+
for (const dir of [...extraBinDirs(), ...current.split(delimiter)]) {
|
|
558
|
+
if (!dir || seen.has(dir))
|
|
559
|
+
continue;
|
|
560
|
+
seen.add(dir);
|
|
561
|
+
parts.push(dir);
|
|
562
|
+
}
|
|
563
|
+
return { ...process.env, ...extra, [pathKey]: parts.join(delimiter) };
|
|
564
|
+
}
|
|
565
|
+
async function resolveBin(bin) {
|
|
566
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
567
|
+
const res = await run(finder, [bin], { timeoutMs: 5000 });
|
|
568
|
+
if (res.code === 0) {
|
|
569
|
+
const first = res.stdout.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
570
|
+
if (first && existsSync(first))
|
|
571
|
+
return first;
|
|
572
|
+
}
|
|
573
|
+
const names = process.platform === "win32" ? [bin, `${bin}.exe`, `${bin}.cmd`] : [bin];
|
|
574
|
+
for (const dir of extraBinDirs()) {
|
|
575
|
+
for (const name of names) {
|
|
576
|
+
const candidate = join2(dir, name);
|
|
577
|
+
if (existsSync(candidate))
|
|
578
|
+
return candidate;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
function clearBinCache() {
|
|
584
|
+
whichCache.clear();
|
|
585
|
+
versionCache.clear();
|
|
586
|
+
}
|
|
587
|
+
function which(bin, opts = {}) {
|
|
588
|
+
if (opts.fresh)
|
|
589
|
+
whichCache.delete(bin);
|
|
533
590
|
let p = whichCache.get(bin);
|
|
534
591
|
if (!p) {
|
|
535
|
-
p = (
|
|
536
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
537
|
-
const res = await run(finder, [bin], { timeoutMs: 5000 });
|
|
538
|
-
if (res.code !== 0)
|
|
539
|
-
return null;
|
|
540
|
-
const first = res.stdout.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
541
|
-
return first ?? null;
|
|
542
|
-
})();
|
|
592
|
+
p = resolveBin(bin);
|
|
543
593
|
whichCache.set(bin, p);
|
|
544
594
|
}
|
|
545
595
|
return p;
|
|
@@ -581,7 +631,7 @@ function withTimeout(p, ms, label) {
|
|
|
581
631
|
}
|
|
582
632
|
|
|
583
633
|
// src/secrets.ts
|
|
584
|
-
import { existsSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
|
|
634
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, chmodSync } from "node:fs";
|
|
585
635
|
import { dirname } from "node:path";
|
|
586
636
|
var SERVICE = "just-usage";
|
|
587
637
|
|
|
@@ -608,7 +658,7 @@ class FileStore {
|
|
|
608
658
|
kind = "file";
|
|
609
659
|
read() {
|
|
610
660
|
const file = paths.secrets();
|
|
611
|
-
if (!
|
|
661
|
+
if (!existsSync2(file))
|
|
612
662
|
return {};
|
|
613
663
|
try {
|
|
614
664
|
return JSON.parse(readFileSync(file, "utf8"));
|
|
@@ -680,8 +730,8 @@ async function readClaudeCredentials(configDir) {
|
|
|
680
730
|
return creds;
|
|
681
731
|
}
|
|
682
732
|
}
|
|
683
|
-
const file =
|
|
684
|
-
if (
|
|
733
|
+
const file = join3(configDir ?? join3(homedir3(), ".claude"), ".credentials.json");
|
|
734
|
+
if (existsSync3(file))
|
|
685
735
|
return parseCreds(readFileSync2(file, "utf8"));
|
|
686
736
|
return null;
|
|
687
737
|
}
|
|
@@ -834,9 +884,9 @@ import { renameSync, rmSync as rmSync2 } from "node:fs";
|
|
|
834
884
|
|
|
835
885
|
// src/adapters/antigravity.ts
|
|
836
886
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
837
|
-
import { existsSync as
|
|
838
|
-
import { homedir as
|
|
839
|
-
import { join as
|
|
887
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
888
|
+
import { homedir as homedir4 } from "node:os";
|
|
889
|
+
import { join as join4 } from "node:path";
|
|
840
890
|
var AGY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
841
891
|
var AGY_CLIENT_SECRET = "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";
|
|
842
892
|
var AGY_REDIRECT_URI = "https://antigravity.google/oauth-callback";
|
|
@@ -853,11 +903,11 @@ var KEYCHAIN_SERVICE = "gemini";
|
|
|
853
903
|
var KEYCHAIN_ACCOUNT = "antigravity";
|
|
854
904
|
var FALLBACK_CLI_VERSION2 = "1.1.26";
|
|
855
905
|
function settingsFile() {
|
|
856
|
-
return
|
|
906
|
+
return join4(homedir4(), ".gemini", "antigravity-cli", "settings.json");
|
|
857
907
|
}
|
|
858
908
|
function usesGeminiApiKey() {
|
|
859
909
|
try {
|
|
860
|
-
if (!
|
|
910
|
+
if (!existsSync4(settingsFile()))
|
|
861
911
|
return false;
|
|
862
912
|
const parsed = JSON.parse(readFileSync3(settingsFile(), "utf8"));
|
|
863
913
|
return parsed.modelProvider === "gemini" && Boolean(process.env.GEMINI_API_KEY?.trim());
|
|
@@ -923,8 +973,18 @@ async function tokenFromSecretTool() {
|
|
|
923
973
|
return null;
|
|
924
974
|
return parseAgyKeyringBlob(res.stdout);
|
|
925
975
|
}
|
|
976
|
+
function tokenFromOauthFile() {
|
|
977
|
+
const file = join4(homedir4(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
|
|
978
|
+
if (!existsSync4(file))
|
|
979
|
+
return null;
|
|
980
|
+
try {
|
|
981
|
+
return parseAgyKeyringBlob(readFileSync3(file, "utf8"));
|
|
982
|
+
} catch {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
926
986
|
async function readDefaultAgyToken() {
|
|
927
|
-
return await tokenFromKeychain() ?? await tokenFromSecretTool();
|
|
987
|
+
return await tokenFromKeychain() ?? await tokenFromSecretTool() ?? tokenFromOauthFile();
|
|
928
988
|
}
|
|
929
989
|
function createPkce() {
|
|
930
990
|
const verifier = randomBytes(32).toString("base64url");
|
|
@@ -1188,20 +1248,20 @@ async function fetchAntigravity(account) {
|
|
|
1188
1248
|
}
|
|
1189
1249
|
|
|
1190
1250
|
// src/adapters/opencode.ts
|
|
1191
|
-
import { existsSync as
|
|
1192
|
-
import { homedir as
|
|
1193
|
-
import { join as
|
|
1251
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
1252
|
+
import { homedir as homedir5 } from "node:os";
|
|
1253
|
+
import { join as join5 } from "node:path";
|
|
1194
1254
|
var USAGE_URL2 = "https://opencode.ai/zen/go/v1/usage";
|
|
1195
1255
|
function openCodeAuthFile() {
|
|
1196
1256
|
if (process.env.OPENCODE_AUTH_FILE)
|
|
1197
1257
|
return process.env.OPENCODE_AUTH_FILE;
|
|
1198
1258
|
const xdg = process.env.XDG_DATA_HOME;
|
|
1199
|
-
const base = xdg && xdg.trim() ? xdg :
|
|
1200
|
-
return
|
|
1259
|
+
const base = xdg && xdg.trim() ? xdg : join5(homedir5(), ".local", "share");
|
|
1260
|
+
return join5(base, "opencode", "auth.json");
|
|
1201
1261
|
}
|
|
1202
1262
|
function readOpenCodeGoKey() {
|
|
1203
1263
|
const file = openCodeAuthFile();
|
|
1204
|
-
if (!
|
|
1264
|
+
if (!existsSync5(file))
|
|
1205
1265
|
return null;
|
|
1206
1266
|
try {
|
|
1207
1267
|
const parsed = JSON.parse(readFileSync4(file, "utf8"));
|
|
@@ -1263,12 +1323,70 @@ async function fetchOpenCode(account) {
|
|
|
1263
1323
|
}
|
|
1264
1324
|
}
|
|
1265
1325
|
|
|
1326
|
+
// src/log.ts
|
|
1327
|
+
import { appendFileSync } from "node:fs";
|
|
1328
|
+
import { homedir as homedir6 } from "node:os";
|
|
1329
|
+
import { join as join6 } from "node:path";
|
|
1330
|
+
var SECRET_KEY = /^(.*[._-]?)?(secret|token|password|authorization|cookie|verifier|refresh_token|access_token|api[_-]?key|key)$/i;
|
|
1331
|
+
var SKIP_KEY = /^(url|authurl|callback|body|headers|authorization)$/i;
|
|
1332
|
+
var MAX_STRING = 400;
|
|
1333
|
+
function logDir() {
|
|
1334
|
+
const override = process.env.JUST_USAGE_LOG_DIR;
|
|
1335
|
+
if (override && override.trim())
|
|
1336
|
+
return override.trim();
|
|
1337
|
+
return join6(homedir6(), ".just-usage", "logs");
|
|
1338
|
+
}
|
|
1339
|
+
function logFile(at = new Date) {
|
|
1340
|
+
return join6(logDir(), `${at.toISOString().slice(0, 10)}.log`);
|
|
1341
|
+
}
|
|
1342
|
+
function sanitizeFields(fields) {
|
|
1343
|
+
if (!fields)
|
|
1344
|
+
return {};
|
|
1345
|
+
const out = {};
|
|
1346
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
1347
|
+
if (v === undefined)
|
|
1348
|
+
continue;
|
|
1349
|
+
if (SECRET_KEY.test(k) || SKIP_KEY.test(k))
|
|
1350
|
+
continue;
|
|
1351
|
+
if (v === null || typeof v === "number" || typeof v === "boolean") {
|
|
1352
|
+
out[k] = v;
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
if (typeof v === "string") {
|
|
1356
|
+
out[k] = v.length <= MAX_STRING ? v : `${v.slice(0, MAX_STRING)}…`;
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (Array.isArray(v) && v.every((x) => typeof x === "string" || typeof x === "number")) {
|
|
1360
|
+
out[k] = v.slice(0, 20);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return out;
|
|
1364
|
+
}
|
|
1365
|
+
function log(level, event, fields) {
|
|
1366
|
+
try {
|
|
1367
|
+
ensureDir(logDir());
|
|
1368
|
+
const line = JSON.stringify({
|
|
1369
|
+
ts: new Date().toISOString(),
|
|
1370
|
+
level,
|
|
1371
|
+
event,
|
|
1372
|
+
pid: process.pid,
|
|
1373
|
+
v: VERSION,
|
|
1374
|
+
...sanitizeFields(fields)
|
|
1375
|
+
});
|
|
1376
|
+
appendFileSync(logFile(), `${line}
|
|
1377
|
+
`, { encoding: "utf8", mode: 384 });
|
|
1378
|
+
} catch {}
|
|
1379
|
+
}
|
|
1380
|
+
function logError(event, e, fields) {
|
|
1381
|
+
log("error", event, { ...fields, message: errorMessage(e) });
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1266
1384
|
// src/registry.ts
|
|
1267
|
-
import { existsSync as
|
|
1268
|
-
import { dirname as dirname2, join as
|
|
1385
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2, rmSync } from "node:fs";
|
|
1386
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
1269
1387
|
function readRegistry() {
|
|
1270
1388
|
const file = paths.registry();
|
|
1271
|
-
if (!
|
|
1389
|
+
if (!existsSync6(file))
|
|
1272
1390
|
return { version: 1, accounts: [] };
|
|
1273
1391
|
try {
|
|
1274
1392
|
const parsed = JSON.parse(readFileSync5(file, "utf8"));
|
|
@@ -1304,7 +1422,7 @@ function newAccountId(provider, hint) {
|
|
|
1304
1422
|
}
|
|
1305
1423
|
}
|
|
1306
1424
|
function profileDirFor(provider, id) {
|
|
1307
|
-
return
|
|
1425
|
+
return join7(paths.profiles(provider), id.split(":")[1] ?? "account");
|
|
1308
1426
|
}
|
|
1309
1427
|
function saveAccount(record) {
|
|
1310
1428
|
const reg = readRegistry();
|
|
@@ -1361,6 +1479,7 @@ async function addClaudeToken(token, label) {
|
|
|
1361
1479
|
createdAt: new Date().toISOString()
|
|
1362
1480
|
};
|
|
1363
1481
|
saveAccount(account);
|
|
1482
|
+
log("info", "account.add", { account: account.id, provider: account.provider, kind: account.kind });
|
|
1364
1483
|
return { account };
|
|
1365
1484
|
}
|
|
1366
1485
|
async function addOpenCodeKey(key, label) {
|
|
@@ -1383,6 +1502,12 @@ async function addOpenCodeKey(key, label) {
|
|
|
1383
1502
|
createdAt: new Date().toISOString()
|
|
1384
1503
|
};
|
|
1385
1504
|
saveAccount(account);
|
|
1505
|
+
log("info", "account.add", {
|
|
1506
|
+
account: account.id,
|
|
1507
|
+
provider: account.provider,
|
|
1508
|
+
kind: account.kind,
|
|
1509
|
+
warning: status === 403 ? "no-subscription" : undefined
|
|
1510
|
+
});
|
|
1386
1511
|
return {
|
|
1387
1512
|
account,
|
|
1388
1513
|
warning: status === 403 ? "Key is valid but has no active OpenCode Go subscription." : undefined
|
|
@@ -1394,7 +1519,9 @@ function renameExtraAccount(id, label) {
|
|
|
1394
1519
|
if (!getAccount(id))
|
|
1395
1520
|
throw new AccountError(`Unknown account: ${id}`, 404);
|
|
1396
1521
|
updateAccount(id, { label: label.trim() });
|
|
1397
|
-
|
|
1522
|
+
const account = getAccount(id);
|
|
1523
|
+
log("info", "account.rename", { account: account.id, provider: account.provider });
|
|
1524
|
+
return account;
|
|
1398
1525
|
}
|
|
1399
1526
|
async function removeExtraAccount(id) {
|
|
1400
1527
|
if (id.endsWith(":default"))
|
|
@@ -1404,6 +1531,7 @@ async function removeExtraAccount(id) {
|
|
|
1404
1531
|
throw new AccountError(`Unknown account: ${id}`, 404);
|
|
1405
1532
|
if (rec.kind === "token")
|
|
1406
1533
|
await secretStore().delete(id);
|
|
1534
|
+
log("info", "account.remove", { account: rec.id, provider: rec.provider, kind: rec.kind });
|
|
1407
1535
|
return rec;
|
|
1408
1536
|
}
|
|
1409
1537
|
function saveCodexProfile(opts) {
|
|
@@ -1425,6 +1553,7 @@ function saveCodexProfile(opts) {
|
|
|
1425
1553
|
createdAt: new Date().toISOString()
|
|
1426
1554
|
};
|
|
1427
1555
|
saveAccount(account);
|
|
1556
|
+
log("info", "account.add", { account: account.id, provider: account.provider, kind: account.kind });
|
|
1428
1557
|
return account;
|
|
1429
1558
|
}
|
|
1430
1559
|
function isLocalCallbackUrl(raw) {
|
|
@@ -1464,7 +1593,7 @@ function dropCodexSession(id, removeDir) {
|
|
|
1464
1593
|
}
|
|
1465
1594
|
}
|
|
1466
1595
|
async function beginCodexAdd(label) {
|
|
1467
|
-
if (!await which("codex"))
|
|
1596
|
+
if (!await which("codex", { fresh: true }))
|
|
1468
1597
|
throw new AccountError("codex is not installed (npm i -g @openai/codex).");
|
|
1469
1598
|
const tmpId = newAccountId("codex", label?.trim() || "pending");
|
|
1470
1599
|
const dir = ensureDir(profileDirFor("codex", tmpId));
|
|
@@ -1483,11 +1612,14 @@ async function beginCodexAdd(label) {
|
|
|
1483
1612
|
handle.completed.then((info) => {
|
|
1484
1613
|
rec.account = saveCodexProfile({ label: rec.label, tmpId, dir, email: info.email });
|
|
1485
1614
|
rec.status = "done";
|
|
1615
|
+
log("info", "account.login.done", { account: rec.account.id, provider: "codex" });
|
|
1486
1616
|
}).catch((e) => {
|
|
1487
1617
|
rec.status = "error";
|
|
1488
1618
|
rec.error = e instanceof Error ? e.message : String(e);
|
|
1619
|
+
logError("account.login.error", e, { provider: "codex" });
|
|
1489
1620
|
});
|
|
1490
1621
|
codexSessions.set(id, rec);
|
|
1622
|
+
log("info", "account.login.start", { provider: "codex", kind: "add" });
|
|
1491
1623
|
return { sessionId: id, authUrl: handle.authUrl };
|
|
1492
1624
|
}
|
|
1493
1625
|
async function beginCodexRelogin(accountId) {
|
|
@@ -1512,11 +1644,14 @@ async function beginCodexRelogin(accountId) {
|
|
|
1512
1644
|
updateAccount(existing.id, { email: info.email });
|
|
1513
1645
|
rec.account = { ...existing, email: info.email };
|
|
1514
1646
|
rec.status = "done";
|
|
1647
|
+
log("info", "account.login.done", { account: existing.id, provider: "codex" });
|
|
1515
1648
|
}).catch((e) => {
|
|
1516
1649
|
rec.status = "error";
|
|
1517
1650
|
rec.error = e instanceof Error ? e.message : String(e);
|
|
1651
|
+
logError("account.login.error", e, { account: existing.id, provider: "codex" });
|
|
1518
1652
|
});
|
|
1519
1653
|
codexSessions.set(id, rec);
|
|
1654
|
+
log("info", "account.login.start", { account: existing.id, provider: "codex", kind: "relogin" });
|
|
1520
1655
|
return { sessionId: id, authUrl: handle.authUrl };
|
|
1521
1656
|
}
|
|
1522
1657
|
function codexSessionStatus(sessionId) {
|
|
@@ -1563,9 +1698,11 @@ function startAgySession(opts) {
|
|
|
1563
1698
|
return { sessionId: id, authUrl };
|
|
1564
1699
|
}
|
|
1565
1700
|
async function beginAntigravityAdd(label) {
|
|
1566
|
-
if (!await which("agy"))
|
|
1701
|
+
if (!await which("agy", { fresh: true }))
|
|
1567
1702
|
throw new AccountError("agy is not installed (https://antigravity.google/docs/cli/install).");
|
|
1568
|
-
|
|
1703
|
+
const started = startAgySession({ label });
|
|
1704
|
+
log("info", "account.login.start", { provider: "antigravity", kind: "add" });
|
|
1705
|
+
return started;
|
|
1569
1706
|
}
|
|
1570
1707
|
async function beginAntigravityRelogin(accountId) {
|
|
1571
1708
|
const existing = getAccount(accountId);
|
|
@@ -1574,7 +1711,9 @@ async function beginAntigravityRelogin(accountId) {
|
|
|
1574
1711
|
if (existing.provider !== "antigravity" || existing.kind !== "token") {
|
|
1575
1712
|
throw new AccountError(`${accountId} cannot be re-authenticated this way.`);
|
|
1576
1713
|
}
|
|
1577
|
-
|
|
1714
|
+
const started = startAgySession({ accountId });
|
|
1715
|
+
log("info", "account.login.start", { account: accountId, provider: "antigravity", kind: "relogin" });
|
|
1716
|
+
return started;
|
|
1578
1717
|
}
|
|
1579
1718
|
function antigravitySessionStatus(sessionId) {
|
|
1580
1719
|
const rec = agySessions.get(sessionId);
|
|
@@ -1617,21 +1756,29 @@ async function submitAntigravityCallback(sessionId, raw) {
|
|
|
1617
1756
|
saveAccount(rec.account);
|
|
1618
1757
|
}
|
|
1619
1758
|
rec.status = "done";
|
|
1759
|
+
log("info", rec.accountId ? "account.login.done" : "account.add", {
|
|
1760
|
+
account: rec.account.id,
|
|
1761
|
+
provider: "antigravity",
|
|
1762
|
+
kind: rec.account.kind
|
|
1763
|
+
});
|
|
1620
1764
|
return rec.account;
|
|
1621
1765
|
} catch (e) {
|
|
1622
1766
|
rec.status = "error";
|
|
1623
1767
|
rec.error = e instanceof Error ? e.message : String(e);
|
|
1768
|
+
logError("account.login.error", e, { provider: "antigravity", account: rec.accountId });
|
|
1624
1769
|
throw new AccountError(rec.error);
|
|
1625
1770
|
}
|
|
1626
1771
|
}
|
|
1627
1772
|
|
|
1628
1773
|
// src/collect.ts
|
|
1629
|
-
import {
|
|
1774
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1775
|
+
import { homedir as homedir9, hostname } from "node:os";
|
|
1776
|
+
import { join as join10 } from "node:path";
|
|
1630
1777
|
|
|
1631
1778
|
// src/adapters/cursor.ts
|
|
1632
|
-
import { existsSync as
|
|
1633
|
-
import { homedir as
|
|
1634
|
-
import { join as
|
|
1779
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
1780
|
+
import { homedir as homedir7 } from "node:os";
|
|
1781
|
+
import { join as join8 } from "node:path";
|
|
1635
1782
|
var USAGE_URL3 = "https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage";
|
|
1636
1783
|
var GROK_BOT_URL = "https://api2.cursor.sh/aiserver.v1.DashboardService/GetSandUsageStatus";
|
|
1637
1784
|
var PLAN_URL = "https://api2.cursor.sh/aiserver.v1.DashboardService/GetPlanInfo";
|
|
@@ -1643,7 +1790,7 @@ function tokenFromAuthJson(parsed) {
|
|
|
1643
1790
|
return typeof parsed.accessToken === "string" && parsed.accessToken ? parsed.accessToken : null;
|
|
1644
1791
|
}
|
|
1645
1792
|
function tokenFromAuthFile(file) {
|
|
1646
|
-
if (!
|
|
1793
|
+
if (!existsSync7(file))
|
|
1647
1794
|
return null;
|
|
1648
1795
|
try {
|
|
1649
1796
|
return tokenFromAuthJson(JSON.parse(readFileSync6(file, "utf8")));
|
|
@@ -1652,16 +1799,16 @@ function tokenFromAuthFile(file) {
|
|
|
1652
1799
|
}
|
|
1653
1800
|
}
|
|
1654
1801
|
function authFiles() {
|
|
1655
|
-
const home =
|
|
1802
|
+
const home = homedir7();
|
|
1656
1803
|
const out = [];
|
|
1657
1804
|
if (process.platform === "win32") {
|
|
1658
|
-
const roaming = process.env.APPDATA ||
|
|
1659
|
-
out.push(
|
|
1805
|
+
const roaming = process.env.APPDATA || join8(home, "AppData", "Roaming");
|
|
1806
|
+
out.push(join8(roaming, "Cursor", "auth.json"));
|
|
1660
1807
|
} else if (process.platform !== "darwin") {
|
|
1661
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
1662
|
-
out.push(
|
|
1808
|
+
const xdg = process.env.XDG_CONFIG_HOME || join8(home, ".config");
|
|
1809
|
+
out.push(join8(xdg, "cursor", "auth.json"));
|
|
1663
1810
|
}
|
|
1664
|
-
out.push(
|
|
1811
|
+
out.push(join8(home, ".cursor", "auth.json"));
|
|
1665
1812
|
return out;
|
|
1666
1813
|
}
|
|
1667
1814
|
async function tokenFromKeychain2() {
|
|
@@ -1841,9 +1988,9 @@ async function fetchCursor(account) {
|
|
|
1841
1988
|
}
|
|
1842
1989
|
|
|
1843
1990
|
// src/adapters/grok.ts
|
|
1844
|
-
import { existsSync as
|
|
1845
|
-
import { homedir as
|
|
1846
|
-
import { join as
|
|
1991
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
1992
|
+
import { homedir as homedir8 } from "node:os";
|
|
1993
|
+
import { join as join9 } from "node:path";
|
|
1847
1994
|
var DEFAULT_PROXY = "https://cli-chat-proxy.grok.com/v1";
|
|
1848
1995
|
var TOKEN_URL = "https://auth.x.ai/oauth2/token";
|
|
1849
1996
|
var FALLBACK_CLI_VERSION3 = "1.0.13";
|
|
@@ -1858,7 +2005,7 @@ var TIER_NAMES = {
|
|
|
1858
2005
|
function authFile() {
|
|
1859
2006
|
if (process.env.GROK_AUTH_FILE)
|
|
1860
2007
|
return process.env.GROK_AUTH_FILE;
|
|
1861
|
-
return
|
|
2008
|
+
return join9(homedir8(), ".grok", "auth.json");
|
|
1862
2009
|
}
|
|
1863
2010
|
function proxyBase() {
|
|
1864
2011
|
const raw = process.env.GROK_CLI_CHAT_PROXY_BASE_URL?.trim();
|
|
@@ -1908,7 +2055,7 @@ function sessionFromAuthJson(parsed) {
|
|
|
1908
2055
|
}
|
|
1909
2056
|
function readGrokSession() {
|
|
1910
2057
|
const file = authFile();
|
|
1911
|
-
if (!
|
|
2058
|
+
if (!existsSync8(file))
|
|
1912
2059
|
return null;
|
|
1913
2060
|
try {
|
|
1914
2061
|
return sessionFromAuthJson(JSON.parse(readFileSync7(file, "utf8")));
|
|
@@ -2102,10 +2249,53 @@ function providerName(id) {
|
|
|
2102
2249
|
}
|
|
2103
2250
|
|
|
2104
2251
|
// src/collect.ts
|
|
2252
|
+
function providerHomeMarkers(id) {
|
|
2253
|
+
const home = homedir9();
|
|
2254
|
+
switch (id) {
|
|
2255
|
+
case "antigravity":
|
|
2256
|
+
return [
|
|
2257
|
+
join10(home, ".gemini", "antigravity-cli", "antigravity-oauth-token"),
|
|
2258
|
+
join10(home, ".gemini", "antigravity-cli", "settings.json"),
|
|
2259
|
+
join10(home, ".gemini", "antigravity-cli", "installation_id")
|
|
2260
|
+
];
|
|
2261
|
+
case "claude":
|
|
2262
|
+
return [join10(home, ".claude", ".credentials.json"), join10(home, ".claude", "settings.json")];
|
|
2263
|
+
case "codex":
|
|
2264
|
+
return [join10(home, ".codex", "auth.json"), join10(home, ".codex", "config.toml")];
|
|
2265
|
+
case "cursor":
|
|
2266
|
+
return [join10(home, ".cursor", "auth.json")];
|
|
2267
|
+
case "grok":
|
|
2268
|
+
return [join10(home, ".grok", "auth.json")];
|
|
2269
|
+
case "opencode": {
|
|
2270
|
+
const xdg = process.env.XDG_DATA_HOME;
|
|
2271
|
+
const base = xdg && xdg.trim() ? xdg : join10(home, ".local", "share");
|
|
2272
|
+
return [join10(base, "opencode", "auth.json")];
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
function providerHomeExists(id) {
|
|
2277
|
+
return providerHomeMarkers(id).some((file) => existsSync9(file));
|
|
2278
|
+
}
|
|
2279
|
+
function isProviderPresent(opts) {
|
|
2280
|
+
if (opts.binPath)
|
|
2281
|
+
return true;
|
|
2282
|
+
if (opts.provider === "opencode")
|
|
2283
|
+
return true;
|
|
2284
|
+
return opts.homeExists;
|
|
2285
|
+
}
|
|
2105
2286
|
async function detectProviders() {
|
|
2287
|
+
clearBinCache();
|
|
2106
2288
|
return Promise.all(PROVIDERS.map(async (p) => {
|
|
2107
|
-
const path = await which(p.bin);
|
|
2108
|
-
|
|
2289
|
+
const path = await which(p.bin, { fresh: true });
|
|
2290
|
+
const homeExists = providerHomeExists(p.id);
|
|
2291
|
+
const installed = isProviderPresent({ binPath: path, homeExists, provider: p.id });
|
|
2292
|
+
log("info", "detect", {
|
|
2293
|
+
provider: p.id,
|
|
2294
|
+
installed,
|
|
2295
|
+
path: path ?? undefined,
|
|
2296
|
+
home: homeExists || undefined
|
|
2297
|
+
});
|
|
2298
|
+
return { id: p.id, installed, version: null };
|
|
2109
2299
|
}));
|
|
2110
2300
|
}
|
|
2111
2301
|
function resolveAccounts(provider, installed) {
|
|
@@ -2120,9 +2310,24 @@ function resolveAccounts(provider, installed) {
|
|
|
2120
2310
|
}
|
|
2121
2311
|
async function fetchAccount(account) {
|
|
2122
2312
|
try {
|
|
2123
|
-
|
|
2313
|
+
const snap = await withTimeout(fetchSnapshot(account), FETCH_TIMEOUT_MS, `${account.provider} fetch`);
|
|
2314
|
+
log(snap.status === "error" ? "error" : "info", "quotas.fetch", {
|
|
2315
|
+
account: snap.account.id,
|
|
2316
|
+
provider: snap.account.provider,
|
|
2317
|
+
status: snap.status,
|
|
2318
|
+
windows: snap.windows.length,
|
|
2319
|
+
message: snap.message ?? undefined
|
|
2320
|
+
});
|
|
2321
|
+
return snap;
|
|
2124
2322
|
} catch (e) {
|
|
2125
|
-
|
|
2323
|
+
const snap = snapshot(account, "error", { message: e instanceof Error ? e.message : String(e) });
|
|
2324
|
+
log("error", "quotas.fetch", {
|
|
2325
|
+
account: account.id,
|
|
2326
|
+
provider: account.provider,
|
|
2327
|
+
status: snap.status,
|
|
2328
|
+
message: snap.message ?? undefined
|
|
2329
|
+
});
|
|
2330
|
+
return snap;
|
|
2126
2331
|
}
|
|
2127
2332
|
}
|
|
2128
2333
|
async function collectReport(update, only) {
|
|
@@ -2132,10 +2337,19 @@ async function collectReport(update, only) {
|
|
|
2132
2337
|
const accounts = resolveAccounts(p.id, pres.installed);
|
|
2133
2338
|
const [snapshots, version] = await Promise.all([
|
|
2134
2339
|
Promise.all(accounts.map(fetchAccount)),
|
|
2135
|
-
pres.installed ? binVersion(p.bin) : Promise.resolve(null)
|
|
2340
|
+
pres.installed ? binVersion(await which(p.bin) ?? p.bin) : Promise.resolve(null)
|
|
2136
2341
|
]);
|
|
2137
2342
|
return { id: p.id, name: p.name, installed: pres.installed, version, accounts: snapshots };
|
|
2138
2343
|
}));
|
|
2344
|
+
const accounts = providers.flatMap((p) => p.accounts);
|
|
2345
|
+
log("info", "quotas.collect", {
|
|
2346
|
+
providers: providers.length,
|
|
2347
|
+
accounts: accounts.length,
|
|
2348
|
+
ok: accounts.filter((a) => a.status === "ok").length,
|
|
2349
|
+
error: accounts.filter((a) => a.status === "error").length,
|
|
2350
|
+
signed_out: accounts.filter((a) => a.status === "signed_out").length,
|
|
2351
|
+
unsupported: accounts.filter((a) => a.status === "unsupported").length
|
|
2352
|
+
});
|
|
2139
2353
|
return {
|
|
2140
2354
|
version: VERSION,
|
|
2141
2355
|
hostname: formatHostname(hostname()),
|
|
@@ -2180,7 +2394,7 @@ class ReportCache {
|
|
|
2180
2394
|
}
|
|
2181
2395
|
|
|
2182
2396
|
// src/instance.ts
|
|
2183
|
-
import { existsSync as
|
|
2397
|
+
import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2184
2398
|
function looksLikeJustUsageCommand(command) {
|
|
2185
2399
|
const s = command.replace(/\\/g, "/").toLowerCase();
|
|
2186
2400
|
return s.includes("just-usage") || /\/just-usage\/(?:src\/|dist\/)?cli\.(ts|js)\b/.test(s);
|
|
@@ -2217,7 +2431,7 @@ function writeRunRecord(opts) {
|
|
|
2217
2431
|
}
|
|
2218
2432
|
function readRunRecord(port) {
|
|
2219
2433
|
const file = paths.runRecord(port);
|
|
2220
|
-
if (!
|
|
2434
|
+
if (!existsSync10(file))
|
|
2221
2435
|
return null;
|
|
2222
2436
|
return parseRunRecord(readFileSync8(file, "utf8"));
|
|
2223
2437
|
}
|
|
@@ -2229,7 +2443,7 @@ function removeRunRecord(port) {
|
|
|
2229
2443
|
}
|
|
2230
2444
|
function listRunPorts() {
|
|
2231
2445
|
const dir = paths.runDir();
|
|
2232
|
-
if (!
|
|
2446
|
+
if (!existsSync10(dir))
|
|
2233
2447
|
return [];
|
|
2234
2448
|
const ports = [];
|
|
2235
2449
|
for (const name of readdirSync(dir)) {
|
|
@@ -2350,13 +2564,17 @@ async function terminate(pids) {
|
|
|
2350
2564
|
async function stopPort(port) {
|
|
2351
2565
|
const found = await oursOnPort(port);
|
|
2352
2566
|
if (found.pids.length === 0) {
|
|
2353
|
-
if (found.listening && !isJustUsageHealth(found.health))
|
|
2567
|
+
if (found.listening && !isJustUsageHealth(found.health)) {
|
|
2568
|
+
log("warn", "stop.busy", { port });
|
|
2354
2569
|
return { status: "busy", port };
|
|
2570
|
+
}
|
|
2355
2571
|
removeRunRecord(port);
|
|
2572
|
+
log("info", "stop.idle", { port });
|
|
2356
2573
|
return { status: "idle", port };
|
|
2357
2574
|
}
|
|
2358
2575
|
await terminate(found.pids);
|
|
2359
2576
|
removeRunRecord(port);
|
|
2577
|
+
log("info", "stop.ok", { port, pids: found.pids });
|
|
2360
2578
|
return { status: "stopped", port, pids: found.pids };
|
|
2361
2579
|
}
|
|
2362
2580
|
async function stopServers(opts) {
|
|
@@ -2714,8 +2932,8 @@ var ui_default = `<!doctype html>
|
|
|
2714
2932
|
if (cached && Array.isArray(cached.providers)) report = cached;
|
|
2715
2933
|
} catch {}
|
|
2716
2934
|
const params = new URLSearchParams(location.search);
|
|
2717
|
-
|
|
2718
|
-
|
|
2935
|
+
let active = localStorage.getItem("ju.tab") || "claude";
|
|
2936
|
+
if (!PROVIDERS.some(([id]) => id === active)) active = "claude";
|
|
2719
2937
|
let settingsOpen = params.get("view") === "settings";
|
|
2720
2938
|
let loading = false;
|
|
2721
2939
|
let busy = false;
|
|
@@ -2864,16 +3082,17 @@ var ui_default = `<!doctype html>
|
|
|
2864
3082
|
else render();
|
|
2865
3083
|
}
|
|
2866
3084
|
|
|
3085
|
+
function syncUrl() {
|
|
3086
|
+
const want = settingsOpen ? \`\${location.pathname}?view=settings\` : location.pathname;
|
|
3087
|
+
if (location.pathname + location.search !== want) history.replaceState(null, "", want);
|
|
3088
|
+
}
|
|
3089
|
+
|
|
2867
3090
|
function setView(open) {
|
|
2868
3091
|
settingsOpen = open;
|
|
2869
3092
|
$("dash").hidden = open;
|
|
2870
3093
|
$("settings").hidden = !open;
|
|
2871
|
-
const btn = $("opts-btn");
|
|
2872
3094
|
$("opts-label").textContent = open ? "Back" : "Options";
|
|
2873
|
-
|
|
2874
|
-
if (open) q.set("view", "settings");
|
|
2875
|
-
else q.set("tab", active);
|
|
2876
|
-
history.replaceState(null, "", q.toString() ? \`?\${q}\` : location.pathname);
|
|
3095
|
+
syncUrl();
|
|
2877
3096
|
if (open) {
|
|
2878
3097
|
renderSettings();
|
|
2879
3098
|
renderChrome();
|
|
@@ -3534,6 +3753,7 @@ var ui_default = `<!doctype html>
|
|
|
3534
3753
|
document.addEventListener("visibilitychange", () => {
|
|
3535
3754
|
if (document.visibilityState === "visible") maybeAutoRefresh();
|
|
3536
3755
|
});
|
|
3756
|
+
syncUrl();
|
|
3537
3757
|
if (report) render();
|
|
3538
3758
|
load(false);
|
|
3539
3759
|
})();
|
|
@@ -3641,9 +3861,23 @@ async function startServer(opts) {
|
|
|
3641
3861
|
cache.invalidate();
|
|
3642
3862
|
return out;
|
|
3643
3863
|
};
|
|
3864
|
+
const quietPath = (path) => path === "/favicon.svg" || path === "/favicon.ico" || path.startsWith("/logos/");
|
|
3644
3865
|
const handler = async (req, res) => {
|
|
3645
3866
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3867
|
+
const started = Date.now();
|
|
3646
3868
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
3869
|
+
if (!quietPath(url.pathname)) {
|
|
3870
|
+
res.on("finish", () => {
|
|
3871
|
+
const status = res.statusCode;
|
|
3872
|
+
log(status >= 500 ? "error" : status >= 400 ? "warn" : "info", "http", {
|
|
3873
|
+
method: req.method ?? "GET",
|
|
3874
|
+
path: url.pathname,
|
|
3875
|
+
refresh: url.searchParams.get("refresh") === "1" || undefined,
|
|
3876
|
+
status,
|
|
3877
|
+
ms: Date.now() - started
|
|
3878
|
+
});
|
|
3879
|
+
});
|
|
3880
|
+
}
|
|
3647
3881
|
try {
|
|
3648
3882
|
const method = req.method ?? "GET";
|
|
3649
3883
|
if ((url.pathname === "/favicon.svg" || url.pathname === "/favicon.ico") && (method === "GET" || method === "HEAD")) {
|
|
@@ -3745,6 +3979,7 @@ async function startServer(opts) {
|
|
|
3745
3979
|
json(res, 404, { error: "not found" });
|
|
3746
3980
|
} catch (e) {
|
|
3747
3981
|
if (e instanceof AccountError) {
|
|
3982
|
+
log("warn", "http.error", { path: url.pathname, status: e.status, message: e.message });
|
|
3748
3983
|
json(res, e.status, { error: e.message });
|
|
3749
3984
|
return;
|
|
3750
3985
|
}
|
|
@@ -3752,6 +3987,7 @@ async function startServer(opts) {
|
|
|
3752
3987
|
json(res, 400, { error: "Invalid JSON." });
|
|
3753
3988
|
return;
|
|
3754
3989
|
}
|
|
3990
|
+
logError("http.error", e, { path: url.pathname, status: 500 });
|
|
3755
3991
|
json(res, 500, { error: e instanceof Error ? e.message : String(e) });
|
|
3756
3992
|
}
|
|
3757
3993
|
};
|
|
@@ -3760,12 +3996,15 @@ async function startServer(opts) {
|
|
|
3760
3996
|
server.once("error", reject);
|
|
3761
3997
|
server.listen(opts.port, opts.host, () => {
|
|
3762
3998
|
writeRunRecord({ port: opts.port, host: opts.host });
|
|
3999
|
+
const urls = reachableUrls(opts.host, opts.port, tailscaleIp);
|
|
4000
|
+
log("info", "serve.start", { host: opts.host, port: opts.port, urls: urls.map((u) => u.url) });
|
|
3763
4001
|
resolve({
|
|
3764
4002
|
close: () => {
|
|
4003
|
+
log("info", "serve.stop", { port: opts.port });
|
|
3765
4004
|
removeRunRecord(opts.port);
|
|
3766
4005
|
server.close();
|
|
3767
4006
|
},
|
|
3768
|
-
urls
|
|
4007
|
+
urls
|
|
3769
4008
|
});
|
|
3770
4009
|
});
|
|
3771
4010
|
});
|
|
@@ -3879,12 +4118,12 @@ function renderReport(report, now = Date.now()) {
|
|
|
3879
4118
|
}
|
|
3880
4119
|
|
|
3881
4120
|
// src/update.ts
|
|
3882
|
-
import { existsSync as
|
|
4121
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3883
4122
|
import { dirname as dirname3 } from "node:path";
|
|
3884
4123
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
3885
4124
|
function readCache() {
|
|
3886
4125
|
const file = paths.updateCache();
|
|
3887
|
-
if (!
|
|
4126
|
+
if (!existsSync11(file))
|
|
3888
4127
|
return null;
|
|
3889
4128
|
try {
|
|
3890
4129
|
const parsed = JSON.parse(readFileSync9(file, "utf8"));
|
|
@@ -3941,11 +4180,15 @@ async function checkForUpdate(force = false) {
|
|
|
3941
4180
|
latest = null;
|
|
3942
4181
|
}
|
|
3943
4182
|
}
|
|
3944
|
-
if (!latest)
|
|
4183
|
+
if (!latest) {
|
|
4184
|
+
log("warn", "update.check", { ok: false });
|
|
3945
4185
|
return cached ? toInfo(cached) : null;
|
|
4186
|
+
}
|
|
3946
4187
|
const entry = { checkedAt: new Date().toISOString(), latest };
|
|
3947
4188
|
writeCache(entry);
|
|
3948
|
-
|
|
4189
|
+
const info = toInfo(entry);
|
|
4190
|
+
log("info", "update.check", { latest: info.latest, available: info.available });
|
|
4191
|
+
return info;
|
|
3949
4192
|
}
|
|
3950
4193
|
function toInfo(c) {
|
|
3951
4194
|
return { current: VERSION, latest: c.latest, available: semverGt(c.latest, VERSION), checkedAt: c.checkedAt };
|
|
@@ -3976,7 +4219,10 @@ function upgradeCommand(pm, version = "latest") {
|
|
|
3976
4219
|
async function runUpgrade(pm, version = "latest") {
|
|
3977
4220
|
const [cmd, args] = upgradeCommand(pm, version);
|
|
3978
4221
|
console.log(`$ ${cmd} ${args.join(" ")}`);
|
|
3979
|
-
|
|
4222
|
+
log("info", "update.upgrade", { pm, version });
|
|
4223
|
+
const code = await runInteractive(cmd, args);
|
|
4224
|
+
log(code === 0 ? "info" : "error", "update.upgrade.done", { pm, version, code: code ?? undefined });
|
|
4225
|
+
return code;
|
|
3980
4226
|
}
|
|
3981
4227
|
|
|
3982
4228
|
// src/cli.ts
|
|
@@ -4008,6 +4254,8 @@ Cursor uses whatever \`cursor-agent\` is logged in as (single account).
|
|
|
4008
4254
|
Grok uses whatever \`grok login --oauth\` stored (single account).
|
|
4009
4255
|
Antigravity extras are extra Google logins; they do not replace \`agy\`'s signed-in account.
|
|
4010
4256
|
|
|
4257
|
+
Logs are appended to ~/.just-usage/logs (one JSON line per action).
|
|
4258
|
+
|
|
4011
4259
|
Stop
|
|
4012
4260
|
\`just-usage stop\` asks the server to exit — the same as Ctrl+C in the terminal
|
|
4013
4261
|
that started it. Use this when that terminal is gone or another instance is
|
|
@@ -4054,6 +4302,7 @@ See also
|
|
|
4054
4302
|
just-usage --help All commands
|
|
4055
4303
|
`;
|
|
4056
4304
|
function fail(msg, code = 1) {
|
|
4305
|
+
log("error", "cli.fail", { message: msg, code });
|
|
4057
4306
|
console.error(msg);
|
|
4058
4307
|
process.exit(code);
|
|
4059
4308
|
}
|
|
@@ -4120,6 +4369,7 @@ Update available: v${u.current} → v${u.latest}. Run: just-usage upgrade
|
|
|
4120
4369
|
fail(`Port ${port} is already in use. Stop it with: ${stop}
|
|
4121
4370
|
Or start another: just-usage serve --port ${port + 1}`);
|
|
4122
4371
|
}
|
|
4372
|
+
logError("serve.error", e, { host, port });
|
|
4123
4373
|
throw e;
|
|
4124
4374
|
}
|
|
4125
4375
|
console.log(`${PACKAGE_NAME} v${VERSION}`);
|
|
@@ -4141,6 +4391,7 @@ Press Ctrl+C to stop. From another terminal: ${stopHint}`);
|
|
|
4141
4391
|
}
|
|
4142
4392
|
async function cmdStatus(argv) {
|
|
4143
4393
|
const { values } = parseArgs({ args: argv, options: { json: { type: "boolean" } }, allowPositionals: true, strict: false });
|
|
4394
|
+
log("info", "cli.status", { json: values.json === true });
|
|
4144
4395
|
const [report, update] = await Promise.all([collectReport(null), checkForUpdate().catch(() => null)]);
|
|
4145
4396
|
report.update = update;
|
|
4146
4397
|
if (values.json) {
|
|
@@ -4151,6 +4402,7 @@ async function cmdStatus(argv) {
|
|
|
4151
4402
|
}
|
|
4152
4403
|
function cmdAccounts() {
|
|
4153
4404
|
const rows = listAccounts();
|
|
4405
|
+
log("info", "cli.accounts", { extra: rows.length });
|
|
4154
4406
|
console.log("Default accounts come from each CLI's own login (codex login, claude /login, cursor-agent login, grok login --oauth, opencode auth login, agy).");
|
|
4155
4407
|
if (rows.length === 0) {
|
|
4156
4408
|
console.log(`
|
|
@@ -4202,9 +4454,11 @@ async function addClaudeProfile(label) {
|
|
|
4202
4454
|
if (!status?.loggedIn) {
|
|
4203
4455
|
const { rmSync } = await import("node:fs");
|
|
4204
4456
|
rmSync(dir, { recursive: true, force: true });
|
|
4457
|
+
log("error", "account.login.error", { provider: "claude", message: "login did not complete" });
|
|
4205
4458
|
fail("Login did not complete; nothing was saved.");
|
|
4206
4459
|
}
|
|
4207
4460
|
saveAccount({ id, provider: "claude", label: label ?? id.split(":")[1], kind: "profile", path: dir, email: null, createdAt: new Date().toISOString() });
|
|
4461
|
+
log("info", "account.add", { account: id, provider: "claude", kind: "profile" });
|
|
4208
4462
|
console.log(`
|
|
4209
4463
|
Added ${id}. Note: on macOS the first read may trigger a Keychain prompt — choose "Always Allow".`);
|
|
4210
4464
|
}
|
|
@@ -4269,6 +4523,7 @@ async function cmdLogin(argv) {
|
|
|
4269
4523
|
fail(`Unknown account: ${id}`);
|
|
4270
4524
|
if (rec.kind !== "profile" || !rec.path)
|
|
4271
4525
|
fail(`${id} is a ${rec.kind} account; remove and re-add it instead.`);
|
|
4526
|
+
log("info", "account.login.start", { account: id, provider: rec.provider, kind: "relogin" });
|
|
4272
4527
|
if (rec.provider === "codex") {
|
|
4273
4528
|
const info = await codexLogin(rec.path, (url) => {
|
|
4274
4529
|
console.log(`
|
|
@@ -4278,10 +4533,15 @@ ${url}
|
|
|
4278
4533
|
openInBrowser(url);
|
|
4279
4534
|
});
|
|
4280
4535
|
updateAccount(id, { email: info.email });
|
|
4536
|
+
log("info", "account.login.done", { account: id, provider: "codex" });
|
|
4281
4537
|
console.log(`Re-authenticated ${id}${info.email ? ` (${info.email})` : ""}.`);
|
|
4282
4538
|
} else if (rec.provider === "claude") {
|
|
4283
4539
|
await runInteractive("claude", ["auth", "login"], { CLAUDE_CONFIG_DIR: rec.path });
|
|
4284
4540
|
const status = await claudeAuthStatus(rec.path);
|
|
4541
|
+
log(status?.loggedIn ? "info" : "error", status?.loggedIn ? "account.login.done" : "account.login.error", {
|
|
4542
|
+
account: id,
|
|
4543
|
+
provider: "claude"
|
|
4544
|
+
});
|
|
4285
4545
|
console.log(status?.loggedIn ? `Re-authenticated ${id}.` : "Login did not complete.");
|
|
4286
4546
|
} else {
|
|
4287
4547
|
fail(`${providerName(rec.provider)} accounts cannot be re-authenticated this way.`);
|
|
@@ -4348,6 +4608,7 @@ async function cmdUpgrade(argv) {
|
|
|
4348
4608
|
async function main() {
|
|
4349
4609
|
const argv = process.argv.slice(2);
|
|
4350
4610
|
const cmd = argv[0];
|
|
4611
|
+
log("info", "cli", { command: cmd ?? "serve" });
|
|
4351
4612
|
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
4352
4613
|
console.log(VERSION);
|
|
4353
4614
|
return;
|
|
@@ -4387,6 +4648,7 @@ ${HELP}`);
|
|
|
4387
4648
|
}
|
|
4388
4649
|
}
|
|
4389
4650
|
main().catch((e) => {
|
|
4651
|
+
logError("cli.crash", e);
|
|
4390
4652
|
console.error(e instanceof Error ? e.message : String(e));
|
|
4391
4653
|
process.exit(1);
|
|
4392
4654
|
});
|
package/package.json
CHANGED