ccsidekick 1.6.0 → 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 +428 -110
- package/dist/ccsidekick.js +490 -139
- 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;
|
|
@@ -2443,7 +2485,7 @@ var DEFAULT_CONFIG = {
|
|
|
2443
2485
|
schema_version: 1,
|
|
2444
2486
|
character: { enabled: true, mode: "random", name: "spiderman", roster: [] },
|
|
2445
2487
|
theme: { name: "character", banding: "solid", mood_shift: false, icons: {} },
|
|
2446
|
-
comments: { character: true, helpful: true, min_severity: "
|
|
2488
|
+
comments: { character: true, helpful: true, min_severity: "medium" },
|
|
2447
2489
|
network: { fx_refresh: false, usage_fetch: false, balance_path: "" },
|
|
2448
2490
|
statusline: { currency: defaultCurrency(), widgets: DEFAULT_WIDGETS }
|
|
2449
2491
|
};
|
|
@@ -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);
|
|
@@ -3471,6 +3555,12 @@ function readGit(cwd, run = defaultRunner2(cwd)) {
|
|
|
3471
3555
|
} = parseNumstat(run(["diff", "HEAD", "--numstat"]));
|
|
3472
3556
|
const rollup = countSubmodules(workTreeRoot) > 0 ? readSubmoduleRollup(run, workTreeRoot) : null;
|
|
3473
3557
|
const behindDefault = readBehindDefault(run, status.branch, defaultBranch);
|
|
3558
|
+
const remoteBranchExists = status.branch !== undefined && !status.upstream ? run([
|
|
3559
|
+
"rev-parse",
|
|
3560
|
+
"--verify",
|
|
3561
|
+
"--quiet",
|
|
3562
|
+
`refs/remotes/origin/${status.branch}`
|
|
3563
|
+
]).trim() !== "" : false;
|
|
3474
3564
|
return {
|
|
3475
3565
|
...opt2("branch", status.branch),
|
|
3476
3566
|
...opt2("sha", status.sha),
|
|
@@ -3487,6 +3577,7 @@ function readGit(cwd, run = defaultRunner2(cwd)) {
|
|
|
3487
3577
|
submoduleBranches: rollup?.branches ?? [],
|
|
3488
3578
|
upstream: status.upstream,
|
|
3489
3579
|
upstreamGone: status.upstreamGone,
|
|
3580
|
+
remoteBranchExists,
|
|
3490
3581
|
...opt2("originRepo", originRepo),
|
|
3491
3582
|
...opt2("defaultBranch", defaultBranch),
|
|
3492
3583
|
...opt2("root", workTreeRoot === "" ? undefined : workTreeRoot),
|
|
@@ -3500,7 +3591,7 @@ function readGit(cwd, run = defaultRunner2(cwd)) {
|
|
|
3500
3591
|
}
|
|
3501
3592
|
// src/sources/helpfulEnv.ts
|
|
3502
3593
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
3503
|
-
import { homedir as
|
|
3594
|
+
import { homedir as homedir3 } from "node:os";
|
|
3504
3595
|
import { delimiter, join as join9 } from "node:path";
|
|
3505
3596
|
var opt3 = (key, value) => value !== undefined ? { [key]: value } : {};
|
|
3506
3597
|
function kubeconfigPath() {
|
|
@@ -3510,7 +3601,7 @@ function kubeconfigPath() {
|
|
|
3510
3601
|
if (first !== undefined)
|
|
3511
3602
|
return first;
|
|
3512
3603
|
}
|
|
3513
|
-
return join9(
|
|
3604
|
+
return join9(homedir3(), ".kube", "config");
|
|
3514
3605
|
}
|
|
3515
3606
|
function readKubeContext() {
|
|
3516
3607
|
try {
|
|
@@ -3814,7 +3905,7 @@ function writeState(sessionDir2, s) {
|
|
|
3814
3905
|
}, () => {});
|
|
3815
3906
|
}
|
|
3816
3907
|
// src/sources/transcript.ts
|
|
3817
|
-
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";
|
|
3818
3909
|
import { basename, dirname as dirname6, join as join12 } from "node:path";
|
|
3819
3910
|
var asObj = (v) => v !== null && typeof v === "object" ? v : undefined;
|
|
3820
3911
|
var asStr = (v) => typeof v === "string" ? v : undefined;
|
|
@@ -4040,7 +4131,7 @@ function reconstructTodos(lines) {
|
|
|
4040
4131
|
}
|
|
4041
4132
|
function statSafe(p) {
|
|
4042
4133
|
try {
|
|
4043
|
-
const s =
|
|
4134
|
+
const s = statSync4(p);
|
|
4044
4135
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
4045
4136
|
} catch {
|
|
4046
4137
|
return null;
|
|
@@ -4053,6 +4144,63 @@ function readSafe(p) {
|
|
|
4053
4144
|
return "";
|
|
4054
4145
|
}
|
|
4055
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);
|
|
4056
4204
|
var EMPTY_TOKENS = {
|
|
4057
4205
|
input: 0,
|
|
4058
4206
|
output: 0,
|
|
@@ -4143,7 +4291,7 @@ function* walkTree(dir, encodedDir) {
|
|
|
4143
4291
|
const full = join12(dir, name);
|
|
4144
4292
|
let st;
|
|
4145
4293
|
try {
|
|
4146
|
-
st =
|
|
4294
|
+
st = statSync4(full);
|
|
4147
4295
|
} catch {
|
|
4148
4296
|
continue;
|
|
4149
4297
|
}
|
|
@@ -4182,22 +4330,16 @@ function scanBounds(lines) {
|
|
|
4182
4330
|
return { sessionId, start, end };
|
|
4183
4331
|
}
|
|
4184
4332
|
function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
4185
|
-
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"));
|
|
4186
4337
|
const { sessionId, start, end } = scanBounds(lines);
|
|
4187
4338
|
const priced = collapseStreaming(lines);
|
|
4188
4339
|
const gate = makeDedupGate();
|
|
4189
4340
|
const costLines = [];
|
|
4190
4341
|
const models = [];
|
|
4191
|
-
const
|
|
4192
|
-
const internModel = (model) => {
|
|
4193
|
-
let i = modelIndex.get(model);
|
|
4194
|
-
if (i === undefined) {
|
|
4195
|
-
i = models.length;
|
|
4196
|
-
models.push(model);
|
|
4197
|
-
modelIndex.set(model, i);
|
|
4198
|
-
}
|
|
4199
|
-
return i;
|
|
4200
|
-
};
|
|
4342
|
+
const internModel = makeInternModel(models);
|
|
4201
4343
|
let total = 0;
|
|
4202
4344
|
let input = 0;
|
|
4203
4345
|
let output = 0;
|
|
@@ -4242,6 +4384,8 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
|
4242
4384
|
return {
|
|
4243
4385
|
mtime: st.mtimeMs,
|
|
4244
4386
|
size: st.size,
|
|
4387
|
+
byteOffset,
|
|
4388
|
+
headHash,
|
|
4245
4389
|
total,
|
|
4246
4390
|
lines: costLines,
|
|
4247
4391
|
models,
|
|
@@ -4249,6 +4393,138 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
|
|
|
4249
4393
|
record
|
|
4250
4394
|
};
|
|
4251
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
|
+
}
|
|
4252
4528
|
function buildAggregate(files, chat) {
|
|
4253
4529
|
const sessionProject = {};
|
|
4254
4530
|
const all = [];
|
|
@@ -4283,17 +4559,27 @@ function scanCostTree(root, cache, clock, price, resolveProject) {
|
|
|
4283
4559
|
return cache;
|
|
4284
4560
|
const chat = cache.aggregate.chat;
|
|
4285
4561
|
const files = {};
|
|
4562
|
+
let changed = false;
|
|
4286
4563
|
for (const { path, encodedDir } of walkJsonl(root)) {
|
|
4287
4564
|
const st = statSafe(path);
|
|
4288
4565
|
if (!st)
|
|
4289
4566
|
continue;
|
|
4290
4567
|
const cached = cache.files[path];
|
|
4291
|
-
|
|
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
|
+
}
|
|
4292
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 };
|
|
4293
4579
|
return { files, aggregate: buildAggregate(files, chat), lastScanTs: now };
|
|
4294
4580
|
}
|
|
4295
4581
|
// src/cli/classify.ts
|
|
4296
|
-
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/;
|
|
4297
4583
|
function asObject(v) {
|
|
4298
4584
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
4299
4585
|
}
|
|
@@ -4309,10 +4595,8 @@ function softFail(toolResponse) {
|
|
|
4309
4595
|
return false;
|
|
4310
4596
|
if (r["isError"] === true || r["interrupted"] === true)
|
|
4311
4597
|
return true;
|
|
4312
|
-
const
|
|
4313
|
-
|
|
4314
|
-
return true;
|
|
4315
|
-
const out = `${asString(r["stdout"]) ?? ""}${stderr ?? ""}`;
|
|
4598
|
+
const out = `${asString(r["stdout"]) ?? ""}
|
|
4599
|
+
${asString(r["stderr"]) ?? ""}`;
|
|
4316
4600
|
return FAIL_RE.test(out);
|
|
4317
4601
|
}
|
|
4318
4602
|
function commandOf(toolInput) {
|
|
@@ -4333,16 +4617,6 @@ function appendClassified(dir, toolName, command, ok, ts) {
|
|
|
4333
4617
|
if (c !== null)
|
|
4334
4618
|
appendEvent(dir, { ts, ...c });
|
|
4335
4619
|
}
|
|
4336
|
-
function appendBatch(dir, calls, ts) {
|
|
4337
|
-
for (const call of calls) {
|
|
4338
|
-
const c = asObject(call);
|
|
4339
|
-
const toolName = c !== undefined ? asString(c["tool_name"]) : undefined;
|
|
4340
|
-
if (c === undefined || toolName === undefined)
|
|
4341
|
-
continue;
|
|
4342
|
-
const ok = !softFail(c["tool_response"]);
|
|
4343
|
-
appendClassified(dir, toolName, commandOf(c["tool_input"]), ok, ts);
|
|
4344
|
-
}
|
|
4345
|
-
}
|
|
4346
4620
|
function runClassify(stdin, env, clock) {
|
|
4347
4621
|
try {
|
|
4348
4622
|
const r = asObject(JSON.parse(stdin));
|
|
@@ -4354,12 +4628,6 @@ function runClassify(stdin, env, clock) {
|
|
|
4354
4628
|
const dir = sessionDir(ccsidekickRoot(env), session);
|
|
4355
4629
|
const ts = clock.now();
|
|
4356
4630
|
const hook = asString(r["hook_event_name"]);
|
|
4357
|
-
if (hook === "PostToolBatch") {
|
|
4358
|
-
const calls = r["tool_calls"];
|
|
4359
|
-
if (Array.isArray(calls))
|
|
4360
|
-
appendBatch(dir, calls, ts);
|
|
4361
|
-
return;
|
|
4362
|
-
}
|
|
4363
4631
|
const toolName = asString(r["tool_name"]);
|
|
4364
4632
|
if (toolName === undefined)
|
|
4365
4633
|
return;
|
|
@@ -4372,7 +4640,7 @@ function runClassify(stdin, env, clock) {
|
|
|
4372
4640
|
} catch {}
|
|
4373
4641
|
}
|
|
4374
4642
|
// src/cli/render.ts
|
|
4375
|
-
import { readFileSync as
|
|
4643
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
4376
4644
|
import { dirname as dirname7, join as join14 } from "node:path";
|
|
4377
4645
|
|
|
4378
4646
|
// src/compose/character.ts
|
|
@@ -6089,16 +6357,6 @@ function composeStatusline(ctx) {
|
|
|
6089
6357
|
return { fields, providerBadge };
|
|
6090
6358
|
}
|
|
6091
6359
|
// src/compose/helpful/catalog.ts
|
|
6092
|
-
var COMPACT_SOON_PCT = 60;
|
|
6093
|
-
var QUOTA_PROJECT_MIN_PCT = 50;
|
|
6094
|
-
var COMPACTION_THRASH_N = 3;
|
|
6095
|
-
var CACHE_RATIO_FLOOR = 0.5;
|
|
6096
|
-
var CACHE_WARMUP_TURNS = 20;
|
|
6097
|
-
var BIG_DIFF_LINES = 1000;
|
|
6098
|
-
var UNTRACKED_N = 20;
|
|
6099
|
-
var STASH_N = 5;
|
|
6100
|
-
var STALE_BRANCH_N = 20;
|
|
6101
|
-
var TODO_STALLED_MIN = 30;
|
|
6102
6360
|
var EFFORT_LOW_LEVEL = "low";
|
|
6103
6361
|
var PROD_CONTEXT_PATTERN = /prod/i;
|
|
6104
6362
|
var SECRET_PATTERNS = [
|
|
@@ -6260,7 +6518,7 @@ var HELPFUL_CATALOG = [
|
|
|
6260
6518
|
},
|
|
6261
6519
|
{
|
|
6262
6520
|
id: "pay_as_you_go_active",
|
|
6263
|
-
severity: "
|
|
6521
|
+
severity: "low",
|
|
6264
6522
|
category: "billing",
|
|
6265
6523
|
momentary: false,
|
|
6266
6524
|
template: "Pay-as-you-go is on. Every request now bills on top of your plan.",
|
|
@@ -6273,7 +6531,7 @@ var HELPFUL_CATALOG = [
|
|
|
6273
6531
|
category: "quota",
|
|
6274
6532
|
momentary: false,
|
|
6275
6533
|
template: "5h limit at {pct}%, {reset} to reset. Save heavy asks for after.",
|
|
6276
|
-
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,
|
|
6277
6535
|
render: (i) => `5h limit at ${pct(i.quota.block?.usedPct ?? 0)}, ${bare(i.quota.block?.resetIn)} to reset. Save heavy asks for after.`
|
|
6278
6536
|
},
|
|
6279
6537
|
{
|
|
@@ -6282,7 +6540,7 @@ var HELPFUL_CATALOG = [
|
|
|
6282
6540
|
category: "quota",
|
|
6283
6541
|
momentary: false,
|
|
6284
6542
|
template: "Weekly quota {pct}% spent, {reset} to go. Ration the big requests.",
|
|
6285
|
-
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,
|
|
6286
6544
|
render: (i) => `Weekly quota ${pct(i.quota.weekly?.usedPct ?? 0)} spent, ${bare(i.quota.weekly?.resetIn)} to go. Ration the big requests.`
|
|
6287
6545
|
},
|
|
6288
6546
|
{
|
|
@@ -6291,7 +6549,7 @@ var HELPFUL_CATALOG = [
|
|
|
6291
6549
|
category: "quota",
|
|
6292
6550
|
momentary: false,
|
|
6293
6551
|
template: "This burn rate empties the 5h block before reset, cutting you off mid-task.",
|
|
6294
|
-
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,
|
|
6295
6553
|
render: () => "This burn rate empties the 5h block before reset, cutting you off mid-task."
|
|
6296
6554
|
},
|
|
6297
6555
|
{
|
|
@@ -6300,7 +6558,7 @@ var HELPFUL_CATALOG = [
|
|
|
6300
6558
|
category: "quota",
|
|
6301
6559
|
momentary: false,
|
|
6302
6560
|
template: "At this pace the weekly quota runs dry before reset, locking you out for days.",
|
|
6303
|
-
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,
|
|
6304
6562
|
render: () => "At this pace the weekly quota runs dry before reset, locking you out for days."
|
|
6305
6563
|
},
|
|
6306
6564
|
{
|
|
@@ -6317,9 +6575,9 @@ var HELPFUL_CATALOG = [
|
|
|
6317
6575
|
severity: "high",
|
|
6318
6576
|
category: "context",
|
|
6319
6577
|
momentary: false,
|
|
6320
|
-
template: "{pct}% full with uncommitted work. Commit
|
|
6578
|
+
template: "{pct}% full with uncommitted work. Commit now while the context is fresh.",
|
|
6321
6579
|
test: (i) => i.context.usedPct > COMPACT_SOON_PCT && dirty(i.git),
|
|
6322
|
-
render: (i) => `${pct(i.context.usedPct)} full with uncommitted work. Commit
|
|
6580
|
+
render: (i) => `${pct(i.context.usedPct)} full with uncommitted work. Commit now while the context is fresh.`
|
|
6323
6581
|
},
|
|
6324
6582
|
{
|
|
6325
6583
|
id: "compaction_thrash",
|
|
@@ -6435,7 +6693,7 @@ var HELPFUL_CATALOG = [
|
|
|
6435
6693
|
category: "git",
|
|
6436
6694
|
momentary: false,
|
|
6437
6695
|
template: "No upstream set. `git push -u origin HEAD` to track it.",
|
|
6438
|
-
test: (i) => i.git !== null && i.git.branch !== undefined && !i.git.upstream,
|
|
6696
|
+
test: (i) => i.git !== null && i.git.branch !== undefined && !i.git.upstream && !i.git.remoteBranchExists,
|
|
6439
6697
|
render: () => "No upstream set. `git push -u origin HEAD` to track it."
|
|
6440
6698
|
},
|
|
6441
6699
|
{
|
|
@@ -6512,7 +6770,7 @@ var HELPFUL_CATALOG = [
|
|
|
6512
6770
|
},
|
|
6513
6771
|
{
|
|
6514
6772
|
id: "detached_head",
|
|
6515
|
-
severity: "
|
|
6773
|
+
severity: "medium",
|
|
6516
6774
|
category: "git",
|
|
6517
6775
|
momentary: false,
|
|
6518
6776
|
template: "Detached HEAD. Branch before committing or new commits can vanish.",
|
|
@@ -6530,7 +6788,7 @@ var HELPFUL_CATALOG = [
|
|
|
6530
6788
|
},
|
|
6531
6789
|
{
|
|
6532
6790
|
id: "effort_low",
|
|
6533
|
-
severity: "
|
|
6791
|
+
severity: "medium",
|
|
6534
6792
|
category: "workflow",
|
|
6535
6793
|
momentary: false,
|
|
6536
6794
|
template: "Effort on low. Hard problems get shallow answers.",
|
|
@@ -6921,16 +7179,33 @@ var PACKS = [
|
|
|
6921
7179
|
"yoda"
|
|
6922
7180
|
];
|
|
6923
7181
|
// src/cli/gc.ts
|
|
6924
|
-
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";
|
|
6925
7191
|
import { join as join13 } from "node:path";
|
|
6926
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
|
+
}
|
|
6927
7202
|
function newestMtime(dir) {
|
|
6928
7203
|
let newest = 0;
|
|
6929
7204
|
try {
|
|
6930
|
-
newest =
|
|
7205
|
+
newest = statSync5(dir).mtimeMs;
|
|
6931
7206
|
for (const name of readdirSync3(dir)) {
|
|
6932
7207
|
try {
|
|
6933
|
-
const m =
|
|
7208
|
+
const m = statSync5(join13(dir, name)).mtimeMs;
|
|
6934
7209
|
if (m > newest)
|
|
6935
7210
|
newest = m;
|
|
6936
7211
|
} catch {}
|
|
@@ -6950,7 +7225,7 @@ function pruneSessions(root, now) {
|
|
|
6950
7225
|
for (const name of names) {
|
|
6951
7226
|
const dir = join13(sessions, name);
|
|
6952
7227
|
try {
|
|
6953
|
-
if (!
|
|
7228
|
+
if (!statSync5(dir).isDirectory())
|
|
6954
7229
|
continue;
|
|
6955
7230
|
} catch {
|
|
6956
7231
|
continue;
|
|
@@ -6963,23 +7238,48 @@ function pruneSessions(root, now) {
|
|
|
6963
7238
|
function pruneCostCache(root) {
|
|
6964
7239
|
const cache = readCostCache(root);
|
|
6965
7240
|
const kept = {};
|
|
7241
|
+
const liveSessions = new Set;
|
|
6966
7242
|
let dropped = false;
|
|
6967
7243
|
for (const [path, entry] of Object.entries(cache.files)) {
|
|
6968
|
-
if (existsSync2(path))
|
|
7244
|
+
if (existsSync2(path)) {
|
|
6969
7245
|
kept[path] = entry;
|
|
6970
|
-
|
|
7246
|
+
liveSessions.add(String(entry.record.session));
|
|
7247
|
+
} else {
|
|
6971
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
|
+
});
|
|
6972
7265
|
}
|
|
6973
|
-
if (dropped)
|
|
6974
|
-
writeCostCache(root, { ...cache, files: kept });
|
|
6975
7266
|
}
|
|
6976
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;
|
|
6977
7273
|
try {
|
|
6978
|
-
pruneSessions(root,
|
|
7274
|
+
pruneSessions(root, now);
|
|
6979
7275
|
} catch {}
|
|
6980
7276
|
try {
|
|
6981
7277
|
pruneCostCache(root);
|
|
6982
7278
|
} catch {}
|
|
7279
|
+
try {
|
|
7280
|
+
mkdirSync5(join13(root, "cache"), { recursive: true });
|
|
7281
|
+
writeFileSync3(stampPath, String(now));
|
|
7282
|
+
} catch {}
|
|
6983
7283
|
}
|
|
6984
7284
|
|
|
6985
7285
|
// src/cli/render.ts
|
|
@@ -6988,7 +7288,7 @@ var TIER_MILESTONES = new Set(TIER_THRESHOLDS);
|
|
|
6988
7288
|
var YEAR_MS = 365 * 86400000;
|
|
6989
7289
|
function readTextSafe(path) {
|
|
6990
7290
|
try {
|
|
6991
|
-
return
|
|
7291
|
+
return readFileSync11(path, "utf8");
|
|
6992
7292
|
} catch {
|
|
6993
7293
|
return "";
|
|
6994
7294
|
}
|
|
@@ -7023,19 +7323,29 @@ function applyThemeIcons(fields, theme, config) {
|
|
|
7023
7323
|
});
|
|
7024
7324
|
}
|
|
7025
7325
|
function readGated(overrides, envInputs, config, root, dir, markers, clock) {
|
|
7026
|
-
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;
|
|
7027
7327
|
const usage = overrides?.usage !== undefined ? overrides.usage : config.network.usage_fetch ? readUsageCached(root) : null;
|
|
7028
7328
|
const balance = overrides?.balance !== undefined ? overrides.balance : config.network.balance_path !== "" ? readBalance(config.network.balance_path, clock) : null;
|
|
7029
7329
|
const helpfulEnv = needsHelpfulEnv(markers) ? readHelpfulEnv(dir) : EMPTY_HELPFUL_ENV;
|
|
7030
7330
|
return { creds, usage, balance, helpfulEnv };
|
|
7031
7331
|
}
|
|
7032
7332
|
var DEFAULT_EMBLEM = "❝";
|
|
7033
|
-
function
|
|
7034
|
-
const loaded = loadPack(name);
|
|
7333
|
+
function packThemeOf(loaded) {
|
|
7035
7334
|
if (!loaded.ok || loaded.pack.theme === undefined)
|
|
7036
7335
|
return null;
|
|
7037
7336
|
return { displayName: loaded.pack.displayName, ...loaded.pack.theme };
|
|
7038
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
|
+
}
|
|
7039
7349
|
var NOOP = () => {};
|
|
7040
7350
|
function runRender(stdin, env, term, clock, overrides) {
|
|
7041
7351
|
try {
|
|
@@ -7098,8 +7408,8 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7098
7408
|
const stacks = deriveStacks(markers, events);
|
|
7099
7409
|
const fresh = freshestEvent(events);
|
|
7100
7410
|
const stack = pickStack(stacks, fresh?.stack);
|
|
7101
|
-
const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock);
|
|
7102
|
-
const theme = resolveTheme(config,
|
|
7411
|
+
const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock, String(session));
|
|
7412
|
+
const theme = resolveTheme(config, makePackThemeLookup(persona, loaded), persona);
|
|
7103
7413
|
const composeInputs = {
|
|
7104
7414
|
provider,
|
|
7105
7415
|
model,
|
|
@@ -7200,6 +7510,10 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7200
7510
|
}
|
|
7201
7511
|
swallow(() => {
|
|
7202
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;
|
|
7203
7517
|
const toWrite = !isDefault && payloadChat !== undefined ? {
|
|
7204
7518
|
...scannedCache,
|
|
7205
7519
|
aggregate: {
|
|
@@ -7223,6 +7537,10 @@ function build(stdin, env, term, clock, overrides) {
|
|
|
7223
7537
|
swallow(() => {
|
|
7224
7538
|
readUsage(root, clock, { enabled: config.network.usage_fetch });
|
|
7225
7539
|
});
|
|
7540
|
+
swallow(() => {
|
|
7541
|
+
if (envInputs.hasApiKey || config.network.usage_fetch)
|
|
7542
|
+
refreshCreds(root, clock);
|
|
7543
|
+
});
|
|
7226
7544
|
swallow(() => {
|
|
7227
7545
|
runGc(root, clock);
|
|
7228
7546
|
});
|
|
@@ -7244,7 +7562,7 @@ function dispatchRender() {
|
|
|
7244
7562
|
noColor: process.env["NO_COLOR"] !== undefined,
|
|
7245
7563
|
isTTY: true
|
|
7246
7564
|
};
|
|
7247
|
-
const { line, persist } = runRender(
|
|
7565
|
+
const { line, persist } = runRender(readFileSync12(0, "utf8"), process.env, term, systemClock);
|
|
7248
7566
|
process.stdout.write(`${line}
|
|
7249
7567
|
`);
|
|
7250
7568
|
persist();
|
|
@@ -7252,7 +7570,7 @@ function dispatchRender() {
|
|
|
7252
7570
|
}
|
|
7253
7571
|
function dispatchClassify() {
|
|
7254
7572
|
try {
|
|
7255
|
-
runClassify(
|
|
7573
|
+
runClassify(readFileSync12(0, "utf8"), process.env, systemClock);
|
|
7256
7574
|
} catch {} finally {
|
|
7257
7575
|
process.exit(0);
|
|
7258
7576
|
}
|