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.
@@ -48,7 +48,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
49
 
50
50
  // src/domain/constants.ts
51
- var COST_TTL_MS = 1500, REFRESH_INTERVAL_SEC = 1, USAGE_TTL_MS = 300000, BALANCE_FRESHNESS_MS = 300000, FX_TTL_MS = 604800000, MIN_RIGHT_WIDTH = 53, FIGURE_COLS = 25, FIGURE_ROWS = 9, GAP = 2, SESSION_TTL_DAYS = 30, HOT_MS = 15000, MOOD_WINDOW_MS = 300000, IDLE_ROTATE_MS = 900000, MOOD_FAIL_N = 3, EGG_EVERY_N = 12, COMEBACK_GAP_DAYS = 3, STREAK_MILESTONES, SESSION_MILESTONES, STREAK_GRACE_DAYS = 1, RECENT_WINDOW_DAYS = 30, ANALYTICS_TTL_MS = 1500, CHAR_LINE_MAX = 66, HELPFUL_SHOW_MS = 300000, HELPFUL_COOLDOWN_MS = 600000, BURN_WINDOW_MS = 18000000, COMPACT_URGENT_PCT = 90, QUOTA_HIGH_PCT = 80, PAY_AS_YOU_GO_CAUTION_PCT = 60, PAY_AS_YOU_GO_NEAR_PCT = 80, BALANCE_LOW = 25, SHIMMER_PERIOD_MS = 24000, THEME_CHROMA_MIN = 40, THEME_MAXCH_MIN = 95, SIGNAL_HUE_RANGES;
51
+ var COST_TTL_MS = 5000, REFRESH_INTERVAL_SEC = 1, USAGE_TTL_MS = 300000, CREDS_TTL_MS = 3600000, BALANCE_FRESHNESS_MS = 300000, FX_TTL_MS = 604800000, MIN_RIGHT_WIDTH = 53, FIGURE_COLS = 25, FIGURE_ROWS = 9, GAP = 2, SESSION_TTL_DAYS = 30, HOT_MS = 15000, MOOD_WINDOW_MS = 300000, IDLE_ROTATE_MS = 900000, MOOD_FAIL_N = 3, EGG_EVERY_N = 12, COMEBACK_GAP_DAYS = 3, STREAK_MILESTONES, SESSION_MILESTONES, STREAK_GRACE_DAYS = 1, RECENT_WINDOW_DAYS = 30, ANALYTICS_TTL_MS = 1500, CHAR_LINE_MAX = 66, HELPFUL_SHOW_MS = 300000, HELPFUL_COOLDOWN_MS = 600000, BURN_WINDOW_MS = 18000000, COMPACT_URGENT_PCT = 90, QUOTA_HIGH_PCT = 80, QUOTA_PACE_MIN_PCT = 20, QUOTA_PROJECT_MIN_PCT = 50, PAY_AS_YOU_GO_CAUTION_PCT = 60, PAY_AS_YOU_GO_NEAR_PCT = 80, BALANCE_LOW = 25, SHIMMER_PERIOD_MS = 24000, THEME_CHROMA_MIN = 40, THEME_MAXCH_MIN = 95, SIGNAL_HUE_RANGES, COMPACT_SOON_PCT = 60, COMPACTION_THRASH_N = 3, CACHE_RATIO_FLOOR = 0.5, CACHE_WARMUP_TURNS = 20, BIG_DIFF_LINES = 1000, UNTRACKED_N = 20, STASH_N = 5, STALE_BRANCH_N = 20, TODO_STALLED_MIN = 30, TIER_THRESHOLDS, DAILY_WINDOW_DAYS = 60, PRICING_TIER_THRESHOLD = 200000;
52
52
  var init_constants = __esm(() => {
53
53
  STREAK_MILESTONES = [3, 7, 30, 100];
54
54
  SESSION_MILESTONES = [10, 50, 100, 250, 500];
@@ -57,10 +57,11 @@ var init_constants = __esm(() => {
57
57
  caution: { min: 20, max: 55 },
58
58
  critical: { wrapMax: 15, wrapMin: 345 }
59
59
  };
60
+ TIER_THRESHOLDS = [3, 15, 50, 100];
60
61
  });
61
62
 
62
63
  // src/domain/enums.ts
63
- var MOODS, PRESSURE_MOODS, TIERS, STACKS, EVENT_CATEGORIES, REACTION_CATEGORIES, isStack = (s) => STACKS.includes(s), isEventCategory = (s) => EVENT_CATEGORIES.includes(s);
64
+ var MOODS, PRESSURE_MOODS, TIERS, STACKS, EVENT_CATEGORIES, REACTION_CATEGORIES, MIN_SEVERITIES, CHARACTER_MODES, BANDINGS, isStack = (s) => STACKS.includes(s), isEventCategory = (s) => EVENT_CATEGORIES.includes(s);
64
65
  var init_enums = __esm(() => {
65
66
  MOODS = ["idle", "busy", "happy", "struggling", "recovery"];
66
67
  PRESSURE_MOODS = ["compact_hint", "block_limit", "weekly_limit"];
@@ -147,6 +148,9 @@ var init_enums = __esm(() => {
147
148
  "db_migrate",
148
149
  "dangerous"
149
150
  ];
151
+ MIN_SEVERITIES = ["low", "medium", "high", "critical"];
152
+ CHARACTER_MODES = ["fixed", "random"];
153
+ BANDINGS = ["solid", "cycle"];
150
154
  });
151
155
 
152
156
  // src/domain/ids.ts
@@ -2012,7 +2016,7 @@ function hot(i, category) {
2012
2016
  function cut(s, max = 24) {
2013
2017
  return s.length <= max ? s : `${s.slice(0, Math.max(0, max - 1))}…`;
2014
2018
  }
2015
- var COMPACT_SOON_PCT = 60, QUOTA_PROJECT_MIN_PCT = 50, COMPACTION_THRASH_N = 3, CACHE_RATIO_FLOOR = 0.5, CACHE_WARMUP_TURNS = 20, BIG_DIFF_LINES = 1000, UNTRACKED_N = 20, STASH_N = 5, STALE_BRANCH_N = 20, TODO_STALLED_MIN = 30, EFFORT_LOW_LEVEL = "low", PROD_CONTEXT_PATTERN, SECRET_PATTERNS, SECRET_SAFE, SECRET_RE, SAFE_RE, detached = (g) => g !== null && g.branch === undefined, dirty = (g) => g !== null && g.staged + g.unstaged > 0, onDefault = (g) => g !== null && g.branch !== undefined && g.defaultBranch !== undefined && g.branch === g.defaultBranch, ahead = (g) => g?.ahead ?? 0, behind = (g) => g?.behind ?? 0, bare = (left) => (left ?? "").replace(/ left$/, ""), branchName = (g) => cut(g?.branch ?? ""), HELPFUL_CATALOG;
2019
+ var EFFORT_LOW_LEVEL = "low", PROD_CONTEXT_PATTERN, SECRET_PATTERNS, SECRET_SAFE, SECRET_RE, SAFE_RE, detached = (g) => g !== null && g.branch === undefined, dirty = (g) => g !== null && g.staged + g.unstaged > 0, onDefault = (g) => g !== null && g.branch !== undefined && g.defaultBranch !== undefined && g.branch === g.defaultBranch, ahead = (g) => g?.ahead ?? 0, behind = (g) => g?.behind ?? 0, bare = (left) => (left ?? "").replace(/ left$/, ""), branchName = (g) => cut(g?.branch ?? ""), HELPFUL_CATALOG;
2016
2020
  var init_catalog = __esm(() => {
2017
2021
  init_domain();
2018
2022
  init_format();
@@ -2149,7 +2153,7 @@ var init_catalog = __esm(() => {
2149
2153
  },
2150
2154
  {
2151
2155
  id: "pay_as_you_go_active",
2152
- severity: "medium",
2156
+ severity: "low",
2153
2157
  category: "billing",
2154
2158
  momentary: false,
2155
2159
  template: "Pay-as-you-go is on. Every request now bills on top of your plan.",
@@ -2162,7 +2166,7 @@ var init_catalog = __esm(() => {
2162
2166
  category: "quota",
2163
2167
  momentary: false,
2164
2168
  template: "5h limit at {pct}%, {reset} to reset. Save heavy asks for after.",
2165
- test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_HIGH_PCT,
2169
+ test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_HIGH_PCT && i.quota.block.overPace === true,
2166
2170
  render: (i) => `5h limit at ${pct(i.quota.block?.usedPct ?? 0)}, ${bare(i.quota.block?.resetIn)} to reset. Save heavy asks for after.`
2167
2171
  },
2168
2172
  {
@@ -2171,7 +2175,7 @@ var init_catalog = __esm(() => {
2171
2175
  category: "quota",
2172
2176
  momentary: false,
2173
2177
  template: "Weekly quota {pct}% spent, {reset} to go. Ration the big requests.",
2174
- test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_HIGH_PCT,
2178
+ test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_HIGH_PCT && i.quota.weekly.overPace === true,
2175
2179
  render: (i) => `Weekly quota ${pct(i.quota.weekly?.usedPct ?? 0)} spent, ${bare(i.quota.weekly?.resetIn)} to go. Ration the big requests.`
2176
2180
  },
2177
2181
  {
@@ -2180,7 +2184,7 @@ var init_catalog = __esm(() => {
2180
2184
  category: "quota",
2181
2185
  momentary: false,
2182
2186
  template: "This burn rate empties the 5h block before reset, cutting you off mid-task.",
2183
- test: (i) => i.quota.block !== undefined && i.quota.block.band !== "nominal" && i.quota.block.usedPct > QUOTA_PROJECT_MIN_PCT,
2187
+ test: (i) => i.quota.block !== undefined && i.quota.block.usedPct > QUOTA_PROJECT_MIN_PCT && i.quota.block.overPace === true,
2184
2188
  render: () => "This burn rate empties the 5h block before reset, cutting you off mid-task."
2185
2189
  },
2186
2190
  {
@@ -2189,7 +2193,7 @@ var init_catalog = __esm(() => {
2189
2193
  category: "quota",
2190
2194
  momentary: false,
2191
2195
  template: "At this pace the weekly quota runs dry before reset, locking you out for days.",
2192
- test: (i) => i.quota.weekly !== undefined && i.quota.weekly.band !== "nominal" && i.quota.weekly.usedPct > QUOTA_PROJECT_MIN_PCT,
2196
+ test: (i) => i.quota.weekly !== undefined && i.quota.weekly.usedPct > QUOTA_PROJECT_MIN_PCT && i.quota.weekly.overPace === true,
2193
2197
  render: () => "At this pace the weekly quota runs dry before reset, locking you out for days."
2194
2198
  },
2195
2199
  {
@@ -2206,9 +2210,9 @@ var init_catalog = __esm(() => {
2206
2210
  severity: "high",
2207
2211
  category: "context",
2208
2212
  momentary: false,
2209
- template: "{pct}% full with uncommitted work. Commit before a compact buries it.",
2213
+ template: "{pct}% full with uncommitted work. Commit now while the context is fresh.",
2210
2214
  test: (i) => i.context.usedPct > COMPACT_SOON_PCT && dirty(i.git),
2211
- render: (i) => `${pct(i.context.usedPct)} full with uncommitted work. Commit before a compact buries it.`
2215
+ render: (i) => `${pct(i.context.usedPct)} full with uncommitted work. Commit now while the context is fresh.`
2212
2216
  },
2213
2217
  {
2214
2218
  id: "compaction_thrash",
@@ -2324,7 +2328,7 @@ var init_catalog = __esm(() => {
2324
2328
  category: "git",
2325
2329
  momentary: false,
2326
2330
  template: "No upstream set. `git push -u origin HEAD` to track it.",
2327
- test: (i) => i.git !== null && i.git.branch !== undefined && !i.git.upstream,
2331
+ test: (i) => i.git !== null && i.git.branch !== undefined && !i.git.upstream && !i.git.remoteBranchExists,
2328
2332
  render: () => "No upstream set. `git push -u origin HEAD` to track it."
2329
2333
  },
2330
2334
  {
@@ -2401,7 +2405,7 @@ var init_catalog = __esm(() => {
2401
2405
  },
2402
2406
  {
2403
2407
  id: "detached_head",
2404
- severity: "low",
2408
+ severity: "medium",
2405
2409
  category: "git",
2406
2410
  momentary: false,
2407
2411
  template: "Detached HEAD. Branch before committing or new commits can vanish.",
@@ -2419,7 +2423,7 @@ var init_catalog = __esm(() => {
2419
2423
  },
2420
2424
  {
2421
2425
  id: "effort_low",
2422
- severity: "low",
2426
+ severity: "medium",
2423
2427
  category: "workflow",
2424
2428
  momentary: false,
2425
2429
  template: "Effort on low. Hard problems get shallow answers.",
@@ -2810,9 +2814,9 @@ function modelKeyOf(modelId, aliases = NO_ALIASES) {
2810
2814
  return resolveKey(modelId, aliases) ?? modelId;
2811
2815
  }
2812
2816
  function tiered(tokens, base, above) {
2813
- if (tokens <= TIER_THRESHOLD || above === undefined)
2817
+ if (tokens <= PRICING_TIER_THRESHOLD || above === undefined)
2814
2818
  return tokens * base;
2815
- return TIER_THRESHOLD * base + (tokens - TIER_THRESHOLD) * above;
2819
+ return PRICING_TIER_THRESHOLD * base + (tokens - PRICING_TIER_THRESHOLD) * above;
2816
2820
  }
2817
2821
  function priceMessage(usage, modelId, aliases = NO_ALIASES, atMs) {
2818
2822
  const row = resolvePrice(modelId, aliases, atMs);
@@ -2829,9 +2833,10 @@ function priceMessage(usage, modelId, aliases = NO_ALIASES, atMs) {
2829
2833
  const speedMult = usage.speed === "fast" ? row.fast_mult : 1;
2830
2834
  return cost * speedMult;
2831
2835
  }
2832
- var PER_MILLION = 1e6, TIER_THRESHOLD = 200000, RAW, TABLE, NO_ALIASES, isAlnum = (c) => c !== "" && /[a-z0-9]/i.test(c);
2836
+ var PER_MILLION = 1e6, RAW, TABLE, NO_ALIASES, isAlnum = (c) => c !== "" && /[a-z0-9]/i.test(c);
2833
2837
  var init_pricing2 = __esm(() => {
2834
2838
  init_pricing();
2839
+ init_domain();
2835
2840
  RAW = pricing_default;
2836
2841
  TABLE = (() => {
2837
2842
  const byKey = new Map;
@@ -2914,6 +2919,7 @@ function joinRecords(attribution, cache, tz) {
2914
2919
  for (const [session, g] of groupBySession(cache)) {
2915
2920
  const attr = attribution[session];
2916
2921
  out.push({
2922
+ session,
2917
2923
  character: attr?.character ?? "unknown",
2918
2924
  project: attr?.project ?? g.projectRaw,
2919
2925
  start: g.start,
@@ -2949,7 +2955,7 @@ function currentStreak(activeDays, today) {
2949
2955
  }
2950
2956
  return streak2;
2951
2957
  }
2952
- function deriveFamiliarity(attribution, character, cache, project, clock) {
2958
+ function deriveFamiliarity(attribution, character, cache, project, clock, currentSession = "") {
2953
2959
  const tz = clock.timezone();
2954
2960
  const records = joinRecords(attribution, cache, tz);
2955
2961
  const seenProject = records.some((r) => r.project === String(project));
@@ -2961,10 +2967,12 @@ function deriveFamiliarity(attribution, character, cache, project, clock) {
2961
2967
  let lastEnd = Number.NEGATIVE_INFINITY;
2962
2968
  let workingSinceMs = Number.POSITIVE_INFINITY;
2963
2969
  for (const r of mine) {
2964
- if (r.end > lastEnd)
2965
- lastEnd = r.end;
2966
2970
  if (r.start < workingSinceMs)
2967
2971
  workingSinceMs = r.start;
2972
+ if (r.session === currentSession)
2973
+ continue;
2974
+ if (r.end > lastEnd)
2975
+ lastEnd = r.end;
2968
2976
  }
2969
2977
  const daysSinceLastSession = Number.isFinite(lastEnd) ? Math.floor((clock.now() - lastEnd) / MS_PER_DAY) : Number.POSITIVE_INFINITY;
2970
2978
  return {
@@ -3003,12 +3011,20 @@ function hourOfDay(ms, tz) {
3003
3011
  }
3004
3012
  function joinFull(attribution, cache, tz, normalizeProject) {
3005
3013
  const tokenPriced = cache.aggregate.tokenPriced;
3014
+ const groups = [...groupBySession(cache)];
3015
+ const rootToName = new Map;
3016
+ for (const [session, g] of groups) {
3017
+ const attr = attribution[session];
3018
+ if (attr !== undefined)
3019
+ rootToName.set(normalizeProject(g.projectRaw), attr.project);
3020
+ }
3006
3021
  const out = [];
3007
- for (const [session, g] of groupBySession(cache)) {
3022
+ for (const [session, g] of groups) {
3008
3023
  const attr = attribution[session];
3024
+ const root = normalizeProject(g.projectRaw);
3009
3025
  out.push({
3010
3026
  session,
3011
- project: normalizeProject(attr?.project ?? g.projectRaw),
3027
+ project: attr?.project ?? rootToName.get(root) ?? root,
3012
3028
  character: attr?.character ?? "unknown",
3013
3029
  start: g.start,
3014
3030
  end: g.end,
@@ -3267,11 +3283,10 @@ function buildModels(byModel) {
3267
3283
  share: totalCost > 0 ? v.cost / totalCost : 0
3268
3284
  })).sort((a, b) => b.costUsd - a.costUsd);
3269
3285
  }
3270
- var MS_PER_DAY = 86400000, TIER_THRESHOLDS, DAILY_WINDOW_DAYS = 60;
3286
+ var MS_PER_DAY = 86400000;
3271
3287
  var init_analytics = __esm(() => {
3272
3288
  init_domain();
3273
3289
  init_pricing2();
3274
- TIER_THRESHOLDS = [3, 15, 50, 100];
3275
3290
  });
3276
3291
 
3277
3292
  // src/derived/signals.ts
@@ -3283,9 +3298,9 @@ function contextBand(pct2) {
3283
3298
  return "critical";
3284
3299
  }
3285
3300
  function quotaBand(usedPct, resetsAtMs, windowMs, nowMs) {
3286
- if (usedPct >= QUOTA_CRITICAL_PCT)
3301
+ if (usedPct >= QUOTA_HIGH_PCT)
3287
3302
  return "critical";
3288
- if (resetsAtMs === undefined)
3303
+ if (resetsAtMs === undefined || usedPct < QUOTA_PACE_MIN_PCT)
3289
3304
  return contextBand(usedPct);
3290
3305
  const usedFraction = usedPct / 100;
3291
3306
  const elapsedFraction = Math.max((nowMs - (resetsAtMs - windowMs)) / windowMs, 0.01);
@@ -3296,7 +3311,16 @@ function quotaBand(usedPct, resetsAtMs, windowMs, nowMs) {
3296
3311
  return "caution";
3297
3312
  return "critical";
3298
3313
  }
3299
- var QUOTA_CRITICAL_PCT = 80;
3314
+ function overQuotaPace(usedPct, resetsAtMs, windowMs, nowMs) {
3315
+ if (resetsAtMs === undefined)
3316
+ return false;
3317
+ const usedFraction = usedPct / 100;
3318
+ const elapsedFraction = Math.max((nowMs - (resetsAtMs - windowMs)) / windowMs, 0.01);
3319
+ return usedFraction / elapsedFraction > 1.5;
3320
+ }
3321
+ var init_signals = __esm(() => {
3322
+ init_domain();
3323
+ });
3300
3324
 
3301
3325
  // src/derived/context.ts
3302
3326
  function deriveContext(payload, scan) {
@@ -3320,6 +3344,7 @@ function deriveContext(payload, scan) {
3320
3344
  }
3321
3345
  var init_context = __esm(() => {
3322
3346
  init_domain();
3347
+ init_signals();
3323
3348
  });
3324
3349
 
3325
3350
  // src/derived/cost.ts
@@ -3604,12 +3629,13 @@ function buildWindow(payloadQuota, oauthQuota, windowMs, clock) {
3604
3629
  return;
3605
3630
  }
3606
3631
  const band2 = quotaBand(usedPct, resetsAtMs, windowMs, clock.now());
3632
+ const overPace = overQuotaPace(usedPct, resetsAtMs, windowMs, clock.now());
3607
3633
  if (resetsAtMs === undefined)
3608
- return { usedPct, band: band2 };
3634
+ return { usedPct, band: band2, overPace };
3609
3635
  const left = resetsAtMs - clock.now();
3610
3636
  if (left <= 0)
3611
- return { usedPct, band: band2 };
3612
- return { usedPct, band: band2, resetIn: fmtLeft(left) };
3637
+ return { usedPct, band: band2, overPace };
3638
+ return { usedPct, band: band2, overPace, resetIn: fmtLeft(left) };
3613
3639
  }
3614
3640
  function buildPayg(usage) {
3615
3641
  const extra = usage?.extra_usage;
@@ -3649,6 +3675,7 @@ var FIVE_HOUR_MS, SEVEN_DAY_MS;
3649
3675
  var init_quota = __esm(() => {
3650
3676
  init_domain();
3651
3677
  init_format();
3678
+ init_signals();
3652
3679
  FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
3653
3680
  SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000;
3654
3681
  });
@@ -4006,7 +4033,7 @@ function readJson(path, fallback) {
4006
4033
  var init_readJson = () => {};
4007
4034
 
4008
4035
  // src/sources/storage/withLock.ts
4009
- import { closeSync, mkdirSync as mkdirSync2, openSync, statSync, unlinkSync } from "node:fs";
4036
+ import { closeSync, mkdirSync as mkdirSync2, openSync, renameSync as renameSync2, statSync, unlinkSync } from "node:fs";
4010
4037
  import { dirname as dirname2 } from "node:path";
4011
4038
  function withLock(lockPath, fn, readOnly) {
4012
4039
  let fd;
@@ -4020,8 +4047,12 @@ function withLock(lockPath, fn, readOnly) {
4020
4047
  } catch {
4021
4048
  try {
4022
4049
  if (Date.now() - statSync(lockPath).mtimeMs > STALE_MS) {
4023
- unlinkSync(lockPath);
4050
+ const aside = `${lockPath}.stale`;
4051
+ renameSync2(lockPath, aside);
4024
4052
  fd = openSync(lockPath, "wx");
4053
+ try {
4054
+ unlinkSync(aside);
4055
+ } catch {}
4025
4056
  }
4026
4057
  } catch {}
4027
4058
  }
@@ -4109,6 +4140,10 @@ function upsertAttribution(root, sessionId, rec) {
4109
4140
  const path = storePath(root);
4110
4141
  withLock(`${path}.lock`, () => {
4111
4142
  const store = coerceStore(readJson(path, undefined));
4143
+ const existing = store[sessionId];
4144
+ if (existing !== undefined && existing.project === rec.project && existing.character === rec.character) {
4145
+ return;
4146
+ }
4112
4147
  store[sessionId] = rec.updatedMs !== undefined ? { project: rec.project, character: rec.character, updatedMs: rec.updatedMs } : { project: rec.project, character: rec.character };
4113
4148
  atomicWrite(path, JSON.stringify(store));
4114
4149
  }, () => {});
@@ -4119,9 +4154,17 @@ var init_analyticsStore = __esm(() => {
4119
4154
  });
4120
4155
 
4121
4156
  // src/sources/balance.ts
4157
+ import { statSync as statSync3 } from "node:fs";
4122
4158
  function readBalance(path, clock) {
4123
4159
  if (path === "")
4124
4160
  return null;
4161
+ try {
4162
+ const st = statSync3(path);
4163
+ if (!st.isFile() || st.size > BALANCE_MAX_BYTES)
4164
+ return null;
4165
+ } catch {
4166
+ return null;
4167
+ }
4125
4168
  const raw = readJson(path, undefined);
4126
4169
  if (typeof raw !== "object" || raw === null)
4127
4170
  return null;
@@ -4139,9 +4182,11 @@ function readBalance(path, clock) {
4139
4182
  return null;
4140
4183
  return { amount, currency, ts };
4141
4184
  }
4185
+ var BALANCE_MAX_BYTES;
4142
4186
  var init_balance = __esm(() => {
4143
4187
  init_domain();
4144
4188
  init_storage();
4189
+ BALANCE_MAX_BYTES = 64 * 1024;
4145
4190
  });
4146
4191
 
4147
4192
  // src/sources/clock.ts
@@ -5400,10 +5445,10 @@ function loadConfig(globalToml = "", projectToml = "") {
5400
5445
  const statusline = section(g, p, "statusline");
5401
5446
  const d = DEFAULT_CONFIG;
5402
5447
  return {
5403
- schema_version: numv(p["schema_version"] ?? g["schema_version"], d.schema_version),
5448
+ schema_version: numv(p["schema_version"], numv(g["schema_version"], d.schema_version)),
5404
5449
  character: {
5405
5450
  enabled: bool(character["enabled"], d.character.enabled),
5406
- mode: oneOf(character["mode"], ["fixed", "random"], d.character.mode),
5451
+ mode: oneOf(character["mode"], CHARACTER_MODES, d.character.mode),
5407
5452
  name: str(character["name"], d.character.name),
5408
5453
  roster: stringArray(character["roster"], d.character.roster)
5409
5454
  },
@@ -5412,7 +5457,7 @@ function loadConfig(globalToml = "", projectToml = "") {
5412
5457
  ...typeof theme["statusline"] === "string" ? { statusline: theme["statusline"] } : {},
5413
5458
  ...typeof theme["logo"] === "string" ? { logo: theme["logo"] } : {},
5414
5459
  ...typeof theme["comment"] === "string" ? { comment: theme["comment"] } : {},
5415
- banding: oneOf(theme["banding"], ["solid", "cycle"], d.theme.banding),
5460
+ banding: oneOf(theme["banding"], BANDINGS, d.theme.banding),
5416
5461
  mood_shift: bool(theme["mood_shift"], d.theme.mood_shift),
5417
5462
  icons: mergeIcons({
5418
5463
  ...obj(obj(g["theme"])["icons"]),
@@ -5422,7 +5467,7 @@ function loadConfig(globalToml = "", projectToml = "") {
5422
5467
  comments: {
5423
5468
  character: bool(comments["character"], d.comments.character),
5424
5469
  helpful: bool(comments["helpful"], d.comments.helpful),
5425
- min_severity: oneOf(comments["min_severity"], ["low", "medium", "high", "critical"], d.comments.min_severity)
5470
+ min_severity: oneOf(comments["min_severity"], MIN_SEVERITIES, d.comments.min_severity)
5426
5471
  },
5427
5472
  network: {
5428
5473
  fx_refresh: bool(network["fx_refresh"], d.network.fx_refresh),
@@ -5445,6 +5490,7 @@ var DEFAULT_WIDGETS, DEFAULT_CONFIG, obj = (v) => v !== null && typeof v === "ob
5445
5490
  });
5446
5491
  var init_config = __esm(() => {
5447
5492
  init_dist();
5493
+ init_domain();
5448
5494
  init_locale();
5449
5495
  DEFAULT_WIDGETS = {
5450
5496
  dir: true,
@@ -5485,7 +5531,7 @@ var init_config = __esm(() => {
5485
5531
  schema_version: 1,
5486
5532
  character: { enabled: true, mode: "random", name: "spiderman", roster: [] },
5487
5533
  theme: { name: "character", banding: "solid", mood_shift: false, icons: {} },
5488
- comments: { character: true, helpful: true, min_severity: "low" },
5534
+ comments: { character: true, helpful: true, min_severity: "medium" },
5489
5535
  network: { fx_refresh: false, usage_fetch: false, balance_path: "" },
5490
5536
  statusline: { currency: defaultCurrency(), widgets: DEFAULT_WIDGETS }
5491
5537
  };
@@ -5598,7 +5644,19 @@ function coerceEntry(v) {
5598
5644
  }
5599
5645
  const projectPath = typeof v["projectPath"] === "string" ? v["projectPath"] : String(record.project);
5600
5646
  const models = Array.isArray(v["models"]) ? v["models"].filter((x) => typeof x === "string") : [];
5601
- return { mtime, size, total, lines, models, projectPath, record };
5647
+ const byteOffset = asNumber(v["byteOffset"]);
5648
+ const headHash = typeof v["headHash"] === "string" ? v["headHash"] : undefined;
5649
+ return {
5650
+ mtime,
5651
+ size,
5652
+ total,
5653
+ lines,
5654
+ models,
5655
+ projectPath,
5656
+ record,
5657
+ ...byteOffset !== undefined ? { byteOffset } : {},
5658
+ ...headHash !== undefined ? { headHash } : {}
5659
+ };
5602
5660
  }
5603
5661
  function coerceByModel(v) {
5604
5662
  if (!isObject(v))
@@ -5855,13 +5913,9 @@ var init_oauthUsage = __esm(() => {
5855
5913
  // src/sources/creds.ts
5856
5914
  import { spawnSync as spawnSync2 } from "node:child_process";
5857
5915
  import { readFileSync as readFileSync4 } from "node:fs";
5858
- import { homedir as homedir3 } from "node:os";
5859
5916
  import { join as join5 } from "node:path";
5860
- function configBase2() {
5861
- return process.env["CLAUDE_CONFIG_DIR"] ?? join5(homedir3(), ".claude");
5862
- }
5863
5917
  function readBlob(run, base) {
5864
- for (const service of [keychainService(base), KEYCHAIN_SERVICE2]) {
5918
+ for (const service of [keychainService(base), KEYCHAIN_SERVICE]) {
5865
5919
  const keychain = run("security", ["find-generic-password", "-s", service, "-w"]);
5866
5920
  if (keychain.trim() !== "")
5867
5921
  return keychain;
@@ -5871,7 +5925,7 @@ function readBlob(run, base) {
5871
5925
  if (file.trim() !== "")
5872
5926
  return file;
5873
5927
  } catch {}
5874
- const secretTool = run("secret-tool", ["lookup", "service", KEYCHAIN_SERVICE2]);
5928
+ const secretTool = run("secret-tool", ["lookup", "service", KEYCHAIN_SERVICE]);
5875
5929
  if (secretTool.trim() !== "")
5876
5930
  return secretTool;
5877
5931
  return "";
@@ -5900,7 +5954,7 @@ function readOrgTier(base) {
5900
5954
  }
5901
5955
  function readCreds(run = defaultRunner) {
5902
5956
  try {
5903
- const base = configBase2();
5957
+ const base = configBase();
5904
5958
  const blob = readBlob(run, base);
5905
5959
  if (blob.trim() !== "") {
5906
5960
  const tier = parseSubscription(blob);
@@ -5914,7 +5968,39 @@ function readCreds(run = defaultRunner) {
5914
5968
  return null;
5915
5969
  }
5916
5970
  }
5917
- var KEYCHAIN_SERVICE2 = "Claude Code-credentials", SUBSCRIPTIONS, isObject3 = (v) => typeof v === "object" && v !== null, defaultRunner = (cmd, args) => {
5971
+ function credsCachePath(root) {
5972
+ return join5(cacheDir(root), CREDS_CACHE_FILE);
5973
+ }
5974
+ function coerceCachedCreds(raw) {
5975
+ if (!isObject3(raw) || typeof raw["at"] !== "number")
5976
+ return null;
5977
+ const at = raw["at"];
5978
+ const info = raw["info"];
5979
+ if (info === null)
5980
+ return { info: null, at };
5981
+ if (isObject3(info) && typeof info["present"] === "boolean") {
5982
+ const sub = info["subscriptionType"];
5983
+ return {
5984
+ at,
5985
+ info: typeof sub === "string" && SUBSCRIPTIONS.has(sub) ? { present: info["present"], subscriptionType: sub } : { present: info["present"] }
5986
+ };
5987
+ }
5988
+ return null;
5989
+ }
5990
+ function readCredsCached(root) {
5991
+ return coerceCachedCreds(readJson(credsCachePath(root), undefined))?.info ?? null;
5992
+ }
5993
+ function refreshCreds(root, clock, run = defaultRunner) {
5994
+ const cached = coerceCachedCreds(readJson(credsCachePath(root), undefined));
5995
+ const now = clock.now();
5996
+ if (cached !== null && now - cached.at <= CREDS_TTL_MS)
5997
+ return;
5998
+ const info = readCreds(run);
5999
+ try {
6000
+ atomicWrite(credsCachePath(root), JSON.stringify({ info, at: now }));
6001
+ } catch {}
6002
+ }
6003
+ var SUBSCRIPTIONS, isObject3 = (v) => typeof v === "object" && v !== null, defaultRunner = (cmd, args) => {
5918
6004
  try {
5919
6005
  const r = spawnSync2(cmd, args, {
5920
6006
  encoding: "utf8",
@@ -5927,9 +6013,11 @@ var KEYCHAIN_SERVICE2 = "Claude Code-credentials", SUBSCRIPTIONS, isObject3 = (v
5927
6013
  } catch {
5928
6014
  return "";
5929
6015
  }
5930
- };
6016
+ }, CREDS_CACHE_FILE = "creds.json";
5931
6017
  var init_creds = __esm(() => {
6018
+ init_domain();
5932
6019
  init_oauthUsage();
6020
+ init_storage();
5933
6021
  SUBSCRIPTIONS = new Set(["pro", "max", "team", "enterprise"]);
5934
6022
  });
5935
6023
 
@@ -6408,12 +6496,14 @@ function readSubmoduleRollup(run, workTreeRoot) {
6408
6496
  return rollup;
6409
6497
  }
6410
6498
  function readGit(cwd, run = defaultRunner2(cwd)) {
6411
- if (run(["rev-parse", "--is-inside-work-tree"]).trim() !== "true")
6499
+ const probe = run(["rev-parse", "--is-inside-work-tree", "--git-dir", "--show-toplevel"]).split(`
6500
+ `);
6501
+ if ((probe[0] ?? "").trim() !== "true")
6412
6502
  return null;
6413
6503
  const status = parseStatus(run(["status", "--porcelain=v2", "--branch", "--show-stash"]));
6414
- const gitDirRaw = run(["rev-parse", "--git-dir"]).trim();
6504
+ const gitDirRaw = (probe[1] ?? "").trim();
6415
6505
  const gitDir = gitDirRaw === "" ? "" : resolve(cwd, gitDirRaw);
6416
- const workTreeRoot = run(["rev-parse", "--show-toplevel"]).trim();
6506
+ const workTreeRoot = (probe[2] ?? "").trim();
6417
6507
  const tag = run(["describe", "--tags", "--exact-match"]).trim() || undefined;
6418
6508
  const originRepo = normalizeOriginRepo(run(["remote", "get-url", "origin"]));
6419
6509
  const defaultBranch = resolveDefaultBranch(run);
@@ -6424,6 +6514,12 @@ function readGit(cwd, run = defaultRunner2(cwd)) {
6424
6514
  } = parseNumstat(run(["diff", "HEAD", "--numstat"]));
6425
6515
  const rollup = countSubmodules(workTreeRoot) > 0 ? readSubmoduleRollup(run, workTreeRoot) : null;
6426
6516
  const behindDefault = readBehindDefault(run, status.branch, defaultBranch);
6517
+ const remoteBranchExists = status.branch !== undefined && !status.upstream ? run([
6518
+ "rev-parse",
6519
+ "--verify",
6520
+ "--quiet",
6521
+ `refs/remotes/origin/${status.branch}`
6522
+ ]).trim() !== "" : false;
6427
6523
  return {
6428
6524
  ...opt2("branch", status.branch),
6429
6525
  ...opt2("sha", status.sha),
@@ -6440,6 +6536,7 @@ function readGit(cwd, run = defaultRunner2(cwd)) {
6440
6536
  submoduleBranches: rollup?.branches ?? [],
6441
6537
  upstream: status.upstream,
6442
6538
  upstreamGone: status.upstreamGone,
6539
+ remoteBranchExists,
6443
6540
  ...opt2("originRepo", originRepo),
6444
6541
  ...opt2("defaultBranch", defaultBranch),
6445
6542
  ...opt2("root", workTreeRoot === "" ? undefined : workTreeRoot),
@@ -6467,7 +6564,7 @@ var init_git = __esm(() => {
6467
6564
 
6468
6565
  // src/sources/helpfulEnv.ts
6469
6566
  import { readFileSync as readFileSync7 } from "node:fs";
6470
- import { homedir as homedir4 } from "node:os";
6567
+ import { homedir as homedir3 } from "node:os";
6471
6568
  import { delimiter, join as join9 } from "node:path";
6472
6569
  function kubeconfigPath() {
6473
6570
  const env = process.env["KUBECONFIG"];
@@ -6476,7 +6573,7 @@ function kubeconfigPath() {
6476
6573
  if (first !== undefined)
6477
6574
  return first;
6478
6575
  }
6479
- return join9(homedir4(), ".kube", "config");
6576
+ return join9(homedir3(), ".kube", "config");
6480
6577
  }
6481
6578
  function readKubeContext() {
6482
6579
  try {
@@ -6792,7 +6889,7 @@ var init_state = __esm(() => {
6792
6889
  });
6793
6890
 
6794
6891
  // src/sources/transcript.ts
6795
- import { readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
6892
+ import { closeSync as closeSync2, openSync as openSync2, readFileSync as readFileSync8, readSync, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
6796
6893
  import { basename as basename2, dirname as dirname5, join as join12 } from "node:path";
6797
6894
  function parseTs(s) {
6798
6895
  if (s === undefined)
@@ -7014,7 +7111,7 @@ function reconstructTodos(lines) {
7014
7111
  }
7015
7112
  function statSafe(p) {
7016
7113
  try {
7017
- const s = statSync3(p);
7114
+ const s = statSync4(p);
7018
7115
  return { mtimeMs: s.mtimeMs, size: s.size };
7019
7116
  } catch {
7020
7117
  return null;
@@ -7027,6 +7124,61 @@ function readSafe(p) {
7027
7124
  return "";
7028
7125
  }
7029
7126
  }
7127
+ function readBufSafe(p) {
7128
+ try {
7129
+ return readFileSync8(p);
7130
+ } catch {
7131
+ return null;
7132
+ }
7133
+ }
7134
+ function readRange(p, start, end) {
7135
+ if (end <= start)
7136
+ return Buffer.alloc(0);
7137
+ let fd;
7138
+ try {
7139
+ fd = openSync2(p, "r");
7140
+ const len = end - start;
7141
+ const buf = Buffer.allocUnsafe(len);
7142
+ let off = 0;
7143
+ while (off < len) {
7144
+ const n = readSync(fd, buf, off, len - off, start + off);
7145
+ if (n === 0)
7146
+ break;
7147
+ off += n;
7148
+ }
7149
+ return off === len ? buf : null;
7150
+ } catch {
7151
+ return null;
7152
+ } finally {
7153
+ if (fd !== undefined)
7154
+ closeSync2(fd);
7155
+ }
7156
+ }
7157
+ function headHashOf(head) {
7158
+ let h = 2166136261;
7159
+ for (const b of head) {
7160
+ h ^= b;
7161
+ h = Math.imul(h, 16777619);
7162
+ }
7163
+ return (h >>> 0).toString(16);
7164
+ }
7165
+ function makeInternModel(models) {
7166
+ const index = new Map;
7167
+ models.forEach((m, i) => index.set(m, i));
7168
+ return (model) => {
7169
+ let i = index.get(model);
7170
+ if (i === undefined) {
7171
+ i = models.length;
7172
+ models.push(model);
7173
+ index.set(model, i);
7174
+ }
7175
+ return i;
7176
+ };
7177
+ }
7178
+ function lastLineEnd(buf) {
7179
+ const i = buf.lastIndexOf(10);
7180
+ return i < 0 ? 0 : i + 1;
7181
+ }
7030
7182
  function emptyScan(mtime, size) {
7031
7183
  return { tokens: EMPTY_TOKENS, messages: 0, compactions: 0, todos: [], burn: [], mtime, size };
7032
7184
  }
@@ -7106,7 +7258,7 @@ function repoRootForCwd(cwd) {
7106
7258
  let result = cwd;
7107
7259
  while (dir !== "" && dir !== dirname5(dir)) {
7108
7260
  try {
7109
- if (statSync3(join12(dir, ".git")).isDirectory()) {
7261
+ if (statSync4(join12(dir, ".git")).isDirectory()) {
7110
7262
  result = dir;
7111
7263
  break;
7112
7264
  }
@@ -7127,7 +7279,7 @@ function* walkTree(dir, encodedDir) {
7127
7279
  const full = join12(dir, name);
7128
7280
  let st;
7129
7281
  try {
7130
- st = statSync3(full);
7282
+ st = statSync4(full);
7131
7283
  } catch {
7132
7284
  continue;
7133
7285
  }
@@ -7166,22 +7318,16 @@ function scanBounds(lines) {
7166
7318
  return { sessionId, start, end };
7167
7319
  }
7168
7320
  function priceFile(path, encodedDir, st, price, resolveProject) {
7169
- const lines = parseAll(readSafe(path));
7321
+ const buf = readBufSafe(path) ?? Buffer.alloc(0);
7322
+ const byteOffset = lastLineEnd(buf);
7323
+ const headHash = headHashOf(buf.subarray(0, headLen(byteOffset)));
7324
+ const lines = parseAll(buf.subarray(0, byteOffset).toString("utf8"));
7170
7325
  const { sessionId, start, end } = scanBounds(lines);
7171
7326
  const priced = collapseStreaming(lines);
7172
7327
  const gate = makeDedupGate();
7173
7328
  const costLines = [];
7174
7329
  const models = [];
7175
- const modelIndex = new Map;
7176
- const internModel = (model) => {
7177
- let i = modelIndex.get(model);
7178
- if (i === undefined) {
7179
- i = models.length;
7180
- models.push(model);
7181
- modelIndex.set(model, i);
7182
- }
7183
- return i;
7184
- };
7330
+ const internModel = makeInternModel(models);
7185
7331
  let total = 0;
7186
7332
  let input = 0;
7187
7333
  let output = 0;
@@ -7226,6 +7372,8 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
7226
7372
  return {
7227
7373
  mtime: st.mtimeMs,
7228
7374
  size: st.size,
7375
+ byteOffset,
7376
+ headHash,
7229
7377
  total,
7230
7378
  lines: costLines,
7231
7379
  models,
@@ -7233,6 +7381,138 @@ function priceFile(path, encodedDir, st, price, resolveProject) {
7233
7381
  record
7234
7382
  };
7235
7383
  }
7384
+ function resumeCostFile(cached, path, st, price) {
7385
+ const tail = readAppendedTail(cached, path, st);
7386
+ if (tail === null)
7387
+ return null;
7388
+ if (tail === "partial")
7389
+ return { ...cached, mtime: st.mtimeMs, size: st.size };
7390
+ const models = [...cached.models];
7391
+ const keyIndex = new Map;
7392
+ cached.lines.forEach((l, i) => {
7393
+ if (l.id !== undefined && l.reqId !== undefined)
7394
+ keyIndex.set(`${l.id}|${l.reqId}`, i);
7395
+ });
7396
+ const gate = makeDedupGate();
7397
+ const acc = {
7398
+ lines: [...cached.lines],
7399
+ models,
7400
+ counted: cached.lines.map((l) => gate(l.id, l.reqId, l.sidechain)),
7401
+ keyIndex,
7402
+ gate,
7403
+ internModel: makeInternModel(models),
7404
+ input: cached.record.tokens.input,
7405
+ output: cached.record.tokens.output,
7406
+ cacheRead: cached.record.tokens.cache_read,
7407
+ cacheCreation: cached.record.tokens.cache_creation,
7408
+ total: cached.total,
7409
+ messages: cached.record.messages,
7410
+ start: cached.record.start,
7411
+ end: cached.record.end
7412
+ };
7413
+ for (const l of collapseStreaming(tail.appended))
7414
+ foldAppendedLine(acc, l, price);
7415
+ return {
7416
+ mtime: st.mtimeMs,
7417
+ size: st.size,
7418
+ byteOffset: tail.newByteOffset,
7419
+ headHash: tail.newHeadHash,
7420
+ total: acc.total,
7421
+ lines: acc.lines,
7422
+ models: acc.models,
7423
+ projectPath: cached.projectPath,
7424
+ record: {
7425
+ ...cached.record,
7426
+ start: acc.start,
7427
+ end: acc.end,
7428
+ tokens: {
7429
+ input: acc.input,
7430
+ output: acc.output,
7431
+ cache_read: acc.cacheRead,
7432
+ cache_creation: acc.cacheCreation
7433
+ },
7434
+ messages: acc.messages
7435
+ }
7436
+ };
7437
+ }
7438
+ function costLineOf(internModel, l, cost, totalTok) {
7439
+ return {
7440
+ ...l.id !== undefined ? { id: l.id } : {},
7441
+ ...l.requestId !== undefined ? { reqId: l.requestId } : {},
7442
+ sidechain: l.isSidechain,
7443
+ ts: l.tsMs ?? 0,
7444
+ cost,
7445
+ ...l.model !== undefined ? { m: internModel(l.model) } : {},
7446
+ tok: totalTok
7447
+ };
7448
+ }
7449
+ function readAppendedTail(cached, path, st) {
7450
+ const offset = cached.byteOffset;
7451
+ if (offset === undefined || offset === 0 || st.size < offset)
7452
+ return null;
7453
+ const head = readRange(path, 0, headLen(offset));
7454
+ if (head === null || headHashOf(head) !== cached.headHash)
7455
+ return null;
7456
+ const tailBuf = readRange(path, offset, st.size);
7457
+ if (tailBuf === null)
7458
+ return null;
7459
+ const added = lastLineEnd(tailBuf);
7460
+ if (added === 0)
7461
+ return "partial";
7462
+ const newByteOffset = offset + added;
7463
+ const newHead = headLen(newByteOffset) === head.length ? head : readRange(path, 0, headLen(newByteOffset));
7464
+ if (newHead === null)
7465
+ return null;
7466
+ return {
7467
+ appended: parseAll(tailBuf.subarray(0, added).toString("utf8")),
7468
+ newByteOffset,
7469
+ newHeadHash: headHashOf(newHead)
7470
+ };
7471
+ }
7472
+ function widenBounds(acc, tsMs) {
7473
+ if (tsMs === undefined)
7474
+ return;
7475
+ if (tsMs > acc.end)
7476
+ acc.end = tsMs;
7477
+ if (acc.start === 0 || tsMs < acc.start)
7478
+ acc.start = tsMs;
7479
+ }
7480
+ function foldAppendedLine(acc, l, price) {
7481
+ widenBounds(acc, l.tsMs);
7482
+ const usage = l.usage;
7483
+ if (usage === undefined)
7484
+ return;
7485
+ const t = tokenize(usage);
7486
+ const totalTok = t.input + t.output + t.cache_read + t.c5m + t.c1h;
7487
+ const key = l.id !== undefined && l.requestId !== undefined ? `${l.id}|${l.requestId}` : null;
7488
+ const existing = key !== null ? acc.keyIndex.get(key) : undefined;
7489
+ const old = existing !== undefined ? acc.lines[existing] : undefined;
7490
+ if (existing !== undefined && old !== undefined) {
7491
+ if (totalTok <= (old.tok ?? 0))
7492
+ return;
7493
+ const cost2 = price(usage, l.model ?? "", l.tsMs);
7494
+ if (acc.counted[existing] === true) {
7495
+ acc.output += totalTok - (old.tok ?? 0);
7496
+ acc.total += cost2 - old.cost;
7497
+ }
7498
+ acc.lines[existing] = costLineOf(acc.internModel, l, cost2, totalTok);
7499
+ return;
7500
+ }
7501
+ const cost = price(usage, l.model ?? "", l.tsMs);
7502
+ const isCounted = acc.gate(l.id, l.requestId, l.isSidechain);
7503
+ if (key !== null)
7504
+ acc.keyIndex.set(key, acc.lines.length);
7505
+ acc.lines.push(costLineOf(acc.internModel, l, cost, totalTok));
7506
+ acc.counted.push(isCounted);
7507
+ if (isCounted) {
7508
+ acc.input += t.input;
7509
+ acc.output += t.output;
7510
+ acc.cacheRead += t.cache_read;
7511
+ acc.cacheCreation += t.c5m + t.c1h;
7512
+ acc.total += cost;
7513
+ acc.messages += 1;
7514
+ }
7515
+ }
7236
7516
  function buildAggregate(files, chat) {
7237
7517
  const sessionProject = {};
7238
7518
  const all = [];
@@ -7267,16 +7547,26 @@ function scanCostTree(root, cache, clock, price, resolveProject) {
7267
7547
  return cache;
7268
7548
  const chat = cache.aggregate.chat;
7269
7549
  const files = {};
7550
+ let changed = false;
7270
7551
  for (const { path, encodedDir } of walkJsonl(root)) {
7271
7552
  const st = statSafe(path);
7272
7553
  if (!st)
7273
7554
  continue;
7274
7555
  const cached = cache.files[path];
7275
- files[path] = cached && cached.mtime === st.mtimeMs && cached.size === st.size ? cached : priceFile(path, encodedDir, st, price, resolveProject);
7556
+ if (cached && cached.mtime === st.mtimeMs && cached.size === st.size) {
7557
+ files[path] = cached;
7558
+ } else {
7559
+ const resumed = cached ? resumeCostFile(cached, path, st, price) : null;
7560
+ files[path] = resumed ?? priceFile(path, encodedDir, st, price, resolveProject);
7561
+ changed = true;
7562
+ }
7276
7563
  }
7564
+ const sameFileSet = Object.keys(files).length === Object.keys(cache.files).length;
7565
+ if (!changed && sameFileSet)
7566
+ return { files, aggregate: cache.aggregate, lastScanTs: now };
7277
7567
  return { files, aggregate: buildAggregate(files, chat), lastScanTs: now };
7278
7568
  }
7279
- var asObj = (v) => v !== null && typeof v === "object" ? v : undefined, asStr = (v) => typeof v === "string" ? v : undefined, asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0, asArr = (v) => Array.isArray(v) ? v : [], EMPTY_TOKENS, repoRootCache;
7569
+ var asObj = (v) => v !== null && typeof v === "object" ? v : undefined, asStr = (v) => typeof v === "string" ? v : undefined, asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0, asArr = (v) => Array.isArray(v) ? v : [], HEAD_HASH_BYTES = 4096, headLen = (byteOffset) => Math.min(HEAD_HASH_BYTES, byteOffset), EMPTY_TOKENS, repoRootCache;
7280
7570
  var init_transcript = __esm(() => {
7281
7571
  init_domain();
7282
7572
  EMPTY_TOKENS = {
@@ -7310,15 +7600,31 @@ var init_sources = __esm(() => {
7310
7600
  });
7311
7601
 
7312
7602
  // src/cli/gc.ts
7313
- import { existsSync as existsSync2, readdirSync as readdirSync3, rmSync, statSync as statSync4 } from "node:fs";
7603
+ import {
7604
+ existsSync as existsSync2,
7605
+ mkdirSync as mkdirSync4,
7606
+ readFileSync as readFileSync9,
7607
+ readdirSync as readdirSync3,
7608
+ rmSync,
7609
+ statSync as statSync5,
7610
+ writeFileSync as writeFileSync3
7611
+ } from "node:fs";
7314
7612
  import { join as join13 } from "node:path";
7613
+ function readGcStamp(path) {
7614
+ try {
7615
+ const n = Number(readFileSync9(path, "utf8"));
7616
+ return Number.isFinite(n) ? n : undefined;
7617
+ } catch {
7618
+ return;
7619
+ }
7620
+ }
7315
7621
  function newestMtime(dir) {
7316
7622
  let newest = 0;
7317
7623
  try {
7318
- newest = statSync4(dir).mtimeMs;
7624
+ newest = statSync5(dir).mtimeMs;
7319
7625
  for (const name of readdirSync3(dir)) {
7320
7626
  try {
7321
- const m = statSync4(join13(dir, name)).mtimeMs;
7627
+ const m = statSync5(join13(dir, name)).mtimeMs;
7322
7628
  if (m > newest)
7323
7629
  newest = m;
7324
7630
  } catch {}
@@ -7338,7 +7644,7 @@ function pruneSessions(root, now) {
7338
7644
  for (const name of names) {
7339
7645
  const dir = join13(sessions, name);
7340
7646
  try {
7341
- if (!statSync4(dir).isDirectory())
7647
+ if (!statSync5(dir).isDirectory())
7342
7648
  continue;
7343
7649
  } catch {
7344
7650
  continue;
@@ -7351,36 +7657,62 @@ function pruneSessions(root, now) {
7351
7657
  function pruneCostCache(root) {
7352
7658
  const cache = readCostCache(root);
7353
7659
  const kept = {};
7660
+ const liveSessions = new Set;
7354
7661
  let dropped = false;
7355
7662
  for (const [path, entry] of Object.entries(cache.files)) {
7356
- if (existsSync2(path))
7663
+ if (existsSync2(path)) {
7357
7664
  kept[path] = entry;
7358
- else
7665
+ liveSessions.add(String(entry.record.session));
7666
+ } else {
7359
7667
  dropped = true;
7668
+ }
7669
+ }
7670
+ const chatKept = {};
7671
+ let chatDropped = false;
7672
+ for (const [session, cost] of Object.entries(cache.aggregate.chat)) {
7673
+ if (liveSessions.has(session))
7674
+ chatKept[session] = cost;
7675
+ else
7676
+ chatDropped = true;
7677
+ }
7678
+ if (dropped || chatDropped) {
7679
+ writeCostCache(root, {
7680
+ ...cache,
7681
+ files: kept,
7682
+ aggregate: { ...cache.aggregate, chat: chatKept }
7683
+ });
7360
7684
  }
7361
- if (dropped)
7362
- writeCostCache(root, { ...cache, files: kept });
7363
7685
  }
7364
7686
  function runGc(root, clock) {
7687
+ const now = clock.now();
7688
+ const stampPath = join13(root, "cache", ".gc-stamp");
7689
+ const last = readGcStamp(stampPath);
7690
+ if (last !== undefined && now - last < GC_MIN_INTERVAL_MS)
7691
+ return;
7365
7692
  try {
7366
- pruneSessions(root, clock.now());
7693
+ pruneSessions(root, now);
7367
7694
  } catch {}
7368
7695
  try {
7369
7696
  pruneCostCache(root);
7370
7697
  } catch {}
7698
+ try {
7699
+ mkdirSync4(join13(root, "cache"), { recursive: true });
7700
+ writeFileSync3(stampPath, String(now));
7701
+ } catch {}
7371
7702
  }
7372
- var DAY_MS = 86400000;
7703
+ var DAY_MS = 86400000, GC_MIN_INTERVAL_MS;
7373
7704
  var init_gc = __esm(() => {
7374
7705
  init_domain();
7375
7706
  init_sources();
7707
+ GC_MIN_INTERVAL_MS = 6 * 60 * 60 * 1000;
7376
7708
  });
7377
7709
 
7378
7710
  // src/cli/render.ts
7379
- import { readFileSync as readFileSync9 } from "node:fs";
7711
+ import { readFileSync as readFileSync10 } from "node:fs";
7380
7712
  import { dirname as dirname6, join as join14 } from "node:path";
7381
7713
  function readTextSafe(path) {
7382
7714
  try {
7383
- return readFileSync9(path, "utf8");
7715
+ return readFileSync10(path, "utf8");
7384
7716
  } catch {
7385
7717
  return "";
7386
7718
  }
@@ -7415,18 +7747,28 @@ function applyThemeIcons(fields, theme, config) {
7415
7747
  });
7416
7748
  }
7417
7749
  function readGated(overrides, envInputs, config, root, dir, markers, clock) {
7418
- const creds = overrides?.creds !== undefined ? overrides.creds : envInputs.hasApiKey || config.network.usage_fetch ? readCreds() : null;
7750
+ const creds = overrides?.creds !== undefined ? overrides.creds : envInputs.hasApiKey || config.network.usage_fetch ? readCredsCached(root) : null;
7419
7751
  const usage = overrides?.usage !== undefined ? overrides.usage : config.network.usage_fetch ? readUsageCached(root) : null;
7420
7752
  const balance = overrides?.balance !== undefined ? overrides.balance : config.network.balance_path !== "" ? readBalance(config.network.balance_path, clock) : null;
7421
7753
  const helpfulEnv = needsHelpfulEnv(markers) ? readHelpfulEnv(dir) : EMPTY_HELPFUL_ENV;
7422
7754
  return { creds, usage, balance, helpfulEnv };
7423
7755
  }
7424
- function lookupPackTheme(name) {
7425
- const loaded = loadPack(name);
7756
+ function packThemeOf(loaded) {
7426
7757
  if (!loaded.ok || loaded.pack.theme === undefined)
7427
7758
  return null;
7428
7759
  return { displayName: loaded.pack.displayName, ...loaded.pack.theme };
7429
7760
  }
7761
+ function makePackThemeLookup(persona, loaded, load = loadPack) {
7762
+ const memo = new Map;
7763
+ return (name) => {
7764
+ const cached = memo.get(name);
7765
+ if (cached !== undefined || memo.has(name))
7766
+ return cached ?? null;
7767
+ const theme = packThemeOf(name === persona ? loaded : load(name));
7768
+ memo.set(name, theme);
7769
+ return theme;
7770
+ };
7771
+ }
7430
7772
  function runRender(stdin, env, term, clock, overrides) {
7431
7773
  try {
7432
7774
  return build(stdin, env, term, clock, overrides);
@@ -7488,8 +7830,8 @@ function build(stdin, env, term, clock, overrides) {
7488
7830
  const stacks = deriveStacks(markers, events);
7489
7831
  const fresh = freshestEvent(events);
7490
7832
  const stack = pickStack(stacks, fresh?.stack);
7491
- const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock);
7492
- const theme = resolveTheme(config, lookupPackTheme, persona);
7833
+ const familiarity = deriveFamiliarity(attribution, persona, scannedCache, project, clock, String(session));
7834
+ const theme = resolveTheme(config, makePackThemeLookup(persona, loaded), persona);
7493
7835
  const composeInputs = {
7494
7836
  provider,
7495
7837
  model,
@@ -7590,6 +7932,10 @@ function build(stdin, env, term, clock, overrides) {
7590
7932
  }
7591
7933
  swallow(() => {
7592
7934
  const payloadChat = payload.cost?.total_cost_usd;
7935
+ const rescanned = scannedCache !== costCache;
7936
+ const chatChanged = !isDefault && payloadChat !== undefined && scannedCache.aggregate.chat[String(session)] !== payloadChat;
7937
+ if (!rescanned && !chatChanged)
7938
+ return;
7593
7939
  const toWrite = !isDefault && payloadChat !== undefined ? {
7594
7940
  ...scannedCache,
7595
7941
  aggregate: {
@@ -7613,6 +7959,10 @@ function build(stdin, env, term, clock, overrides) {
7613
7959
  swallow(() => {
7614
7960
  readUsage(root, clock, { enabled: config.network.usage_fetch });
7615
7961
  });
7962
+ swallow(() => {
7963
+ if (envInputs.hasApiKey || config.network.usage_fetch)
7964
+ refreshCreds(root, clock);
7965
+ });
7616
7966
  swallow(() => {
7617
7967
  runGc(root, clock);
7618
7968
  });
@@ -7639,7 +7989,7 @@ var init_render2 = __esm(() => {
7639
7989
  });
7640
7990
 
7641
7991
  // src/cli/settings.ts
7642
- import { existsSync as existsSync3, readFileSync as readFileSync10, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
7992
+ import { existsSync as existsSync3, readFileSync as readFileSync11, readdirSync as readdirSync4, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
7643
7993
  import { basename as basename3, dirname as dirname7, join as join15 } from "node:path";
7644
7994
  function asRecord(v) {
7645
7995
  return typeof v === "object" && v !== null && !Array.isArray(v) ? v : {};
@@ -7670,16 +8020,16 @@ function pruneBackups(settingsPath) {
7670
8020
  }
7671
8021
  function safeWriteJson(path, text, restoreText) {
7672
8022
  const tmp = `${path}.tmp.${String(process.pid)}.${Math.random().toString(36).slice(2)}`;
7673
- writeFileSync3(tmp, text);
7674
- renameSync2(tmp, path);
8023
+ writeFileSync4(tmp, text);
8024
+ renameSync3(tmp, path);
7675
8025
  let ok = false;
7676
8026
  try {
7677
- ok = parseJsonObject(readFileSync10(path, "utf8")) !== null;
8027
+ ok = parseJsonObject(readFileSync11(path, "utf8")) !== null;
7678
8028
  } catch {
7679
8029
  ok = false;
7680
8030
  }
7681
8031
  if (!ok) {
7682
- writeFileSync3(path, restoreText);
8032
+ writeFileSync4(path, restoreText);
7683
8033
  throw new Error(`ccsidekick: settings write failed verification at ${path}; restored prior content`);
7684
8034
  }
7685
8035
  }
@@ -7689,7 +8039,7 @@ function installSettings(opts) {
7689
8039
  let base = {};
7690
8040
  let originalText = "";
7691
8041
  if (exists) {
7692
- originalText = readFileSync10(settingsPath, "utf8");
8042
+ originalText = readFileSync11(settingsPath, "utf8");
7693
8043
  const parsed = parseJsonObject(originalText);
7694
8044
  if (parsed === null) {
7695
8045
  throw new Error(`ccsidekick: refusing to modify unparseable settings at ${settingsPath}`);
@@ -7714,7 +8064,7 @@ function installSettings(opts) {
7714
8064
  spinnerVerbs: { mode: "replace", verbs: [...spinnerVerbs] }
7715
8065
  };
7716
8066
  if (exists) {
7717
- writeFileSync3(`${settingsPath}.ccsidekick-bak.${String(Date.now())}`, originalText);
8067
+ writeFileSync4(`${settingsPath}.ccsidekick-bak.${String(Date.now())}`, originalText);
7718
8068
  pruneBackups(settingsPath);
7719
8069
  }
7720
8070
  safeWriteJson(settingsPath, `${JSON.stringify(merged, null, 2)}
@@ -7728,11 +8078,11 @@ var init_settings = __esm(() => {
7728
8078
  init_dist();
7729
8079
  init_domain();
7730
8080
  init_sources();
7731
- HOOK_EVENTS = ["PostToolUse", "PostToolUseFailure", "PostToolBatch"];
8081
+ HOOK_EVENTS = ["PostToolUse", "PostToolUseFailure"];
7732
8082
  });
7733
8083
 
7734
8084
  // src/cli/setup.ts
7735
- import { readFileSync as readFileSync11 } from "node:fs";
8085
+ import { readFileSync as readFileSync12 } from "node:fs";
7736
8086
  import { join as join16 } from "node:path";
7737
8087
  function themeNames() {
7738
8088
  return [CHARACTER_THEME, ...Object.keys(THEMES), ...PACKS];
@@ -7884,14 +8234,14 @@ function setupHelp() {
7884
8234
  "",
7885
8235
  "Flags (only the ones you pass are applied):",
7886
8236
  ` --character <name> ${PACKS.join(", ")}`,
7887
- ` --mode <mode> ${MODES.join(", ")}`,
8237
+ ` --mode <mode> ${CHARACTER_MODES.join(", ")}`,
7888
8238
  " --roster <a,b,c> characters for random mode (from --character's list)",
7889
8239
  ` --theme <name> ${themeNames().join(", ")}`,
7890
8240
  " --currency <code> e.g. USD, EUR, INR",
7891
8241
  " --budget <usd> monthly budget, a number >= 0",
7892
8242
  " --comments <on|off> the character's own comment line",
7893
8243
  " --helpful <on|off> the helpful-tip line",
7894
- ` --min-severity <sev> ${SEVERITIES.join(", ")}`,
8244
+ ` --min-severity <sev> ${MIN_SEVERITIES.join(", ")}`,
7895
8245
  " --widgets <a,b,c> statusline widgets to enable (others are turned off)",
7896
8246
  " --usage-fetch <on|off> account-usage lookup to Anthropic (needed by the pay_as_you_go widget; no tokens)",
7897
8247
  " --global | --local save target (default: global)",
@@ -7904,12 +8254,12 @@ function setupHelp() {
7904
8254
  }
7905
8255
  function defaultReadConfig(path) {
7906
8256
  try {
7907
- return readFileSync11(path, "utf8");
8257
+ return readFileSync12(path, "utf8");
7908
8258
  } catch {
7909
8259
  return null;
7910
8260
  }
7911
8261
  }
7912
- var WIDGET_IDS, MODES, SEVERITIES, VALUE_FLAGS, BOOL_FLAGS, splitList = (raw) => raw.split(",").map((s) => s.trim()).filter((s) => s !== ""), invalid = (flag, value, valid) => `ccsidekick: invalid --${flag} "${value}". Valid: ${valid.join(", ")}`, parseOnOff = (flag, value, errors) => {
8262
+ var WIDGET_IDS, VALUE_FLAGS, BOOL_FLAGS, splitList = (raw) => raw.split(",").map((s) => s.trim()).filter((s) => s !== ""), invalid = (flag, value, valid) => `ccsidekick: invalid --${flag} "${value}". Valid: ${valid.join(", ")}`, parseOnOff = (flag, value, errors) => {
7913
8263
  if (value === "on")
7914
8264
  return true;
7915
8265
  if (value === "off")
@@ -7919,12 +8269,11 @@ var WIDGET_IDS, MODES, SEVERITIES, VALUE_FLAGS, BOOL_FLAGS, splitList = (raw) =>
7919
8269
  }, memberOf = (value, valid) => valid.includes(value), VALIDATORS;
7920
8270
  var init_setup = __esm(() => {
7921
8271
  init_data();
8272
+ init_domain();
7922
8273
  init_packs();
7923
8274
  init_render();
7924
8275
  init_sources();
7925
8276
  WIDGET_IDS = Object.keys(DEFAULT_CONFIG.statusline.widgets);
7926
- MODES = ["fixed", "random"];
7927
- SEVERITIES = ["low", "medium", "high", "critical"];
7928
8277
  VALUE_FLAGS = new Set([
7929
8278
  "character",
7930
8279
  "mode",
@@ -7945,7 +8294,7 @@ var init_setup = __esm(() => {
7945
8294
  enumInto(v, PACKS, "character", e, (x) => f.character = x);
7946
8295
  },
7947
8296
  mode: (v, f, e) => {
7948
- enumInto(v, MODES, "mode", e, (x) => f.mode = x);
8297
+ enumInto(v, CHARACTER_MODES, "mode", e, (x) => f.mode = x);
7949
8298
  },
7950
8299
  roster: (v, f, e) => {
7951
8300
  listInto(v, PACKS, "roster", e, (x) => f.roster = x);
@@ -7978,7 +8327,7 @@ var init_setup = __esm(() => {
7978
8327
  f.helpful = b;
7979
8328
  },
7980
8329
  "min-severity": (v, f, e) => {
7981
- enumInto(v, SEVERITIES, "min-severity", e, (x) => f.minSeverity = x);
8330
+ enumInto(v, MIN_SEVERITIES, "min-severity", e, (x) => f.minSeverity = x);
7982
8331
  },
7983
8332
  widgets: (v, f, e) => {
7984
8333
  listInto(v, WIDGET_IDS, "widgets", e, (x) => f.widgets = x);
@@ -7992,7 +8341,7 @@ var init_setup = __esm(() => {
7992
8341
  });
7993
8342
 
7994
8343
  // src/cli/uninstall.ts
7995
- import { existsSync as existsSync4, readFileSync as readFileSync12, readdirSync as readdirSync5 } from "node:fs";
8344
+ import { existsSync as existsSync4, readFileSync as readFileSync13, readdirSync as readdirSync5 } from "node:fs";
7996
8345
  import { basename as basename4, dirname as dirname8, join as join17 } from "node:path";
7997
8346
  function asRecord2(v) {
7998
8347
  return typeof v === "object" && v !== null && !Array.isArray(v) ? v : {};
@@ -8039,7 +8388,7 @@ function restoreNewestBackup(settingsPath) {
8039
8388
  }
8040
8389
  if (newest === undefined)
8041
8390
  return null;
8042
- atomicWrite(settingsPath, readFileSync12(join17(dir, newest.name), "utf8"));
8391
+ atomicWrite(settingsPath, readFileSync13(join17(dir, newest.name), "utf8"));
8043
8392
  return newest.name;
8044
8393
  }
8045
8394
  function runRestore(settingsPath, emit) {
@@ -8093,7 +8442,7 @@ function runUninstall(opts) {
8093
8442
  return;
8094
8443
  }
8095
8444
  if (existsSync4(settingsPath)) {
8096
- const parsed = parseJsonObject2(readFileSync12(settingsPath, "utf8"));
8445
+ const parsed = parseJsonObject2(readFileSync13(settingsPath, "utf8"));
8097
8446
  if (parsed === null) {
8098
8447
  throw new Error(`ccsidekick: refusing to modify unparseable settings at ${settingsPath}`);
8099
8448
  }
@@ -8175,7 +8524,7 @@ var init_metrics = __esm(() => {
8175
8524
  var ACCENT = "#ffd700", FRAME = "gray", DANGER = "red";
8176
8525
 
8177
8526
  // src/tui/save.ts
8178
- import { mkdirSync as mkdirSync4 } from "node:fs";
8527
+ import { mkdirSync as mkdirSync5 } from "node:fs";
8179
8528
  import { join as join19 } from "node:path";
8180
8529
  function defaultResolveVerbs(name) {
8181
8530
  const loaded = loadPack(name);
@@ -8212,7 +8561,7 @@ function save(config, scope, dir, renderBin, opts = {}) {
8212
8561
  writeConfigToml(join19(cwd, ".ccsidekick"), normalized);
8213
8562
  if (opts.wireLocalSettings === true) {
8214
8563
  const claudeDir = join19(cwd, ".claude");
8215
- mkdirSync4(claudeDir, { recursive: true });
8564
+ mkdirSync5(claudeDir, { recursive: true });
8216
8565
  installSettings({
8217
8566
  settingsPath: join19(claudeDir, "settings.json"),
8218
8567
  renderBin,
@@ -10910,14 +11259,14 @@ var init_PreviewPanel = __esm(() => {
10910
11259
 
10911
11260
  // src/tui/preview/fixture.ts
10912
11261
  import { execFileSync } from "node:child_process";
10913
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, mkdtempSync, realpathSync, writeFileSync as writeFileSync4 } from "node:fs";
11262
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, mkdtempSync, realpathSync, writeFileSync as writeFileSync5 } from "node:fs";
10914
11263
  import { tmpdir } from "node:os";
10915
11264
  import { join as join20 } from "node:path";
10916
11265
  function seedTranscriptFixture(workdir, root) {
10917
11266
  const dir = root !== undefined ? join20(root, "projects", workdir.replace(/[/.]/g, "-")) : workdir;
10918
11267
  const path = join20(dir, "preview-transcript.jsonl");
10919
11268
  try {
10920
- mkdirSync5(dir, { recursive: true });
11269
+ mkdirSync6(dir, { recursive: true });
10921
11270
  if (!existsSync6(path)) {
10922
11271
  const lines = [
10923
11272
  JSON.stringify({ type: "system", subtype: "compact_boundary" }),
@@ -10941,7 +11290,7 @@ function seedTranscriptFixture(workdir, root) {
10941
11290
  }
10942
11291
  })
10943
11292
  ];
10944
- writeFileSync4(path, `${lines.join(`
11293
+ writeFileSync5(path, `${lines.join(`
10945
11294
  `)}
10946
11295
  `);
10947
11296
  }
@@ -10985,7 +11334,7 @@ function gitEnv() {
10985
11334
  }
10986
11335
  function setupGitFixture(workdir) {
10987
11336
  try {
10988
- mkdirSync5(workdir, { recursive: true });
11337
+ mkdirSync6(workdir, { recursive: true });
10989
11338
  if (existsSync6(join20(workdir, ".git")))
10990
11339
  return;
10991
11340
  const env2 = gitEnv();
@@ -10996,20 +11345,20 @@ function setupGitFixture(workdir) {
10996
11345
  git("config", "user.email", "wayne@example.com");
10997
11346
  git("config", "user.name", "Bruce Wayne");
10998
11347
  git("remote", "add", "origin", "https://github.com/wayne/ccsidekick.git");
10999
- writeFileSync4(join20(workdir, "README.md"), `# ccsidekick
11348
+ writeFileSync5(join20(workdir, "README.md"), `# ccsidekick
11000
11349
 
11001
11350
  original line
11002
11351
  keep me
11003
11352
  `);
11004
11353
  git("add", "README.md");
11005
11354
  git("commit", "-q", "-m", "initial commit");
11006
- writeFileSync4(join20(workdir, "README.md"), `# ccsidekick
11355
+ writeFileSync5(join20(workdir, "README.md"), `# ccsidekick
11007
11356
 
11008
11357
  edited line
11009
11358
  keep me
11010
11359
  new line
11011
11360
  `);
11012
- writeFileSync4(join20(workdir, "src.ts"), `export const answer = 42;
11361
+ writeFileSync5(join20(workdir, "src.ts"), `export const answer = 42;
11013
11362
  `);
11014
11363
  git("add", "src.ts");
11015
11364
  } catch {}
@@ -11034,11 +11383,11 @@ function seedCostFixture(root, workdir) {
11034
11383
  const projectsRoot = join20(root, "projects");
11035
11384
  const currentProjectDir = join20(projectsRoot, workdir.replace(/[/.]/g, "-"));
11036
11385
  const otherProjectDir = join20(projectsRoot, "-other-project");
11037
- mkdirSync5(currentProjectDir, { recursive: true });
11038
- mkdirSync5(otherProjectDir, { recursive: true });
11039
- writeFileSync4(join20(currentProjectDir, "sess-a.jsonl"), `${transcriptLine("sess-a-1", "claude-opus-4-8", 1e5, 20000)}
11386
+ mkdirSync6(currentProjectDir, { recursive: true });
11387
+ mkdirSync6(otherProjectDir, { recursive: true });
11388
+ writeFileSync5(join20(currentProjectDir, "sess-a.jsonl"), `${transcriptLine("sess-a-1", "claude-opus-4-8", 1e5, 20000)}
11040
11389
  `);
11041
- writeFileSync4(join20(otherProjectDir, "sess-b.jsonl"), `${transcriptLine("sess-b-1", "claude-opus-4-8", 200000, 50000)}
11390
+ writeFileSync5(join20(otherProjectDir, "sess-b.jsonl"), `${transcriptLine("sess-b-1", "claude-opus-4-8", 200000, 50000)}
11042
11391
  `);
11043
11392
  }
11044
11393
  function scratchRoot(scratchDir) {
@@ -11049,7 +11398,7 @@ function scratchRoot(scratchDir) {
11049
11398
  }
11050
11399
  function previewEnv(root, extra) {
11051
11400
  const homeBase = join20(root, "home");
11052
- mkdirSync5(homeBase, { recursive: true });
11401
+ mkdirSync6(homeBase, { recursive: true });
11053
11402
  let home = homeBase;
11054
11403
  try {
11055
11404
  home = realpathSync(homeBase);
@@ -11258,8 +11607,8 @@ function checkedTargets(targets, checked) {
11258
11607
  }
11259
11608
 
11260
11609
  // src/tui/sections/configdir.tsx
11261
- import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
11262
- import { homedir as homedir5 } from "node:os";
11610
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7 } from "node:fs";
11611
+ import { homedir as homedir4 } from "node:os";
11263
11612
  import { Box as Box22, Text as Text27, useApp, useInput as useInput7 } from "ink";
11264
11613
  import { useState as useState6 } from "react";
11265
11614
  import { jsxDEV as jsxDEV14 } from "react/jsx-dev-runtime";
@@ -11303,7 +11652,7 @@ function handleCustomKey(input, key, d) {
11303
11652
  function handleConfirmKey(key, d) {
11304
11653
  if (key.return && d.pending !== null) {
11305
11654
  try {
11306
- mkdirSync6(d.pending, { recursive: true });
11655
+ mkdirSync7(d.pending, { recursive: true });
11307
11656
  d.addDir(d.pending);
11308
11657
  } catch {
11309
11658
  d.setError(`Could not create ${d.pending}`);
@@ -11370,7 +11719,7 @@ function ConfigDirPicker({
11370
11719
  onQuit
11371
11720
  }) {
11372
11721
  const { exit } = useApp();
11373
- const home = homedir5();
11722
+ const home = homedir4();
11374
11723
  const [cursor4, setCursor] = useState6(0);
11375
11724
  const [mode, setMode] = useState6("list");
11376
11725
  const [text, setText] = useState6("");
@@ -11633,7 +11982,10 @@ function commentsFields(d) {
11633
11982
  value: d.comments.min_severity,
11634
11983
  next: (c) => ({
11635
11984
  ...c,
11636
- comments: { ...c.comments, min_severity: step(SEVERITIES2, c.comments.min_severity, 1) }
11985
+ comments: {
11986
+ ...c.comments,
11987
+ min_severity: step(MIN_SEVERITIES, c.comments.min_severity, 1)
11988
+ }
11637
11989
  })
11638
11990
  };
11639
11991
  return d.comments.helpful ? [characterComments, helpfulComments, minSeverity] : [characterComments, helpfulComments];
@@ -11681,16 +12033,15 @@ function sectionFields(section2, d) {
11681
12033
  return [];
11682
12034
  }
11683
12035
  }
11684
- var onOff = (b) => b ? "on" : "off", WIDGET_IDS2, SEVERITIES2, BANDINGS, step = (arr, cur, dir) => {
12036
+ var onOff = (b) => b ? "on" : "off", WIDGET_IDS2, step = (arr, cur, dir) => {
11685
12037
  const n = arr.length;
11686
12038
  const i = arr.indexOf(cur);
11687
12039
  return arr[((i + dir) % n + n) % n] ?? cur;
11688
12040
  };
11689
12041
  var init_forms = __esm(() => {
12042
+ init_domain();
11690
12043
  init_sources();
11691
12044
  WIDGET_IDS2 = Object.keys(DEFAULT_CONFIG.statusline.widgets);
11692
- SEVERITIES2 = ["low", "medium", "high", "critical"];
11693
- BANDINGS = ["solid", "cycle"];
11694
12045
  });
11695
12046
 
11696
12047
  // src/tui/sections/FormSection.tsx
@@ -12831,7 +13182,7 @@ var init_inputRoute = __esm(() => {
12831
13182
  });
12832
13183
 
12833
13184
  // src/tui/shell/saveTarget.ts
12834
- import { existsSync as existsSync8, readFileSync as readFileSync13 } from "node:fs";
13185
+ import { existsSync as existsSync8, readFileSync as readFileSync14 } from "node:fs";
12835
13186
  import { join as join22 } from "node:path";
12836
13187
  function chipFor(targets) {
12837
13188
  if (targets.every((t) => t.scope === "global"))
@@ -12844,7 +13195,7 @@ function alreadyWired(settingsPath) {
12844
13195
  if (!existsSync8(settingsPath))
12845
13196
  return false;
12846
13197
  try {
12847
- const parsed = JSON.parse(readFileSync13(settingsPath, "utf8"));
13198
+ const parsed = JSON.parse(readFileSync14(settingsPath, "utf8"));
12848
13199
  return isOurStatusLine(parsed.statusLine);
12849
13200
  } catch {
12850
13201
  return false;
@@ -13098,7 +13449,7 @@ var init_useMouseWheel = __esm(() => {
13098
13449
  });
13099
13450
 
13100
13451
  // src/tui/shell/Dashboard.tsx
13101
- import { readFileSync as readFileSync14 } from "node:fs";
13452
+ import { readFileSync as readFileSync15 } from "node:fs";
13102
13453
  import { join as join23 } from "node:path";
13103
13454
  import { Box as Box30, Text as Text35, useApp as useApp2, useInput as useInput8 } from "ink";
13104
13455
  import { useCallback as useCallback6, useMemo as useMemo9, useState as useState8 } from "react";
@@ -13897,7 +14248,7 @@ var seed = (configDir2, initial) => {
13897
14248
  if (initial)
13898
14249
  return initial;
13899
14250
  try {
13900
- return loadConfig(readFileSync14(join23(configDir2, "ccsidekick", "config.toml"), "utf8"));
14251
+ return loadConfig(readFileSync15(join23(configDir2, "ccsidekick", "config.toml"), "utf8"));
13901
14252
  } catch {
13902
14253
  return loadConfig("");
13903
14254
  }
@@ -14540,8 +14891,8 @@ var init_Wizard = __esm(() => {
14540
14891
  });
14541
14892
 
14542
14893
  // src/tui/shell/App.tsx
14543
- import { existsSync as existsSync9, readFileSync as readFileSync15 } from "node:fs";
14544
- import { homedir as homedir6 } from "node:os";
14894
+ import { existsSync as existsSync9, readFileSync as readFileSync16 } from "node:fs";
14895
+ import { homedir as homedir5 } from "node:os";
14545
14896
  import { join as join24 } from "node:path";
14546
14897
  import { useInput as useInput10 } from "ink";
14547
14898
  import { useMemo as useMemo10, useState as useState10 } from "react";
@@ -14555,7 +14906,7 @@ function welcomeTheme(suggestedDir) {
14555
14906
  if (suggestedDir === undefined || suggestedDir === "")
14556
14907
  return THEMES.houston;
14557
14908
  try {
14558
- const text = readFileSync15(join24(suggestedDir, "ccsidekick", "config.toml"), "utf8");
14909
+ const text = readFileSync16(join24(suggestedDir, "ccsidekick", "config.toml"), "utf8");
14559
14910
  return pickTheme(loadConfig(text).theme.name);
14560
14911
  } catch {
14561
14912
  return THEMES.houston;
@@ -14604,7 +14955,7 @@ function App(props) {
14604
14955
  const rows = props.rows ?? live.rows;
14605
14956
  const atFloor = breakpointFor(columns, rows) === "floor";
14606
14957
  useInput10(() => {});
14607
- const home = props.homeDir ?? homedir6();
14958
+ const home = props.homeDir ?? homedir5();
14608
14959
  const cwd = props.cwd ?? process.cwd();
14609
14960
  const suggested = props.suggestedDir ?? env2["CLAUDE_CONFIG_DIR"];
14610
14961
  const wordmarkHues = useMemo10(() => welcomeTheme(suggested).hues, [suggested]);
@@ -14715,12 +15066,12 @@ var init_shell = __esm(() => {
14715
15066
  init_cli();
14716
15067
  init_domain();
14717
15068
  init_tui();
14718
- import { homedir as homedir7 } from "node:os";
15069
+ import { homedir as homedir6 } from "node:os";
14719
15070
  import { join as join25 } from "node:path";
14720
15071
  import { fileURLToPath as fileURLToPath2 } from "node:url";
14721
15072
  var sub = process.argv[2];
14722
15073
  function claudeDir() {
14723
- return process.env["CLAUDE_CONFIG_DIR"] ?? join25(homedir7(), ".claude");
15074
+ return process.env["CLAUDE_CONFIG_DIR"] ?? join25(homedir6(), ".claude");
14724
15075
  }
14725
15076
  function printHelp() {
14726
15077
  process.stdout.write([
@@ -14765,7 +15116,7 @@ async function launchTui() {
14765
15116
  const renderBin = fileURLToPath2(new URL("ccsidekick-render.js", import.meta.url));
14766
15117
  const suggested = process.env["CLAUDE_CONFIG_DIR"];
14767
15118
  const instance = render(createElement(App2, {
14768
- homeDir: homedir7(),
15119
+ homeDir: homedir6(),
14769
15120
  renderBin,
14770
15121
  ...suggested !== undefined ? { suggestedDir: suggested } : {}
14771
15122
  }));
@@ -14798,7 +15149,7 @@ async function main() {
14798
15149
  renderBin,
14799
15150
  readConfig: defaultReadConfig,
14800
15151
  cwd: process.cwd(),
14801
- homeDir: homedir7(),
15152
+ homeDir: homedir6(),
14802
15153
  env: process.env,
14803
15154
  out: (s) => process.stdout.write(s),
14804
15155
  err: (s) => process.stderr.write(s)