ccsidekick 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ccsidekick-render.js +414 -103
- package/dist/ccsidekick.js +476 -132
- package/package.json +1 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/ccsidekick-render.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
5
5
|
|
|
6
6
|
// src/cli/classify.ts
|
|
7
7
|
import { createHash as createHash4 } from "node:crypto";
|
|
8
8
|
|
|
9
9
|
// src/domain/constants.ts
|
|
10
|
-
var COST_TTL_MS =
|
|
10
|
+
var COST_TTL_MS = 5000;
|
|
11
11
|
var USAGE_TTL_MS = 300000;
|
|
12
|
+
var CREDS_TTL_MS = 3600000;
|
|
12
13
|
var BALANCE_FRESHNESS_MS = 300000;
|
|
13
14
|
var FX_TTL_MS = 604800000;
|
|
14
15
|
var MIN_RIGHT_WIDTH = 53;
|
|
@@ -33,6 +34,8 @@ var HELPFUL_COOLDOWN_MS = 600000;
|
|
|
33
34
|
var BURN_WINDOW_MS = 18000000;
|
|
34
35
|
var COMPACT_URGENT_PCT = 90;
|
|
35
36
|
var QUOTA_HIGH_PCT = 80;
|
|
37
|
+
var QUOTA_PACE_MIN_PCT = 20;
|
|
38
|
+
var QUOTA_PROJECT_MIN_PCT = 50;
|
|
36
39
|
var PAY_AS_YOU_GO_CAUTION_PCT = 60;
|
|
37
40
|
var PAY_AS_YOU_GO_NEAR_PCT = 80;
|
|
38
41
|
var BALANCE_LOW = 25;
|
|
@@ -44,6 +47,17 @@ var SIGNAL_HUE_RANGES = {
|
|
|
44
47
|
caution: { min: 20, max: 55 },
|
|
45
48
|
critical: { wrapMax: 15, wrapMin: 345 }
|
|
46
49
|
};
|
|
50
|
+
var COMPACT_SOON_PCT = 60;
|
|
51
|
+
var COMPACTION_THRASH_N = 3;
|
|
52
|
+
var CACHE_RATIO_FLOOR = 0.5;
|
|
53
|
+
var CACHE_WARMUP_TURNS = 20;
|
|
54
|
+
var BIG_DIFF_LINES = 1000;
|
|
55
|
+
var UNTRACKED_N = 20;
|
|
56
|
+
var STASH_N = 5;
|
|
57
|
+
var STALE_BRANCH_N = 20;
|
|
58
|
+
var TODO_STALLED_MIN = 30;
|
|
59
|
+
var TIER_THRESHOLDS = [3, 15, 50, 100];
|
|
60
|
+
var PRICING_TIER_THRESHOLD = 200000;
|
|
47
61
|
// src/domain/enums.ts
|
|
48
62
|
var MOODS = ["idle", "busy", "happy", "struggling", "recovery"];
|
|
49
63
|
var PRESSURE_MOODS = ["compact_hint", "block_limit", "weekly_limit"];
|
|
@@ -130,6 +144,9 @@ var REACTION_CATEGORIES = [
|
|
|
130
144
|
"db_migrate",
|
|
131
145
|
"dangerous"
|
|
132
146
|
];
|
|
147
|
+
var MIN_SEVERITIES = ["low", "medium", "high", "critical"];
|
|
148
|
+
var CHARACTER_MODES = ["fixed", "random"];
|
|
149
|
+
var BANDINGS = ["solid", "cycle"];
|
|
133
150
|
var isStack = (s) => STACKS.includes(s);
|
|
134
151
|
var isEventCategory = (s) => EVENT_CATEGORIES.includes(s);
|
|
135
152
|
// src/domain/ids.ts
|
|
@@ -324,7 +341,6 @@ var pricing_default = [
|
|
|
324
341
|
|
|
325
342
|
// src/derived/pricing.ts
|
|
326
343
|
var PER_MILLION = 1e6;
|
|
327
|
-
var TIER_THRESHOLD = 200000;
|
|
328
344
|
var RAW = pricing_default;
|
|
329
345
|
var TABLE = (() => {
|
|
330
346
|
const byKey = new Map;
|
|
@@ -403,9 +419,9 @@ function resolvePrice(modelId, aliases = NO_ALIASES, atMs) {
|
|
|
403
419
|
return rows ? pickRow(rows, atMs) : null;
|
|
404
420
|
}
|
|
405
421
|
function tiered(tokens, base, above) {
|
|
406
|
-
if (tokens <=
|
|
422
|
+
if (tokens <= PRICING_TIER_THRESHOLD || above === undefined)
|
|
407
423
|
return tokens * base;
|
|
408
|
-
return
|
|
424
|
+
return PRICING_TIER_THRESHOLD * base + (tokens - PRICING_TIER_THRESHOLD) * above;
|
|
409
425
|
}
|
|
410
426
|
function priceMessage(usage, modelId, aliases = NO_ALIASES, atMs) {
|
|
411
427
|
const row = resolvePrice(modelId, aliases, atMs);
|
|
@@ -480,6 +496,7 @@ function joinRecords(attribution, cache, tz) {
|
|
|
480
496
|
for (const [session, g] of groupBySession(cache)) {
|
|
481
497
|
const attr = attribution[session];
|
|
482
498
|
out.push({
|
|
499
|
+
session,
|
|
483
500
|
character: attr?.character ?? "unknown",
|
|
484
501
|
project: attr?.project ?? g.projectRaw,
|
|
485
502
|
start: g.start,
|
|
@@ -489,7 +506,6 @@ function joinRecords(attribution, cache, tz) {
|
|
|
489
506
|
}
|
|
490
507
|
return out;
|
|
491
508
|
}
|
|
492
|
-
var TIER_THRESHOLDS = [3, 15, 50, 100];
|
|
493
509
|
function tierForCount(n) {
|
|
494
510
|
const [t1, t2, t3, t4] = TIER_THRESHOLDS;
|
|
495
511
|
if (n < t1)
|
|
@@ -516,7 +532,7 @@ function currentStreak(activeDays, today) {
|
|
|
516
532
|
}
|
|
517
533
|
return streak;
|
|
518
534
|
}
|
|
519
|
-
function deriveFamiliarity(attribution, character, cache, project, clock) {
|
|
535
|
+
function deriveFamiliarity(attribution, character, cache, project, clock, currentSession = "") {
|
|
520
536
|
const tz = clock.timezone();
|
|
521
537
|
const records = joinRecords(attribution, cache, tz);
|
|
522
538
|
const seenProject = records.some((r) => r.project === String(project));
|
|
@@ -528,10 +544,12 @@ function deriveFamiliarity(attribution, character, cache, project, clock) {
|
|
|
528
544
|
let lastEnd = Number.NEGATIVE_INFINITY;
|
|
529
545
|
let workingSinceMs = Number.POSITIVE_INFINITY;
|
|
530
546
|
for (const r of mine) {
|
|
531
|
-
if (r.end > lastEnd)
|
|
532
|
-
lastEnd = r.end;
|
|
533
547
|
if (r.start < workingSinceMs)
|
|
534
548
|
workingSinceMs = r.start;
|
|
549
|
+
if (r.session === currentSession)
|
|
550
|
+
continue;
|
|
551
|
+
if (r.end > lastEnd)
|
|
552
|
+
lastEnd = r.end;
|
|
535
553
|
}
|
|
536
554
|
const daysSinceLastSession = Number.isFinite(lastEnd) ? Math.floor((clock.now() - lastEnd) / MS_PER_DAY) : Number.POSITIVE_INFINITY;
|
|
537
555
|
return {
|
|
@@ -757,7 +775,6 @@ function classify(toolName, command, ok) {
|
|
|
757
775
|
return category !== undefined ? { category } : null;
|
|
758
776
|
}
|
|
759
777
|
// src/derived/signals.ts
|
|
760
|
-
var QUOTA_CRITICAL_PCT = 80;
|
|
761
778
|
function contextBand(pct) {
|
|
762
779
|
if (pct < 34)
|
|
763
780
|
return "nominal";
|
|
@@ -766,9 +783,9 @@ function contextBand(pct) {
|
|
|
766
783
|
return "critical";
|
|
767
784
|
}
|
|
768
785
|
function quotaBand(usedPct, resetsAtMs, windowMs, nowMs) {
|
|
769
|
-
if (usedPct >=
|
|
786
|
+
if (usedPct >= QUOTA_HIGH_PCT)
|
|
770
787
|
return "critical";
|
|
771
|
-
if (resetsAtMs === undefined)
|
|
788
|
+
if (resetsAtMs === undefined || usedPct < QUOTA_PACE_MIN_PCT)
|
|
772
789
|
return contextBand(usedPct);
|
|
773
790
|
const usedFraction = usedPct / 100;
|
|
774
791
|
const elapsedFraction = Math.max((nowMs - (resetsAtMs - windowMs)) / windowMs, 0.01);
|
|
@@ -779,6 +796,13 @@ function quotaBand(usedPct, resetsAtMs, windowMs, nowMs) {
|
|
|
779
796
|
return "caution";
|
|
780
797
|
return "critical";
|
|
781
798
|
}
|
|
799
|
+
function overQuotaPace(usedPct, resetsAtMs, windowMs, nowMs) {
|
|
800
|
+
if (resetsAtMs === undefined)
|
|
801
|
+
return false;
|
|
802
|
+
const usedFraction = usedPct / 100;
|
|
803
|
+
const elapsedFraction = Math.max((nowMs - (resetsAtMs - windowMs)) / windowMs, 0.01);
|
|
804
|
+
return usedFraction / elapsedFraction > 1.5;
|
|
805
|
+
}
|
|
782
806
|
|
|
783
807
|
// src/derived/context.ts
|
|
784
808
|
function deriveContext(payload, scan) {
|
|
@@ -1149,12 +1173,13 @@ function buildWindow(payloadQuota, oauthQuota, windowMs, clock) {
|
|
|
1149
1173
|
return;
|
|
1150
1174
|
}
|
|
1151
1175
|
const band = quotaBand(usedPct, resetsAtMs, windowMs, clock.now());
|
|
1176
|
+
const overPace = overQuotaPace(usedPct, resetsAtMs, windowMs, clock.now());
|
|
1152
1177
|
if (resetsAtMs === undefined)
|
|
1153
|
-
return { usedPct, band };
|
|
1178
|
+
return { usedPct, band, overPace };
|
|
1154
1179
|
const left = resetsAtMs - clock.now();
|
|
1155
1180
|
if (left <= 0)
|
|
1156
|
-
return { usedPct, band };
|
|
1157
|
-
return { usedPct, band, resetIn: fmtLeft(left) };
|
|
1181
|
+
return { usedPct, band, overPace };
|
|
1182
|
+
return { usedPct, band, overPace, resetIn: fmtLeft(left) };
|
|
1158
1183
|
}
|
|
1159
1184
|
function buildPayg(usage) {
|
|
1160
1185
|
const extra = usage?.extra_usage;
|
|
@@ -1252,7 +1277,7 @@ function readJson(path, fallback) {
|
|
|
1252
1277
|
}
|
|
1253
1278
|
|
|
1254
1279
|
// src/sources/storage/withLock.ts
|
|
1255
|
-
import { closeSync, mkdirSync as mkdirSync2, openSync, statSync, unlinkSync } from "node:fs";
|
|
1280
|
+
import { closeSync, mkdirSync as mkdirSync2, openSync, renameSync as renameSync2, statSync, unlinkSync } from "node:fs";
|
|
1256
1281
|
import { dirname as dirname2 } from "node:path";
|
|
1257
1282
|
var STALE_MS = 30000;
|
|
1258
1283
|
function withLock(lockPath, fn, readOnly) {
|
|
@@ -1267,8 +1292,12 @@ function withLock(lockPath, fn, readOnly) {
|
|
|
1267
1292
|
} catch {
|
|
1268
1293
|
try {
|
|
1269
1294
|
if (Date.now() - statSync(lockPath).mtimeMs > STALE_MS) {
|
|
1270
|
-
|
|
1295
|
+
const aside = `${lockPath}.stale`;
|
|
1296
|
+
renameSync2(lockPath, aside);
|
|
1271
1297
|
fd = openSync(lockPath, "wx");
|
|
1298
|
+
try {
|
|
1299
|
+
unlinkSync(aside);
|
|
1300
|
+
} catch {}
|
|
1272
1301
|
}
|
|
1273
1302
|
} catch {}
|
|
1274
1303
|
}
|
|
@@ -1355,14 +1384,27 @@ function upsertAttribution(root, sessionId, rec) {
|
|
|
1355
1384
|
const path = storePath(root);
|
|
1356
1385
|
withLock(`${path}.lock`, () => {
|
|
1357
1386
|
const store = coerceStore(readJson(path, undefined));
|
|
1387
|
+
const existing = store[sessionId];
|
|
1388
|
+
if (existing !== undefined && existing.project === rec.project && existing.character === rec.character) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1358
1391
|
store[sessionId] = rec.updatedMs !== undefined ? { project: rec.project, character: rec.character, updatedMs: rec.updatedMs } : { project: rec.project, character: rec.character };
|
|
1359
1392
|
atomicWrite(path, JSON.stringify(store));
|
|
1360
1393
|
}, () => {});
|
|
1361
1394
|
}
|
|
1362
1395
|
// src/sources/balance.ts
|
|
1396
|
+
import { statSync as statSync3 } from "node:fs";
|
|
1397
|
+
var BALANCE_MAX_BYTES = 64 * 1024;
|
|
1363
1398
|
function readBalance(path, clock) {
|
|
1364
1399
|
if (path === "")
|
|
1365
1400
|
return null;
|
|
1401
|
+
try {
|
|
1402
|
+
const st = statSync3(path);
|
|
1403
|
+
if (!st.isFile() || st.size > BALANCE_MAX_BYTES)
|
|
1404
|
+
return null;
|
|
1405
|
+
} catch {
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1366
1408
|
const raw = readJson(path, undefined);
|
|
1367
1409
|
if (typeof raw !== "object" || raw === null)
|
|
1368
1410
|
return null;
|
|
@@ -2493,10 +2535,10 @@ function loadConfig(globalToml = "", projectToml = "") {
|
|
|
2493
2535
|
const statusline = section(g, p, "statusline");
|
|
2494
2536
|
const d = DEFAULT_CONFIG;
|
|
2495
2537
|
return {
|
|
2496
|
-
schema_version: numv(p["schema_version"]
|
|
2538
|
+
schema_version: numv(p["schema_version"], numv(g["schema_version"], d.schema_version)),
|
|
2497
2539
|
character: {
|
|
2498
2540
|
enabled: bool(character["enabled"], d.character.enabled),
|
|
2499
|
-
mode: oneOf(character["mode"],
|
|
2541
|
+
mode: oneOf(character["mode"], CHARACTER_MODES, d.character.mode),
|
|
2500
2542
|
name: str(character["name"], d.character.name),
|
|
2501
2543
|
roster: stringArray(character["roster"], d.character.roster)
|
|
2502
2544
|
},
|
|
@@ -2505,7 +2547,7 @@ function loadConfig(globalToml = "", projectToml = "") {
|
|
|
2505
2547
|
...typeof theme["statusline"] === "string" ? { statusline: theme["statusline"] } : {},
|
|
2506
2548
|
...typeof theme["logo"] === "string" ? { logo: theme["logo"] } : {},
|
|
2507
2549
|
...typeof theme["comment"] === "string" ? { comment: theme["comment"] } : {},
|
|
2508
|
-
banding: oneOf(theme["banding"],
|
|
2550
|
+
banding: oneOf(theme["banding"], BANDINGS, d.theme.banding),
|
|
2509
2551
|
mood_shift: bool(theme["mood_shift"], d.theme.mood_shift),
|
|
2510
2552
|
icons: mergeIcons({
|
|
2511
2553
|
...obj(obj(g["theme"])["icons"]),
|
|
@@ -2515,7 +2557,7 @@ function loadConfig(globalToml = "", projectToml = "") {
|
|
|
2515
2557
|
comments: {
|
|
2516
2558
|
character: bool(comments["character"], d.comments.character),
|
|
2517
2559
|
helpful: bool(comments["helpful"], d.comments.helpful),
|
|
2518
|
-
min_severity: oneOf(comments["min_severity"],
|
|
2560
|
+
min_severity: oneOf(comments["min_severity"], MIN_SEVERITIES, d.comments.min_severity)
|
|
2519
2561
|
},
|
|
2520
2562
|
network: {
|
|
2521
2563
|
fx_refresh: bool(network["fx_refresh"], d.network.fx_refresh),
|
|
@@ -2645,7 +2687,19 @@ function coerceEntry(v) {
|
|
|
2645
2687
|
}
|
|
2646
2688
|
const projectPath = typeof v["projectPath"] === "string" ? v["projectPath"] : String(record.project);
|
|
2647
2689
|
const models = Array.isArray(v["models"]) ? v["models"].filter((x) => typeof x === "string") : [];
|
|
2648
|
-
|
|
2690
|
+
const byteOffset = asNumber(v["byteOffset"]);
|
|
2691
|
+
const headHash = typeof v["headHash"] === "string" ? v["headHash"] : undefined;
|
|
2692
|
+
return {
|
|
2693
|
+
mtime,
|
|
2694
|
+
size,
|
|
2695
|
+
total,
|
|
2696
|
+
lines,
|
|
2697
|
+
models,
|
|
2698
|
+
projectPath,
|
|
2699
|
+
record,
|
|
2700
|
+
...byteOffset !== undefined ? { byteOffset } : {},
|
|
2701
|
+
...headHash !== undefined ? { headHash } : {}
|
|
2702
|
+
};
|
|
2649
2703
|
}
|
|
2650
2704
|
function coerceByModel(v) {
|
|
2651
2705
|
if (!isObject(v))
|
|
@@ -2702,7 +2756,6 @@ function writeCostCache(root, c) {
|
|
|
2702
2756
|
// src/sources/creds.ts
|
|
2703
2757
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2704
2758
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
2705
|
-
import { homedir as homedir3 } from "node:os";
|
|
2706
2759
|
import { join as join5 } from "node:path";
|
|
2707
2760
|
|
|
2708
2761
|
// src/sources/oauthUsage.ts
|
|
@@ -2899,7 +2952,6 @@ function readUsage(root, clock, opts) {
|
|
|
2899
2952
|
}
|
|
2900
2953
|
|
|
2901
2954
|
// src/sources/creds.ts
|
|
2902
|
-
var KEYCHAIN_SERVICE2 = "Claude Code-credentials";
|
|
2903
2955
|
var SUBSCRIPTIONS = new Set(["pro", "max", "team", "enterprise"]);
|
|
2904
2956
|
var isObject3 = (v) => typeof v === "object" && v !== null;
|
|
2905
2957
|
var defaultRunner = (cmd, args) => {
|
|
@@ -2916,11 +2968,8 @@ var defaultRunner = (cmd, args) => {
|
|
|
2916
2968
|
return "";
|
|
2917
2969
|
}
|
|
2918
2970
|
};
|
|
2919
|
-
function configBase2() {
|
|
2920
|
-
return process.env["CLAUDE_CONFIG_DIR"] ?? join5(homedir3(), ".claude");
|
|
2921
|
-
}
|
|
2922
2971
|
function readBlob(run, base) {
|
|
2923
|
-
for (const service of [keychainService(base),
|
|
2972
|
+
for (const service of [keychainService(base), KEYCHAIN_SERVICE]) {
|
|
2924
2973
|
const keychain = run("security", ["find-generic-password", "-s", service, "-w"]);
|
|
2925
2974
|
if (keychain.trim() !== "")
|
|
2926
2975
|
return keychain;
|
|
@@ -2930,7 +2979,7 @@ function readBlob(run, base) {
|
|
|
2930
2979
|
if (file.trim() !== "")
|
|
2931
2980
|
return file;
|
|
2932
2981
|
} catch {}
|
|
2933
|
-
const secretTool = run("secret-tool", ["lookup", "service",
|
|
2982
|
+
const secretTool = run("secret-tool", ["lookup", "service", KEYCHAIN_SERVICE]);
|
|
2934
2983
|
if (secretTool.trim() !== "")
|
|
2935
2984
|
return secretTool;
|
|
2936
2985
|
return "";
|
|
@@ -2959,7 +3008,7 @@ function readOrgTier(base) {
|
|
|
2959
3008
|
}
|
|
2960
3009
|
function readCreds(run = defaultRunner) {
|
|
2961
3010
|
try {
|
|
2962
|
-
const base =
|
|
3011
|
+
const base = configBase();
|
|
2963
3012
|
const blob = readBlob(run, base);
|
|
2964
3013
|
if (blob.trim() !== "") {
|
|
2965
3014
|
const tier = parseSubscription(blob);
|
|
@@ -2973,6 +3022,39 @@ function readCreds(run = defaultRunner) {
|
|
|
2973
3022
|
return null;
|
|
2974
3023
|
}
|
|
2975
3024
|
}
|
|
3025
|
+
var CREDS_CACHE_FILE = "creds.json";
|
|
3026
|
+
function credsCachePath(root) {
|
|
3027
|
+
return join5(cacheDir(root), CREDS_CACHE_FILE);
|
|
3028
|
+
}
|
|
3029
|
+
function coerceCachedCreds(raw) {
|
|
3030
|
+
if (!isObject3(raw) || typeof raw["at"] !== "number")
|
|
3031
|
+
return null;
|
|
3032
|
+
const at = raw["at"];
|
|
3033
|
+
const info = raw["info"];
|
|
3034
|
+
if (info === null)
|
|
3035
|
+
return { info: null, at };
|
|
3036
|
+
if (isObject3(info) && typeof info["present"] === "boolean") {
|
|
3037
|
+
const sub = info["subscriptionType"];
|
|
3038
|
+
return {
|
|
3039
|
+
at,
|
|
3040
|
+
info: typeof sub === "string" && SUBSCRIPTIONS.has(sub) ? { present: info["present"], subscriptionType: sub } : { present: info["present"] }
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
return null;
|
|
3044
|
+
}
|
|
3045
|
+
function readCredsCached(root) {
|
|
3046
|
+
return coerceCachedCreds(readJson(credsCachePath(root), undefined))?.info ?? null;
|
|
3047
|
+
}
|
|
3048
|
+
function refreshCreds(root, clock, run = defaultRunner) {
|
|
3049
|
+
const cached = coerceCachedCreds(readJson(credsCachePath(root), undefined));
|
|
3050
|
+
const now = clock.now();
|
|
3051
|
+
if (cached !== null && now - cached.at <= CREDS_TTL_MS)
|
|
3052
|
+
return;
|
|
3053
|
+
const info = readCreds(run);
|
|
3054
|
+
try {
|
|
3055
|
+
atomicWrite(credsCachePath(root), JSON.stringify({ info, at: now }));
|
|
3056
|
+
} catch {}
|
|
3057
|
+
}
|
|
2976
3058
|
// src/sources/env.ts
|
|
2977
3059
|
var on = (v) => v === "1" || v === "true";
|
|
2978
3060
|
function isCustomHost(base) {
|
|
@@ -3455,12 +3537,14 @@ function readSubmoduleRollup(run, workTreeRoot) {
|
|
|
3455
3537
|
return rollup;
|
|
3456
3538
|
}
|
|
3457
3539
|
function readGit(cwd, run = defaultRunner2(cwd)) {
|
|
3458
|
-
|
|
3540
|
+
const probe = run(["rev-parse", "--is-inside-work-tree", "--git-dir", "--show-toplevel"]).split(`
|
|
3541
|
+
`);
|
|
3542
|
+
if ((probe[0] ?? "").trim() !== "true")
|
|
3459
3543
|
return null;
|
|
3460
3544
|
const status = parseStatus(run(["status", "--porcelain=v2", "--branch", "--show-stash"]));
|
|
3461
|
-
const gitDirRaw =
|
|
3545
|
+
const gitDirRaw = (probe[1] ?? "").trim();
|
|
3462
3546
|
const gitDir = gitDirRaw === "" ? "" : resolve(cwd, gitDirRaw);
|
|
3463
|
-
const workTreeRoot =
|
|
3547
|
+
const workTreeRoot = (probe[2] ?? "").trim();
|
|
3464
3548
|
const tag = run(["describe", "--tags", "--exact-match"]).trim() || undefined;
|
|
3465
3549
|
const originRepo = normalizeOriginRepo(run(["remote", "get-url", "origin"]));
|
|
3466
3550
|
const defaultBranch = resolveDefaultBranch(run);
|
|
@@ -3507,7 +3591,7 @@ function readGit(cwd, run = defaultRunner2(cwd)) {
|
|
|
3507
3591
|
}
|
|
3508
3592
|
// src/sources/helpfulEnv.ts
|
|
3509
3593
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
3510
|
-
import { homedir as
|
|
3594
|
+
import { homedir as homedir3 } from "node:os";
|
|
3511
3595
|
import { delimiter, join as join9 } from "node:path";
|
|
3512
3596
|
var opt3 = (key, value) => value !== undefined ? { [key]: value } : {};
|
|
3513
3597
|
function kubeconfigPath() {
|
|
@@ -3517,7 +3601,7 @@ function kubeconfigPath() {
|
|
|
3517
3601
|
if (first !== undefined)
|
|
3518
3602
|
return first;
|
|
3519
3603
|
}
|
|
3520
|
-
return join9(
|
|
3604
|
+
return join9(homedir3(), ".kube", "config");
|
|
3521
3605
|
}
|
|
3522
3606
|
function readKubeContext() {
|
|
3523
3607
|
try {
|
|
@@ -3821,7 +3905,7 @@ function writeState(sessionDir2, s) {
|
|
|
3821
3905
|
}, () => {});
|
|
3822
3906
|
}
|
|
3823
3907
|
// src/sources/transcript.ts
|
|
3824
|
-
import { readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as
|
|
3908
|
+
import { closeSync as closeSync2, openSync as openSync2, readFileSync as readFileSync8, readSync, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
|
|
3825
3909
|
import { basename, dirname as dirname6, join as join12 } from "node:path";
|
|
3826
3910
|
var asObj = (v) => v !== null && typeof v === "object" ? v : undefined;
|
|
3827
3911
|
var asStr = (v) => typeof v === "string" ? v : undefined;
|
|
@@ -4047,7 +4131,7 @@ function reconstructTodos(lines) {
|
|
|
4047
4131
|
}
|
|
4048
4132
|
function statSafe(p) {
|
|
4049
4133
|
try {
|
|
4050
|
-
const s =
|
|
4134
|
+
const s = statSync4(p);
|
|
4051
4135
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
4052
4136
|
} catch {
|
|
4053
4137
|
return null;
|
|
@@ -4060,6 +4144,63 @@ function readSafe(p) {
|
|
|
4060
4144
|
return "";
|
|
4061
4145
|
}
|
|
4062
4146
|
}
|
|
4147
|
+
function readBufSafe(p) {
|
|
4148
|
+
try {
|
|
4149
|
+
return readFileSync8(p);
|
|
4150
|
+
} catch {
|
|
4151
|
+
return null;
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
function readRange(p, start, end) {
|
|
4155
|
+
if (end <= start)
|
|
4156
|
+
return Buffer.alloc(0);
|
|
4157
|
+
let fd;
|
|
4158
|
+
try {
|
|
4159
|
+
fd = openSync2(p, "r");
|
|
4160
|
+
const len = end - start;
|
|
4161
|
+
const buf = Buffer.allocUnsafe(len);
|
|
4162
|
+
let off = 0;
|
|
4163
|
+
while (off < len) {
|
|
4164
|
+
const n = readSync(fd, buf, off, len - off, start + off);
|
|
4165
|
+
if (n === 0)
|
|
4166
|
+
break;
|
|
4167
|
+
off += n;
|
|
4168
|
+
}
|
|
4169
|
+
return off === len ? buf : null;
|
|
4170
|
+
} catch {
|
|
4171
|
+
return null;
|
|
4172
|
+
} finally {
|
|
4173
|
+
if (fd !== undefined)
|
|
4174
|
+
closeSync2(fd);
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
var HEAD_HASH_BYTES = 4096;
|
|
4178
|
+
function headHashOf(head) {
|
|
4179
|
+
let h = 2166136261;
|
|
4180
|
+
for (const b of head) {
|
|
4181
|
+
h ^= b;
|
|
4182
|
+
h = Math.imul(h, 16777619);
|
|
4183
|
+
}
|
|
4184
|
+
return (h >>> 0).toString(16);
|
|
4185
|
+
}
|
|
4186
|
+
function makeInternModel(models) {
|
|
4187
|
+
const index = new Map;
|
|
4188
|
+
models.forEach((m, i) => index.set(m, i));
|
|
4189
|
+
return (model) => {
|
|
4190
|
+
let i = index.get(model);
|
|
4191
|
+
if (i === undefined) {
|
|
4192
|
+
i = models.length;
|
|
4193
|
+
models.push(model);
|
|
4194
|
+
index.set(model, i);
|
|
4195
|
+
}
|
|
4196
|
+
return i;
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
function lastLineEnd(buf) {
|
|
4200
|
+
const i = buf.lastIndexOf(10);
|
|
4201
|
+
return i < 0 ? 0 : i + 1;
|
|
4202
|
+
}
|
|
4203
|
+
var headLen = (byteOffset) => Math.min(HEAD_HASH_BYTES, byteOffset);
|
|
4063
4204
|
var EMPTY_TOKENS = {
|
|
4064
4205
|
input: 0,
|
|
4065
4206
|
output: 0,
|
|
@@ -4150,7 +4291,7 @@ function* walkTree(dir, encodedDir) {
|
|
|
4150
4291
|
const full = join12(dir, name);
|
|
4151
4292
|
let st;
|
|
4152
4293
|
try {
|
|
4153
|
-
st =
|
|
4294
|
+
st = statSync4(full);
|
|
4154
4295
|
} catch {
|
|
4155
4296
|
continue;
|
|
4156
4297
|
}
|
|
@@ -4189,22 +4330,16 @@ function scanBounds(lines) {
|
|
|
4189
4330
|
return { sessionId, start, end };
|
|
4190
4331
|
}
|
|
4191
4332
|
function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
4192
|
-
const
|
|
4333
|
+
const buf = readBufSafe(path) ?? Buffer.alloc(0);
|
|
4334
|
+
const byteOffset = lastLineEnd(buf);
|
|
4335
|
+
const headHash = headHashOf(buf.subarray(0, headLen(byteOffset)));
|
|
4336
|
+
const lines = parseAll(buf.subarray(0, byteOffset).toString("utf8"));
|
|
4193
4337
|
const { sessionId, start, end } = scanBounds(lines);
|
|
4194
4338
|
const priced = collapseStreaming(lines);
|
|
4195
4339
|
const gate = makeDedupGate();
|
|
4196
4340
|
const costLines = [];
|
|
4197
4341
|
const models = [];
|
|
4198
|
-
const
|
|
4199
|
-
const internModel = (model) => {
|
|
4200
|
-
let i = modelIndex.get(model);
|
|
4201
|
-
if (i === undefined) {
|
|
4202
|
-
i = models.length;
|
|
4203
|
-
models.push(model);
|
|
4204
|
-
modelIndex.set(model, i);
|
|
4205
|
-
}
|
|
4206
|
-
return i;
|
|
4207
|
-
};
|
|
4342
|
+
const internModel = makeInternModel(models);
|
|
4208
4343
|
let total = 0;
|
|
4209
4344
|
let input = 0;
|
|
4210
4345
|
let output = 0;
|
|
@@ -4249,6 +4384,8 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
|
4249
4384
|
return {
|
|
4250
4385
|
mtime: st.mtimeMs,
|
|
4251
4386
|
size: st.size,
|
|
4387
|
+
byteOffset,
|
|
4388
|
+
headHash,
|
|
4252
4389
|
total,
|
|
4253
4390
|
lines: costLines,
|
|
4254
4391
|
models,
|
|
@@ -4256,6 +4393,138 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
|
4256
4393
|
record
|
|
4257
4394
|
};
|
|
4258
4395
|
}
|
|
4396
|
+
function resumeCostFile(cached, path, st, price) {
|
|
4397
|
+
const tail = readAppendedTail(cached, path, st);
|
|
4398
|
+
if (tail === null)
|
|
4399
|
+
return null;
|
|
4400
|
+
if (tail === "partial")
|
|
4401
|
+
return { ...cached, mtime: st.mtimeMs, size: st.size };
|
|
4402
|
+
const models = [...cached.models];
|
|
4403
|
+
const keyIndex = new Map;
|
|
4404
|
+
cached.lines.forEach((l, i) => {
|
|
4405
|
+
if (l.id !== undefined && l.reqId !== undefined)
|
|
4406
|
+
keyIndex.set(`${l.id}|${l.reqId}`, i);
|
|
4407
|
+
});
|
|
4408
|
+
const gate = makeDedupGate();
|
|
4409
|
+
const acc = {
|
|
4410
|
+
lines: [...cached.lines],
|
|
4411
|
+
models,
|
|
4412
|
+
counted: cached.lines.map((l) => gate(l.id, l.reqId, l.sidechain)),
|
|
4413
|
+
keyIndex,
|
|
4414
|
+
gate,
|
|
4415
|
+
internModel: makeInternModel(models),
|
|
4416
|
+
input: cached.record.tokens.input,
|
|
4417
|
+
output: cached.record.tokens.output,
|
|
4418
|
+
cacheRead: cached.record.tokens.cache_read,
|
|
4419
|
+
cacheCreation: cached.record.tokens.cache_creation,
|
|
4420
|
+
total: cached.total,
|
|
4421
|
+
messages: cached.record.messages,
|
|
4422
|
+
start: cached.record.start,
|
|
4423
|
+
end: cached.record.end
|
|
4424
|
+
};
|
|
4425
|
+
for (const l of collapseStreaming(tail.appended))
|
|
4426
|
+
foldAppendedLine(acc, l, price);
|
|
4427
|
+
return {
|
|
4428
|
+
mtime: st.mtimeMs,
|
|
4429
|
+
size: st.size,
|
|
4430
|
+
byteOffset: tail.newByteOffset,
|
|
4431
|
+
headHash: tail.newHeadHash,
|
|
4432
|
+
total: acc.total,
|
|
4433
|
+
lines: acc.lines,
|
|
4434
|
+
models: acc.models,
|
|
4435
|
+
projectPath: cached.projectPath,
|
|
4436
|
+
record: {
|
|
4437
|
+
...cached.record,
|
|
4438
|
+
start: acc.start,
|
|
4439
|
+
end: acc.end,
|
|
4440
|
+
tokens: {
|
|
4441
|
+
input: acc.input,
|
|
4442
|
+
output: acc.output,
|
|
4443
|
+
cache_read: acc.cacheRead,
|
|
4444
|
+
cache_creation: acc.cacheCreation
|
|
4445
|
+
},
|
|
4446
|
+
messages: acc.messages
|
|
4447
|
+
}
|
|
4448
|
+
};
|
|
4449
|
+
}
|
|
4450
|
+
function costLineOf(internModel, l, cost, totalTok) {
|
|
4451
|
+
return {
|
|
4452
|
+
...l.id !== undefined ? { id: l.id } : {},
|
|
4453
|
+
...l.requestId !== undefined ? { reqId: l.requestId } : {},
|
|
4454
|
+
sidechain: l.isSidechain,
|
|
4455
|
+
ts: l.tsMs ?? 0,
|
|
4456
|
+
cost,
|
|
4457
|
+
...l.model !== undefined ? { m: internModel(l.model) } : {},
|
|
4458
|
+
tok: totalTok
|
|
4459
|
+
};
|
|
4460
|
+
}
|
|
4461
|
+
function readAppendedTail(cached, path, st) {
|
|
4462
|
+
const offset = cached.byteOffset;
|
|
4463
|
+
if (offset === undefined || offset === 0 || st.size < offset)
|
|
4464
|
+
return null;
|
|
4465
|
+
const head = readRange(path, 0, headLen(offset));
|
|
4466
|
+
if (head === null || headHashOf(head) !== cached.headHash)
|
|
4467
|
+
return null;
|
|
4468
|
+
const tailBuf = readRange(path, offset, st.size);
|
|
4469
|
+
if (tailBuf === null)
|
|
4470
|
+
return null;
|
|
4471
|
+
const added = lastLineEnd(tailBuf);
|
|
4472
|
+
if (added === 0)
|
|
4473
|
+
return "partial";
|
|
4474
|
+
const newByteOffset = offset + added;
|
|
4475
|
+
const newHead = headLen(newByteOffset) === head.length ? head : readRange(path, 0, headLen(newByteOffset));
|
|
4476
|
+
if (newHead === null)
|
|
4477
|
+
return null;
|
|
4478
|
+
return {
|
|
4479
|
+
appended: parseAll(tailBuf.subarray(0, added).toString("utf8")),
|
|
4480
|
+
newByteOffset,
|
|
4481
|
+
newHeadHash: headHashOf(newHead)
|
|
4482
|
+
};
|
|
4483
|
+
}
|
|
4484
|
+
function widenBounds(acc, tsMs) {
|
|
4485
|
+
if (tsMs === undefined)
|
|
4486
|
+
return;
|
|
4487
|
+
if (tsMs > acc.end)
|
|
4488
|
+
acc.end = tsMs;
|
|
4489
|
+
if (acc.start === 0 || tsMs < acc.start)
|
|
4490
|
+
acc.start = tsMs;
|
|
4491
|
+
}
|
|
4492
|
+
function foldAppendedLine(acc, l, price) {
|
|
4493
|
+
widenBounds(acc, l.tsMs);
|
|
4494
|
+
const usage = l.usage;
|
|
4495
|
+
if (usage === undefined)
|
|
4496
|
+
return;
|
|
4497
|
+
const t = tokenize(usage);
|
|
4498
|
+
const totalTok = t.input + t.output + t.cache_read + t.c5m + t.c1h;
|
|
4499
|
+
const key = l.id !== undefined && l.requestId !== undefined ? `${l.id}|${l.requestId}` : null;
|
|
4500
|
+
const existing = key !== null ? acc.keyIndex.get(key) : undefined;
|
|
4501
|
+
const old = existing !== undefined ? acc.lines[existing] : undefined;
|
|
4502
|
+
if (existing !== undefined && old !== undefined) {
|
|
4503
|
+
if (totalTok <= (old.tok ?? 0))
|
|
4504
|
+
return;
|
|
4505
|
+
const cost2 = price(usage, l.model ?? "", l.tsMs);
|
|
4506
|
+
if (acc.counted[existing] === true) {
|
|
4507
|
+
acc.output += totalTok - (old.tok ?? 0);
|
|
4508
|
+
acc.total += cost2 - old.cost;
|
|
4509
|
+
}
|
|
4510
|
+
acc.lines[existing] = costLineOf(acc.internModel, l, cost2, totalTok);
|
|
4511
|
+
return;
|
|
4512
|
+
}
|
|
4513
|
+
const cost = price(usage, l.model ?? "", l.tsMs);
|
|
4514
|
+
const isCounted = acc.gate(l.id, l.requestId, l.isSidechain);
|
|
4515
|
+
if (key !== null)
|
|
4516
|
+
acc.keyIndex.set(key, acc.lines.length);
|
|
4517
|
+
acc.lines.push(costLineOf(acc.internModel, l, cost, totalTok));
|
|
4518
|
+
acc.counted.push(isCounted);
|
|
4519
|
+
if (isCounted) {
|
|
4520
|
+
acc.input += t.input;
|
|
4521
|
+
acc.output += t.output;
|
|
4522
|
+
acc.cacheRead += t.cache_read;
|
|
4523
|
+
acc.cacheCreation += t.c5m + t.c1h;
|
|
4524
|
+
acc.total += cost;
|
|
4525
|
+
acc.messages += 1;
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4259
4528
|
function buildAggregate(files, chat) {
|
|
4260
4529
|
const sessionProject = {};
|
|
4261
4530
|
const all = [];
|
|
@@ -4290,17 +4559,27 @@ function scanCostTree(root, cache, clock, price, resolveProject) {
|
|
|
4290
4559
|
return cache;
|
|
4291
4560
|
const chat = cache.aggregate.chat;
|
|
4292
4561
|
const files = {};
|
|
4562
|
+
let changed = false;
|
|
4293
4563
|
for (const { path, encodedDir } of walkJsonl(root)) {
|
|
4294
4564
|
const st = statSafe(path);
|
|
4295
4565
|
if (!st)
|
|
4296
4566
|
continue;
|
|
4297
4567
|
const cached = cache.files[path];
|
|
4298
|
-
|
|
4568
|
+
if (cached && cached.mtime === st.mtimeMs && cached.size === st.size) {
|
|
4569
|
+
files[path] = cached;
|
|
4570
|
+
} else {
|
|
4571
|
+
const resumed = cached ? resumeCostFile(cached, path, st, price) : null;
|
|
4572
|
+
files[path] = resumed ?? priceFile(path, encodedDir, st, price, resolveProject);
|
|
4573
|
+
changed = true;
|
|
4574
|
+
}
|
|
4299
4575
|
}
|
|
4576
|
+
const sameFileSet = Object.keys(files).length === Object.keys(cache.files).length;
|
|
4577
|
+
if (!changed && sameFileSet)
|
|
4578
|
+
return { files, aggregate: cache.aggregate, lastScanTs: now };
|
|
4300
4579
|
return { files, aggregate: buildAggregate(files, chat), lastScanTs: now };
|
|
4301
4580
|
}
|
|
4302
4581
|
// src/cli/classify.ts
|
|
4303
|
-
var FAIL_RE =
|
|
4582
|
+
var FAIL_RE = /\bFAILED\b|\bFAIL\b|✗|✕|\bnot ok\b|error:|error\[|error TS\d|error CS\d|\bpanic:|Traceback \(most recent call last\)|[1-9]\d* (?:failed|failing|errors?)\b/;
|
|
4304
4583
|
function asObject(v) {
|
|
4305
4584
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
4306
4585
|
}
|
|
@@ -4316,10 +4595,8 @@ function softFail(toolResponse) {
|
|
|
4316
4595
|
return false;
|
|
4317
4596
|
if (r["isError"] === true || r["interrupted"] === true)
|
|
4318
4597
|
return true;
|
|
4319
|
-
const
|
|
4320
|
-
|
|
4321
|
-
return true;
|
|
4322
|
-
const out = `${asString(r["stdout"]) ?? ""}${stderr ?? ""}`;
|
|
4598
|
+
const out = `${asString(r["stdout"]) ?? ""}
|
|
4599
|
+
${asString(r["stderr"]) ?? ""}`;
|
|
4323
4600
|
return FAIL_RE.test(out);
|
|
4324
4601
|
}
|
|
4325
4602
|
function commandOf(toolInput) {
|
|
@@ -4340,16 +4617,6 @@ function appendClassified(dir, toolName, command, ok, ts) {
|
|
|
4340
4617
|
if (c !== null)
|
|
4341
4618
|
appendEvent(dir, { ts, ...c });
|
|
4342
4619
|
}
|
|
4343
|
-
function appendBatch(dir, calls, ts) {
|
|
4344
|
-
for (const call of calls) {
|
|
4345
|
-
const c = asObject(call);
|
|
4346
|
-
const toolName = c !== undefined ? asString(c["tool_name"]) : undefined;
|
|
4347
|
-
if (c === undefined || toolName === undefined)
|
|
4348
|
-
continue;
|
|
4349
|
-
const ok = !softFail(c["tool_response"]);
|
|
4350
|
-
appendClassified(dir, toolName, commandOf(c["tool_input"]), ok, ts);
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
4620
|
function runClassify(stdin, env, clock) {
|
|
4354
4621
|
try {
|
|
4355
4622
|
const r = asObject(JSON.parse(stdin));
|
|
@@ -4361,12 +4628,6 @@ function runClassify(stdin, env, clock) {
|
|
|
4361
4628
|
const dir = sessionDir(ccsidekickRoot(env), session);
|
|
4362
4629
|
const ts = clock.now();
|
|
4363
4630
|
const hook = asString(r["hook_event_name"]);
|
|
4364
|
-
if (hook === "PostToolBatch") {
|
|
4365
|
-
const calls = r["tool_calls"];
|
|
4366
|
-
if (Array.isArray(calls))
|
|
4367
|
-
appendBatch(dir, calls, ts);
|
|
4368
|
-
return;
|
|
4369
|
-
}
|
|
4370
4631
|
const toolName = asString(r["tool_name"]);
|
|
4371
4632
|
if (toolName === undefined)
|
|
4372
4633
|
return;
|
|
@@ -4379,7 +4640,7 @@ function runClassify(stdin, env, clock) {
|
|
|
4379
4640
|
} catch {}
|
|
4380
4641
|
}
|
|
4381
4642
|
// src/cli/render.ts
|
|
4382
|
-
import { readFileSync as
|
|
4643
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
4383
4644
|
import { dirname as dirname7, join as join14 } from "node:path";
|
|
4384
4645
|
|
|
4385
4646
|
// src/compose/character.ts
|
|
@@ -6096,16 +6357,6 @@ function composeStatusline(ctx) {
|
|
|
6096
6357
|
return { fields, providerBadge };
|
|
6097
6358
|
}
|
|
6098
6359
|
// src/compose/helpful/catalog.ts
|
|
6099
|
-
var COMPACT_SOON_PCT = 60;
|
|
6100
|
-
var QUOTA_PROJECT_MIN_PCT = 50;
|
|
6101
|
-
var COMPACTION_THRASH_N = 3;
|
|
6102
|
-
var CACHE_RATIO_FLOOR = 0.5;
|
|
6103
|
-
var CACHE_WARMUP_TURNS = 20;
|
|
6104
|
-
var BIG_DIFF_LINES = 1000;
|
|
6105
|
-
var UNTRACKED_N = 20;
|
|
6106
|
-
var STASH_N = 5;
|
|
6107
|
-
var STALE_BRANCH_N = 20;
|
|
6108
|
-
var TODO_STALLED_MIN = 30;
|
|
6109
6360
|
var EFFORT_LOW_LEVEL = "low";
|
|
6110
6361
|
var PROD_CONTEXT_PATTERN = /prod/i;
|
|
6111
6362
|
var SECRET_PATTERNS = [
|
|
@@ -6280,7 +6531,7 @@ var HELPFUL_CATALOG = [
|
|
|
6280
6531
|
category: "quota",
|
|
6281
6532
|
momentary: false,
|
|
6282
6533
|
template: "5h limit at {pct}%, {reset} to reset. Save heavy asks for after.",
|
|
6283
|
-
test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_HIGH_PCT,
|
|
6534
|
+
test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_HIGH_PCT && i.quota.block.overPace === true,
|
|
6284
6535
|
render: (i) => `5h limit at ${pct(i.quota.block?.usedPct ?? 0)}, ${bare(i.quota.block?.resetIn)} to reset. Save heavy asks for after.`
|
|
6285
6536
|
},
|
|
6286
6537
|
{
|
|
@@ -6289,7 +6540,7 @@ var HELPFUL_CATALOG = [
|
|
|
6289
6540
|
category: "quota",
|
|
6290
6541
|
momentary: false,
|
|
6291
6542
|
template: "Weekly quota {pct}% spent, {reset} to go. Ration the big requests.",
|
|
6292
|
-
test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_HIGH_PCT,
|
|
6543
|
+
test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_HIGH_PCT && i.quota.weekly.overPace === true,
|
|
6293
6544
|
render: (i) => `Weekly quota ${pct(i.quota.weekly?.usedPct ?? 0)} spent, ${bare(i.quota.weekly?.resetIn)} to go. Ration the big requests.`
|
|
6294
6545
|
},
|
|
6295
6546
|
{
|
|
@@ -6298,7 +6549,7 @@ var HELPFUL_CATALOG = [
|
|
|
6298
6549
|
category: "quota",
|
|
6299
6550
|
momentary: false,
|
|
6300
6551
|
template: "This burn rate empties the 5h block before reset, cutting you off mid-task.",
|
|
6301
|
-
test: (i) => i.quota.block !== undefined && i.quota.block.
|
|
6552
|
+
test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_PROJECT_MIN_PCT && i.quota.block.overPace === true,
|
|
6302
6553
|
render: () => "This burn rate empties the 5h block before reset, cutting you off mid-task."
|
|
6303
6554
|
},
|
|
6304
6555
|
{
|
|
@@ -6307,7 +6558,7 @@ var HELPFUL_CATALOG = [
|
|
|
6307
6558
|
category: "quota",
|
|
6308
6559
|
momentary: false,
|
|
6309
6560
|
template: "At this pace the weekly quota runs dry before reset, locking you out for days.",
|
|
6310
|
-
test: (i) => i.quota.weekly !== undefined && i.quota.weekly.
|
|
6561
|
+
test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_PROJECT_MIN_PCT && i.quota.weekly.overPace === true,
|
|
6311
6562
|
render: () => "At this pace the weekly quota runs dry before reset, locking you out for days."
|
|
6312
6563
|
},
|
|
6313
6564
|
{
|
|
@@ -6928,16 +7179,33 @@ var PACKS = [
|
|
|
6928
7179
|
"yoda"
|
|
6929
7180
|
];
|
|
6930
7181
|
// src/cli/gc.ts
|
|
6931
|
-
import {
|
|
7182
|
+
import {
|
|
7183
|
+
existsSync as existsSync2,
|
|
7184
|
+
mkdirSync as mkdirSync5,
|
|
7185
|
+
readFileSync as readFileSync10,
|
|
7186
|
+
readdirSync as readdirSync3,
|
|
7187
|
+
rmSync,
|
|
7188
|
+
statSync as statSync5,
|
|
7189
|
+
writeFileSync as writeFileSync3
|
|
7190
|
+
} from "node:fs";
|
|
6932
7191
|
import { join as join13 } from "node:path";
|
|
6933
7192
|
var DAY_MS = 86400000;
|
|
7193
|
+
var GC_MIN_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
7194
|
+
function readGcStamp(path) {
|
|
7195
|
+
try {
|
|
7196
|
+
const n = Number(readFileSync10(path, "utf8"));
|
|
7197
|
+
return Number.isFinite(n) ? n : undefined;
|
|
7198
|
+
} catch {
|
|
7199
|
+
return;
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
6934
7202
|
function newestMtime(dir) {
|
|
6935
7203
|
let newest = 0;
|
|
6936
7204
|
try {
|
|
6937
|
-
newest =
|
|
7205
|
+
newest = statSync5(dir).mtimeMs;
|
|
6938
7206
|
for (const name of readdirSync3(dir)) {
|
|
6939
7207
|
try {
|
|
6940
|
-
const m =
|
|
7208
|
+
const m = statSync5(join13(dir, name)).mtimeMs;
|
|
6941
7209
|
if (m > newest)
|
|
6942
7210
|
newest = m;
|
|
6943
7211
|
} catch {}
|
|
@@ -6957,7 +7225,7 @@ function pruneSessions(root, now) {
|
|
|
6957
7225
|
for (const name of names) {
|
|
6958
7226
|
const dir = join13(sessions, name);
|
|
6959
7227
|
try {
|
|
6960
|
-
if (!
|
|
7228
|
+
if (!statSync5(dir).isDirectory())
|
|
6961
7229
|
continue;
|
|
6962
7230
|
} catch {
|
|
6963
7231
|
continue;
|
|
@@ -6970,23 +7238,48 @@ function pruneSessions(root, now) {
|
|
|
6970
7238
|
function pruneCostCache(root) {
|
|
6971
7239
|
const cache = readCostCache(root);
|
|
6972
7240
|
const kept = {};
|
|
7241
|
+
const liveSessions = new Set;
|
|
6973
7242
|
let dropped = false;
|
|
6974
7243
|
for (const [path, entry] of Object.entries(cache.files)) {
|
|
6975
|
-
if (existsSync2(path))
|
|
7244
|
+
if (existsSync2(path)) {
|
|
6976
7245
|
kept[path] = entry;
|
|
6977
|
-
|
|
7246
|
+
liveSessions.add(String(entry.record.session));
|
|
7247
|
+
} else {
|
|
6978
7248
|
dropped = true;
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
const chatKept = {};
|
|
7252
|
+
let chatDropped = false;
|
|
7253
|
+
for (const [session, cost] of Object.entries(cache.aggregate.chat)) {
|
|
7254
|
+
if (liveSessions.has(session))
|
|
7255
|
+
chatKept[session] = cost;
|
|
7256
|
+
else
|
|
7257
|
+
chatDropped = true;
|
|
7258
|
+
}
|
|
7259
|
+
if (dropped || chatDropped) {
|
|
7260
|
+
writeCostCache(root, {
|
|
7261
|
+
...cache,
|
|
7262
|
+
files: kept,
|
|
7263
|
+
aggregate: { ...cache.aggregate, chat: chatKept }
|
|
7264
|
+
});
|
|
6979
7265
|
}
|
|
6980
|
-
if (dropped)
|
|
6981
|
-
writeCostCache(root, { ...cache, files: kept });
|
|
6982
7266
|
}
|
|
6983
7267
|
function runGc(root, clock) {
|
|
7268
|
+
const now = clock.now();
|
|
7269
|
+
const stampPath = join13(root, "cache", ".gc-stamp");
|
|
7270
|
+
const last = readGcStamp(stampPath);
|
|
7271
|
+
if (last !== undefined && now - last < GC_MIN_INTERVAL_MS)
|
|
7272
|
+
return;
|
|
6984
7273
|
try {
|
|
6985
|
-
pruneSessions(root,
|
|
7274
|
+
pruneSessions(root, now);
|
|
6986
7275
|
} catch {}
|
|
6987
7276
|
try {
|
|
6988
7277
|
pruneCostCache(root);
|
|
6989
7278
|
} catch {}
|
|
7279
|
+
try {
|
|
7280
|
+
mkdirSync5(join13(root, "cache"), { recursive: true });
|
|
7281
|
+
writeFileSync3(stampPath, String(now));
|
|
7282
|
+
} catch {}
|
|
6990
7283
|
}
|
|
6991
7284
|
|
|
6992
7285
|
// src/cli/render.ts
|
|
@@ -6995,7 +7288,7 @@ var TIER_MILESTONES = new Set(TIER_THRESHOLDS);
|
|
|
6995
7288
|
var YEAR_MS = 365 * 86400000;
|
|
6996
7289
|
function readTextSafe(path) {
|
|
6997
7290
|
try {
|
|
6998
|
-
return
|
|
7291
|
+
return readFileSync11(path, "utf8");
|
|
6999
7292
|
} catch {
|
|
7000
7293
|
return "";
|
|
7001
7294
|
}
|
|
@@ -7030,19 +7323,29 @@ function applyThemeIcons(fields, theme, config) {
|
|
|
7030
7323
|
});
|
|
7031
7324
|
}
|
|
7032
7325
|
function readGated(overrides, envInputs, config, root, dir, markers, clock) {
|
|
7033
|
-
const creds = overrides?.creds !== undefined ? overrides.creds : envInputs.hasApiKey || config.network.usage_fetch ?
|
|
7326
|
+
const creds = overrides?.creds !== undefined ? overrides.creds : envInputs.hasApiKey || config.network.usage_fetch ? readCredsCached(root) : null;
|
|
7034
7327
|
const usage = overrides?.usage !== undefined ? overrides.usage : config.network.usage_fetch ? readUsageCached(root) : null;
|
|
7035
7328
|
const balance = overrides?.balance !== undefined ? overrides.balance : config.network.balance_path !== "" ? readBalance(config.network.balance_path, clock) : null;
|
|
7036
7329
|
const helpfulEnv = needsHelpfulEnv(markers) ? readHelpfulEnv(dir) : EMPTY_HELPFUL_ENV;
|
|
7037
7330
|
return { creds, usage, balance, helpfulEnv };
|
|
7038
7331
|
}
|
|
7039
7332
|
var DEFAULT_EMBLEM = "❝";
|
|
7040
|
-
function
|
|
7041
|
-
const loaded = loadPack(name);
|
|
7333
|
+
function packThemeOf(loaded) {
|
|
7042
7334
|
if (!loaded.ok || loaded.pack.theme === undefined)
|
|
7043
7335
|
return null;
|
|
7044
7336
|
return { displayName: loaded.pack.displayName, ...loaded.pack.theme };
|
|
7045
7337
|
}
|
|
7338
|
+
function makePackThemeLookup(persona, loaded, load = loadPack) {
|
|
7339
|
+
const memo = new Map;
|
|
7340
|
+
return (name) => {
|
|
7341
|
+
const cached = memo.get(name);
|
|
7342
|
+
if (cached !== undefined || memo.has(name))
|
|
7343
|
+
return cached ?? null;
|
|
7344
|
+
const theme = packThemeOf(name === persona ? loaded : load(name));
|
|
7345
|
+
memo.set(name, theme);
|
|
7346
|
+
return theme;
|
|
7347
|
+
};
|
|
7348
|
+
}
|
|
7046
7349
|
var NOOP = () => {};
|
|
7047
7350
|
function runRender(stdin, env, term, clock, overrides) {
|
|
7048
7351
|
try {
|
|
@@ -7105,8 +7408,8 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7105
7408
|
const stacks = deriveStacks(markers, events);
|
|
7106
7409
|
const fresh = freshestEvent(events);
|
|
7107
7410
|
const stack = pickStack(stacks, fresh?.stack);
|
|
7108
|
-
const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock);
|
|
7109
|
-
const theme = resolveTheme(config,
|
|
7411
|
+
const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock, String(session));
|
|
7412
|
+
const theme = resolveTheme(config, makePackThemeLookup(persona, loaded), persona);
|
|
7110
7413
|
const composeInputs = {
|
|
7111
7414
|
provider,
|
|
7112
7415
|
model,
|
|
@@ -7207,6 +7510,10 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7207
7510
|
}
|
|
7208
7511
|
swallow(() => {
|
|
7209
7512
|
const payloadChat = payload.cost?.total_cost_usd;
|
|
7513
|
+
const rescanned = scannedCache !== costCache;
|
|
7514
|
+
const chatChanged = !isDefault && payloadChat !== undefined && scannedCache.aggregate.chat[String(session)] !== payloadChat;
|
|
7515
|
+
if (!rescanned && !chatChanged)
|
|
7516
|
+
return;
|
|
7210
7517
|
const toWrite = !isDefault && payloadChat !== undefined ? {
|
|
7211
7518
|
...scannedCache,
|
|
7212
7519
|
aggregate: {
|
|
@@ -7230,6 +7537,10 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7230
7537
|
swallow(() => {
|
|
7231
7538
|
readUsage(root, clock, { enabled: config.network.usage_fetch });
|
|
7232
7539
|
});
|
|
7540
|
+
swallow(() => {
|
|
7541
|
+
if (envInputs.hasApiKey || config.network.usage_fetch)
|
|
7542
|
+
refreshCreds(root, clock);
|
|
7543
|
+
});
|
|
7233
7544
|
swallow(() => {
|
|
7234
7545
|
runGc(root, clock);
|
|
7235
7546
|
});
|
|
@@ -7251,7 +7562,7 @@ function dispatchRender() {
|
|
|
7251
7562
|
noColor: process.env["NO_COLOR"] !== undefined,
|
|
7252
7563
|
isTTY: true
|
|
7253
7564
|
};
|
|
7254
|
-
const { line, persist } = runRender(
|
|
7565
|
+
const { line, persist } = runRender(readFileSync12(0, "utf8"), process.env, term, systemClock);
|
|
7255
7566
|
process.stdout.write(`${line}
|
|
7256
7567
|
`);
|
|
7257
7568
|
persist();
|
|
@@ -7259,7 +7570,7 @@ function dispatchRender() {
|
|
|
7259
7570
|
}
|
|
7260
7571
|
function dispatchClassify() {
|
|
7261
7572
|
try {
|
|
7262
|
-
runClassify(
|
|
7573
|
+
runClassify(readFileSync12(0, "utf8"), process.env, systemClock);
|
|
7263
7574
|
} catch {} finally {
|
|
7264
7575
|
process.exit(0);
|
|
7265
7576
|
}
|