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