@hasna/statusline 0.0.6 → 0.0.7

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/README.md CHANGED
@@ -45,6 +45,8 @@ anything that returns `null` or throws. A failed segment never breaks the host U
45
45
  | `auth-email` | off | Account email this session is logged in as | the agent's own login record for this config dir, else the registry | no login recorded | `dev@example.com` |
46
46
  | `five-hour-limit` | off | Percentage of the 5-hour rate limit used | `rate_limits.five_hour.used_percentage` | host reports no limit data | `5h:42%` |
47
47
  | `seven-day-limit` | off | Percentage of the 7-day rate limit used | `rate_limits.seven_day.used_percentage` | host reports no limit data | `7d:12%` |
48
+ | `usage-session` | off | Session (5-hour) usage **remaining** for this pane's own account | this process's `CLAUDE_CONFIG_DIR` account uuid → the accounts usage cache at `~/.hasna/accounts/cache/usage/<uuid>.json` (kept warm by `@hasna/accounts`; **no cloud call**) | never omitted — shows `5h —` when usage is unknown, missing, stale, or rate-limited | `5h 87%` |
49
+ | `usage-weekly` | off | Weekly usage **remaining** for this pane's own account (the binding limit) | same local usage cache as `usage-session` | never omitted — shows `7d —` when usage is unavailable | `7d 92%` |
48
50
  | `thread-title` | off | Thread title set with `/rename` | `session_name` | thread not renamed | `ship it` |
49
51
  | `context-used` | off | Percentage of context window used | session transcript JSONL (`contextUsage`) | transcript missing/unreadable | `10%` |
50
52
  | `context-remaining` | on | Percentage of context window remaining | transcript via `contextUsage` | transcript missing/unreadable | `90% left` |
@@ -49,4 +49,28 @@ export declare function sessionAccount(env?: Env, tool?: string): Promise<Sessio
49
49
  * blob, which is large enough to only touch when a segment asks for it.
50
50
  */
51
51
  export declare function sessionAccountEmail(env?: Env, tool?: string): Promise<string | null>;
52
+ /**
53
+ * The account uuid the host agent recorded as logged in for this config dir.
54
+ * Read from the same live state blob `sessionAccountEmail` prefers, so it names
55
+ * whoever is actually signed in now — the account whose usage the warmer
56
+ * cached — rather than a registry entry that can lag a re-login or a switch.
57
+ * Returns null when no record is present or the value is not a safe cache key.
58
+ */
59
+ export declare function sessionAccountUuid(env?: Env): string | null;
60
+ /** Usage headroom for this session's account, as percent still available. */
61
+ export interface SessionUsage {
62
+ /** The ~5-hour session window's remaining percent, or null when unknown. */
63
+ sessionHeadroom: number | null;
64
+ /** The weekly window's remaining percent — the binding limit — or null. */
65
+ weeklyHeadroom: number | null;
66
+ }
67
+ /**
68
+ * Session and weekly usage headroom for the account this pane is running as,
69
+ * read straight from the accounts usage cache — never the cloud API, which the
70
+ * launched, registry-stripped session cannot reach and could not afford on
71
+ * every render anyway. Returns nulls (so a segment shows a neutral marker)
72
+ * whenever the account is unknown, the cache is missing, stale, corrupt, or
73
+ * keyed to a different account. Never throws.
74
+ */
75
+ export declare function sessionUsage(env?: Env, now?: Date): SessionUsage;
52
76
  export {};
package/dist/cli.js CHANGED
@@ -250,6 +250,11 @@ var STATE_FILE_PREFIX = ".claude";
250
250
  var STATE_FILE_SUFFIX = ".json";
251
251
  var CUSTOM_OAUTH_ENV = "CLAUDE_CODE_CUSTOM_OAUTH_URL";
252
252
  var OAUTH_VARIANTS = ["", "-custom-oauth", "-local-oauth", "-staging-oauth"];
253
+ var CACHE_SUBDIR = "cache";
254
+ var USAGE_CACHE_SUBDIR = "usage";
255
+ var USAGE_CACHE_MAX_AGE_MS = 15 * 60 * 1000;
256
+ var SESSION_WINDOW_MAX_MS = 5 * 60 * 60 * 1000;
257
+ var SAFE_UUID = /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/;
253
258
  function str(value) {
254
259
  return typeof value === "string" && value.trim() ? value.trim() : null;
255
260
  }
@@ -396,6 +401,95 @@ async function sessionAccountEmail(env = process.env, tool) {
396
401
  return null;
397
402
  }
398
403
  }
404
+ function accountsHomeDir(env) {
405
+ const override = str(env[ACCOUNTS_HOME_ENV]);
406
+ return override ? expandHome(override, env) : join2(home(env), ...DEFAULT_ACCOUNTS_HOME);
407
+ }
408
+ function sessionAccountUuid(env = process.env) {
409
+ try {
410
+ const stateFile = sessionStateFile(env);
411
+ if (!stateFile)
412
+ return null;
413
+ const uuid = str(readJson(stateFile)?.oauthAccount?.accountUuid);
414
+ return uuid && SAFE_UUID.test(uuid) ? uuid : null;
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ function classifyWindow(w, fetchedAt) {
420
+ if (w.scoped === true)
421
+ return "scoped";
422
+ const group = str(w.group);
423
+ if (group === "session" || group === "weekly")
424
+ return group;
425
+ const id = str(w.id);
426
+ if (id) {
427
+ if (id.includes("session"))
428
+ return "session";
429
+ if (id.includes("weekly"))
430
+ return "weekly";
431
+ }
432
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
433
+ if (Number.isFinite(resetsAt) && fetchedAt !== null && resetsAt - fetchedAt > SESSION_WINDOW_MAX_MS) {
434
+ return "weekly";
435
+ }
436
+ return "unknown";
437
+ }
438
+ function windowHeadroom(w, now, fetchedAt) {
439
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
440
+ const rolled = Number.isFinite(resetsAt) && resetsAt <= now && (fetchedAt === null || resetsAt > fetchedAt);
441
+ if (rolled)
442
+ return 100;
443
+ const util = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : null;
444
+ if (util === null)
445
+ return null;
446
+ return Math.max(0, Math.min(100, Math.round(100 - util)));
447
+ }
448
+ function deriveUsage(usage, now) {
449
+ const u = usage ?? {};
450
+ const windows = Array.isArray(u.windows) ? u.windows : [];
451
+ const fetchedAtMs = typeof u.fetchedAt === "string" ? Date.parse(u.fetchedAt) : Number.NaN;
452
+ const fetchedAt = Number.isFinite(fetchedAtMs) ? fetchedAtMs : null;
453
+ let session = null;
454
+ let weekly = null;
455
+ let unknownMin = null;
456
+ for (const w of windows) {
457
+ const cls = classifyWindow(w, fetchedAt);
458
+ if (cls === "scoped")
459
+ continue;
460
+ const h = windowHeadroom(w, now, fetchedAt);
461
+ if (h === null)
462
+ continue;
463
+ if (cls === "session")
464
+ session = session === null ? h : Math.min(session, h);
465
+ else if (cls === "weekly")
466
+ weekly = weekly === null ? h : Math.min(weekly, h);
467
+ else
468
+ unknownMin = unknownMin === null ? h : Math.min(unknownMin, h);
469
+ }
470
+ const weeklyHeadroom = weekly !== null ? unknownMin !== null ? Math.min(weekly, unknownMin) : weekly : unknownMin;
471
+ return { sessionHeadroom: session, weeklyHeadroom };
472
+ }
473
+ function sessionUsage(env = process.env, now = new Date) {
474
+ const empty = { sessionHeadroom: null, weeklyHeadroom: null };
475
+ try {
476
+ const uuid = sessionAccountUuid(env);
477
+ if (!uuid)
478
+ return empty;
479
+ const path = join2(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
480
+ if (!existsSync3(path))
481
+ return empty;
482
+ const entry = readJson(path);
483
+ if (!entry || entry.accountUuid !== uuid)
484
+ return empty;
485
+ const fetchedAt = typeof entry.fetchedAt === "string" ? Date.parse(entry.fetchedAt) : Number.NaN;
486
+ if (!Number.isFinite(fetchedAt) || now.getTime() - fetchedAt > USAGE_CACHE_MAX_AGE_MS)
487
+ return empty;
488
+ return deriveUsage(entry.usage, now.getTime());
489
+ } catch {
490
+ return empty;
491
+ }
492
+ }
399
493
 
400
494
  // src/settings.ts
401
495
  import { readFileSync as readFileSync4 } from "fs";
@@ -434,6 +528,18 @@ function modelName(ctx) {
434
528
  function limitPercent(pct) {
435
529
  return typeof pct === "number" ? pct : 0;
436
530
  }
531
+ function usageLabel(prefix, headroom) {
532
+ return headroom === null ? `${prefix} \u2014` : `${prefix} ${headroom}%`;
533
+ }
534
+ function usageColor(headroom, binding = false) {
535
+ if (headroom === null)
536
+ return "dim";
537
+ if (headroom < 20)
538
+ return "red";
539
+ if (headroom < 50)
540
+ return "yellow";
541
+ return binding ? "green" : null;
542
+ }
437
543
  var segments = [
438
544
  {
439
545
  id: "machine",
@@ -572,6 +678,20 @@ var segments = [
572
678
  return typeof pct === "number" ? `7d:${Math.round(pct)}%` : null;
573
679
  }
574
680
  },
681
+ {
682
+ id: "usage-session",
683
+ description: "Session (5-hour) usage remaining for this pane's own account, from the local accounts usage cache",
684
+ defaultEnabled: false,
685
+ color: () => usageColor(sessionUsage(process.env).sessionHeadroom),
686
+ render: () => usageLabel("5h", sessionUsage(process.env).sessionHeadroom)
687
+ },
688
+ {
689
+ id: "usage-weekly",
690
+ description: "Weekly usage remaining for this pane's own account \u2014 the binding limit \u2014 from the local accounts usage cache",
691
+ defaultEnabled: false,
692
+ color: () => usageColor(sessionUsage(process.env).weeklyHeadroom, true),
693
+ render: () => usageLabel("7d", sessionUsage(process.env).weeklyHeadroom)
694
+ },
575
695
  {
576
696
  id: "thread-title",
577
697
  description: "Thread title set with /rename (omitted until named)",
@@ -719,7 +839,7 @@ async function renderLine(ctx, cfg) {
719
839
  // package.json
720
840
  var package_default = {
721
841
  name: "@hasna/statusline",
722
- version: "0.0.6",
842
+ version: "0.0.7",
723
843
  description: "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
724
844
  license: "Apache-2.0",
725
845
  type: "module",
package/dist/index.js CHANGED
@@ -249,6 +249,11 @@ var STATE_FILE_PREFIX = ".claude";
249
249
  var STATE_FILE_SUFFIX = ".json";
250
250
  var CUSTOM_OAUTH_ENV = "CLAUDE_CODE_CUSTOM_OAUTH_URL";
251
251
  var OAUTH_VARIANTS = ["", "-custom-oauth", "-local-oauth", "-staging-oauth"];
252
+ var CACHE_SUBDIR = "cache";
253
+ var USAGE_CACHE_SUBDIR = "usage";
254
+ var USAGE_CACHE_MAX_AGE_MS = 15 * 60 * 1000;
255
+ var SESSION_WINDOW_MAX_MS = 5 * 60 * 60 * 1000;
256
+ var SAFE_UUID = /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/;
252
257
  function str(value) {
253
258
  return typeof value === "string" && value.trim() ? value.trim() : null;
254
259
  }
@@ -395,6 +400,95 @@ async function sessionAccountEmail(env = process.env, tool) {
395
400
  return null;
396
401
  }
397
402
  }
403
+ function accountsHomeDir(env) {
404
+ const override = str(env[ACCOUNTS_HOME_ENV]);
405
+ return override ? expandHome(override, env) : join2(home(env), ...DEFAULT_ACCOUNTS_HOME);
406
+ }
407
+ function sessionAccountUuid(env = process.env) {
408
+ try {
409
+ const stateFile = sessionStateFile(env);
410
+ if (!stateFile)
411
+ return null;
412
+ const uuid = str(readJson(stateFile)?.oauthAccount?.accountUuid);
413
+ return uuid && SAFE_UUID.test(uuid) ? uuid : null;
414
+ } catch {
415
+ return null;
416
+ }
417
+ }
418
+ function classifyWindow(w, fetchedAt) {
419
+ if (w.scoped === true)
420
+ return "scoped";
421
+ const group = str(w.group);
422
+ if (group === "session" || group === "weekly")
423
+ return group;
424
+ const id = str(w.id);
425
+ if (id) {
426
+ if (id.includes("session"))
427
+ return "session";
428
+ if (id.includes("weekly"))
429
+ return "weekly";
430
+ }
431
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
432
+ if (Number.isFinite(resetsAt) && fetchedAt !== null && resetsAt - fetchedAt > SESSION_WINDOW_MAX_MS) {
433
+ return "weekly";
434
+ }
435
+ return "unknown";
436
+ }
437
+ function windowHeadroom(w, now, fetchedAt) {
438
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
439
+ const rolled = Number.isFinite(resetsAt) && resetsAt <= now && (fetchedAt === null || resetsAt > fetchedAt);
440
+ if (rolled)
441
+ return 100;
442
+ const util = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : null;
443
+ if (util === null)
444
+ return null;
445
+ return Math.max(0, Math.min(100, Math.round(100 - util)));
446
+ }
447
+ function deriveUsage(usage, now) {
448
+ const u = usage ?? {};
449
+ const windows = Array.isArray(u.windows) ? u.windows : [];
450
+ const fetchedAtMs = typeof u.fetchedAt === "string" ? Date.parse(u.fetchedAt) : Number.NaN;
451
+ const fetchedAt = Number.isFinite(fetchedAtMs) ? fetchedAtMs : null;
452
+ let session = null;
453
+ let weekly = null;
454
+ let unknownMin = null;
455
+ for (const w of windows) {
456
+ const cls = classifyWindow(w, fetchedAt);
457
+ if (cls === "scoped")
458
+ continue;
459
+ const h = windowHeadroom(w, now, fetchedAt);
460
+ if (h === null)
461
+ continue;
462
+ if (cls === "session")
463
+ session = session === null ? h : Math.min(session, h);
464
+ else if (cls === "weekly")
465
+ weekly = weekly === null ? h : Math.min(weekly, h);
466
+ else
467
+ unknownMin = unknownMin === null ? h : Math.min(unknownMin, h);
468
+ }
469
+ const weeklyHeadroom = weekly !== null ? unknownMin !== null ? Math.min(weekly, unknownMin) : weekly : unknownMin;
470
+ return { sessionHeadroom: session, weeklyHeadroom };
471
+ }
472
+ function sessionUsage(env = process.env, now = new Date) {
473
+ const empty = { sessionHeadroom: null, weeklyHeadroom: null };
474
+ try {
475
+ const uuid = sessionAccountUuid(env);
476
+ if (!uuid)
477
+ return empty;
478
+ const path = join2(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
479
+ if (!existsSync3(path))
480
+ return empty;
481
+ const entry = readJson(path);
482
+ if (!entry || entry.accountUuid !== uuid)
483
+ return empty;
484
+ const fetchedAt = typeof entry.fetchedAt === "string" ? Date.parse(entry.fetchedAt) : Number.NaN;
485
+ if (!Number.isFinite(fetchedAt) || now.getTime() - fetchedAt > USAGE_CACHE_MAX_AGE_MS)
486
+ return empty;
487
+ return deriveUsage(entry.usage, now.getTime());
488
+ } catch {
489
+ return empty;
490
+ }
491
+ }
398
492
 
399
493
  // src/settings.ts
400
494
  import { readFileSync as readFileSync4 } from "fs";
@@ -433,6 +527,18 @@ function modelName(ctx) {
433
527
  function limitPercent(pct) {
434
528
  return typeof pct === "number" ? pct : 0;
435
529
  }
530
+ function usageLabel(prefix, headroom) {
531
+ return headroom === null ? `${prefix} \u2014` : `${prefix} ${headroom}%`;
532
+ }
533
+ function usageColor(headroom, binding = false) {
534
+ if (headroom === null)
535
+ return "dim";
536
+ if (headroom < 20)
537
+ return "red";
538
+ if (headroom < 50)
539
+ return "yellow";
540
+ return binding ? "green" : null;
541
+ }
436
542
  var segments = [
437
543
  {
438
544
  id: "machine",
@@ -571,6 +677,20 @@ var segments = [
571
677
  return typeof pct === "number" ? `7d:${Math.round(pct)}%` : null;
572
678
  }
573
679
  },
680
+ {
681
+ id: "usage-session",
682
+ description: "Session (5-hour) usage remaining for this pane's own account, from the local accounts usage cache",
683
+ defaultEnabled: false,
684
+ color: () => usageColor(sessionUsage(process.env).sessionHeadroom),
685
+ render: () => usageLabel("5h", sessionUsage(process.env).sessionHeadroom)
686
+ },
687
+ {
688
+ id: "usage-weekly",
689
+ description: "Weekly usage remaining for this pane's own account \u2014 the binding limit \u2014 from the local accounts usage cache",
690
+ defaultEnabled: false,
691
+ color: () => usageColor(sessionUsage(process.env).weeklyHeadroom, true),
692
+ render: () => usageLabel("7d", sessionUsage(process.env).weeklyHeadroom)
693
+ },
574
694
  {
575
695
  id: "thread-title",
576
696
  description: "Thread title set with /rename (omitted until named)",
@@ -718,7 +838,7 @@ async function renderLine(ctx, cfg) {
718
838
  // package.json
719
839
  var package_default = {
720
840
  name: "@hasna/statusline",
721
- version: "0.0.6",
841
+ version: "0.0.7",
722
842
  description: "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
723
843
  license: "Apache-2.0",
724
844
  type: "module",
package/dist/mcp/index.js CHANGED
@@ -250,6 +250,11 @@ var STATE_FILE_PREFIX = ".claude";
250
250
  var STATE_FILE_SUFFIX = ".json";
251
251
  var CUSTOM_OAUTH_ENV = "CLAUDE_CODE_CUSTOM_OAUTH_URL";
252
252
  var OAUTH_VARIANTS = ["", "-custom-oauth", "-local-oauth", "-staging-oauth"];
253
+ var CACHE_SUBDIR = "cache";
254
+ var USAGE_CACHE_SUBDIR = "usage";
255
+ var USAGE_CACHE_MAX_AGE_MS = 15 * 60 * 1000;
256
+ var SESSION_WINDOW_MAX_MS = 5 * 60 * 60 * 1000;
257
+ var SAFE_UUID = /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/;
253
258
  function str(value) {
254
259
  return typeof value === "string" && value.trim() ? value.trim() : null;
255
260
  }
@@ -396,6 +401,95 @@ async function sessionAccountEmail(env = process.env, tool) {
396
401
  return null;
397
402
  }
398
403
  }
404
+ function accountsHomeDir(env) {
405
+ const override = str(env[ACCOUNTS_HOME_ENV]);
406
+ return override ? expandHome(override, env) : join2(home(env), ...DEFAULT_ACCOUNTS_HOME);
407
+ }
408
+ function sessionAccountUuid(env = process.env) {
409
+ try {
410
+ const stateFile = sessionStateFile(env);
411
+ if (!stateFile)
412
+ return null;
413
+ const uuid = str(readJson(stateFile)?.oauthAccount?.accountUuid);
414
+ return uuid && SAFE_UUID.test(uuid) ? uuid : null;
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ function classifyWindow(w, fetchedAt) {
420
+ if (w.scoped === true)
421
+ return "scoped";
422
+ const group = str(w.group);
423
+ if (group === "session" || group === "weekly")
424
+ return group;
425
+ const id = str(w.id);
426
+ if (id) {
427
+ if (id.includes("session"))
428
+ return "session";
429
+ if (id.includes("weekly"))
430
+ return "weekly";
431
+ }
432
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
433
+ if (Number.isFinite(resetsAt) && fetchedAt !== null && resetsAt - fetchedAt > SESSION_WINDOW_MAX_MS) {
434
+ return "weekly";
435
+ }
436
+ return "unknown";
437
+ }
438
+ function windowHeadroom(w, now, fetchedAt) {
439
+ const resetsAt = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
440
+ const rolled = Number.isFinite(resetsAt) && resetsAt <= now && (fetchedAt === null || resetsAt > fetchedAt);
441
+ if (rolled)
442
+ return 100;
443
+ const util = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : null;
444
+ if (util === null)
445
+ return null;
446
+ return Math.max(0, Math.min(100, Math.round(100 - util)));
447
+ }
448
+ function deriveUsage(usage, now) {
449
+ const u = usage ?? {};
450
+ const windows = Array.isArray(u.windows) ? u.windows : [];
451
+ const fetchedAtMs = typeof u.fetchedAt === "string" ? Date.parse(u.fetchedAt) : Number.NaN;
452
+ const fetchedAt = Number.isFinite(fetchedAtMs) ? fetchedAtMs : null;
453
+ let session = null;
454
+ let weekly = null;
455
+ let unknownMin = null;
456
+ for (const w of windows) {
457
+ const cls = classifyWindow(w, fetchedAt);
458
+ if (cls === "scoped")
459
+ continue;
460
+ const h = windowHeadroom(w, now, fetchedAt);
461
+ if (h === null)
462
+ continue;
463
+ if (cls === "session")
464
+ session = session === null ? h : Math.min(session, h);
465
+ else if (cls === "weekly")
466
+ weekly = weekly === null ? h : Math.min(weekly, h);
467
+ else
468
+ unknownMin = unknownMin === null ? h : Math.min(unknownMin, h);
469
+ }
470
+ const weeklyHeadroom = weekly !== null ? unknownMin !== null ? Math.min(weekly, unknownMin) : weekly : unknownMin;
471
+ return { sessionHeadroom: session, weeklyHeadroom };
472
+ }
473
+ function sessionUsage(env = process.env, now = new Date) {
474
+ const empty = { sessionHeadroom: null, weeklyHeadroom: null };
475
+ try {
476
+ const uuid = sessionAccountUuid(env);
477
+ if (!uuid)
478
+ return empty;
479
+ const path = join2(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
480
+ if (!existsSync3(path))
481
+ return empty;
482
+ const entry = readJson(path);
483
+ if (!entry || entry.accountUuid !== uuid)
484
+ return empty;
485
+ const fetchedAt = typeof entry.fetchedAt === "string" ? Date.parse(entry.fetchedAt) : Number.NaN;
486
+ if (!Number.isFinite(fetchedAt) || now.getTime() - fetchedAt > USAGE_CACHE_MAX_AGE_MS)
487
+ return empty;
488
+ return deriveUsage(entry.usage, now.getTime());
489
+ } catch {
490
+ return empty;
491
+ }
492
+ }
399
493
 
400
494
  // src/settings.ts
401
495
  import { readFileSync as readFileSync4 } from "fs";
@@ -434,6 +528,18 @@ function modelName(ctx) {
434
528
  function limitPercent(pct) {
435
529
  return typeof pct === "number" ? pct : 0;
436
530
  }
531
+ function usageLabel(prefix, headroom) {
532
+ return headroom === null ? `${prefix} \u2014` : `${prefix} ${headroom}%`;
533
+ }
534
+ function usageColor(headroom, binding = false) {
535
+ if (headroom === null)
536
+ return "dim";
537
+ if (headroom < 20)
538
+ return "red";
539
+ if (headroom < 50)
540
+ return "yellow";
541
+ return binding ? "green" : null;
542
+ }
437
543
  var segments = [
438
544
  {
439
545
  id: "machine",
@@ -572,6 +678,20 @@ var segments = [
572
678
  return typeof pct === "number" ? `7d:${Math.round(pct)}%` : null;
573
679
  }
574
680
  },
681
+ {
682
+ id: "usage-session",
683
+ description: "Session (5-hour) usage remaining for this pane's own account, from the local accounts usage cache",
684
+ defaultEnabled: false,
685
+ color: () => usageColor(sessionUsage(process.env).sessionHeadroom),
686
+ render: () => usageLabel("5h", sessionUsage(process.env).sessionHeadroom)
687
+ },
688
+ {
689
+ id: "usage-weekly",
690
+ description: "Weekly usage remaining for this pane's own account \u2014 the binding limit \u2014 from the local accounts usage cache",
691
+ defaultEnabled: false,
692
+ color: () => usageColor(sessionUsage(process.env).weeklyHeadroom, true),
693
+ render: () => usageLabel("7d", sessionUsage(process.env).weeklyHeadroom)
694
+ },
575
695
  {
576
696
  id: "thread-title",
577
697
  description: "Thread title set with /rename (omitted until named)",
@@ -719,7 +839,7 @@ async function renderLine(ctx, cfg) {
719
839
  // package.json
720
840
  var package_default = {
721
841
  name: "@hasna/statusline",
722
- version: "0.0.6",
842
+ version: "0.0.7",
723
843
  description: "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
724
844
  license: "Apache-2.0",
725
845
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/statusline",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",