ccsidekick 1.6.1 → 1.6.3
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 +425 -103
- package/dist/ccsidekick.js +476 -132
- package/package.json +3 -3
|
@@ -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;
|
|
@@ -1385,6 +1427,17 @@ var systemClock = {
|
|
|
1385
1427
|
now: () => Date.now(),
|
|
1386
1428
|
timezone: () => Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
1387
1429
|
};
|
|
1430
|
+
var fixedClock = (nowMs, tz = "UTC") => ({
|
|
1431
|
+
now: () => nowMs,
|
|
1432
|
+
timezone: () => tz
|
|
1433
|
+
});
|
|
1434
|
+
var resolveClock = (env) => {
|
|
1435
|
+
const raw = env["CCSIDEKICK_NOW"];
|
|
1436
|
+
const nowMs = raw === undefined ? NaN : Number(raw);
|
|
1437
|
+
if (raw === undefined || raw.trim() === "" || !Number.isFinite(nowMs))
|
|
1438
|
+
return systemClock;
|
|
1439
|
+
return fixedClock(nowMs, env["CCSIDEKICK_TZ"] ?? "UTC");
|
|
1440
|
+
};
|
|
1388
1441
|
// ../../node_modules/.bun/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
|
|
1389
1442
|
/*!
|
|
1390
1443
|
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
@@ -2493,10 +2546,10 @@ function loadConfig(globalToml = "", projectToml = "") {
|
|
|
2493
2546
|
const statusline = section(g, p, "statusline");
|
|
2494
2547
|
const d = DEFAULT_CONFIG;
|
|
2495
2548
|
return {
|
|
2496
|
-
schema_version: numv(p["schema_version"]
|
|
2549
|
+
schema_version: numv(p["schema_version"], numv(g["schema_version"], d.schema_version)),
|
|
2497
2550
|
character: {
|
|
2498
2551
|
enabled: bool(character["enabled"], d.character.enabled),
|
|
2499
|
-
mode: oneOf(character["mode"],
|
|
2552
|
+
mode: oneOf(character["mode"], CHARACTER_MODES, d.character.mode),
|
|
2500
2553
|
name: str(character["name"], d.character.name),
|
|
2501
2554
|
roster: stringArray(character["roster"], d.character.roster)
|
|
2502
2555
|
},
|
|
@@ -2505,7 +2558,7 @@ function loadConfig(globalToml = "", projectToml = "") {
|
|
|
2505
2558
|
...typeof theme["statusline"] === "string" ? { statusline: theme["statusline"] } : {},
|
|
2506
2559
|
...typeof theme["logo"] === "string" ? { logo: theme["logo"] } : {},
|
|
2507
2560
|
...typeof theme["comment"] === "string" ? { comment: theme["comment"] } : {},
|
|
2508
|
-
banding: oneOf(theme["banding"],
|
|
2561
|
+
banding: oneOf(theme["banding"], BANDINGS, d.theme.banding),
|
|
2509
2562
|
mood_shift: bool(theme["mood_shift"], d.theme.mood_shift),
|
|
2510
2563
|
icons: mergeIcons({
|
|
2511
2564
|
...obj(obj(g["theme"])["icons"]),
|
|
@@ -2515,7 +2568,7 @@ function loadConfig(globalToml = "", projectToml = "") {
|
|
|
2515
2568
|
comments: {
|
|
2516
2569
|
character: bool(comments["character"], d.comments.character),
|
|
2517
2570
|
helpful: bool(comments["helpful"], d.comments.helpful),
|
|
2518
|
-
min_severity: oneOf(comments["min_severity"],
|
|
2571
|
+
min_severity: oneOf(comments["min_severity"], MIN_SEVERITIES, d.comments.min_severity)
|
|
2519
2572
|
},
|
|
2520
2573
|
network: {
|
|
2521
2574
|
fx_refresh: bool(network["fx_refresh"], d.network.fx_refresh),
|
|
@@ -2645,7 +2698,19 @@ function coerceEntry(v) {
|
|
|
2645
2698
|
}
|
|
2646
2699
|
const projectPath = typeof v["projectPath"] === "string" ? v["projectPath"] : String(record.project);
|
|
2647
2700
|
const models = Array.isArray(v["models"]) ? v["models"].filter((x) => typeof x === "string") : [];
|
|
2648
|
-
|
|
2701
|
+
const byteOffset = asNumber(v["byteOffset"]);
|
|
2702
|
+
const headHash = typeof v["headHash"] === "string" ? v["headHash"] : undefined;
|
|
2703
|
+
return {
|
|
2704
|
+
mtime,
|
|
2705
|
+
size,
|
|
2706
|
+
total,
|
|
2707
|
+
lines,
|
|
2708
|
+
models,
|
|
2709
|
+
projectPath,
|
|
2710
|
+
record,
|
|
2711
|
+
...byteOffset !== undefined ? { byteOffset } : {},
|
|
2712
|
+
...headHash !== undefined ? { headHash } : {}
|
|
2713
|
+
};
|
|
2649
2714
|
}
|
|
2650
2715
|
function coerceByModel(v) {
|
|
2651
2716
|
if (!isObject(v))
|
|
@@ -2702,7 +2767,6 @@ function writeCostCache(root, c) {
|
|
|
2702
2767
|
// src/sources/creds.ts
|
|
2703
2768
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2704
2769
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
2705
|
-
import { homedir as homedir3 } from "node:os";
|
|
2706
2770
|
import { join as join5 } from "node:path";
|
|
2707
2771
|
|
|
2708
2772
|
// src/sources/oauthUsage.ts
|
|
@@ -2899,7 +2963,6 @@ function readUsage(root, clock, opts) {
|
|
|
2899
2963
|
}
|
|
2900
2964
|
|
|
2901
2965
|
// src/sources/creds.ts
|
|
2902
|
-
var KEYCHAIN_SERVICE2 = "Claude Code-credentials";
|
|
2903
2966
|
var SUBSCRIPTIONS = new Set(["pro", "max", "team", "enterprise"]);
|
|
2904
2967
|
var isObject3 = (v) => typeof v === "object" && v !== null;
|
|
2905
2968
|
var defaultRunner = (cmd, args) => {
|
|
@@ -2916,11 +2979,8 @@ var defaultRunner = (cmd, args) => {
|
|
|
2916
2979
|
return "";
|
|
2917
2980
|
}
|
|
2918
2981
|
};
|
|
2919
|
-
function configBase2() {
|
|
2920
|
-
return process.env["CLAUDE_CONFIG_DIR"] ?? join5(homedir3(), ".claude");
|
|
2921
|
-
}
|
|
2922
2982
|
function readBlob(run, base) {
|
|
2923
|
-
for (const service of [keychainService(base),
|
|
2983
|
+
for (const service of [keychainService(base), KEYCHAIN_SERVICE]) {
|
|
2924
2984
|
const keychain = run("security", ["find-generic-password", "-s", service, "-w"]);
|
|
2925
2985
|
if (keychain.trim() !== "")
|
|
2926
2986
|
return keychain;
|
|
@@ -2930,7 +2990,7 @@ function readBlob(run, base) {
|
|
|
2930
2990
|
if (file.trim() !== "")
|
|
2931
2991
|
return file;
|
|
2932
2992
|
} catch {}
|
|
2933
|
-
const secretTool = run("secret-tool", ["lookup", "service",
|
|
2993
|
+
const secretTool = run("secret-tool", ["lookup", "service", KEYCHAIN_SERVICE]);
|
|
2934
2994
|
if (secretTool.trim() !== "")
|
|
2935
2995
|
return secretTool;
|
|
2936
2996
|
return "";
|
|
@@ -2959,7 +3019,7 @@ function readOrgTier(base) {
|
|
|
2959
3019
|
}
|
|
2960
3020
|
function readCreds(run = defaultRunner) {
|
|
2961
3021
|
try {
|
|
2962
|
-
const base =
|
|
3022
|
+
const base = configBase();
|
|
2963
3023
|
const blob = readBlob(run, base);
|
|
2964
3024
|
if (blob.trim() !== "") {
|
|
2965
3025
|
const tier = parseSubscription(blob);
|
|
@@ -2973,6 +3033,39 @@ function readCreds(run = defaultRunner) {
|
|
|
2973
3033
|
return null;
|
|
2974
3034
|
}
|
|
2975
3035
|
}
|
|
3036
|
+
var CREDS_CACHE_FILE = "creds.json";
|
|
3037
|
+
function credsCachePath(root) {
|
|
3038
|
+
return join5(cacheDir(root), CREDS_CACHE_FILE);
|
|
3039
|
+
}
|
|
3040
|
+
function coerceCachedCreds(raw) {
|
|
3041
|
+
if (!isObject3(raw) || typeof raw["at"] !== "number")
|
|
3042
|
+
return null;
|
|
3043
|
+
const at = raw["at"];
|
|
3044
|
+
const info = raw["info"];
|
|
3045
|
+
if (info === null)
|
|
3046
|
+
return { info: null, at };
|
|
3047
|
+
if (isObject3(info) && typeof info["present"] === "boolean") {
|
|
3048
|
+
const sub = info["subscriptionType"];
|
|
3049
|
+
return {
|
|
3050
|
+
at,
|
|
3051
|
+
info: typeof sub === "string" && SUBSCRIPTIONS.has(sub) ? { present: info["present"], subscriptionType: sub } : { present: info["present"] }
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
function readCredsCached(root) {
|
|
3057
|
+
return coerceCachedCreds(readJson(credsCachePath(root), undefined))?.info ?? null;
|
|
3058
|
+
}
|
|
3059
|
+
function refreshCreds(root, clock, run = defaultRunner) {
|
|
3060
|
+
const cached = coerceCachedCreds(readJson(credsCachePath(root), undefined));
|
|
3061
|
+
const now = clock.now();
|
|
3062
|
+
if (cached !== null && now - cached.at <= CREDS_TTL_MS)
|
|
3063
|
+
return;
|
|
3064
|
+
const info = readCreds(run);
|
|
3065
|
+
try {
|
|
3066
|
+
atomicWrite(credsCachePath(root), JSON.stringify({ info, at: now }));
|
|
3067
|
+
} catch {}
|
|
3068
|
+
}
|
|
2976
3069
|
// src/sources/env.ts
|
|
2977
3070
|
var on = (v) => v === "1" || v === "true";
|
|
2978
3071
|
function isCustomHost(base) {
|
|
@@ -3455,12 +3548,14 @@ function readSubmoduleRollup(run, workTreeRoot) {
|
|
|
3455
3548
|
return rollup;
|
|
3456
3549
|
}
|
|
3457
3550
|
function readGit(cwd, run = defaultRunner2(cwd)) {
|
|
3458
|
-
|
|
3551
|
+
const probe = run(["rev-parse", "--is-inside-work-tree", "--git-dir", "--show-toplevel"]).split(`
|
|
3552
|
+
`);
|
|
3553
|
+
if ((probe[0] ?? "").trim() !== "true")
|
|
3459
3554
|
return null;
|
|
3460
3555
|
const status = parseStatus(run(["status", "--porcelain=v2", "--branch", "--show-stash"]));
|
|
3461
|
-
const gitDirRaw =
|
|
3556
|
+
const gitDirRaw = (probe[1] ?? "").trim();
|
|
3462
3557
|
const gitDir = gitDirRaw === "" ? "" : resolve(cwd, gitDirRaw);
|
|
3463
|
-
const workTreeRoot =
|
|
3558
|
+
const workTreeRoot = (probe[2] ?? "").trim();
|
|
3464
3559
|
const tag = run(["describe", "--tags", "--exact-match"]).trim() || undefined;
|
|
3465
3560
|
const originRepo = normalizeOriginRepo(run(["remote", "get-url", "origin"]));
|
|
3466
3561
|
const defaultBranch = resolveDefaultBranch(run);
|
|
@@ -3507,7 +3602,7 @@ function readGit(cwd, run = defaultRunner2(cwd)) {
|
|
|
3507
3602
|
}
|
|
3508
3603
|
// src/sources/helpfulEnv.ts
|
|
3509
3604
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
3510
|
-
import { homedir as
|
|
3605
|
+
import { homedir as homedir3 } from "node:os";
|
|
3511
3606
|
import { delimiter, join as join9 } from "node:path";
|
|
3512
3607
|
var opt3 = (key, value) => value !== undefined ? { [key]: value } : {};
|
|
3513
3608
|
function kubeconfigPath() {
|
|
@@ -3517,7 +3612,7 @@ function kubeconfigPath() {
|
|
|
3517
3612
|
if (first !== undefined)
|
|
3518
3613
|
return first;
|
|
3519
3614
|
}
|
|
3520
|
-
return join9(
|
|
3615
|
+
return join9(homedir3(), ".kube", "config");
|
|
3521
3616
|
}
|
|
3522
3617
|
function readKubeContext() {
|
|
3523
3618
|
try {
|
|
@@ -3821,7 +3916,7 @@ function writeState(sessionDir2, s) {
|
|
|
3821
3916
|
}, () => {});
|
|
3822
3917
|
}
|
|
3823
3918
|
// src/sources/transcript.ts
|
|
3824
|
-
import { readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as
|
|
3919
|
+
import { closeSync as closeSync2, openSync as openSync2, readFileSync as readFileSync8, readSync, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
|
|
3825
3920
|
import { basename, dirname as dirname6, join as join12 } from "node:path";
|
|
3826
3921
|
var asObj = (v) => v !== null && typeof v === "object" ? v : undefined;
|
|
3827
3922
|
var asStr = (v) => typeof v === "string" ? v : undefined;
|
|
@@ -4047,7 +4142,7 @@ function reconstructTodos(lines) {
|
|
|
4047
4142
|
}
|
|
4048
4143
|
function statSafe(p) {
|
|
4049
4144
|
try {
|
|
4050
|
-
const s =
|
|
4145
|
+
const s = statSync4(p);
|
|
4051
4146
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
4052
4147
|
} catch {
|
|
4053
4148
|
return null;
|
|
@@ -4060,6 +4155,63 @@ function readSafe(p) {
|
|
|
4060
4155
|
return "";
|
|
4061
4156
|
}
|
|
4062
4157
|
}
|
|
4158
|
+
function readBufSafe(p) {
|
|
4159
|
+
try {
|
|
4160
|
+
return readFileSync8(p);
|
|
4161
|
+
} catch {
|
|
4162
|
+
return null;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
function readRange(p, start, end) {
|
|
4166
|
+
if (end <= start)
|
|
4167
|
+
return Buffer.alloc(0);
|
|
4168
|
+
let fd;
|
|
4169
|
+
try {
|
|
4170
|
+
fd = openSync2(p, "r");
|
|
4171
|
+
const len = end - start;
|
|
4172
|
+
const buf = Buffer.allocUnsafe(len);
|
|
4173
|
+
let off = 0;
|
|
4174
|
+
while (off < len) {
|
|
4175
|
+
const n = readSync(fd, buf, off, len - off, start + off);
|
|
4176
|
+
if (n === 0)
|
|
4177
|
+
break;
|
|
4178
|
+
off += n;
|
|
4179
|
+
}
|
|
4180
|
+
return off === len ? buf : null;
|
|
4181
|
+
} catch {
|
|
4182
|
+
return null;
|
|
4183
|
+
} finally {
|
|
4184
|
+
if (fd !== undefined)
|
|
4185
|
+
closeSync2(fd);
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
var HEAD_HASH_BYTES = 4096;
|
|
4189
|
+
function headHashOf(head) {
|
|
4190
|
+
let h = 2166136261;
|
|
4191
|
+
for (const b of head) {
|
|
4192
|
+
h ^= b;
|
|
4193
|
+
h = Math.imul(h, 16777619);
|
|
4194
|
+
}
|
|
4195
|
+
return (h >>> 0).toString(16);
|
|
4196
|
+
}
|
|
4197
|
+
function makeInternModel(models) {
|
|
4198
|
+
const index = new Map;
|
|
4199
|
+
models.forEach((m, i) => index.set(m, i));
|
|
4200
|
+
return (model) => {
|
|
4201
|
+
let i = index.get(model);
|
|
4202
|
+
if (i === undefined) {
|
|
4203
|
+
i = models.length;
|
|
4204
|
+
models.push(model);
|
|
4205
|
+
index.set(model, i);
|
|
4206
|
+
}
|
|
4207
|
+
return i;
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
4210
|
+
function lastLineEnd(buf) {
|
|
4211
|
+
const i = buf.lastIndexOf(10);
|
|
4212
|
+
return i < 0 ? 0 : i + 1;
|
|
4213
|
+
}
|
|
4214
|
+
var headLen = (byteOffset) => Math.min(HEAD_HASH_BYTES, byteOffset);
|
|
4063
4215
|
var EMPTY_TOKENS = {
|
|
4064
4216
|
input: 0,
|
|
4065
4217
|
output: 0,
|
|
@@ -4150,7 +4302,7 @@ function* walkTree(dir, encodedDir) {
|
|
|
4150
4302
|
const full = join12(dir, name);
|
|
4151
4303
|
let st;
|
|
4152
4304
|
try {
|
|
4153
|
-
st =
|
|
4305
|
+
st = statSync4(full);
|
|
4154
4306
|
} catch {
|
|
4155
4307
|
continue;
|
|
4156
4308
|
}
|
|
@@ -4189,22 +4341,16 @@ function scanBounds(lines) {
|
|
|
4189
4341
|
return { sessionId, start, end };
|
|
4190
4342
|
}
|
|
4191
4343
|
function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
4192
|
-
const
|
|
4344
|
+
const buf = readBufSafe(path) ?? Buffer.alloc(0);
|
|
4345
|
+
const byteOffset = lastLineEnd(buf);
|
|
4346
|
+
const headHash = headHashOf(buf.subarray(0, headLen(byteOffset)));
|
|
4347
|
+
const lines = parseAll(buf.subarray(0, byteOffset).toString("utf8"));
|
|
4193
4348
|
const { sessionId, start, end } = scanBounds(lines);
|
|
4194
4349
|
const priced = collapseStreaming(lines);
|
|
4195
4350
|
const gate = makeDedupGate();
|
|
4196
4351
|
const costLines = [];
|
|
4197
4352
|
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
|
-
};
|
|
4353
|
+
const internModel = makeInternModel(models);
|
|
4208
4354
|
let total = 0;
|
|
4209
4355
|
let input = 0;
|
|
4210
4356
|
let output = 0;
|
|
@@ -4249,6 +4395,8 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
|
4249
4395
|
return {
|
|
4250
4396
|
mtime: st.mtimeMs,
|
|
4251
4397
|
size: st.size,
|
|
4398
|
+
byteOffset,
|
|
4399
|
+
headHash,
|
|
4252
4400
|
total,
|
|
4253
4401
|
lines: costLines,
|
|
4254
4402
|
models,
|
|
@@ -4256,6 +4404,138 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
|
4256
4404
|
record
|
|
4257
4405
|
};
|
|
4258
4406
|
}
|
|
4407
|
+
function resumeCostFile(cached, path, st, price) {
|
|
4408
|
+
const tail = readAppendedTail(cached, path, st);
|
|
4409
|
+
if (tail === null)
|
|
4410
|
+
return null;
|
|
4411
|
+
if (tail === "partial")
|
|
4412
|
+
return { ...cached, mtime: st.mtimeMs, size: st.size };
|
|
4413
|
+
const models = [...cached.models];
|
|
4414
|
+
const keyIndex = new Map;
|
|
4415
|
+
cached.lines.forEach((l, i) => {
|
|
4416
|
+
if (l.id !== undefined && l.reqId !== undefined)
|
|
4417
|
+
keyIndex.set(`${l.id}|${l.reqId}`, i);
|
|
4418
|
+
});
|
|
4419
|
+
const gate = makeDedupGate();
|
|
4420
|
+
const acc = {
|
|
4421
|
+
lines: [...cached.lines],
|
|
4422
|
+
models,
|
|
4423
|
+
counted: cached.lines.map((l) => gate(l.id, l.reqId, l.sidechain)),
|
|
4424
|
+
keyIndex,
|
|
4425
|
+
gate,
|
|
4426
|
+
internModel: makeInternModel(models),
|
|
4427
|
+
input: cached.record.tokens.input,
|
|
4428
|
+
output: cached.record.tokens.output,
|
|
4429
|
+
cacheRead: cached.record.tokens.cache_read,
|
|
4430
|
+
cacheCreation: cached.record.tokens.cache_creation,
|
|
4431
|
+
total: cached.total,
|
|
4432
|
+
messages: cached.record.messages,
|
|
4433
|
+
start: cached.record.start,
|
|
4434
|
+
end: cached.record.end
|
|
4435
|
+
};
|
|
4436
|
+
for (const l of collapseStreaming(tail.appended))
|
|
4437
|
+
foldAppendedLine(acc, l, price);
|
|
4438
|
+
return {
|
|
4439
|
+
mtime: st.mtimeMs,
|
|
4440
|
+
size: st.size,
|
|
4441
|
+
byteOffset: tail.newByteOffset,
|
|
4442
|
+
headHash: tail.newHeadHash,
|
|
4443
|
+
total: acc.total,
|
|
4444
|
+
lines: acc.lines,
|
|
4445
|
+
models: acc.models,
|
|
4446
|
+
projectPath: cached.projectPath,
|
|
4447
|
+
record: {
|
|
4448
|
+
...cached.record,
|
|
4449
|
+
start: acc.start,
|
|
4450
|
+
end: acc.end,
|
|
4451
|
+
tokens: {
|
|
4452
|
+
input: acc.input,
|
|
4453
|
+
output: acc.output,
|
|
4454
|
+
cache_read: acc.cacheRead,
|
|
4455
|
+
cache_creation: acc.cacheCreation
|
|
4456
|
+
},
|
|
4457
|
+
messages: acc.messages
|
|
4458
|
+
}
|
|
4459
|
+
};
|
|
4460
|
+
}
|
|
4461
|
+
function costLineOf(internModel, l, cost, totalTok) {
|
|
4462
|
+
return {
|
|
4463
|
+
...l.id !== undefined ? { id: l.id } : {},
|
|
4464
|
+
...l.requestId !== undefined ? { reqId: l.requestId } : {},
|
|
4465
|
+
sidechain: l.isSidechain,
|
|
4466
|
+
ts: l.tsMs ?? 0,
|
|
4467
|
+
cost,
|
|
4468
|
+
...l.model !== undefined ? { m: internModel(l.model) } : {},
|
|
4469
|
+
tok: totalTok
|
|
4470
|
+
};
|
|
4471
|
+
}
|
|
4472
|
+
function readAppendedTail(cached, path, st) {
|
|
4473
|
+
const offset = cached.byteOffset;
|
|
4474
|
+
if (offset === undefined || offset === 0 || st.size < offset)
|
|
4475
|
+
return null;
|
|
4476
|
+
const head = readRange(path, 0, headLen(offset));
|
|
4477
|
+
if (head === null || headHashOf(head) !== cached.headHash)
|
|
4478
|
+
return null;
|
|
4479
|
+
const tailBuf = readRange(path, offset, st.size);
|
|
4480
|
+
if (tailBuf === null)
|
|
4481
|
+
return null;
|
|
4482
|
+
const added = lastLineEnd(tailBuf);
|
|
4483
|
+
if (added === 0)
|
|
4484
|
+
return "partial";
|
|
4485
|
+
const newByteOffset = offset + added;
|
|
4486
|
+
const newHead = headLen(newByteOffset) === head.length ? head : readRange(path, 0, headLen(newByteOffset));
|
|
4487
|
+
if (newHead === null)
|
|
4488
|
+
return null;
|
|
4489
|
+
return {
|
|
4490
|
+
appended: parseAll(tailBuf.subarray(0, added).toString("utf8")),
|
|
4491
|
+
newByteOffset,
|
|
4492
|
+
newHeadHash: headHashOf(newHead)
|
|
4493
|
+
};
|
|
4494
|
+
}
|
|
4495
|
+
function widenBounds(acc, tsMs) {
|
|
4496
|
+
if (tsMs === undefined)
|
|
4497
|
+
return;
|
|
4498
|
+
if (tsMs > acc.end)
|
|
4499
|
+
acc.end = tsMs;
|
|
4500
|
+
if (acc.start === 0 || tsMs < acc.start)
|
|
4501
|
+
acc.start = tsMs;
|
|
4502
|
+
}
|
|
4503
|
+
function foldAppendedLine(acc, l, price) {
|
|
4504
|
+
widenBounds(acc, l.tsMs);
|
|
4505
|
+
const usage = l.usage;
|
|
4506
|
+
if (usage === undefined)
|
|
4507
|
+
return;
|
|
4508
|
+
const t = tokenize(usage);
|
|
4509
|
+
const totalTok = t.input + t.output + t.cache_read + t.c5m + t.c1h;
|
|
4510
|
+
const key = l.id !== undefined && l.requestId !== undefined ? `${l.id}|${l.requestId}` : null;
|
|
4511
|
+
const existing = key !== null ? acc.keyIndex.get(key) : undefined;
|
|
4512
|
+
const old = existing !== undefined ? acc.lines[existing] : undefined;
|
|
4513
|
+
if (existing !== undefined && old !== undefined) {
|
|
4514
|
+
if (totalTok <= (old.tok ?? 0))
|
|
4515
|
+
return;
|
|
4516
|
+
const cost2 = price(usage, l.model ?? "", l.tsMs);
|
|
4517
|
+
if (acc.counted[existing] === true) {
|
|
4518
|
+
acc.output += totalTok - (old.tok ?? 0);
|
|
4519
|
+
acc.total += cost2 - old.cost;
|
|
4520
|
+
}
|
|
4521
|
+
acc.lines[existing] = costLineOf(acc.internModel, l, cost2, totalTok);
|
|
4522
|
+
return;
|
|
4523
|
+
}
|
|
4524
|
+
const cost = price(usage, l.model ?? "", l.tsMs);
|
|
4525
|
+
const isCounted = acc.gate(l.id, l.requestId, l.isSidechain);
|
|
4526
|
+
if (key !== null)
|
|
4527
|
+
acc.keyIndex.set(key, acc.lines.length);
|
|
4528
|
+
acc.lines.push(costLineOf(acc.internModel, l, cost, totalTok));
|
|
4529
|
+
acc.counted.push(isCounted);
|
|
4530
|
+
if (isCounted) {
|
|
4531
|
+
acc.input += t.input;
|
|
4532
|
+
acc.output += t.output;
|
|
4533
|
+
acc.cacheRead += t.cache_read;
|
|
4534
|
+
acc.cacheCreation += t.c5m + t.c1h;
|
|
4535
|
+
acc.total += cost;
|
|
4536
|
+
acc.messages += 1;
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4259
4539
|
function buildAggregate(files, chat) {
|
|
4260
4540
|
const sessionProject = {};
|
|
4261
4541
|
const all = [];
|
|
@@ -4290,17 +4570,27 @@ function scanCostTree(root, cache, clock, price, resolveProject) {
|
|
|
4290
4570
|
return cache;
|
|
4291
4571
|
const chat = cache.aggregate.chat;
|
|
4292
4572
|
const files = {};
|
|
4573
|
+
let changed = false;
|
|
4293
4574
|
for (const { path, encodedDir } of walkJsonl(root)) {
|
|
4294
4575
|
const st = statSafe(path);
|
|
4295
4576
|
if (!st)
|
|
4296
4577
|
continue;
|
|
4297
4578
|
const cached = cache.files[path];
|
|
4298
|
-
|
|
4579
|
+
if (cached && cached.mtime === st.mtimeMs && cached.size === st.size) {
|
|
4580
|
+
files[path] = cached;
|
|
4581
|
+
} else {
|
|
4582
|
+
const resumed = cached ? resumeCostFile(cached, path, st, price) : null;
|
|
4583
|
+
files[path] = resumed ?? priceFile(path, encodedDir, st, price, resolveProject);
|
|
4584
|
+
changed = true;
|
|
4585
|
+
}
|
|
4299
4586
|
}
|
|
4587
|
+
const sameFileSet = Object.keys(files).length === Object.keys(cache.files).length;
|
|
4588
|
+
if (!changed && sameFileSet)
|
|
4589
|
+
return { files, aggregate: cache.aggregate, lastScanTs: now };
|
|
4300
4590
|
return { files, aggregate: buildAggregate(files, chat), lastScanTs: now };
|
|
4301
4591
|
}
|
|
4302
4592
|
// src/cli/classify.ts
|
|
4303
|
-
var FAIL_RE =
|
|
4593
|
+
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
4594
|
function asObject(v) {
|
|
4305
4595
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
4306
4596
|
}
|
|
@@ -4316,10 +4606,8 @@ function softFail(toolResponse) {
|
|
|
4316
4606
|
return false;
|
|
4317
4607
|
if (r["isError"] === true || r["interrupted"] === true)
|
|
4318
4608
|
return true;
|
|
4319
|
-
const
|
|
4320
|
-
|
|
4321
|
-
return true;
|
|
4322
|
-
const out = `${asString(r["stdout"]) ?? ""}${stderr ?? ""}`;
|
|
4609
|
+
const out = `${asString(r["stdout"]) ?? ""}
|
|
4610
|
+
${asString(r["stderr"]) ?? ""}`;
|
|
4323
4611
|
return FAIL_RE.test(out);
|
|
4324
4612
|
}
|
|
4325
4613
|
function commandOf(toolInput) {
|
|
@@ -4340,16 +4628,6 @@ function appendClassified(dir, toolName, command, ok, ts) {
|
|
|
4340
4628
|
if (c !== null)
|
|
4341
4629
|
appendEvent(dir, { ts, ...c });
|
|
4342
4630
|
}
|
|
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
4631
|
function runClassify(stdin, env, clock) {
|
|
4354
4632
|
try {
|
|
4355
4633
|
const r = asObject(JSON.parse(stdin));
|
|
@@ -4361,12 +4639,6 @@ function runClassify(stdin, env, clock) {
|
|
|
4361
4639
|
const dir = sessionDir(ccsidekickRoot(env), session);
|
|
4362
4640
|
const ts = clock.now();
|
|
4363
4641
|
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
4642
|
const toolName = asString(r["tool_name"]);
|
|
4371
4643
|
if (toolName === undefined)
|
|
4372
4644
|
return;
|
|
@@ -4379,7 +4651,7 @@ function runClassify(stdin, env, clock) {
|
|
|
4379
4651
|
} catch {}
|
|
4380
4652
|
}
|
|
4381
4653
|
// src/cli/render.ts
|
|
4382
|
-
import { readFileSync as
|
|
4654
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
4383
4655
|
import { dirname as dirname7, join as join14 } from "node:path";
|
|
4384
4656
|
|
|
4385
4657
|
// src/compose/character.ts
|
|
@@ -6096,16 +6368,6 @@ function composeStatusline(ctx) {
|
|
|
6096
6368
|
return { fields, providerBadge };
|
|
6097
6369
|
}
|
|
6098
6370
|
// 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
6371
|
var EFFORT_LOW_LEVEL = "low";
|
|
6110
6372
|
var PROD_CONTEXT_PATTERN = /prod/i;
|
|
6111
6373
|
var SECRET_PATTERNS = [
|
|
@@ -6280,7 +6542,7 @@ var HELPFUL_CATALOG = [
|
|
|
6280
6542
|
category: "quota",
|
|
6281
6543
|
momentary: false,
|
|
6282
6544
|
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,
|
|
6545
|
+
test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_HIGH_PCT && i.quota.block.overPace === true,
|
|
6284
6546
|
render: (i) => `5h limit at ${pct(i.quota.block?.usedPct ?? 0)}, ${bare(i.quota.block?.resetIn)} to reset. Save heavy asks for after.`
|
|
6285
6547
|
},
|
|
6286
6548
|
{
|
|
@@ -6289,7 +6551,7 @@ var HELPFUL_CATALOG = [
|
|
|
6289
6551
|
category: "quota",
|
|
6290
6552
|
momentary: false,
|
|
6291
6553
|
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,
|
|
6554
|
+
test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_HIGH_PCT && i.quota.weekly.overPace === true,
|
|
6293
6555
|
render: (i) => `Weekly quota ${pct(i.quota.weekly?.usedPct ?? 0)} spent, ${bare(i.quota.weekly?.resetIn)} to go. Ration the big requests.`
|
|
6294
6556
|
},
|
|
6295
6557
|
{
|
|
@@ -6298,7 +6560,7 @@ var HELPFUL_CATALOG = [
|
|
|
6298
6560
|
category: "quota",
|
|
6299
6561
|
momentary: false,
|
|
6300
6562
|
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.
|
|
6563
|
+
test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_PROJECT_MIN_PCT && i.quota.block.overPace === true,
|
|
6302
6564
|
render: () => "This burn rate empties the 5h block before reset, cutting you off mid-task."
|
|
6303
6565
|
},
|
|
6304
6566
|
{
|
|
@@ -6307,7 +6569,7 @@ var HELPFUL_CATALOG = [
|
|
|
6307
6569
|
category: "quota",
|
|
6308
6570
|
momentary: false,
|
|
6309
6571
|
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.
|
|
6572
|
+
test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_PROJECT_MIN_PCT && i.quota.weekly.overPace === true,
|
|
6311
6573
|
render: () => "At this pace the weekly quota runs dry before reset, locking you out for days."
|
|
6312
6574
|
},
|
|
6313
6575
|
{
|
|
@@ -6928,16 +7190,33 @@ var PACKS = [
|
|
|
6928
7190
|
"yoda"
|
|
6929
7191
|
];
|
|
6930
7192
|
// src/cli/gc.ts
|
|
6931
|
-
import {
|
|
7193
|
+
import {
|
|
7194
|
+
existsSync as existsSync2,
|
|
7195
|
+
mkdirSync as mkdirSync5,
|
|
7196
|
+
readFileSync as readFileSync10,
|
|
7197
|
+
readdirSync as readdirSync3,
|
|
7198
|
+
rmSync,
|
|
7199
|
+
statSync as statSync5,
|
|
7200
|
+
writeFileSync as writeFileSync3
|
|
7201
|
+
} from "node:fs";
|
|
6932
7202
|
import { join as join13 } from "node:path";
|
|
6933
7203
|
var DAY_MS = 86400000;
|
|
7204
|
+
var GC_MIN_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
7205
|
+
function readGcStamp(path) {
|
|
7206
|
+
try {
|
|
7207
|
+
const n = Number(readFileSync10(path, "utf8"));
|
|
7208
|
+
return Number.isFinite(n) ? n : undefined;
|
|
7209
|
+
} catch {
|
|
7210
|
+
return;
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
6934
7213
|
function newestMtime(dir) {
|
|
6935
7214
|
let newest = 0;
|
|
6936
7215
|
try {
|
|
6937
|
-
newest =
|
|
7216
|
+
newest = statSync5(dir).mtimeMs;
|
|
6938
7217
|
for (const name of readdirSync3(dir)) {
|
|
6939
7218
|
try {
|
|
6940
|
-
const m =
|
|
7219
|
+
const m = statSync5(join13(dir, name)).mtimeMs;
|
|
6941
7220
|
if (m > newest)
|
|
6942
7221
|
newest = m;
|
|
6943
7222
|
} catch {}
|
|
@@ -6957,7 +7236,7 @@ function pruneSessions(root, now) {
|
|
|
6957
7236
|
for (const name of names) {
|
|
6958
7237
|
const dir = join13(sessions, name);
|
|
6959
7238
|
try {
|
|
6960
|
-
if (!
|
|
7239
|
+
if (!statSync5(dir).isDirectory())
|
|
6961
7240
|
continue;
|
|
6962
7241
|
} catch {
|
|
6963
7242
|
continue;
|
|
@@ -6970,23 +7249,48 @@ function pruneSessions(root, now) {
|
|
|
6970
7249
|
function pruneCostCache(root) {
|
|
6971
7250
|
const cache = readCostCache(root);
|
|
6972
7251
|
const kept = {};
|
|
7252
|
+
const liveSessions = new Set;
|
|
6973
7253
|
let dropped = false;
|
|
6974
7254
|
for (const [path, entry] of Object.entries(cache.files)) {
|
|
6975
|
-
if (existsSync2(path))
|
|
7255
|
+
if (existsSync2(path)) {
|
|
6976
7256
|
kept[path] = entry;
|
|
6977
|
-
|
|
7257
|
+
liveSessions.add(String(entry.record.session));
|
|
7258
|
+
} else {
|
|
6978
7259
|
dropped = true;
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
const chatKept = {};
|
|
7263
|
+
let chatDropped = false;
|
|
7264
|
+
for (const [session, cost] of Object.entries(cache.aggregate.chat)) {
|
|
7265
|
+
if (liveSessions.has(session))
|
|
7266
|
+
chatKept[session] = cost;
|
|
7267
|
+
else
|
|
7268
|
+
chatDropped = true;
|
|
7269
|
+
}
|
|
7270
|
+
if (dropped || chatDropped) {
|
|
7271
|
+
writeCostCache(root, {
|
|
7272
|
+
...cache,
|
|
7273
|
+
files: kept,
|
|
7274
|
+
aggregate: { ...cache.aggregate, chat: chatKept }
|
|
7275
|
+
});
|
|
6979
7276
|
}
|
|
6980
|
-
if (dropped)
|
|
6981
|
-
writeCostCache(root, { ...cache, files: kept });
|
|
6982
7277
|
}
|
|
6983
7278
|
function runGc(root, clock) {
|
|
7279
|
+
const now = clock.now();
|
|
7280
|
+
const stampPath = join13(root, "cache", ".gc-stamp");
|
|
7281
|
+
const last = readGcStamp(stampPath);
|
|
7282
|
+
if (last !== undefined && now - last < GC_MIN_INTERVAL_MS)
|
|
7283
|
+
return;
|
|
6984
7284
|
try {
|
|
6985
|
-
pruneSessions(root,
|
|
7285
|
+
pruneSessions(root, now);
|
|
6986
7286
|
} catch {}
|
|
6987
7287
|
try {
|
|
6988
7288
|
pruneCostCache(root);
|
|
6989
7289
|
} catch {}
|
|
7290
|
+
try {
|
|
7291
|
+
mkdirSync5(join13(root, "cache"), { recursive: true });
|
|
7292
|
+
writeFileSync3(stampPath, String(now));
|
|
7293
|
+
} catch {}
|
|
6990
7294
|
}
|
|
6991
7295
|
|
|
6992
7296
|
// src/cli/render.ts
|
|
@@ -6995,7 +7299,7 @@ var TIER_MILESTONES = new Set(TIER_THRESHOLDS);
|
|
|
6995
7299
|
var YEAR_MS = 365 * 86400000;
|
|
6996
7300
|
function readTextSafe(path) {
|
|
6997
7301
|
try {
|
|
6998
|
-
return
|
|
7302
|
+
return readFileSync11(path, "utf8");
|
|
6999
7303
|
} catch {
|
|
7000
7304
|
return "";
|
|
7001
7305
|
}
|
|
@@ -7030,19 +7334,29 @@ function applyThemeIcons(fields, theme, config) {
|
|
|
7030
7334
|
});
|
|
7031
7335
|
}
|
|
7032
7336
|
function readGated(overrides, envInputs, config, root, dir, markers, clock) {
|
|
7033
|
-
const creds = overrides?.creds !== undefined ? overrides.creds : envInputs.hasApiKey || config.network.usage_fetch ?
|
|
7337
|
+
const creds = overrides?.creds !== undefined ? overrides.creds : envInputs.hasApiKey || config.network.usage_fetch ? readCredsCached(root) : null;
|
|
7034
7338
|
const usage = overrides?.usage !== undefined ? overrides.usage : config.network.usage_fetch ? readUsageCached(root) : null;
|
|
7035
7339
|
const balance = overrides?.balance !== undefined ? overrides.balance : config.network.balance_path !== "" ? readBalance(config.network.balance_path, clock) : null;
|
|
7036
7340
|
const helpfulEnv = needsHelpfulEnv(markers) ? readHelpfulEnv(dir) : EMPTY_HELPFUL_ENV;
|
|
7037
7341
|
return { creds, usage, balance, helpfulEnv };
|
|
7038
7342
|
}
|
|
7039
7343
|
var DEFAULT_EMBLEM = "❝";
|
|
7040
|
-
function
|
|
7041
|
-
const loaded = loadPack(name);
|
|
7344
|
+
function packThemeOf(loaded) {
|
|
7042
7345
|
if (!loaded.ok || loaded.pack.theme === undefined)
|
|
7043
7346
|
return null;
|
|
7044
7347
|
return { displayName: loaded.pack.displayName, ...loaded.pack.theme };
|
|
7045
7348
|
}
|
|
7349
|
+
function makePackThemeLookup(persona, loaded, load = loadPack) {
|
|
7350
|
+
const memo = new Map;
|
|
7351
|
+
return (name) => {
|
|
7352
|
+
const cached = memo.get(name);
|
|
7353
|
+
if (cached !== undefined || memo.has(name))
|
|
7354
|
+
return cached ?? null;
|
|
7355
|
+
const theme = packThemeOf(name === persona ? loaded : load(name));
|
|
7356
|
+
memo.set(name, theme);
|
|
7357
|
+
return theme;
|
|
7358
|
+
};
|
|
7359
|
+
}
|
|
7046
7360
|
var NOOP = () => {};
|
|
7047
7361
|
function runRender(stdin, env, term, clock, overrides) {
|
|
7048
7362
|
try {
|
|
@@ -7105,8 +7419,8 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7105
7419
|
const stacks = deriveStacks(markers, events);
|
|
7106
7420
|
const fresh = freshestEvent(events);
|
|
7107
7421
|
const stack = pickStack(stacks, fresh?.stack);
|
|
7108
|
-
const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock);
|
|
7109
|
-
const theme = resolveTheme(config,
|
|
7422
|
+
const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock, String(session));
|
|
7423
|
+
const theme = resolveTheme(config, makePackThemeLookup(persona, loaded), persona);
|
|
7110
7424
|
const composeInputs = {
|
|
7111
7425
|
provider,
|
|
7112
7426
|
model,
|
|
@@ -7207,6 +7521,10 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7207
7521
|
}
|
|
7208
7522
|
swallow(() => {
|
|
7209
7523
|
const payloadChat = payload.cost?.total_cost_usd;
|
|
7524
|
+
const rescanned = scannedCache !== costCache;
|
|
7525
|
+
const chatChanged = !isDefault && payloadChat !== undefined && scannedCache.aggregate.chat[String(session)] !== payloadChat;
|
|
7526
|
+
if (!rescanned && !chatChanged)
|
|
7527
|
+
return;
|
|
7210
7528
|
const toWrite = !isDefault && payloadChat !== undefined ? {
|
|
7211
7529
|
...scannedCache,
|
|
7212
7530
|
aggregate: {
|
|
@@ -7230,6 +7548,10 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7230
7548
|
swallow(() => {
|
|
7231
7549
|
readUsage(root, clock, { enabled: config.network.usage_fetch });
|
|
7232
7550
|
});
|
|
7551
|
+
swallow(() => {
|
|
7552
|
+
if (envInputs.hasApiKey || config.network.usage_fetch)
|
|
7553
|
+
refreshCreds(root, clock);
|
|
7554
|
+
});
|
|
7233
7555
|
swallow(() => {
|
|
7234
7556
|
runGc(root, clock);
|
|
7235
7557
|
});
|
|
@@ -7251,7 +7573,7 @@ function dispatchRender() {
|
|
|
7251
7573
|
noColor: process.env["NO_COLOR"] !== undefined,
|
|
7252
7574
|
isTTY: true
|
|
7253
7575
|
};
|
|
7254
|
-
const { line, persist } = runRender(
|
|
7576
|
+
const { line, persist } = runRender(readFileSync12(0, "utf8"), process.env, term, resolveClock(process.env));
|
|
7255
7577
|
process.stdout.write(`${line}
|
|
7256
7578
|
`);
|
|
7257
7579
|
persist();
|
|
@@ -7259,7 +7581,7 @@ function dispatchRender() {
|
|
|
7259
7581
|
}
|
|
7260
7582
|
function dispatchClassify() {
|
|
7261
7583
|
try {
|
|
7262
|
-
runClassify(
|
|
7584
|
+
runClassify(readFileSync12(0, "utf8"), process.env, systemClock);
|
|
7263
7585
|
} catch {} finally {
|
|
7264
7586
|
process.exit(0);
|
|
7265
7587
|
}
|