claude-token-saver 2.1.0 → 2.5.0

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/src/history.js CHANGED
@@ -3,6 +3,11 @@
3
3
  * (none → warning, warning A → warning B, warning → none). One markdown file
4
4
  * per calendar day so users can pinpoint "when did this start" easily.
5
5
  *
6
+ * Each event is written bilingually: the canonical English line first, the
7
+ * Korean translation as an indented "└" continuation right below. The chip
8
+ * text itself stays as-is (its symbol+English is part of the UX surface), but
9
+ * the diagnostic detail and resolved-status verbs are translated.
10
+ *
6
11
  * Storage path is platform-aware (see paths.userDataDir):
7
12
  * Windows: %APPDATA%\claude-token-saver\history\YYYY-MM-DD.md
8
13
  * macOS: ~/Library/Application Support/claude-token-saver/history/YYYY-MM-DD.md
@@ -15,6 +20,9 @@
15
20
  import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
16
21
  import { join } from 'node:path';
17
22
  import { userDataDir } from './paths.js';
23
+ import { formatResetIn, formatResetClock } from './format-time.js';
24
+ import { labelForKey } from './window-labels.js';
25
+ import { ISSUE_TIPS, CHIP_TO_CODES, CAP_TIPS } from './advice.js';
18
26
 
19
27
  const BASE_DIR = userDataDir();
20
28
  const HISTORY_DIR = join(BASE_DIR, 'history');
@@ -53,14 +61,116 @@ function saveState(state) {
53
61
  writeFileSync(STATE_PATH, JSON.stringify(state) + '\n');
54
62
  }
55
63
 
56
- function appendDayLine(line, date = new Date()) {
64
+ /**
65
+ * Map a chip's English label to its Korean equivalent.
66
+ * Returns the input unchanged if no mapping is registered (forward-compat
67
+ * with chips added in advice.js after this map was last updated).
68
+ */
69
+ function chipKo(chip) {
70
+ if (!chip) return chip;
71
+ const map = {
72
+ '⚠ 1M ON': '⚠ 1M 컨텍스트 활성',
73
+ '⚠ Cache miss': '⚠ 캐시 미스',
74
+ '⚠ Rebuild churn': '⚠ 캐시 재빌드 빈발',
75
+ '⚠ Input spike': '⚠ 입력 급증',
76
+ '⚠ Output heavy': '⚠ 출력 과다',
77
+ '⚠ Call surge': '⚠ 호출 급증',
78
+ '⚠ 5m TTL': '⚠ 5분 TTL',
79
+ '⏳ Cache expires': '⏳ 캐시 만료 임박',
80
+ '💰 Cache saved': '💰 캐시 절약',
81
+ '🧠 Cache hit': '🧠 캐시 적중',
82
+ };
83
+ return map[chip] || chip;
84
+ }
85
+
86
+ /**
87
+ * Translate the diagnostic detail string to Korean. The detail is constructed
88
+ * in cli.js and follows two stable shapes:
89
+ * "Context auto-promoted to 1M (max single-request {N}k tokens)"
90
+ * "session {ID}: CODE_A, CODE_B"
91
+ * Anything else falls through unchanged.
92
+ */
93
+ function detailKo(detail) {
94
+ if (!detail) return detail;
95
+ const m1 = detail.match(/^Context auto-promoted to 1M \(max single-request (\d+)k tokens\)$/);
96
+ if (m1) return `1M 컨텍스트 자동 활성 (단일 요청 최대 ${m1[1]}k 토큰)`;
97
+ const m2 = detail.match(/^session ([^:]+): (.+)$/);
98
+ if (m2) {
99
+ const codeKo = {
100
+ LOW_HIT_RATE: '캐시 적중률 낮음',
101
+ FREQUENT_CACHE_REBUILD: '캐시 재빌드 빈발',
102
+ OUTPUT_HEAVY: '출력 과다',
103
+ INPUT_SPIKE: '입력 급증',
104
+ CALL_SURGE: '호출 급증',
105
+ TTL_5M: '5분 TTL',
106
+ };
107
+ const codes = m2[2]
108
+ .split(',')
109
+ .map((c) => c.trim())
110
+ .map((c) => codeKo[c] || c)
111
+ .join(', ');
112
+ return `세션 ${m2[1]}: ${codes}`;
113
+ }
114
+ return detail;
115
+ }
116
+
117
+ /**
118
+ * Resolve the diagnostic codes that apply to a transition. We try, in order:
119
+ * 1. `detail` of shape "session ID: CODE_A, CODE_B" (chip transitions with
120
+ * explicit per-session codes — the richest source).
121
+ * 2. `chip` text mapped via CHIP_TO_CODES (covers 1M ON and chips that fire
122
+ * without a per-session detail).
123
+ * Returns an array of unique codes, possibly empty.
124
+ */
125
+ function codesForEvent(chip, detail) {
126
+ const out = [];
127
+ if (detail) {
128
+ const m = detail.match(/^session [^:]+:\s*(.+)$/);
129
+ if (m) {
130
+ for (const c of m[1].split(',').map((s) => s.trim()).filter(Boolean)) {
131
+ if (!out.includes(c)) out.push(c);
132
+ }
133
+ }
134
+ }
135
+ if (chip && CHIP_TO_CODES[chip]) {
136
+ for (const c of CHIP_TO_CODES[chip]) if (!out.includes(c)) out.push(c);
137
+ }
138
+ return out;
139
+ }
140
+
141
+ /**
142
+ * Build the "💡 ..." tip block for a given chip+detail pair. Returns
143
+ * { en: string, ko: string } where each may be empty if no tips apply
144
+ * (resolution events, unknown chips, etc.).
145
+ */
146
+ function tipsForEvent(chip, detail) {
147
+ const codes = codesForEvent(chip, detail);
148
+ const enLines = [];
149
+ const koLines = [];
150
+ for (const code of codes) {
151
+ const tip = ISSUE_TIPS[code];
152
+ if (!tip) continue;
153
+ enLines.push(` 💡 ${tip.en}`);
154
+ koLines.push(` 💡 ${tip.ko}`);
155
+ }
156
+ return { en: enLines.join('\n'), ko: koLines.join('\n') };
157
+ }
158
+
159
+ function appendDayLine(en, ko, date = new Date(), tips = null) {
57
160
  ensureDir(HISTORY_DIR);
58
161
  const path = join(HISTORY_DIR, `${ymd(date)}.md`);
162
+ // Bilingual event line. Tip lines (when present) follow on their own lines so
163
+ // the file reads as: event-en / └ event-ko / 💡 tip-en / 💡 tip-ko.
164
+ let block = ko && ko !== en ? `${en}\n └ ${ko}\n` : `${en}\n`;
165
+ if (tips && tips.en) block += `${tips.en}\n`;
166
+ if (tips && tips.ko) block += `${tips.ko}\n`;
59
167
  if (!existsSync(path)) {
60
- writeFileSync(path, `# Token Monitor — ${ymd(date)}\n\n## Events\n${line}\n`);
168
+ const header = `# Token Monitor / 토큰 모니터 — ${ymd(date)}\n\n## Events / 이벤트\n`;
169
+ writeFileSync(path, header + block);
61
170
  } else {
62
171
  const existing = readFileSync(path, 'utf8');
63
- writeFileSync(path, existing.endsWith('\n') ? existing + line + '\n' : existing + '\n' + line + '\n');
172
+ const sep = existing.endsWith('\n') ? '' : '\n';
173
+ writeFileSync(path, existing + sep + block);
64
174
  }
65
175
  }
66
176
 
@@ -77,16 +187,28 @@ export function recordChip(chip, contextHints = {}) {
77
187
 
78
188
  if (current === last) return false;
79
189
 
80
- let line;
190
+ const detail = contextHints.detail || null;
191
+ const detailEn = detail ? ` — ${detail}` : '';
192
+ const detailKr = detail ? ` — ${detailKo(detail)}` : '';
193
+
194
+ let en, ko;
195
+ // Tips only on warning entry/transition (not resolution) — based on the
196
+ // *new* chip so users see how to handle what's currently active.
197
+ let tips = null;
81
198
  if (current && !last) {
82
- line = `- ${hms(now)} ${current}` + (contextHints.detail ? ` — ${contextHints.detail}` : '');
199
+ en = `- ${hms(now)} ${current}${detailEn}`;
200
+ ko = `${chipKo(current)}${detailKr}`;
201
+ tips = tipsForEvent(current, detail);
83
202
  } else if (current && last) {
84
- line = `- ${hms(now)} ${last} → ${current}` + (contextHints.detail ? ` — ${contextHints.detail}` : '');
203
+ en = `- ${hms(now)} ${last} → ${current}${detailEn}`;
204
+ ko = `${chipKo(last)} → ${chipKo(current)}${detailKr}`;
205
+ tips = tipsForEvent(current, detail);
85
206
  } else {
86
207
  // current === null, last was something — warning resolved
87
- line = `- ${hms(now)} ✓ resolved (was ${last})`;
208
+ en = `- ${hms(now)} ✓ resolved (was ${last})`;
209
+ ko = `✓ 해소됨 (이전: ${chipKo(last)})`;
88
210
  }
89
- appendDayLine(line, now);
211
+ appendDayLine(en, ko, now, tips);
90
212
  saveState({ chip: current, ts: now.toISOString() });
91
213
  return true;
92
214
  }
@@ -110,6 +232,84 @@ export function readRecent(days = 7) {
110
232
  return out;
111
233
  }
112
234
 
235
+ /**
236
+ * Record entering or exiting the cap-warn (>=90%) zone for a rate-limit
237
+ * window. Each window (keyed by its stdin name — e.g. `five_hour`, `seven_day`)
238
+ * has its own dedup slot, so the daily file gets two transitions max per
239
+ * window per warning episode.
240
+ *
241
+ * @param {{ key: string, usedPct: number, resetsAt: number|null } | null} window
242
+ * @returns {boolean} true when a line was appended
243
+ */
244
+ export function recordCapTransition(window) {
245
+ if (!window || typeof window.key !== 'string') return false;
246
+ const state = loadState();
247
+ const slotKey = `cap_${window.key}`;
248
+ const wasWarn = !!state[slotKey];
249
+ const isWarn = Number.isFinite(window.usedPct) && window.usedPct >= 90;
250
+ if (wasWarn === isWarn) return false;
251
+
252
+ const now = new Date();
253
+ const labels = labelForKey(window.key);
254
+ const labelEn = labels.short;
255
+ // Korean labels map only the well-known windows; everything else falls back
256
+ // to the English short label (still readable for the bilingual line).
257
+ const KO_OVERRIDES = {
258
+ five_hour: '5시간 윈도',
259
+ seven_day: '7일 윈도',
260
+ seven_day_sonnet: '7일 윈도 (Sonnet)',
261
+ seven_day_opus: '7일 윈도 (Opus)',
262
+ };
263
+ const labelKo = KO_OVERRIDES[window.key] || labels.short;
264
+ let en;
265
+ let ko;
266
+ if (isWarn) {
267
+ const pct = Math.round(window.usedPct);
268
+ const reset = formatResetIn(window.resetsAt, now);
269
+ const clock = formatResetClock(window.resetsAt, now);
270
+ const tail = reset && clock
271
+ ? ` (resets in ${reset}, at ${clock})`
272
+ : reset
273
+ ? ` (resets in ${reset})`
274
+ : clock
275
+ ? ` (resets at ${clock})`
276
+ : '';
277
+ const tailKo = reset && clock
278
+ ? ` (${clock}에 리셋, 남은 ${reset})`
279
+ : reset
280
+ ? ` (리셋까지 ${reset})`
281
+ : clock
282
+ ? ` (${clock}에 리셋)`
283
+ : '';
284
+ en = `- ${hms(now)} 🚨 ${labelEn} ${pct}% cap warning${tail}`;
285
+ ko = `🚨 ${labelKo} ${pct}% 캡 경고${tailKo}`;
286
+ } else {
287
+ en = `- ${hms(now)} ✓ ${labelEn} cap warning resolved`;
288
+ ko = `✓ ${labelKo} 캡 경고 해소`;
289
+ }
290
+ // Cap-warn entry → handoff tip; resolution → no tip (just the ✓ line).
291
+ const capTips = isWarn ? { en: ` 💡 ${CAP_TIPS.en}`, ko: ` 💡 ${CAP_TIPS.ko}` } : null;
292
+ appendDayLine(en, ko, now, capTips);
293
+ state[slotKey] = isWarn;
294
+ saveState(state);
295
+ return true;
296
+ }
297
+
298
+ /**
299
+ * Record a handoff write — invoked by the `handoff` subcommand so
300
+ * `claude-token-saver history` shows when work was backed up to a HANDOFF file.
301
+ *
302
+ * @param {string} filePath
303
+ * @returns {boolean}
304
+ */
305
+ export function recordHandoff(filePath) {
306
+ const now = new Date();
307
+ const en = `- ${hms(now)} 📝 handoff written: ${filePath}`;
308
+ const ko = `📝 핸드오프 백업 작성: ${filePath}`;
309
+ appendDayLine(en, ko, now);
310
+ return true;
311
+ }
312
+
113
313
  /**
114
314
  * List all available history file dates (sorted newest first).
115
315
  */
package/src/installer.js CHANGED
@@ -15,7 +15,7 @@ import { claudeUserDir } from './paths.js';
15
15
 
16
16
  const SKILL_BODY = `---
17
17
  name: claude-token-saver
18
- description: Use when the user mentions Claude Code token usage, prompt cache hit rate, TTL/expiry, the 1M context window, cache misses, output spikes, or anything in the statusline produced by claude-token-saver (chips like "⚠ 1M ON", "⚠ Input spike", "⚠ Cache miss", "⚠ 5m TTL", "⚠ Rebuild churn", "⚠ Output heavy", "⚠ Call surge", "⏳ Cache expires", "💰 Cache saved", "🧠 Cache hit"). Also use when they ask to view token-usage history or want to understand a warning they just saw.
18
+ description: Use when the user mentions Claude Code token usage, prompt cache hit rate, TTL/expiry, the 1M context window, cache misses, output spikes, rate-limit caps (5h/7d), or anything in the statusline produced by claude-token-saver (chips like "🚨 5H 94%", "🚨 7D 92%", "⚠ 1M ON", "⚠ Input spike", "⚠ Cache miss", "⚠ 5m TTL", "⚠ Rebuild churn", "⚠ Output heavy", "⚠ Call surge", "⏳ Cache expires", "💰 Cache saved", "🧠 Cache hit"). Also use when they ask to view token-usage history, want to understand a warning they just saw, or want to back up work before a session cap with \`claude-token-saver handoff\`.
19
19
  ---
20
20
 
21
21
  # claude-token-saver — Claude Code Token Monitor
@@ -26,28 +26,38 @@ countdown, savings, and (when relevant) a leading warning chip.
26
26
 
27
27
  ## When this skill should activate
28
28
 
29
- - The user references any chip wording: \`⚠ 1M ON\`, \`⚠ Input spike\`,
30
- \`⚠ Cache miss\`, \`⚠ 5m TTL\`, \`⚠ Rebuild churn\`, \`⚠ Output heavy\`,
31
- \`⚠ Call surge\`.
29
+ - The user references any chip wording: \`🚨 5H NN%\`, \`🚨 7D NN%\`,
30
+ \`⚠ 1M ON\`, \`⚠ Input spike\`, \`⚠ Cache miss\`, \`⚠ 5m TTL\`,
31
+ \`⚠ Rebuild churn\`, \`⚠ Output heavy\`, \`⚠ Call surge\`.
32
32
  - The user asks "why is my cache hit rate low", "what does this warning mean",
33
33
  "when did this start happening", or similar.
34
+ - The user is approaching a rate-limit cap and wants to back up the current
35
+ work so a fresh session can continue (point them at
36
+ \`claude-token-saver handoff\`).
34
37
  - The user wants to see the token-usage history file or asks for a summary
35
38
  of recent warnings.
36
39
 
37
40
  ## What to do
38
41
 
39
- 1. **Identify the chip.** If the user pasted a statusline, pull out the leading
40
- \`⚠ ...\` chip. That maps to a specific issue category.
41
- 2. **Show recent history.** Run \`claude-token-saver history\` (default last 7
42
- days) to see the chronology of warning transitions. Each entry is timestamped
43
- and includes a short detail string.
44
- 3. **Drill down on the live state.** Run \`claude-token-saver --days 1\` (or
42
+ 1. **Lead with the most recent warning + how to handle it.** Run
43
+ \`claude-token-saver last\` first. It returns the most recent warning event
44
+ (chip + detail + timestamp) plus the full advice block for it. Surface that
45
+ to the user before anything else — this is what they came for.
46
+ 2. **Identify the chip.** If the user pasted a statusline (instead of relying
47
+ on \`last\`), pull out the leading \`⚠ ...\` chip. That maps to a specific
48
+ issue category.
49
+ 3. **Show recent history.** Run \`claude-token-saver history\` (default last 7
50
+ days) to see the chronology of warning transitions. Each entry is timestamped,
51
+ bilingual (English line + 한국어), and includes a \`💡\` action tip inline.
52
+ 4. **Drill down on the live state.** Run \`claude-token-saver --days 1\` (or
45
53
  another window) to render the full table view, which lists per-session
46
54
  spikes and recommended actions.
47
- 4. **Explain the warning** in plain language. Use the chip → cause table:
55
+ 5. **Explain the warning** in plain language. Use the chip → cause table:
48
56
 
49
57
  | Chip | Likely cause |
50
58
  | ------------------ | ----------------------------------------------------- |
59
+ | \`🚨 5H NN%\` | 5-hour rate-limit window at NN% (>=90%). Cap is imminent. |
60
+ | \`🚨 7D NN%\` | 7-day rate-limit window at NN% (>=90%). Pace yourself. |
51
61
  | \`⚠ 1M ON\` | Auto-promoted to 1M context (Opus 4.7+ Max default). |
52
62
  | \`⚠ Input spike\` | One request consumed >250k or >3× the recent p95. |
53
63
  | \`⚠ Cache miss\` | Cache hit rate dropped below ~70%. |
@@ -56,17 +66,24 @@ countdown, savings, and (when relevant) a leading warning chip.
56
66
  | \`⚠ Output heavy\` | Output ratio dominates input — inspect long generations. |
57
67
  | \`⚠ Call surge\` | Request count is well above baseline. |
58
68
 
59
- 5. **Suggest the next action.** For 1M ON, mention
60
- \`CLAUDE_CODE_DISABLE_1M_CONTEXT=1\`. For 5m TTL, point at the Max plan's
61
- 1h bucket. For input spike, suggest splitting the conversation or
62
- compacting context.
69
+ 6. **Suggest the next action.** For \`🚨 5H/7D\` chips, recommend running
70
+ \`claude-token-saver handoff\` to back up the current work to a
71
+ \`HANDOFF-*.md\` file before the cap hits, then continue in a fresh
72
+ session. For 1M ON, mention \`CLAUDE_CODE_DISABLE_1M_CONTEXT=1\`. For
73
+ 5m TTL, point at the Max plan's 1h bucket. For input spike, suggest
74
+ splitting the conversation or compacting context.
63
75
 
64
76
  ## Useful commands
65
77
 
78
+ - \`claude-token-saver last\` — most recent warning + full advice (start here).
79
+ - \`claude-token-saver last --days 7\` — widen the lookback window.
66
80
  - \`claude-token-saver\` — full table report (default last 1 day).
67
81
  - \`claude-token-saver --days 7\` — wider window.
68
- - \`claude-token-saver history\` — recent warning transitions per day.
82
+ - \`claude-token-saver history\` — recent warning transitions per day, with
83
+ inline \`💡\` action tips.
69
84
  - \`claude-token-saver history --days 30\` — longer history.
85
+ - \`claude-token-saver handoff\` — write a HANDOFF-*.md template in cwd
86
+ capturing git status + cap snapshot, so a fresh session can resume cleanly.
70
87
  - \`claude-token-saver mode\` — show statusline preferences.
71
88
  - \`claude-token-saver mode icon verbose 1d\` — change preferences.
72
89
 
@@ -85,23 +102,31 @@ description: Show recent claude-token-saver warning history and a fresh report.
85
102
  ---
86
103
 
87
104
  You are responding to the \`/token-monitor\` slash command. The user wants a
88
- quick read of their Claude Code token usage and any active warnings.
105
+ quick read of their Claude Code token usage and any active warnings — most
106
+ importantly: **what just happened, and how do I handle it?**
89
107
 
90
108
  Steps:
91
109
 
92
- 1. Run \`claude-token-saver history --days 7\` and capture the output. This
93
- prints recent warning transitions (timestamps + chip + short detail).
94
- 2. Run \`claude-token-saver --days 1\` and capture the output. This prints the
95
- full table view: TTL breakdown, cost impact, daily trend, and any active
96
- spikes with recommended actions.
97
- 3. Summarize for the user:
98
- - **Active warnings** — list the most recent unresolved chip(s) with the
99
- time they appeared.
100
- - **Today's pattern** — when warnings cluster in time, mention it.
101
- - **Recommended action** — pick the highest-leverage suggestion from the
102
- table report's "Recommended actions" section.
103
- 4. If the history is empty, say so plainly — no warnings means the cache has
104
- been healthy in the configured window.
110
+ 1. **Lead with the most recent warning.** Run \`claude-token-saver last\`
111
+ first and surface its output verbatim (or lightly summarized) at the top
112
+ of your reply. This returns the latest warning event (chip + detail +
113
+ timestamp) followed by the full advice block. If \`last\` says no recent
114
+ warnings, mention that and skip ahead — you can stop here unless the user
115
+ asked for more.
116
+ 2. Run \`claude-token-saver history --days 7\` and capture the output. Use it
117
+ only to add context — e.g. "this is the 3rd cache miss today" — not to
118
+ re-print the whole file. Each entry is bilingual and includes a \`💡\`
119
+ action tip inline.
120
+ 3. Run \`claude-token-saver --days 1\` and capture the output for any extra
121
+ color you want to add: TTL breakdown, cost impact, daily trend, or active
122
+ spikes. Skip if step 1 already covered what the user needs.
123
+ 4. Summarize for the user:
124
+ - **What just fired** — the chip + the time + a sentence on what caused it
125
+ (from \`last\`).
126
+ - **What to do** — the action tip from \`last\`. For cap-warn (\`🚨 5H/7D NN%\`),
127
+ surface \`claude-token-saver handoff\` prominently so they can back up
128
+ state before the cap blocks them.
129
+ - **Today's pattern** (optional) — when warnings cluster in time, mention it.
105
130
 
106
131
  Keep the summary to ~10 lines. The user can re-run the underlying commands
107
132
  themselves for the full output.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Friendly labels + icons for `rate_limits.*` keys from Claude Code's stdin
3
+ * payload. Known keys (`five_hour`, `seven_day`) get curated short/long labels
4
+ * and dedicated icons. Unknown keys are passed through with a derived label so
5
+ * any future window Anthropic adds (e.g. `seven_day_sonnet`) renders without
6
+ * a code change.
7
+ */
8
+
9
+ // `short` is used by the cap-warn chip and history records (stable shape so
10
+ // parsers/dedup keep working). `usageLabel` is the friendlier word that the
11
+ // always-on usage segment renders next to the icon — empty string means the
12
+ // icon alone carries the meaning (5H 'session/now' is the implicit default).
13
+ const KNOWN = {
14
+ // ✦ reads as "AI token unit" (Anthropic/OpenAI/Gemini sparkle motif), more
15
+ // on-theme than the 🪙 coin which suggested in-app currency. Same width as
16
+ // 📅 in monospace terminals so the chip alignment stays stable.
17
+ // 'current' mirrors how `weekly` reads next to 7D — names the window in
18
+ // plain English so a glance at `✦ current ████▒░ 72%` tells the eye what's
19
+ // being measured without parsing the icon's meaning.
20
+ five_hour: { short: '5H', long: 'Current session', icon: '✦', usageLabel: 'current' },
21
+ seven_day: { short: '7D', long: 'Current week', icon: '📅', usageLabel: 'weekly' },
22
+ // Speculative — `/usage` shows a Sonnet-only weekly bucket, so if Anthropic
23
+ // ever surfaces it on stdin we render with a sensible default already.
24
+ seven_day_sonnet: { short: '7D-S', long: 'Current week (Sonnet)', icon: '🅂', usageLabel: 'weekly (Sonnet)' },
25
+ seven_day_opus: { short: '7D-O', long: 'Current week (Opus)', icon: '🅾', usageLabel: 'weekly (Opus)' },
26
+ };
27
+
28
+ function deriveShort(key) {
29
+ // "five_hour" → "5H"; "seven_day_sonnet" → "7DS"; arbitrary key → uppercase initials
30
+ const m = key.match(/^(\d+)_?([a-z]+)/);
31
+ if (m) {
32
+ const num = m[1];
33
+ const word = m[2];
34
+ const letter = word.charAt(0).toUpperCase();
35
+ const tail = key.slice(m[0].length);
36
+ const suffix = tail
37
+ .split('_')
38
+ .filter(Boolean)
39
+ .map((s) => s.charAt(0).toUpperCase())
40
+ .join('');
41
+ return `${num}${letter}${suffix}`;
42
+ }
43
+ return key
44
+ .split('_')
45
+ .filter(Boolean)
46
+ .map((s) => s.charAt(0).toUpperCase())
47
+ .join('') || key;
48
+ }
49
+
50
+ function deriveLong(key) {
51
+ return key
52
+ .split('_')
53
+ .map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w))
54
+ .join(' ');
55
+ }
56
+
57
+ export function labelForKey(key) {
58
+ if (KNOWN[key]) return KNOWN[key];
59
+ const short = deriveShort(key);
60
+ return { short, long: deriveLong(key), icon: '⏱', usageLabel: short };
61
+ }
@@ -1,52 +0,0 @@
1
- #!/bin/sh
2
- # Combine rz1989s/claude-code-statusline (rich layout: repo, cost, MCP, prayer times)
3
- # with claude-token-saver (cache hit rate, TTL countdown, 1M-context detection,
4
- # spike diagnosis). The two projects don't overlap — rz1989s runs first, our
5
- # cache chip is appended as the final segment.
6
- #
7
- # Install:
8
- # 1) Follow rz1989s install instructions so bash ~/.claude/statusline.sh works:
9
- # https://github.com/rz1989s/claude-code-statusline
10
- # 2) npm install -g claude-token-saver (or rely on npx — fallback below)
11
- # 3) Save this file as: ~/.claude/statusline-with-rz1989s.sh
12
- # chmod +x ~/.claude/statusline-with-rz1989s.sh
13
- # 4) In ~/.claude/settings.json:
14
- # {
15
- # "statusLine": {
16
- # "type": "command",
17
- # "command": "bash ~/.claude/statusline-with-rz1989s.sh",
18
- # "refreshInterval": 1
19
- # }
20
- # }
21
- #
22
- # refreshInterval: 1 keeps our TTL countdown ticking while you're idle.
23
- # Drop to 2 or 5 for lower local CPU if your rz1989s config does heavy work.
24
-
25
- # Claude Code sends the session JSON on stdin. Both tools want to read it,
26
- # so we buffer it and tee to each.
27
- input=$(cat)
28
-
29
- # --- 1) rz1989s layout (if installed) ---
30
- RZ_STATUSLINE="${CLAUDE_RZ_STATUSLINE:-$HOME/.claude/statusline.sh}"
31
- if [ -f "$RZ_STATUSLINE" ]; then
32
- printf '%s' "$input" | bash "$RZ_STATUSLINE"
33
- # Separator between the two tools. Dim pipe.
34
- printf ' \033[90m|\033[00m '
35
- fi
36
-
37
- # --- 2) claude-token-saver ---
38
- # Pass --exclude-session so the current session's tool calls don't reset the
39
- # TTL countdown. The path comes from the session JSON if present.
40
- session_path=$(printf '%s' "$input" | sed -n 's/.*"path"[[:space:]]*:[[:space:]]*"\([^"]*\.jsonl\)".*/\1/p' | head -n1)
41
- exclude_flag=""
42
- if [ -n "$session_path" ]; then
43
- exclude_flag="--exclude-session $session_path"
44
- fi
45
-
46
- if command -v claude-token-saver >/dev/null 2>&1; then
47
- # shellcheck disable=SC2086
48
- claude-token-saver --statusline --icon $exclude_flag 2>/dev/null || true
49
- else
50
- # shellcheck disable=SC2086
51
- npx --yes claude-token-saver@latest --statusline --icon $exclude_flag 2>/dev/null || true
52
- fi