@hasna/statusline 0.0.7 → 0.0.9

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) {
@@ -125,6 +156,23 @@ function compactNum(n) {
125
156
  return `${trimmed(n / 1000)}k`;
126
157
  return `${n}`;
127
158
  }
159
+ var BYTE_UNITS = ["K", "M", "G", "T", "P"];
160
+ function compactBytes(bytes) {
161
+ if (!Number.isFinite(bytes) || bytes < 0)
162
+ return "";
163
+ if (bytes < 1024)
164
+ return `${Math.floor(bytes)}B`;
165
+ let value = bytes / 1024;
166
+ let unit = 0;
167
+ while (unit < BYTE_UNITS.length - 1) {
168
+ const rounded = Number(value.toFixed(1));
169
+ if (rounded < 1024)
170
+ break;
171
+ value /= 1024;
172
+ unit += 1;
173
+ }
174
+ return `${trimmed(value)}${BYTE_UNITS[unit]}`;
175
+ }
128
176
  function money(n) {
129
177
  return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
130
178
  }
@@ -134,10 +182,10 @@ function compactDuration(ms) {
134
182
  return `${s}s`;
135
183
  if (s < 3600) {
136
184
  const rem2 = s % 60;
137
- return rem2 ? `${Math.floor(s / 60)}m${rem2}s` : `${Math.floor(s / 60)}m`;
185
+ return rem2 ? `${Math.floor(s / 60)}m ${rem2}s` : `${Math.floor(s / 60)}m`;
138
186
  }
139
187
  const rem = Math.floor(s % 3600 / 60);
140
- return rem ? `${Math.floor(s / 3600)}h${rem}m` : `${Math.floor(s / 3600)}h`;
188
+ return rem ? `${Math.floor(s / 3600)}h ${rem}m` : `${Math.floor(s / 3600)}h`;
141
189
  }
142
190
 
143
191
  // src/git.ts
@@ -194,14 +242,145 @@ function trackedLineCount(cwd) {
194
242
  }
195
243
 
196
244
  // src/context-window.ts
197
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
198
- function contextUsage(ctx) {
199
- const path = ctx.transcriptPath;
245
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
246
+
247
+ // src/providers/codex.ts
248
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
249
+ import { homedir as homedir2 } from "os";
250
+ import { join as join2 } from "path";
251
+ function latestCodexSessionPath(home = homedir2()) {
252
+ const root = join2(home, ".codex", "sessions");
253
+ if (!existsSync2(root))
254
+ return null;
255
+ let newest = null;
256
+ let newestMtime = 0;
257
+ try {
258
+ for (const year of readdirSync(root)) {
259
+ const yearDir = join2(root, year);
260
+ if (!statSync(yearDir, { throwIfNoEntry: false })?.isDirectory())
261
+ continue;
262
+ for (const month of readdirSync(yearDir)) {
263
+ const monthDir = join2(yearDir, month);
264
+ if (!statSync(monthDir, { throwIfNoEntry: false })?.isDirectory())
265
+ continue;
266
+ for (const day of readdirSync(monthDir)) {
267
+ const dayDir = join2(monthDir, day);
268
+ if (!statSync(dayDir, { throwIfNoEntry: false })?.isDirectory())
269
+ continue;
270
+ for (const file of readdirSync(dayDir)) {
271
+ if (!file.startsWith("rollout-") || !file.endsWith(".jsonl"))
272
+ continue;
273
+ const full = join2(dayDir, file);
274
+ const mtime = statSync(full, { throwIfNoEntry: false })?.mtimeMs ?? 0;
275
+ if (mtime > newestMtime) {
276
+ newestMtime = mtime;
277
+ newest = full;
278
+ }
279
+ }
280
+ }
281
+ }
282
+ }
283
+ } catch {
284
+ return null;
285
+ }
286
+ return newest;
287
+ }
288
+ function codexCacheRate(sessionPath) {
289
+ const path = sessionPath ?? latestCodexSessionPath();
200
290
  if (!path || !existsSync2(path))
201
291
  return null;
202
292
  let lines;
203
293
  try {
204
294
  lines = readFileSync2(path, "utf8").split(`
295
+ `);
296
+ } catch {
297
+ return null;
298
+ }
299
+ let usage = null;
300
+ for (const line of lines) {
301
+ if (!line || !line.includes("token_count"))
302
+ continue;
303
+ try {
304
+ const entry = JSON.parse(line);
305
+ if (entry?.type !== "event_msg")
306
+ continue;
307
+ if (entry?.payload?.type !== "token_count")
308
+ continue;
309
+ const info = entry?.payload?.info?.total_token_usage;
310
+ if (info && typeof info === "object")
311
+ usage = info;
312
+ } catch {
313
+ continue;
314
+ }
315
+ }
316
+ if (!usage)
317
+ return null;
318
+ const read = usage.cached_input_tokens || 0;
319
+ const input = usage.input_tokens || 0;
320
+ const write = usage.cache_write_input_tokens || 0;
321
+ const divisor = input + read + write;
322
+ if (divisor <= 0)
323
+ return null;
324
+ return Math.min(1, Math.max(0, read / divisor));
325
+ }
326
+
327
+ // src/providers/opencode.ts
328
+ import { existsSync as existsSync3 } from "fs";
329
+ import { homedir as homedir3 } from "os";
330
+ import { join as join3 } from "path";
331
+ import { Database } from "bun:sqlite";
332
+ function opencodeCacheRate(opts = {}) {
333
+ const dbPath = opts.dbPath ?? join3(homedir3(), ".local", "share", "opencode", "opencode.db");
334
+ if (!existsSync3(dbPath))
335
+ return null;
336
+ let db;
337
+ try {
338
+ db = new Database(dbPath, { readonly: true });
339
+ } catch {
340
+ return null;
341
+ }
342
+ try {
343
+ let row = tokenRow(db, "session", opts.sessionId);
344
+ if (!row)
345
+ row = tokenRow(db, "session_v2", opts.sessionId);
346
+ if (!row)
347
+ return null;
348
+ const input = row.tokens_input ?? 0;
349
+ const read = row.tokens_cache_read ?? 0;
350
+ const write = row.tokens_cache_write ?? 0;
351
+ const divisor = input + read + write;
352
+ if (divisor <= 0)
353
+ return null;
354
+ return Math.min(1, Math.max(0, read / divisor));
355
+ } catch {
356
+ return null;
357
+ } finally {
358
+ db.close();
359
+ }
360
+ }
361
+ function tokenRow(db, table, sessionId) {
362
+ try {
363
+ if (sessionId) {
364
+ return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
365
+ FROM ${table} WHERE id = ? LIMIT 1`).get(sessionId);
366
+ }
367
+ return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
368
+ FROM ${table}
369
+ WHERE tokens_input > 0 OR tokens_cache_read > 0 OR tokens_cache_write > 0
370
+ ORDER BY time_updated DESC LIMIT 1`).get();
371
+ } catch {
372
+ return null;
373
+ }
374
+ }
375
+
376
+ // src/context-window.ts
377
+ function lastUsageBlock(ctx) {
378
+ const path = ctx.transcriptPath;
379
+ if (!path || !existsSync4(path))
380
+ return null;
381
+ let lines;
382
+ try {
383
+ lines = readFileSync3(path, "utf8").split(`
205
384
  `);
206
385
  } catch {
207
386
  return null;
@@ -215,26 +394,137 @@ function contextUsage(ctx) {
215
394
  const usage = entry?.message?.usage;
216
395
  if (!usage || typeof usage.input_tokens !== "number")
217
396
  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
- };
397
+ return usage;
224
398
  } catch {
225
399
  continue;
226
400
  }
227
401
  }
228
402
  return null;
229
403
  }
404
+ function contextUsage(ctx) {
405
+ const usage = lastUsageBlock(ctx);
406
+ if (!usage)
407
+ return null;
408
+ const used = usage.input_tokens + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
409
+ return {
410
+ used,
411
+ output: usage.output_tokens || 0,
412
+ window: windowSize(ctx)
413
+ };
414
+ }
415
+ function cacheRate(ctx) {
416
+ if (ctx.provider === "codex")
417
+ return codexCacheRate();
418
+ if (ctx.provider === "opencode")
419
+ return opencodeCacheRate();
420
+ const usage = lastUsageBlock(ctx);
421
+ if (!usage)
422
+ return null;
423
+ const read = usage.cache_read_input_tokens || 0;
424
+ const creation = usage.cache_creation_input_tokens || 0;
425
+ const input = usage.input_tokens || 0;
426
+ const divisor = input + creation + read;
427
+ if (divisor <= 0)
428
+ return null;
429
+ return read / divisor;
430
+ }
230
431
  function windowSize(ctx) {
231
432
  return ctx.model?.id?.includes("[1m]") ? 1e6 : 200000;
232
433
  }
233
434
 
435
+ // src/disk.ts
436
+ import { statfsSync } from "fs";
437
+ var DISK_MEMO = Symbol("statusline.disk-free");
438
+ var DISK_WARN_PERCENT = 20;
439
+ var DISK_CRITICAL_PERCENT = 10;
440
+ function finiteNonNegative(value) {
441
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
442
+ }
443
+ function readDiskStats(path) {
444
+ if (typeof statfsSync !== "function")
445
+ return null;
446
+ try {
447
+ const stats = statfsSync(path);
448
+ if (!stats)
449
+ return null;
450
+ return {
451
+ bsize: Number(stats.bsize),
452
+ blocks: Number(stats.blocks),
453
+ bavail: Number(stats.bavail)
454
+ };
455
+ } catch {
456
+ return null;
457
+ }
458
+ }
459
+ function diskFree(path, reader = readDiskStats) {
460
+ let stats;
461
+ try {
462
+ stats = reader(path);
463
+ } catch {
464
+ return null;
465
+ }
466
+ if (!stats)
467
+ return null;
468
+ const bsize = finiteNonNegative(stats.bsize);
469
+ const blocks = finiteNonNegative(stats.blocks);
470
+ const bavail = finiteNonNegative(stats.bavail);
471
+ if (bsize === null || blocks === null || bavail === null)
472
+ return null;
473
+ if (bsize <= 0 || blocks <= 0)
474
+ return null;
475
+ const available = Math.min(bavail, blocks);
476
+ const totalBytes = bsize * blocks;
477
+ const freeBytes = bsize * available;
478
+ if (!Number.isFinite(totalBytes) || !Number.isFinite(freeBytes))
479
+ return null;
480
+ return {
481
+ freeBytes,
482
+ totalBytes,
483
+ freePercent: available / blocks * 100
484
+ };
485
+ }
486
+ function diskFreeFor(path, memo, reader = readDiskStats) {
487
+ if (!memo)
488
+ return diskFree(path, reader);
489
+ const hit = memo.get(DISK_MEMO);
490
+ if (hit && hit.path === path)
491
+ return hit.value;
492
+ const value = diskFree(path, reader);
493
+ memo.set(DISK_MEMO, { path, value });
494
+ return value;
495
+ }
496
+ function diskLevel(freePercent) {
497
+ if (!Number.isFinite(freePercent))
498
+ return null;
499
+ if (freePercent < DISK_CRITICAL_PERCENT)
500
+ return "critical";
501
+ if (freePercent < DISK_WARN_PERCENT)
502
+ return "low";
503
+ return null;
504
+ }
505
+ function formatDiskFree(disk) {
506
+ const value = compactBytes(disk.freeBytes);
507
+ if (!value)
508
+ return "";
509
+ return diskLevel(disk.freePercent) ? `disk ${value} low` : `disk ${value}`;
510
+ }
511
+ function diskColor(disk) {
512
+ if (!disk)
513
+ return null;
514
+ switch (diskLevel(disk.freePercent)) {
515
+ case "critical":
516
+ return "red";
517
+ case "low":
518
+ return "yellow";
519
+ default:
520
+ return "dim";
521
+ }
522
+ }
523
+
234
524
  // 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";
525
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync } from "fs";
526
+ import { homedir as homedir4 } from "os";
527
+ import { join as join4, resolve, sep } from "path";
238
528
  var CONFIG_DIR_ENV = "CLAUDE_CONFIG_DIR";
239
529
  var DEFAULT_CONFIG_SUBDIR = ".claude";
240
530
  var STORE_PATH_ENV = "ACCOUNTS_STORE_PATH";
@@ -258,10 +548,10 @@ function str(value) {
258
548
  return typeof value === "string" && value.trim() ? value.trim() : null;
259
549
  }
260
550
  function home(env) {
261
- return str(env.HOME) ?? homedir2();
551
+ return str(env.HOME) ?? homedir4();
262
552
  }
263
553
  function expandHome(path, env) {
264
- return path === "~" || path.startsWith(`~${sep}`) ? join2(home(env), path.slice(1)) : path;
554
+ return path === "~" || path.startsWith(`~${sep}`) ? join4(home(env), path.slice(1)) : path;
265
555
  }
266
556
  function canonical(path, env) {
267
557
  const abs = resolve(expandHome(path, env));
@@ -273,7 +563,7 @@ function canonical(path, env) {
273
563
  }
274
564
  function readJson(path) {
275
565
  try {
276
- return JSON.parse(readFileSync3(path, "utf8"));
566
+ return JSON.parse(readFileSync4(path, "utf8"));
277
567
  } catch {
278
568
  return null;
279
569
  }
@@ -294,14 +584,14 @@ async function accountsPaths(env) {
294
584
  return { store: storage.storePath(), profiles: storage.profilesDir() };
295
585
  } catch {}
296
586
  }
297
- const base = homeOverride ?? join2(home(env), ...DEFAULT_ACCOUNTS_HOME);
587
+ const base = homeOverride ?? join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
298
588
  return {
299
- store: storeOverride ?? join2(base, STORE_FILE),
300
- profiles: join2(base, PROFILES_SUBDIR)
589
+ store: storeOverride ?? join4(base, STORE_FILE),
590
+ profiles: join4(base, PROFILES_SUBDIR)
301
591
  };
302
592
  }
303
593
  function registryEntry(store, configDir, env, tool) {
304
- if (!existsSync3(store))
594
+ if (!existsSync5(store))
305
595
  return null;
306
596
  const entries = readJson(store)?.profiles;
307
597
  if (!Array.isArray(entries))
@@ -322,7 +612,7 @@ function registryEntry(store, configDir, env, tool) {
322
612
  return tool ? untyped : first;
323
613
  }
324
614
  function layoutProfile(profilesRoot, configDir, env) {
325
- if (!existsSync3(configDir))
615
+ if (!existsSync5(configDir))
326
616
  return null;
327
617
  const prefix = canonical(profilesRoot, env) + sep;
328
618
  if (!configDir.startsWith(prefix))
@@ -336,7 +626,7 @@ function layoutProfile(profilesRoot, configDir, env) {
336
626
  return { configDir, profile, tool, source: "layout" };
337
627
  }
338
628
  function switchedOccupant(configDir) {
339
- const raw = readJson(join2(configDir, AUTH_STATE_SUBDIR, SWITCHED_ACCOUNT_MARKER_FILE));
629
+ const raw = readJson(join4(configDir, AUTH_STATE_SUBDIR, SWITCHED_ACCOUNT_MARKER_FILE));
340
630
  if (!raw)
341
631
  return null;
342
632
  const profile = str(raw.profile);
@@ -344,17 +634,17 @@ function switchedOccupant(configDir) {
344
634
  return profile || email ? { profile, email } : null;
345
635
  }
346
636
  function sessionConfigDir(env = process.env) {
347
- return canonical(str(env[CONFIG_DIR_ENV]) ?? join2(home(env), DEFAULT_CONFIG_SUBDIR), env);
637
+ return canonical(str(env[CONFIG_DIR_ENV]) ?? join4(home(env), DEFAULT_CONFIG_SUBDIR), env);
348
638
  }
349
639
  function sessionStateFile(env = process.env) {
350
640
  const base = canonical(str(env[CONFIG_DIR_ENV]) ?? home(env), env);
351
- const preferred = join2(base, STATE_FILE_PREFERRED);
352
- if (existsSync3(preferred))
641
+ const preferred = join4(base, STATE_FILE_PREFERRED);
642
+ if (existsSync5(preferred))
353
643
  return preferred;
354
644
  const variants = str(env[CUSTOM_OAUTH_ENV]) ? ["-custom-oauth", ...OAUTH_VARIANTS] : OAUTH_VARIANTS;
355
645
  for (const variant of variants) {
356
- const candidate = join2(base, `${STATE_FILE_PREFIX}${variant}${STATE_FILE_SUFFIX}`);
357
- if (existsSync3(candidate))
646
+ const candidate = join4(base, `${STATE_FILE_PREFIX}${variant}${STATE_FILE_SUFFIX}`);
647
+ if (existsSync5(candidate))
358
648
  return candidate;
359
649
  }
360
650
  return null;
@@ -402,7 +692,7 @@ async function sessionAccountEmail(env = process.env, tool) {
402
692
  }
403
693
  function accountsHomeDir(env) {
404
694
  const override = str(env[ACCOUNTS_HOME_ENV]);
405
- return override ? expandHome(override, env) : join2(home(env), ...DEFAULT_ACCOUNTS_HOME);
695
+ return override ? expandHome(override, env) : join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
406
696
  }
407
697
  function sessionAccountUuid(env = process.env) {
408
698
  try {
@@ -475,8 +765,8 @@ function sessionUsage(env = process.env, now = new Date) {
475
765
  const uuid = sessionAccountUuid(env);
476
766
  if (!uuid)
477
767
  return empty;
478
- const path = join2(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
479
- if (!existsSync3(path))
768
+ const path = join4(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
769
+ if (!existsSync5(path))
480
770
  return empty;
481
771
  const entry = readJson(path);
482
772
  if (!entry || entry.accountUuid !== uuid)
@@ -491,11 +781,11 @@ function sessionUsage(env = process.env, now = new Date) {
491
781
  }
492
782
 
493
783
  // src/settings.ts
494
- import { readFileSync as readFileSync4 } from "fs";
495
- import { join as join3 } from "path";
784
+ import { readFileSync as readFileSync5 } from "fs";
785
+ import { join as join5 } from "path";
496
786
  function sessionFastMode(env = process.env) {
497
787
  try {
498
- const raw = readFileSync4(join3(sessionConfigDir(env), "settings.json"), "utf8");
788
+ const raw = readFileSync5(join5(sessionConfigDir(env), "settings.json"), "utf8");
499
789
  return JSON.parse(raw)?.fastMode === true;
500
790
  } catch {
501
791
  return false;
@@ -519,6 +809,29 @@ function friendlyModel(id) {
519
809
  return { name: name || mid, tag };
520
810
  }
521
811
  var dir = (ctx) => ctx.cwd || process.cwd();
812
+ function abbreviateHome(path) {
813
+ const home2 = homedir5();
814
+ if (!home2 || home2 === "/")
815
+ return path;
816
+ if (path === home2)
817
+ return "~";
818
+ return path.startsWith(`${home2}/`) ? `~${path.slice(home2.length)}` : path;
819
+ }
820
+ var CWD_MAX = 40;
821
+ function truncateLeft(path, max) {
822
+ return path.length <= max ? path : `\u2026${path.slice(path.length - (max - 1))}`;
823
+ }
824
+ function contextColor(ctx) {
825
+ const u = contextUsage(ctx);
826
+ if (!u || !u.window)
827
+ return null;
828
+ const pct = Math.min(100, Math.round(u.used / u.window * 100));
829
+ if (pct >= 80)
830
+ return "red";
831
+ if (pct >= 50)
832
+ return "yellow";
833
+ return "green";
834
+ }
522
835
  function modelName(ctx) {
523
836
  if (ctx.model?.displayName)
524
837
  return ctx.model.displayName.toLowerCase();
@@ -527,6 +840,12 @@ function modelName(ctx) {
527
840
  function limitPercent(pct) {
528
841
  return typeof pct === "number" ? pct : 0;
529
842
  }
843
+ function anthropicSession(ctx) {
844
+ if (ctx.rateLimits)
845
+ return true;
846
+ const id = ctx.model?.id;
847
+ return typeof id === "string" && /claude/i.test(id);
848
+ }
530
849
  function usageLabel(prefix, headroom) {
531
850
  return headroom === null ? `${prefix} \u2014` : `${prefix} ${headroom}%`;
532
851
  }
@@ -590,16 +909,39 @@ var segments = [
590
909
  return n ? compactNum(n) : null;
591
910
  }
592
911
  },
912
+ {
913
+ id: "disk-free",
914
+ description: "Free space on the volume holding the working directory",
915
+ defaultEnabled: false,
916
+ color: (ctx, memo) => diskColor(diskFreeFor(dir(ctx), memo)),
917
+ render(ctx, memo) {
918
+ const disk = diskFreeFor(dir(ctx), memo);
919
+ return disk ? formatDiskFree(disk) || null : null;
920
+ }
921
+ },
593
922
  {
594
923
  id: "current-dir",
595
924
  description: "Current working directory (basename)",
596
925
  defaultEnabled: false,
597
926
  render: (ctx) => dir(ctx).split("/").pop() || null
598
927
  },
928
+ {
929
+ id: "cwd",
930
+ description: "Current working directory (full path)",
931
+ defaultEnabled: false,
932
+ color: "blue",
933
+ render(ctx) {
934
+ const path = ctx.cwd;
935
+ if (typeof path !== "string" || !path)
936
+ return null;
937
+ return truncateLeft(abbreviateHome(path), CWD_MAX);
938
+ }
939
+ },
599
940
  {
600
941
  id: "model",
601
942
  description: "Current model name",
602
943
  defaultEnabled: false,
944
+ color: "dim",
603
945
  render(ctx) {
604
946
  if (!ctx.model?.id)
605
947
  return null;
@@ -610,6 +952,7 @@ var segments = [
610
952
  id: "model-context",
611
953
  description: "Model name with context-size tag (e.g. [1m])",
612
954
  defaultEnabled: true,
955
+ color: "dim",
613
956
  render(ctx) {
614
957
  if (!ctx.model?.id)
615
958
  return null;
@@ -621,7 +964,7 @@ var segments = [
621
964
  id: "model-with-reasoning",
622
965
  description: "Model name with its reasoning effort (or thinking, when on)",
623
966
  defaultEnabled: false,
624
- color: "cyan",
967
+ color: "dim",
625
968
  render(ctx) {
626
969
  const name = modelName(ctx);
627
970
  if (!name)
@@ -681,15 +1024,15 @@ var segments = [
681
1024
  id: "usage-session",
682
1025
  description: "Session (5-hour) usage remaining for this pane's own account, from the local accounts usage cache",
683
1026
  defaultEnabled: false,
684
- color: () => usageColor(sessionUsage(process.env).sessionHeadroom),
685
- render: () => usageLabel("5h", sessionUsage(process.env).sessionHeadroom)
1027
+ color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).sessionHeadroom) : null,
1028
+ render: (ctx) => anthropicSession(ctx) ? usageLabel("5h", sessionUsage(process.env).sessionHeadroom) : null
686
1029
  },
687
1030
  {
688
1031
  id: "usage-weekly",
689
1032
  description: "Weekly usage remaining for this pane's own account \u2014 the binding limit \u2014 from the local accounts usage cache",
690
1033
  defaultEnabled: false,
691
- color: () => usageColor(sessionUsage(process.env).weeklyHeadroom, true),
692
- render: () => usageLabel("7d", sessionUsage(process.env).weeklyHeadroom)
1034
+ color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).weeklyHeadroom, true) : null,
1035
+ render: (ctx) => anthropicSession(ctx) ? usageLabel("7d", sessionUsage(process.env).weeklyHeadroom) : null
693
1036
  },
694
1037
  {
695
1038
  id: "thread-title",
@@ -702,17 +1045,19 @@ var segments = [
702
1045
  id: "context-used",
703
1046
  description: "Percentage of context window used (omitted when unknown)",
704
1047
  defaultEnabled: false,
1048
+ color: contextColor,
705
1049
  render(ctx) {
706
1050
  const u = contextUsage(ctx);
707
1051
  if (!u)
708
1052
  return null;
709
- return `${Math.min(100, Math.round(u.used / u.window * 100))}%`;
1053
+ return `ctx ${Math.min(100, Math.round(u.used / u.window * 100))}%`;
710
1054
  }
711
1055
  },
712
1056
  {
713
1057
  id: "context-remaining",
714
1058
  description: "Percentage of context window remaining (omitted when unknown)",
715
1059
  defaultEnabled: true,
1060
+ color: contextColor,
716
1061
  render(ctx) {
717
1062
  const u = contextUsage(ctx);
718
1063
  if (!u)
@@ -731,10 +1076,20 @@ var segments = [
731
1076
  return `${compactNum(u.used + u.output)} tok`;
732
1077
  }
733
1078
  },
1079
+ {
1080
+ id: "cache-rate",
1081
+ description: "Percentage of input tokens served from cache (omitted when unknown)",
1082
+ defaultEnabled: true,
1083
+ render(ctx) {
1084
+ const r = cacheRate(ctx);
1085
+ return r === null ? null : `${Math.round(r * 100)}% cached`;
1086
+ }
1087
+ },
734
1088
  {
735
1089
  id: "cost",
736
1090
  description: "Session cost in USD (omitted when zero)",
737
1091
  defaultEnabled: true,
1092
+ color: "dim",
738
1093
  render(ctx) {
739
1094
  const c = ctx.cost?.totalCostUsd;
740
1095
  return c ? money(c) : null;
@@ -744,6 +1099,7 @@ var segments = [
744
1099
  id: "duration",
745
1100
  description: "Session wall-clock duration",
746
1101
  defaultEnabled: false,
1102
+ color: "dim",
747
1103
  render(ctx) {
748
1104
  const ms = ctx.cost?.totalDurationMs;
749
1105
  return ms ? compactDuration(ms) : null;
@@ -810,13 +1166,14 @@ function colorsEnabled(cfg, env = process.env) {
810
1166
  return false;
811
1167
  return cfg.colors !== false;
812
1168
  }
813
- function paint(segment, text, ctx) {
814
- const color = typeof segment.color === "function" ? segment.color(ctx) : segment.color;
1169
+ function paint(segment, text, ctx, memo) {
1170
+ const color = typeof segment.color === "function" ? segment.color(ctx, memo) : segment.color;
815
1171
  const code = color ? ANSI[color] : undefined;
816
1172
  return code ? `${code}${text}${RESET}` : text;
817
1173
  }
818
1174
  async function renderLine(ctx, cfg) {
819
1175
  const colorize = colorsEnabled(cfg);
1176
+ const memo = new Map;
820
1177
  const rows = [[]];
821
1178
  for (const id of cfg.segments) {
822
1179
  if (id === "newline") {
@@ -827,9 +1184,9 @@ async function renderLine(ctx, cfg) {
827
1184
  if (!segment)
828
1185
  continue;
829
1186
  try {
830
- const out = await segment.render(ctx);
1187
+ const out = await segment.render(ctx, memo);
831
1188
  if (out)
832
- rows[rows.length - 1].push(colorize ? paint(segment, out, ctx) : out);
1189
+ rows[rows.length - 1].push(colorize ? paint(segment, out, ctx, memo) : out);
833
1190
  } catch {}
834
1191
  }
835
1192
  return rows.map((parts) => parts.join(cfg.separator)).filter((row) => row.length > 0).join(`
@@ -838,7 +1195,7 @@ async function renderLine(ctx, cfg) {
838
1195
  // package.json
839
1196
  var package_default = {
840
1197
  name: "@hasna/statusline",
841
- version: "0.0.7",
1198
+ version: "0.0.9",
842
1199
  description: "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
843
1200
  license: "Apache-2.0",
844
1201
  type: "module",
@@ -877,11 +1234,12 @@ var package_default = {
877
1234
  },
878
1235
  repository: {
879
1236
  type: "git",
880
- url: "git+https://github.com/hasna/statusline.git"
1237
+ url: "https://github.com/hasna/apps.git",
1238
+ directory: "apps/statusline"
881
1239
  },
882
- homepage: "https://github.com/hasna/statusline#readme",
1240
+ homepage: "https://github.com/hasna/apps/tree/main/apps/statusline#readme",
883
1241
  bugs: {
884
- url: "https://github.com/hasna/statusline/issues"
1242
+ url: "https://github.com/hasna/apps/issues"
885
1243
  },
886
1244
  keywords: [
887
1245
  "statusline",
@@ -898,36 +1256,37 @@ var package_default = {
898
1256
  bun: ">=1.0.0"
899
1257
  },
900
1258
  dependencies: {
901
- "@modelcontextprotocol/sdk": "1.12.1",
1259
+ "@modelcontextprotocol/sdk": "^1.26.0",
902
1260
  zod: "3.25.76"
903
1261
  },
904
1262
  devDependencies: {
905
- "@types/bun": "^1.2.4",
906
- typescript: "5.9.3"
1263
+ "@types/bun": "1.3.14",
1264
+ typescript: "5.9.3",
1265
+ "bun-types": "1.3.14"
907
1266
  }
908
1267
  };
909
1268
  // src/install.ts
910
- import { copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
911
- import { homedir as homedir3 } from "os";
912
- import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
1269
+ import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
1270
+ import { homedir as homedir6 } from "os";
1271
+ import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
913
1272
  function defaultSettingsPath(env) {
914
- return join4(env.HOME || homedir3(), ".claude", "settings.json");
1273
+ return join6(env.HOME || homedir6(), ".claude", "settings.json");
915
1274
  }
916
1275
  function claudeSettingsPath(env = process.env) {
917
- const home2 = env.HOME || homedir3();
1276
+ const home2 = env.HOME || homedir6();
918
1277
  const raw = env.CLAUDE_CONFIG_DIR?.trim();
919
1278
  if (!raw)
920
1279
  return defaultSettingsPath(env);
921
- const configDir = raw === "~" || raw.startsWith("~/") ? join4(home2, raw.slice(1)) : raw;
922
- return join4(configDir, "settings.json");
1280
+ const configDir = raw === "~" || raw.startsWith("~/") ? join6(home2, raw.slice(1)) : raw;
1281
+ return join6(configDir, "settings.json");
923
1282
  }
924
1283
  function installClaude(settingsPath, env = process.env) {
925
1284
  const target = settingsPath ?? claudeSettingsPath(env);
926
1285
  const binary = Bun.which("statusline") || "statusline";
927
1286
  let settings = {};
928
- if (existsSync4(target)) {
1287
+ if (existsSync6(target)) {
929
1288
  try {
930
- settings = JSON.parse(readFileSync5(target, "utf8"));
1289
+ settings = JSON.parse(readFileSync6(target, "utf8"));
931
1290
  } catch (error) {
932
1291
  throw new Error(`${target} is not valid JSON \u2014 fix or move it first`);
933
1292
  }
@@ -935,7 +1294,7 @@ function installClaude(settingsPath, env = process.env) {
935
1294
  }
936
1295
  settings.statusLine = { ...settings.statusLine, type: "command", command: `${binary} render` };
937
1296
  const configDir = dirname2(target);
938
- if (!existsSync4(configDir)) {
1297
+ if (!existsSync6(configDir)) {
939
1298
  if (resolve2(target) !== resolve2(defaultSettingsPath(env))) {
940
1299
  throw new Error(`config dir ${configDir} does not exist \u2014 create the profile first`);
941
1300
  }
@@ -1002,6 +1361,14 @@ function resetStatuslineConfig() {
1002
1361
  return defaultConfig();
1003
1362
  }
1004
1363
  async function renderStatusline(input = {}, config = loadConfig()) {
1364
+ const provider = process.env.STATUSLINE_PROVIDER;
1365
+ if (provider === "codex" || provider === "opencode") {
1366
+ const ctx = {
1367
+ provider,
1368
+ cwd: typeof input.cwd === "string" ? input.cwd : process.cwd()
1369
+ };
1370
+ return renderLine(ctx, config);
1371
+ }
1005
1372
  return renderLine(parseClaudeInput(input), config);
1006
1373
  }
1007
1374
  function sampleClaudeInput(cwd = process.cwd()) {
@@ -1044,9 +1411,12 @@ export {
1044
1411
  previewStatusline,
1045
1412
  parseClaudeInput,
1046
1413
  orderSegments,
1414
+ opencodeCacheRate,
1047
1415
  money,
1048
1416
  loadConfig,
1049
1417
  listSegments,
1418
+ latestCodexSessionPath,
1419
+ lastUsageBlock,
1050
1420
  lastCommitEpoch,
1051
1421
  installClaude,
1052
1422
  gitRoot,
@@ -1061,5 +1431,7 @@ export {
1061
1431
  compactNum,
1062
1432
  compactDuration,
1063
1433
  compactAge,
1064
- claudeSettingsPath
1434
+ codexCacheRate,
1435
+ claudeSettingsPath,
1436
+ cacheRate
1065
1437
  };