@hasna/statusline 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,35 @@
1
1
  // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
2
4
  var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
3
33
  var __returnValue = (v) => v;
4
34
  function __exportSetter(name, newValue) {
5
35
  this[name] = __returnValue.bind(null, newValue);
@@ -29,6 +59,7 @@ function defaultConfig() {
29
59
  "loc",
30
60
  "model-context",
31
61
  "context-remaining",
62
+ "cache-rate",
32
63
  "cost"
33
64
  ]
34
65
  };
@@ -94,7 +125,7 @@ function numberOr(v) {
94
125
  }
95
126
 
96
127
  // src/segments/index.ts
97
- import { hostname } from "os";
128
+ import { homedir as homedir5, hostname } from "os";
98
129
 
99
130
  // src/format.ts
100
131
  function compactAge(seconds) {
@@ -134,10 +165,10 @@ function compactDuration(ms) {
134
165
  return `${s}s`;
135
166
  if (s < 3600) {
136
167
  const rem2 = s % 60;
137
- return rem2 ? `${Math.floor(s / 60)}m${rem2}s` : `${Math.floor(s / 60)}m`;
168
+ return rem2 ? `${Math.floor(s / 60)}m ${rem2}s` : `${Math.floor(s / 60)}m`;
138
169
  }
139
170
  const rem = Math.floor(s % 3600 / 60);
140
- return rem ? `${Math.floor(s / 3600)}h${rem}m` : `${Math.floor(s / 3600)}h`;
171
+ return rem ? `${Math.floor(s / 3600)}h ${rem}m` : `${Math.floor(s / 3600)}h`;
141
172
  }
142
173
 
143
174
  // src/git.ts
@@ -194,14 +225,145 @@ function trackedLineCount(cwd) {
194
225
  }
195
226
 
196
227
  // src/context-window.ts
197
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
198
- function contextUsage(ctx) {
199
- const path = ctx.transcriptPath;
228
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
229
+
230
+ // src/providers/codex.ts
231
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
232
+ import { homedir as homedir2 } from "os";
233
+ import { join as join2 } from "path";
234
+ function latestCodexSessionPath(home = homedir2()) {
235
+ const root = join2(home, ".codex", "sessions");
236
+ if (!existsSync2(root))
237
+ return null;
238
+ let newest = null;
239
+ let newestMtime = 0;
240
+ try {
241
+ for (const year of readdirSync(root)) {
242
+ const yearDir = join2(root, year);
243
+ if (!statSync(yearDir, { throwIfNoEntry: false })?.isDirectory())
244
+ continue;
245
+ for (const month of readdirSync(yearDir)) {
246
+ const monthDir = join2(yearDir, month);
247
+ if (!statSync(monthDir, { throwIfNoEntry: false })?.isDirectory())
248
+ continue;
249
+ for (const day of readdirSync(monthDir)) {
250
+ const dayDir = join2(monthDir, day);
251
+ if (!statSync(dayDir, { throwIfNoEntry: false })?.isDirectory())
252
+ continue;
253
+ for (const file of readdirSync(dayDir)) {
254
+ if (!file.startsWith("rollout-") || !file.endsWith(".jsonl"))
255
+ continue;
256
+ const full = join2(dayDir, file);
257
+ const mtime = statSync(full, { throwIfNoEntry: false })?.mtimeMs ?? 0;
258
+ if (mtime > newestMtime) {
259
+ newestMtime = mtime;
260
+ newest = full;
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ } catch {
267
+ return null;
268
+ }
269
+ return newest;
270
+ }
271
+ function codexCacheRate(sessionPath) {
272
+ const path = sessionPath ?? latestCodexSessionPath();
200
273
  if (!path || !existsSync2(path))
201
274
  return null;
202
275
  let lines;
203
276
  try {
204
277
  lines = readFileSync2(path, "utf8").split(`
278
+ `);
279
+ } catch {
280
+ return null;
281
+ }
282
+ let usage = null;
283
+ for (const line of lines) {
284
+ if (!line || !line.includes("token_count"))
285
+ continue;
286
+ try {
287
+ const entry = JSON.parse(line);
288
+ if (entry?.type !== "event_msg")
289
+ continue;
290
+ if (entry?.payload?.type !== "token_count")
291
+ continue;
292
+ const info = entry?.payload?.info?.total_token_usage;
293
+ if (info && typeof info === "object")
294
+ usage = info;
295
+ } catch {
296
+ continue;
297
+ }
298
+ }
299
+ if (!usage)
300
+ return null;
301
+ const read = usage.cached_input_tokens || 0;
302
+ const input = usage.input_tokens || 0;
303
+ const write = usage.cache_write_input_tokens || 0;
304
+ const divisor = input + read + write;
305
+ if (divisor <= 0)
306
+ return null;
307
+ return Math.min(1, Math.max(0, read / divisor));
308
+ }
309
+
310
+ // src/providers/opencode.ts
311
+ import { existsSync as existsSync3 } from "fs";
312
+ import { homedir as homedir3 } from "os";
313
+ import { join as join3 } from "path";
314
+ import { Database } from "bun:sqlite";
315
+ function opencodeCacheRate(opts = {}) {
316
+ const dbPath = opts.dbPath ?? join3(homedir3(), ".local", "share", "opencode", "opencode.db");
317
+ if (!existsSync3(dbPath))
318
+ return null;
319
+ let db;
320
+ try {
321
+ db = new Database(dbPath, { readonly: true });
322
+ } catch {
323
+ return null;
324
+ }
325
+ try {
326
+ let row = tokenRow(db, "session", opts.sessionId);
327
+ if (!row)
328
+ row = tokenRow(db, "session_v2", opts.sessionId);
329
+ if (!row)
330
+ return null;
331
+ const input = row.tokens_input ?? 0;
332
+ const read = row.tokens_cache_read ?? 0;
333
+ const write = row.tokens_cache_write ?? 0;
334
+ const divisor = input + read + write;
335
+ if (divisor <= 0)
336
+ return null;
337
+ return Math.min(1, Math.max(0, read / divisor));
338
+ } catch {
339
+ return null;
340
+ } finally {
341
+ db.close();
342
+ }
343
+ }
344
+ function tokenRow(db, table, sessionId) {
345
+ try {
346
+ if (sessionId) {
347
+ return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
348
+ FROM ${table} WHERE id = ? LIMIT 1`).get(sessionId);
349
+ }
350
+ return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
351
+ FROM ${table}
352
+ WHERE tokens_input > 0 OR tokens_cache_read > 0 OR tokens_cache_write > 0
353
+ ORDER BY time_updated DESC LIMIT 1`).get();
354
+ } catch {
355
+ return null;
356
+ }
357
+ }
358
+
359
+ // src/context-window.ts
360
+ function lastUsageBlock(ctx) {
361
+ const path = ctx.transcriptPath;
362
+ if (!path || !existsSync4(path))
363
+ return null;
364
+ let lines;
365
+ try {
366
+ lines = readFileSync3(path, "utf8").split(`
205
367
  `);
206
368
  } catch {
207
369
  return null;
@@ -215,26 +377,48 @@ function contextUsage(ctx) {
215
377
  const usage = entry?.message?.usage;
216
378
  if (!usage || typeof usage.input_tokens !== "number")
217
379
  continue;
218
- const used = usage.input_tokens + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
219
- return {
220
- used,
221
- output: usage.output_tokens || 0,
222
- window: windowSize(ctx)
223
- };
380
+ return usage;
224
381
  } catch {
225
382
  continue;
226
383
  }
227
384
  }
228
385
  return null;
229
386
  }
387
+ function contextUsage(ctx) {
388
+ const usage = lastUsageBlock(ctx);
389
+ if (!usage)
390
+ return null;
391
+ const used = usage.input_tokens + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
392
+ return {
393
+ used,
394
+ output: usage.output_tokens || 0,
395
+ window: windowSize(ctx)
396
+ };
397
+ }
398
+ function cacheRate(ctx) {
399
+ if (ctx.provider === "codex")
400
+ return codexCacheRate();
401
+ if (ctx.provider === "opencode")
402
+ return opencodeCacheRate();
403
+ const usage = lastUsageBlock(ctx);
404
+ if (!usage)
405
+ return null;
406
+ const read = usage.cache_read_input_tokens || 0;
407
+ const creation = usage.cache_creation_input_tokens || 0;
408
+ const input = usage.input_tokens || 0;
409
+ const divisor = input + creation + read;
410
+ if (divisor <= 0)
411
+ return null;
412
+ return read / divisor;
413
+ }
230
414
  function windowSize(ctx) {
231
415
  return ctx.model?.id?.includes("[1m]") ? 1e6 : 200000;
232
416
  }
233
417
 
234
418
  // src/accounts.ts
235
- import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync } from "fs";
236
- import { homedir as homedir2 } from "os";
237
- import { join as join2, resolve, sep } from "path";
419
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync } from "fs";
420
+ import { homedir as homedir4 } from "os";
421
+ import { join as join4, resolve, sep } from "path";
238
422
  var CONFIG_DIR_ENV = "CLAUDE_CONFIG_DIR";
239
423
  var DEFAULT_CONFIG_SUBDIR = ".claude";
240
424
  var STORE_PATH_ENV = "ACCOUNTS_STORE_PATH";
@@ -249,14 +433,19 @@ var STATE_FILE_PREFIX = ".claude";
249
433
  var STATE_FILE_SUFFIX = ".json";
250
434
  var CUSTOM_OAUTH_ENV = "CLAUDE_CODE_CUSTOM_OAUTH_URL";
251
435
  var OAUTH_VARIANTS = ["", "-custom-oauth", "-local-oauth", "-staging-oauth"];
436
+ var CACHE_SUBDIR = "cache";
437
+ var USAGE_CACHE_SUBDIR = "usage";
438
+ var USAGE_CACHE_MAX_AGE_MS = 15 * 60 * 1000;
439
+ var SESSION_WINDOW_MAX_MS = 5 * 60 * 60 * 1000;
440
+ var SAFE_UUID = /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/;
252
441
  function str(value) {
253
442
  return typeof value === "string" && value.trim() ? value.trim() : null;
254
443
  }
255
444
  function home(env) {
256
- return str(env.HOME) ?? homedir2();
445
+ return str(env.HOME) ?? homedir4();
257
446
  }
258
447
  function expandHome(path, env) {
259
- return path === "~" || path.startsWith(`~${sep}`) ? join2(home(env), path.slice(1)) : path;
448
+ return path === "~" || path.startsWith(`~${sep}`) ? join4(home(env), path.slice(1)) : path;
260
449
  }
261
450
  function canonical(path, env) {
262
451
  const abs = resolve(expandHome(path, env));
@@ -268,7 +457,7 @@ function canonical(path, env) {
268
457
  }
269
458
  function readJson(path) {
270
459
  try {
271
- return JSON.parse(readFileSync3(path, "utf8"));
460
+ return JSON.parse(readFileSync4(path, "utf8"));
272
461
  } catch {
273
462
  return null;
274
463
  }
@@ -289,14 +478,14 @@ async function accountsPaths(env) {
289
478
  return { store: storage.storePath(), profiles: storage.profilesDir() };
290
479
  } catch {}
291
480
  }
292
- const base = homeOverride ?? join2(home(env), ...DEFAULT_ACCOUNTS_HOME);
481
+ const base = homeOverride ?? join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
293
482
  return {
294
- store: storeOverride ?? join2(base, STORE_FILE),
295
- profiles: join2(base, PROFILES_SUBDIR)
483
+ store: storeOverride ?? join4(base, STORE_FILE),
484
+ profiles: join4(base, PROFILES_SUBDIR)
296
485
  };
297
486
  }
298
487
  function registryEntry(store, configDir, env, tool) {
299
- if (!existsSync3(store))
488
+ if (!existsSync5(store))
300
489
  return null;
301
490
  const entries = readJson(store)?.profiles;
302
491
  if (!Array.isArray(entries))
@@ -317,7 +506,7 @@ function registryEntry(store, configDir, env, tool) {
317
506
  return tool ? untyped : first;
318
507
  }
319
508
  function layoutProfile(profilesRoot, configDir, env) {
320
- if (!existsSync3(configDir))
509
+ if (!existsSync5(configDir))
321
510
  return null;
322
511
  const prefix = canonical(profilesRoot, env) + sep;
323
512
  if (!configDir.startsWith(prefix))
@@ -331,7 +520,7 @@ function layoutProfile(profilesRoot, configDir, env) {
331
520
  return { configDir, profile, tool, source: "layout" };
332
521
  }
333
522
  function switchedOccupant(configDir) {
334
- const raw = readJson(join2(configDir, AUTH_STATE_SUBDIR, SWITCHED_ACCOUNT_MARKER_FILE));
523
+ const raw = readJson(join4(configDir, AUTH_STATE_SUBDIR, SWITCHED_ACCOUNT_MARKER_FILE));
335
524
  if (!raw)
336
525
  return null;
337
526
  const profile = str(raw.profile);
@@ -339,17 +528,17 @@ function switchedOccupant(configDir) {
339
528
  return profile || email ? { profile, email } : null;
340
529
  }
341
530
  function sessionConfigDir(env = process.env) {
342
- return canonical(str(env[CONFIG_DIR_ENV]) ?? join2(home(env), DEFAULT_CONFIG_SUBDIR), env);
531
+ return canonical(str(env[CONFIG_DIR_ENV]) ?? join4(home(env), DEFAULT_CONFIG_SUBDIR), env);
343
532
  }
344
533
  function sessionStateFile(env = process.env) {
345
534
  const base = canonical(str(env[CONFIG_DIR_ENV]) ?? home(env), env);
346
- const preferred = join2(base, STATE_FILE_PREFERRED);
347
- if (existsSync3(preferred))
535
+ const preferred = join4(base, STATE_FILE_PREFERRED);
536
+ if (existsSync5(preferred))
348
537
  return preferred;
349
538
  const variants = str(env[CUSTOM_OAUTH_ENV]) ? ["-custom-oauth", ...OAUTH_VARIANTS] : OAUTH_VARIANTS;
350
539
  for (const variant of variants) {
351
- const candidate = join2(base, `${STATE_FILE_PREFIX}${variant}${STATE_FILE_SUFFIX}`);
352
- if (existsSync3(candidate))
540
+ const candidate = join4(base, `${STATE_FILE_PREFIX}${variant}${STATE_FILE_SUFFIX}`);
541
+ if (existsSync5(candidate))
353
542
  return candidate;
354
543
  }
355
544
  return null;
@@ -395,13 +584,102 @@ async function sessionAccountEmail(env = process.env, tool) {
395
584
  return null;
396
585
  }
397
586
  }
587
+ function accountsHomeDir(env) {
588
+ const override = str(env[ACCOUNTS_HOME_ENV]);
589
+ return override ? expandHome(override, env) : join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
590
+ }
591
+ function sessionAccountUuid(env = process.env) {
592
+ try {
593
+ const stateFile = sessionStateFile(env);
594
+ if (!stateFile)
595
+ return null;
596
+ const uuid = str(readJson(stateFile)?.oauthAccount?.accountUuid);
597
+ return uuid && SAFE_UUID.test(uuid) ? uuid : null;
598
+ } catch {
599
+ return null;
600
+ }
601
+ }
602
+ function classifyWindow(w, fetchedAt) {
603
+ if (w.scoped === true)
604
+ return "scoped";
605
+ const group = str(w.group);
606
+ if (group === "session" || group === "weekly")
607
+ return group;
608
+ const id = str(w.id);
609
+ if (id) {
610
+ if (id.includes("session"))
611
+ return "session";
612
+ if (id.includes("weekly"))
613
+ return "weekly";
614
+ }
615
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
616
+ if (Number.isFinite(resetsAt) && fetchedAt !== null && resetsAt - fetchedAt > SESSION_WINDOW_MAX_MS) {
617
+ return "weekly";
618
+ }
619
+ return "unknown";
620
+ }
621
+ function windowHeadroom(w, now, fetchedAt) {
622
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
623
+ const rolled = Number.isFinite(resetsAt) && resetsAt <= now && (fetchedAt === null || resetsAt > fetchedAt);
624
+ if (rolled)
625
+ return 100;
626
+ const util = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : null;
627
+ if (util === null)
628
+ return null;
629
+ return Math.max(0, Math.min(100, Math.round(100 - util)));
630
+ }
631
+ function deriveUsage(usage, now) {
632
+ const u = usage ?? {};
633
+ const windows = Array.isArray(u.windows) ? u.windows : [];
634
+ const fetchedAtMs = typeof u.fetchedAt === "string" ? Date.parse(u.fetchedAt) : Number.NaN;
635
+ const fetchedAt = Number.isFinite(fetchedAtMs) ? fetchedAtMs : null;
636
+ let session = null;
637
+ let weekly = null;
638
+ let unknownMin = null;
639
+ for (const w of windows) {
640
+ const cls = classifyWindow(w, fetchedAt);
641
+ if (cls === "scoped")
642
+ continue;
643
+ const h = windowHeadroom(w, now, fetchedAt);
644
+ if (h === null)
645
+ continue;
646
+ if (cls === "session")
647
+ session = session === null ? h : Math.min(session, h);
648
+ else if (cls === "weekly")
649
+ weekly = weekly === null ? h : Math.min(weekly, h);
650
+ else
651
+ unknownMin = unknownMin === null ? h : Math.min(unknownMin, h);
652
+ }
653
+ const weeklyHeadroom = weekly !== null ? unknownMin !== null ? Math.min(weekly, unknownMin) : weekly : unknownMin;
654
+ return { sessionHeadroom: session, weeklyHeadroom };
655
+ }
656
+ function sessionUsage(env = process.env, now = new Date) {
657
+ const empty = { sessionHeadroom: null, weeklyHeadroom: null };
658
+ try {
659
+ const uuid = sessionAccountUuid(env);
660
+ if (!uuid)
661
+ return empty;
662
+ const path = join4(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
663
+ if (!existsSync5(path))
664
+ return empty;
665
+ const entry = readJson(path);
666
+ if (!entry || entry.accountUuid !== uuid)
667
+ return empty;
668
+ const fetchedAt = typeof entry.fetchedAt === "string" ? Date.parse(entry.fetchedAt) : Number.NaN;
669
+ if (!Number.isFinite(fetchedAt) || now.getTime() - fetchedAt > USAGE_CACHE_MAX_AGE_MS)
670
+ return empty;
671
+ return deriveUsage(entry.usage, now.getTime());
672
+ } catch {
673
+ return empty;
674
+ }
675
+ }
398
676
 
399
677
  // src/settings.ts
400
- import { readFileSync as readFileSync4 } from "fs";
401
- import { join as join3 } from "path";
678
+ import { readFileSync as readFileSync5 } from "fs";
679
+ import { join as join5 } from "path";
402
680
  function sessionFastMode(env = process.env) {
403
681
  try {
404
- const raw = readFileSync4(join3(sessionConfigDir(env), "settings.json"), "utf8");
682
+ const raw = readFileSync5(join5(sessionConfigDir(env), "settings.json"), "utf8");
405
683
  return JSON.parse(raw)?.fastMode === true;
406
684
  } catch {
407
685
  return false;
@@ -425,6 +703,29 @@ function friendlyModel(id) {
425
703
  return { name: name || mid, tag };
426
704
  }
427
705
  var dir = (ctx) => ctx.cwd || process.cwd();
706
+ function abbreviateHome(path) {
707
+ const home2 = homedir5();
708
+ if (!home2 || home2 === "/")
709
+ return path;
710
+ if (path === home2)
711
+ return "~";
712
+ return path.startsWith(`${home2}/`) ? `~${path.slice(home2.length)}` : path;
713
+ }
714
+ var CWD_MAX = 40;
715
+ function truncateLeft(path, max) {
716
+ return path.length <= max ? path : `\u2026${path.slice(path.length - (max - 1))}`;
717
+ }
718
+ function contextColor(ctx) {
719
+ const u = contextUsage(ctx);
720
+ if (!u || !u.window)
721
+ return null;
722
+ const pct = Math.min(100, Math.round(u.used / u.window * 100));
723
+ if (pct >= 80)
724
+ return "red";
725
+ if (pct >= 50)
726
+ return "yellow";
727
+ return "green";
728
+ }
428
729
  function modelName(ctx) {
429
730
  if (ctx.model?.displayName)
430
731
  return ctx.model.displayName.toLowerCase();
@@ -433,6 +734,24 @@ function modelName(ctx) {
433
734
  function limitPercent(pct) {
434
735
  return typeof pct === "number" ? pct : 0;
435
736
  }
737
+ function anthropicSession(ctx) {
738
+ if (ctx.rateLimits)
739
+ return true;
740
+ const id = ctx.model?.id;
741
+ return typeof id === "string" && /claude/i.test(id);
742
+ }
743
+ function usageLabel(prefix, headroom) {
744
+ return headroom === null ? `${prefix} \u2014` : `${prefix} ${headroom}%`;
745
+ }
746
+ function usageColor(headroom, binding = false) {
747
+ if (headroom === null)
748
+ return "dim";
749
+ if (headroom < 20)
750
+ return "red";
751
+ if (headroom < 50)
752
+ return "yellow";
753
+ return binding ? "green" : null;
754
+ }
436
755
  var segments = [
437
756
  {
438
757
  id: "machine",
@@ -490,10 +809,23 @@ var segments = [
490
809
  defaultEnabled: false,
491
810
  render: (ctx) => dir(ctx).split("/").pop() || null
492
811
  },
812
+ {
813
+ id: "cwd",
814
+ description: "Current working directory (full path)",
815
+ defaultEnabled: false,
816
+ color: "blue",
817
+ render(ctx) {
818
+ const path = ctx.cwd;
819
+ if (typeof path !== "string" || !path)
820
+ return null;
821
+ return truncateLeft(abbreviateHome(path), CWD_MAX);
822
+ }
823
+ },
493
824
  {
494
825
  id: "model",
495
826
  description: "Current model name",
496
827
  defaultEnabled: false,
828
+ color: "dim",
497
829
  render(ctx) {
498
830
  if (!ctx.model?.id)
499
831
  return null;
@@ -504,6 +836,7 @@ var segments = [
504
836
  id: "model-context",
505
837
  description: "Model name with context-size tag (e.g. [1m])",
506
838
  defaultEnabled: true,
839
+ color: "dim",
507
840
  render(ctx) {
508
841
  if (!ctx.model?.id)
509
842
  return null;
@@ -515,7 +848,7 @@ var segments = [
515
848
  id: "model-with-reasoning",
516
849
  description: "Model name with its reasoning effort (or thinking, when on)",
517
850
  defaultEnabled: false,
518
- color: "cyan",
851
+ color: "dim",
519
852
  render(ctx) {
520
853
  const name = modelName(ctx);
521
854
  if (!name)
@@ -571,6 +904,20 @@ var segments = [
571
904
  return typeof pct === "number" ? `7d:${Math.round(pct)}%` : null;
572
905
  }
573
906
  },
907
+ {
908
+ id: "usage-session",
909
+ description: "Session (5-hour) usage remaining for this pane's own account, from the local accounts usage cache",
910
+ defaultEnabled: false,
911
+ color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).sessionHeadroom) : null,
912
+ render: (ctx) => anthropicSession(ctx) ? usageLabel("5h", sessionUsage(process.env).sessionHeadroom) : null
913
+ },
914
+ {
915
+ id: "usage-weekly",
916
+ description: "Weekly usage remaining for this pane's own account \u2014 the binding limit \u2014 from the local accounts usage cache",
917
+ defaultEnabled: false,
918
+ color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).weeklyHeadroom, true) : null,
919
+ render: (ctx) => anthropicSession(ctx) ? usageLabel("7d", sessionUsage(process.env).weeklyHeadroom) : null
920
+ },
574
921
  {
575
922
  id: "thread-title",
576
923
  description: "Thread title set with /rename (omitted until named)",
@@ -582,17 +929,19 @@ var segments = [
582
929
  id: "context-used",
583
930
  description: "Percentage of context window used (omitted when unknown)",
584
931
  defaultEnabled: false,
932
+ color: contextColor,
585
933
  render(ctx) {
586
934
  const u = contextUsage(ctx);
587
935
  if (!u)
588
936
  return null;
589
- return `${Math.min(100, Math.round(u.used / u.window * 100))}%`;
937
+ return `ctx ${Math.min(100, Math.round(u.used / u.window * 100))}%`;
590
938
  }
591
939
  },
592
940
  {
593
941
  id: "context-remaining",
594
942
  description: "Percentage of context window remaining (omitted when unknown)",
595
943
  defaultEnabled: true,
944
+ color: contextColor,
596
945
  render(ctx) {
597
946
  const u = contextUsage(ctx);
598
947
  if (!u)
@@ -611,10 +960,20 @@ var segments = [
611
960
  return `${compactNum(u.used + u.output)} tok`;
612
961
  }
613
962
  },
963
+ {
964
+ id: "cache-rate",
965
+ description: "Percentage of input tokens served from cache (omitted when unknown)",
966
+ defaultEnabled: true,
967
+ render(ctx) {
968
+ const r = cacheRate(ctx);
969
+ return r === null ? null : `${Math.round(r * 100)}% cached`;
970
+ }
971
+ },
614
972
  {
615
973
  id: "cost",
616
974
  description: "Session cost in USD (omitted when zero)",
617
975
  defaultEnabled: true,
976
+ color: "dim",
618
977
  render(ctx) {
619
978
  const c = ctx.cost?.totalCostUsd;
620
979
  return c ? money(c) : null;
@@ -624,6 +983,7 @@ var segments = [
624
983
  id: "duration",
625
984
  description: "Session wall-clock duration",
626
985
  defaultEnabled: false,
986
+ color: "dim",
627
987
  render(ctx) {
628
988
  const ms = ctx.cost?.totalDurationMs;
629
989
  return ms ? compactDuration(ms) : null;
@@ -718,7 +1078,7 @@ async function renderLine(ctx, cfg) {
718
1078
  // package.json
719
1079
  var package_default = {
720
1080
  name: "@hasna/statusline",
721
- version: "0.0.6",
1081
+ version: "0.0.8",
722
1082
  description: "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
723
1083
  license: "Apache-2.0",
724
1084
  type: "module",
@@ -757,11 +1117,12 @@ var package_default = {
757
1117
  },
758
1118
  repository: {
759
1119
  type: "git",
760
- url: "git+https://github.com/hasna/statusline.git"
1120
+ url: "https://github.com/hasna/apps.git",
1121
+ directory: "apps/statusline"
761
1122
  },
762
- homepage: "https://github.com/hasna/statusline#readme",
1123
+ homepage: "https://github.com/hasna/apps/tree/main/apps/statusline#readme",
763
1124
  bugs: {
764
- url: "https://github.com/hasna/statusline/issues"
1125
+ url: "https://github.com/hasna/apps/issues"
765
1126
  },
766
1127
  keywords: [
767
1128
  "statusline",
@@ -778,36 +1139,37 @@ var package_default = {
778
1139
  bun: ">=1.0.0"
779
1140
  },
780
1141
  dependencies: {
781
- "@modelcontextprotocol/sdk": "1.12.1",
1142
+ "@modelcontextprotocol/sdk": "^1.26.0",
782
1143
  zod: "3.25.76"
783
1144
  },
784
1145
  devDependencies: {
785
- "@types/bun": "^1.2.4",
786
- typescript: "5.9.3"
1146
+ "@types/bun": "1.3.14",
1147
+ typescript: "5.9.3",
1148
+ "bun-types": "1.3.14"
787
1149
  }
788
1150
  };
789
1151
  // src/install.ts
790
- import { copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
791
- import { homedir as homedir3 } from "os";
792
- import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
1152
+ import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
1153
+ import { homedir as homedir6 } from "os";
1154
+ import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
793
1155
  function defaultSettingsPath(env) {
794
- return join4(env.HOME || homedir3(), ".claude", "settings.json");
1156
+ return join6(env.HOME || homedir6(), ".claude", "settings.json");
795
1157
  }
796
1158
  function claudeSettingsPath(env = process.env) {
797
- const home2 = env.HOME || homedir3();
1159
+ const home2 = env.HOME || homedir6();
798
1160
  const raw = env.CLAUDE_CONFIG_DIR?.trim();
799
1161
  if (!raw)
800
1162
  return defaultSettingsPath(env);
801
- const configDir = raw === "~" || raw.startsWith("~/") ? join4(home2, raw.slice(1)) : raw;
802
- return join4(configDir, "settings.json");
1163
+ const configDir = raw === "~" || raw.startsWith("~/") ? join6(home2, raw.slice(1)) : raw;
1164
+ return join6(configDir, "settings.json");
803
1165
  }
804
1166
  function installClaude(settingsPath, env = process.env) {
805
1167
  const target = settingsPath ?? claudeSettingsPath(env);
806
1168
  const binary = Bun.which("statusline") || "statusline";
807
1169
  let settings = {};
808
- if (existsSync4(target)) {
1170
+ if (existsSync6(target)) {
809
1171
  try {
810
- settings = JSON.parse(readFileSync5(target, "utf8"));
1172
+ settings = JSON.parse(readFileSync6(target, "utf8"));
811
1173
  } catch (error) {
812
1174
  throw new Error(`${target} is not valid JSON \u2014 fix or move it first`);
813
1175
  }
@@ -815,7 +1177,7 @@ function installClaude(settingsPath, env = process.env) {
815
1177
  }
816
1178
  settings.statusLine = { ...settings.statusLine, type: "command", command: `${binary} render` };
817
1179
  const configDir = dirname2(target);
818
- if (!existsSync4(configDir)) {
1180
+ if (!existsSync6(configDir)) {
819
1181
  if (resolve2(target) !== resolve2(defaultSettingsPath(env))) {
820
1182
  throw new Error(`config dir ${configDir} does not exist \u2014 create the profile first`);
821
1183
  }
@@ -882,6 +1244,14 @@ function resetStatuslineConfig() {
882
1244
  return defaultConfig();
883
1245
  }
884
1246
  async function renderStatusline(input = {}, config = loadConfig()) {
1247
+ const provider = process.env.STATUSLINE_PROVIDER;
1248
+ if (provider === "codex" || provider === "opencode") {
1249
+ const ctx = {
1250
+ provider,
1251
+ cwd: typeof input.cwd === "string" ? input.cwd : process.cwd()
1252
+ };
1253
+ return renderLine(ctx, config);
1254
+ }
885
1255
  return renderLine(parseClaudeInput(input), config);
886
1256
  }
887
1257
  function sampleClaudeInput(cwd = process.cwd()) {
@@ -924,9 +1294,12 @@ export {
924
1294
  previewStatusline,
925
1295
  parseClaudeInput,
926
1296
  orderSegments,
1297
+ opencodeCacheRate,
927
1298
  money,
928
1299
  loadConfig,
929
1300
  listSegments,
1301
+ latestCodexSessionPath,
1302
+ lastUsageBlock,
930
1303
  lastCommitEpoch,
931
1304
  installClaude,
932
1305
  gitRoot,
@@ -941,5 +1314,7 @@ export {
941
1314
  compactNum,
942
1315
  compactDuration,
943
1316
  compactAge,
944
- claudeSettingsPath
1317
+ codexCacheRate,
1318
+ claudeSettingsPath,
1319
+ cacheRate
945
1320
  };