claudeline 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +195 -300
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -262,7 +262,7 @@ function listComponents() {
262
262
  {
263
263
  name: "Account",
264
264
  prefix: "account",
265
- items: ["email", "name", "display-name", "org", "plan", "tier"]
265
+ items: ["email"]
266
266
  },
267
267
  {
268
268
  name: "Usage/Limits",
@@ -439,7 +439,7 @@ var THEMES = {
439
439
  nerd: "nerd:nf-dev-nodejs_small env:node-short sep:dot nerd:nf-cod-folder fs:dir sep:dot nerd:nf-dev-git_branch git:branch git:status",
440
440
  compact: "claude:model sep:slash fs:dir sep:slash git:branch",
441
441
  colorful: "bold:magenta:claude:model sep:arrow cyan:nerd:nf-cod-folder cyan:fs:dir sep:arrow green:nerd:nf-dev-git_branch green:git:branch yellow:git:status sep:arrow blue:nerd:nf-fa-gauge blue:ctx:percent",
442
- luca: "bold:magenta:claude:model if:effort(dim:sep:middot claude:effort-icon claude:effort) dim:sep:middot cyan:nerd:nf-md-source_branch cyan:git:repo sep:none text:: sep:none green:git:branch if:subdir(sep:none white:text:/ sep:none white:fs:relative) if:dirty(dim:sep:middot git:dirty) dim:sep:middot white:account:email sep:newline bold:white:usage:5h-bar:8 usage:5h (usage:5h-pace) dim:usage:5h-reset dim:sep:middot bold:white:usage:week-bar:8 usage:week (usage:week-pace) dim:usage:week-reset dim:sep:middot usage:intensity usage:velocity dim:sep:middot white:cost:total"
442
+ luca: "bold:magenta:claude:model-letter if:effort(dim:sep:dot claude:effort-icon claude:effort) dim:sep:middot cyan:nerd:nf-md-source_branch cyan:git:repo sep:none text:: sep:none green:git:branch if:subdir(sep:none white:text:/ sep:none white:fs:relative) if:dirty(dim:sep:middot git:dirty) dim:sep:middot white:account:email sep:newline dim:nerd:nf-md-clock_fast usage:5h-bar:10 usage:5h usage:5h-pace dim:usage:5h-reset dim:sep:middot dim:nerd:nf-md-calendar_week usage:week-bar:10 usage:week usage:week-pace dim:usage:week-reset dim:sep:middot white:cost:total"
443
443
  };
444
444
  function getTheme(name) {
445
445
  return THEMES[name] || null;
@@ -636,193 +636,9 @@ function getSeparator(name) {
636
636
  }
637
637
 
638
638
  // src/usage.ts
639
- import * as fs2 from "fs";
640
- import * as path2 from "path";
641
- import * as os2 from "os";
642
- import * as crypto from "crypto";
643
- import { execSync } from "child_process";
644
- var CLUSAGE_API_FILE = path2.join(os2.homedir(), ".claude", "clusage-api.json");
645
- var CLUSAGE_STALE_MS = 10 * 60 * 1e3;
646
- var _clusageCache = null;
647
- function readClusageAPI() {
648
- try {
649
- const raw = fs2.readFileSync(CLUSAGE_API_FILE, "utf8");
650
- const payload = JSON.parse(raw);
651
- if (!payload.updatedAt || !payload.accounts?.length) return null;
652
- const age = Date.now() - new Date(payload.updatedAt).getTime();
653
- if (age > CLUSAGE_STALE_MS) return null;
654
- _clusageCache = payload;
655
- return payload;
656
- } catch {
657
- return null;
658
- }
659
- }
660
- function getClusageAccount() {
661
- const payload = _clusageCache ?? readClusageAPI();
662
- if (!payload || payload.accounts.length === 0) return null;
663
- return payload.accounts[0];
664
- }
665
- var CACHE_TTL_MS = 5 * 60 * 1e3;
666
- var PROFILE_CACHE_TTL_MS = 60 * 60 * 1e3;
667
- var ANTHROPIC_BETA_HEADER = "oauth-2025-04-20";
668
- function tokenHash(token) {
669
- return crypto.createHash("sha256").update(token).digest("hex").slice(0, 12);
670
- }
671
- function getCacheFile(token, type) {
672
- const dir = path2.join(getClaudeConfigDir(), "cache");
673
- try {
674
- fs2.mkdirSync(dir, { recursive: true });
675
- } catch {
676
- }
677
- return path2.join(dir, `claudeline-${type}-${tokenHash(token)}.json`);
678
- }
679
- function readCache(cacheFile, ttlMs) {
680
- try {
681
- const raw = fs2.readFileSync(cacheFile, "utf8");
682
- const cached = JSON.parse(raw);
683
- if (Date.now() - cached.fetched_at < ttlMs) {
684
- return cached.data;
685
- }
686
- } catch {
687
- }
688
- return null;
689
- }
690
- function writeCache(cacheFile, data) {
691
- try {
692
- fs2.writeFileSync(cacheFile, JSON.stringify({ data, fetched_at: Date.now() }));
693
- } catch {
694
- }
695
- }
696
- function extractToken(raw) {
697
- try {
698
- const parsed = JSON.parse(raw);
699
- if (typeof parsed === "string") return parsed;
700
- if (parsed.claudeAiOauth?.accessToken) return parsed.claudeAiOauth.accessToken;
701
- if (parsed.accessToken) return parsed.accessToken;
702
- if (parsed.access_token) return parsed.access_token;
703
- } catch {
704
- }
705
- return null;
706
- }
707
- function getKeychainService() {
708
- const configDir = process.env.CLAUDE_CONFIG_DIR;
709
- if (configDir) {
710
- const suffix = crypto.createHash("sha256").update(configDir).digest("hex").slice(0, 8);
711
- return `Claude Code-credentials-${suffix}`;
712
- }
713
- return "Claude Code-credentials";
714
- }
715
- function getOAuthTokenMacOS() {
716
- try {
717
- const service = getKeychainService();
718
- const raw = execSync(
719
- `security find-generic-password -s "${service}" -w 2>/dev/null`,
720
- { encoding: "utf8" }
721
- ).trim();
722
- return extractToken(raw);
723
- } catch {
724
- return null;
725
- }
726
- }
727
- function getOAuthTokenLinux() {
728
- try {
729
- const configDir = process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude");
730
- const credPath = path2.join(configDir, ".credentials.json");
731
- const raw = fs2.readFileSync(credPath, "utf8").trim();
732
- return extractToken(raw);
733
- } catch {
734
- return null;
735
- }
736
- }
737
- function getOAuthToken() {
738
- switch (process.platform) {
739
- case "darwin":
740
- return getOAuthTokenMacOS();
741
- case "linux":
742
- return getOAuthTokenLinux();
743
- default:
744
- return null;
745
- }
746
- }
747
- function apiGet(token, urlPath) {
748
- try {
749
- return execSync(
750
- `curl -s --max-time 5 -H "Content-Type: application/json" -H "User-Agent: claude-code/2.1.69" -H "Authorization: Bearer $__CLAUDE_TOKEN" -H "anthropic-beta: ${ANTHROPIC_BETA_HEADER}" "https://api.anthropic.com${urlPath}"`,
751
- { encoding: "utf8", env: { ...process.env, __CLAUDE_TOKEN: token } }
752
- ).trim();
753
- } catch {
754
- return null;
755
- }
756
- }
757
- function fetchUsageDirect(token) {
758
- try {
759
- const result = apiGet(token, "/api/oauth/usage");
760
- if (!result) return null;
761
- const data = JSON.parse(result);
762
- if (data.five_hour && data.seven_day) return data;
763
- return null;
764
- } catch {
765
- return null;
766
- }
767
- }
768
- function fetchProfileDirect(token) {
769
- try {
770
- const result = apiGet(token, "/api/oauth/profile");
771
- if (!result) return null;
772
- const data = JSON.parse(result);
773
- if (data.account?.email) return { email: data.account.email };
774
- return null;
775
- } catch {
776
- return null;
777
- }
778
- }
779
- function getUsageDataDirect() {
780
- const token = getOAuthToken();
781
- if (!token) return null;
782
- const cacheFile = getCacheFile(token, "usage");
783
- const cached = readCache(cacheFile, CACHE_TTL_MS);
784
- if (cached) return cached;
785
- const data = fetchUsageDirect(token);
786
- if (data) writeCache(cacheFile, data);
787
- return data;
788
- }
789
- function getProfileDataDirect() {
790
- const token = getOAuthToken();
791
- if (!token) return null;
792
- const cacheFile = getCacheFile(token, "profile");
793
- const cached = readCache(cacheFile, PROFILE_CACHE_TTL_MS);
794
- if (cached) return cached;
795
- const data = fetchProfileDirect(token);
796
- if (data) writeCache(cacheFile, data);
797
- return data;
798
- }
799
- function getUsageData() {
800
- const account = getClusageAccount();
801
- if (account) {
802
- return {
803
- five_hour: {
804
- utilization: account.fiveHour.utilization,
805
- resets_at: account.fiveHour.resetsAt
806
- },
807
- seven_day: {
808
- utilization: account.sevenDay.utilization,
809
- resets_at: account.sevenDay.resetsAt
810
- }
811
- };
812
- }
813
- return getUsageDataDirect();
814
- }
815
- function getProfileData() {
816
- const account = getClusageAccount();
817
- if (account?.profile) {
818
- return { email: account.profile.email };
819
- }
820
- return getProfileDataDirect();
821
- }
822
- function formatTimeUntil(isoDate) {
823
- const reset = new Date(isoDate).getTime();
639
+ function formatTimeUntil(resetUnix) {
824
640
  const now = Date.now();
825
- let diff = Math.max(0, reset - now);
641
+ let diff = Math.max(0, resetUnix * 1e3 - now);
826
642
  if (diff === 0) return "now";
827
643
  const days = Math.floor(diff / 864e5);
828
644
  diff %= 864e5;
@@ -835,12 +651,12 @@ function formatTimeUntil(isoDate) {
835
651
  }
836
652
  var FIVE_HOUR_MS = 5 * 60 * 60 * 1e3;
837
653
  var SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1e3;
838
- function calculatePace(window, windowDurationMs) {
839
- const resetMs = new Date(window.resets_at).getTime();
654
+ function calculatePace(usedPct, resetUnix, windowDurationMs) {
655
+ const resetMs = resetUnix * 1e3;
840
656
  const remainingMs = Math.max(0, resetMs - Date.now());
841
657
  const elapsedFraction = 1 - remainingMs / windowDurationMs;
842
658
  const target = elapsedFraction * 100;
843
- const delta = window.utilization - target;
659
+ const delta = usedPct - target;
844
660
  return { target, delta };
845
661
  }
846
662
  function paceColor(delta) {
@@ -848,60 +664,67 @@ function paceColor(delta) {
848
664
  if (delta < -10) return "\x1B[0;36m";
849
665
  return "\x1B[0;32m";
850
666
  }
851
- function formatPaceIcon(delta, noColor) {
667
+ function formatPaceIcon(delta, noColor2) {
852
668
  const icon = delta > 10 ? "\uF0E7" : delta < -10 ? "\uF017" : "\uF00C";
853
- if (noColor) return icon;
669
+ if (noColor2) return icon;
854
670
  return `${paceColor(delta)}${icon}\x1B[0m`;
855
671
  }
856
- function formatPaceDelta(delta, noColor) {
672
+ function formatPaceDelta(delta, noColor2) {
857
673
  const rounded = Math.round(delta);
858
674
  let text;
859
675
  if (rounded > 0) text = "+" + rounded + "%";
860
676
  else if (rounded < 0) text = rounded + "%";
861
677
  else text = "\xB10%";
862
- if (noColor) return text;
678
+ if (noColor2) return text;
863
679
  return `${paceColor(delta)}${text}\x1B[0m`;
864
680
  }
865
- function makeBar(pct, width, label) {
866
- const filled = Math.round(pct / 100 * width);
867
- const bar = "\u25B0".repeat(filled) + "\u25B1".repeat(width - filled);
868
- return label ? label + bar : bar;
681
+ function barColor(pct) {
682
+ if (pct < 50) return "\x1B[0;32m";
683
+ if (pct < 75) return "\x1B[0;33m";
684
+ if (pct < 90) return "\x1B[0;31m";
685
+ return "\x1B[0;1;31m";
869
686
  }
870
- function evaluateAccountComponent(key) {
871
- const data = getProfileData();
872
- if (!data) return "";
873
- switch (key) {
874
- case "email":
875
- return data.email;
876
- default:
877
- return "";
878
- }
687
+ var BLOCKS = ["\u2591", "\u258E", "\u258C", "\u258A", "\u2588"];
688
+ function makeBar(pct, width, noColor2, label) {
689
+ const clamped = Math.max(0, Math.min(100, pct));
690
+ const fillExact = clamped / 100 * width;
691
+ const fullCells = Math.floor(fillExact);
692
+ const fractional = fillExact - fullCells;
693
+ const partialIdx = Math.round(fractional * 4);
694
+ const partialChar = BLOCKS[partialIdx];
695
+ const emptyCells = width - fullCells - 1;
696
+ const filled = "\u2588".repeat(fullCells) + partialChar + "\u2591".repeat(Math.max(0, emptyCells));
697
+ const color = barColor(clamped);
698
+ const bar = noColor2 ? filled : `${color}${filled}\x1B[0m`;
699
+ return label ? label + bar : bar;
879
700
  }
880
- function evaluateUsageComponent(key, args, noColor = false) {
881
- const data = getUsageData();
882
- if (!data) return "";
701
+ function evaluateUsageComponent(key, data, args, noColor2 = false) {
702
+ const limits = data.rate_limits;
703
+ if (!limits) return "";
704
+ const fiveHour = limits.five_hour;
705
+ const sevenDay = limits.seven_day;
883
706
  switch (key) {
884
707
  case "5h":
885
- return Math.round(data.five_hour.utilization) + "%";
708
+ return Math.round(fiveHour.used_percentage) + "%";
886
709
  case "week":
887
710
  case "7d":
888
- return Math.round(data.seven_day.utilization) + "%";
711
+ return Math.round(sevenDay.used_percentage) + "%";
889
712
  case "5h-reset":
890
- return formatTimeUntil(data.five_hour.resets_at);
713
+ return formatTimeUntil(fiveHour.resets_at);
891
714
  case "week-reset":
892
715
  case "7d-reset":
893
- return formatTimeUntil(data.seven_day.resets_at);
716
+ return formatTimeUntil(sevenDay.resets_at);
894
717
  case "5h-bar": {
895
718
  const width = args ? parseInt(args, 10) || 10 : 10;
896
- return makeBar(data.five_hour.utilization, width, "H");
719
+ return makeBar(fiveHour.used_percentage, width, noColor2);
897
720
  }
898
721
  case "week-bar":
899
722
  case "7d-bar": {
900
723
  const width = args ? parseInt(args, 10) || 10 : 10;
901
- return makeBar(data.seven_day.utilization, width, "W");
724
+ return makeBar(sevenDay.used_percentage, width, noColor2);
902
725
  }
903
726
  case "5h-icon": {
904
- const pct = data.five_hour.utilization;
727
+ const pct = fiveHour.used_percentage;
905
728
  const icon = "\uF111";
906
729
  if (pct < 50) return `\x1B[0;32m${icon}\x1B[0m`;
907
730
  if (pct < 75) return `\x1B[0;33m${icon}\x1B[0m`;
@@ -910,7 +733,7 @@ function evaluateUsageComponent(key, args, noColor = false) {
910
733
  }
911
734
  case "week-icon":
912
735
  case "7d-icon": {
913
- const pct = data.seven_day.utilization;
736
+ const pct = sevenDay.used_percentage;
914
737
  const icon = "\uF111";
915
738
  if (pct < 50) return `\x1B[0;32m${icon}\x1B[0m`;
916
739
  if (pct < 75) return `\x1B[0;33m${icon}\x1B[0m`;
@@ -918,67 +741,128 @@ function evaluateUsageComponent(key, args, noColor = false) {
918
741
  return `\x1B[0;1;31m${icon}\x1B[0m`;
919
742
  }
920
743
  case "5h-target": {
921
- const { target } = calculatePace(data.five_hour, FIVE_HOUR_MS);
744
+ const { target } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
922
745
  return Math.round(target) + "%";
923
746
  }
924
747
  case "week-target":
925
748
  case "7d-target": {
926
- const { target } = calculatePace(data.seven_day, SEVEN_DAY_MS);
749
+ const { target } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
927
750
  return Math.round(target) + "%";
928
751
  }
929
752
  case "5h-pace": {
930
- const { delta } = calculatePace(data.five_hour, FIVE_HOUR_MS);
931
- return formatPaceDelta(delta, noColor);
753
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
754
+ return formatPaceDelta(delta, noColor2);
932
755
  }
933
756
  case "week-pace":
934
757
  case "7d-pace": {
935
- const { delta } = calculatePace(data.seven_day, SEVEN_DAY_MS);
936
- return formatPaceDelta(delta, noColor);
758
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
759
+ return formatPaceDelta(delta, noColor2);
937
760
  }
938
761
  case "5h-pace-icon": {
939
- const { delta } = calculatePace(data.five_hour, FIVE_HOUR_MS);
940
- return formatPaceIcon(delta, noColor);
762
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
763
+ return formatPaceIcon(delta, noColor2);
941
764
  }
942
765
  case "week-pace-icon":
943
766
  case "7d-pace-icon": {
944
- const { delta } = calculatePace(data.seven_day, SEVEN_DAY_MS);
945
- return formatPaceIcon(delta, noColor);
946
- }
947
- // Clusage-only momentum/projection data (no direct API fallback)
948
- case "velocity": {
949
- const m = getClusageAccount()?.momentum;
950
- return m ? m.velocity.toFixed(1) + " pp/hr" : "";
767
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
768
+ return formatPaceIcon(delta, noColor2);
951
769
  }
952
- case "intensity": {
953
- const m = getClusageAccount()?.momentum;
954
- return m?.intensity ?? "";
955
- }
956
- case "eta": {
957
- const m = getClusageAccount()?.momentum;
958
- if (!m?.etaToCeiling) return "";
959
- const h = Math.floor(m.etaToCeiling / 3600);
960
- const min = Math.floor(m.etaToCeiling % 3600 / 60);
961
- return h > 0 ? `${h}h ${min}m` : `${min}m`;
962
- }
963
- case "7d-granular": {
964
- const p = getClusageAccount()?.projection;
965
- return p?.granularSevenDayUtilization != null ? p.granularSevenDayUtilization.toFixed(1) + "%" : "";
770
+ default:
771
+ return "";
772
+ }
773
+ }
774
+
775
+ // src/account.ts
776
+ import * as fs2 from "fs";
777
+ import * as path2 from "path";
778
+ import * as os2 from "os";
779
+ import * as crypto from "crypto";
780
+ import { execSync } from "child_process";
781
+ var PROFILE_CACHE_TTL_MS = 60 * 60 * 1e3;
782
+ function extractToken(raw) {
783
+ try {
784
+ const parsed = JSON.parse(raw);
785
+ if (typeof parsed === "string") return parsed;
786
+ if (parsed.claudeAiOauth?.accessToken) return parsed.claudeAiOauth.accessToken;
787
+ if (parsed.accessToken) return parsed.accessToken;
788
+ if (parsed.access_token) return parsed.access_token;
789
+ } catch {
790
+ }
791
+ return null;
792
+ }
793
+ function getKeychainService() {
794
+ const configDir = process.env.CLAUDE_CONFIG_DIR;
795
+ if (configDir) {
796
+ const suffix = crypto.createHash("sha256").update(configDir).digest("hex").slice(0, 8);
797
+ return `Claude Code-credentials-${suffix}`;
798
+ }
799
+ return "Claude Code-credentials";
800
+ }
801
+ function getOAuthToken() {
802
+ try {
803
+ if (process.platform === "darwin") {
804
+ const service = getKeychainService();
805
+ const raw = execSync(
806
+ `security find-generic-password -s "${service}" -w 2>/dev/null`,
807
+ { encoding: "utf8" }
808
+ ).trim();
809
+ return extractToken(raw);
966
810
  }
967
- case "7d-projected": {
968
- const p = getClusageAccount()?.projection;
969
- return p ? Math.round(p.projectedAtReset) + "%" : "";
811
+ if (process.platform === "linux") {
812
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude");
813
+ const raw = fs2.readFileSync(path2.join(configDir, ".credentials.json"), "utf8").trim();
814
+ return extractToken(raw);
970
815
  }
971
- case "budget": {
972
- const p = getClusageAccount()?.projection;
973
- return p ? p.dailyBudget.toFixed(1) + " pp/day" : "";
816
+ } catch {
817
+ }
818
+ return null;
819
+ }
820
+ function getCacheFile(token) {
821
+ const hash = crypto.createHash("sha256").update(token).digest("hex").slice(0, 12);
822
+ const dir = path2.join(getClaudeConfigDir(), "cache");
823
+ try {
824
+ fs2.mkdirSync(dir, { recursive: true });
825
+ } catch {
826
+ }
827
+ return path2.join(dir, `claudeline-profile-${hash}.json`);
828
+ }
829
+ function readCachedProfile(cacheFile) {
830
+ try {
831
+ const cached = JSON.parse(fs2.readFileSync(cacheFile, "utf8"));
832
+ if (Date.now() - cached.fetched_at < PROFILE_CACHE_TTL_MS) {
833
+ return cached.email;
974
834
  }
975
- case "budget-status": {
976
- const p = getClusageAccount()?.projection;
977
- return p?.status ?? "";
835
+ } catch {
836
+ }
837
+ return null;
838
+ }
839
+ function fetchEmail(token) {
840
+ try {
841
+ const result = execSync(
842
+ `curl -s --max-time 5 -H "Content-Type: application/json" -H "User-Agent: claude-code/2.1.80" -H "Authorization: Bearer $__CLAUDE_TOKEN" -H "anthropic-beta: oauth-2025-04-20" "https://api.anthropic.com/api/oauth/profile"`,
843
+ { encoding: "utf8", env: { ...process.env, __CLAUDE_TOKEN: token } }
844
+ ).trim();
845
+ const data = JSON.parse(result);
846
+ return data.account?.email ?? null;
847
+ } catch {
848
+ return null;
849
+ }
850
+ }
851
+ function evaluateAccountComponent(key) {
852
+ if (key !== "email") return "";
853
+ const token = getOAuthToken();
854
+ if (!token) return "";
855
+ const cacheFile = getCacheFile(token);
856
+ const cached = readCachedProfile(cacheFile);
857
+ if (cached) return cached;
858
+ const email = fetchEmail(token);
859
+ if (email) {
860
+ try {
861
+ fs2.writeFileSync(cacheFile, JSON.stringify({ email, fetched_at: Date.now() }));
862
+ } catch {
978
863
  }
979
- default:
980
- return "";
981
864
  }
865
+ return email ?? "";
982
866
  }
983
867
 
984
868
  // src/nerdfonts.ts
@@ -1131,53 +1015,44 @@ function execCommand(cmd) {
1131
1015
  return "";
1132
1016
  }
1133
1017
  }
1134
- function applyStyles(text, styles, noColor) {
1135
- if (noColor || styles.length === 0 || !text) return text;
1018
+ function applyStyles(text, styles, noColor2) {
1019
+ if (noColor2 || styles.length === 0 || !text) return text;
1136
1020
  const codes = styles.map((s) => COLORS[s]).filter(Boolean);
1137
1021
  if (codes.length === 0) return text;
1138
1022
  return `\x1B[0;${codes.join(";")}m${text}\x1B[${RESET}m`;
1139
1023
  }
1140
- var settingsCache = /* @__PURE__ */ new Map();
1141
- var SETTINGS_CACHE_TTL = 1e4;
1142
- function readSettingsFile(filePath) {
1143
- const cached = settingsCache.get(filePath);
1144
- if (cached && Date.now() - cached.ts < SETTINGS_CACHE_TTL) {
1145
- return cached.data;
1146
- }
1024
+ var VALID_EFFORTS = ["low", "medium", "high"];
1025
+ function isValidEffort(value) {
1026
+ return typeof value === "string" && VALID_EFFORTS.includes(value);
1027
+ }
1028
+ function readEffortFromSettings(filePath) {
1147
1029
  try {
1148
1030
  const data = JSON.parse(fs3.readFileSync(filePath, "utf8"));
1149
- settingsCache.set(filePath, { data, ts: Date.now() });
1150
- return data;
1031
+ return isValidEffort(data.effortLevel) ? data.effortLevel : null;
1151
1032
  } catch {
1152
- return {};
1033
+ return null;
1153
1034
  }
1154
1035
  }
1155
- var VALID_EFFORTS = ["low", "medium", "high"];
1156
1036
  function resolveEffort(data) {
1157
- const stdinEffort = data.model?.reasoning_effort;
1158
- if (stdinEffort && VALID_EFFORTS.includes(stdinEffort)) {
1159
- return stdinEffort;
1160
- }
1161
- let effort = "high";
1162
- const globalPath = path3.join(os3.homedir(), ".claude", "settings.json");
1163
- const globalSettings = readSettingsFile(globalPath);
1164
- if (typeof globalSettings.effortLevel === "string" && VALID_EFFORTS.includes(globalSettings.effortLevel)) {
1165
- effort = globalSettings.effortLevel;
1037
+ if (isValidEffort(data.model?.reasoning_effort)) {
1038
+ return data.model.reasoning_effort;
1166
1039
  }
1040
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path3.join(os3.homedir(), ".claude");
1041
+ let effort = readEffortFromSettings(path3.join(configDir, "settings.json"));
1167
1042
  const projectDir = data.workspace?.project_dir;
1168
1043
  if (projectDir) {
1169
- const projectPath = path3.join(projectDir, ".claude", "settings.json");
1170
- const projectSettings = readSettingsFile(projectPath);
1171
- if (typeof projectSettings.effortLevel === "string" && VALID_EFFORTS.includes(projectSettings.effortLevel)) {
1172
- effort = projectSettings.effortLevel;
1173
- }
1044
+ effort = readEffortFromSettings(path3.join(projectDir, ".claude", "settings.json")) ?? effort;
1174
1045
  }
1175
1046
  return effort;
1176
1047
  }
1177
- function evaluateClaudeComponent(key, data, noColor = false, noIcons = false) {
1048
+ function evaluateClaudeComponent(key, data, noColor2 = false, noIcons = false) {
1178
1049
  switch (key) {
1179
1050
  case "model":
1180
1051
  return data.model?.display_name || "Claude";
1052
+ case "model-short": {
1053
+ const name = data.model?.display_name || "Claude";
1054
+ return name.replace(/\s*\(.*\)$/, "");
1055
+ }
1181
1056
  case "model-id":
1182
1057
  return data.model?.id || "";
1183
1058
  case "model-letter":
@@ -1191,9 +1066,10 @@ function evaluateClaudeComponent(key, data, noColor = false, noIcons = false) {
1191
1066
  case "effort":
1192
1067
  case "effort-icon": {
1193
1068
  const effort = resolveEffort(data);
1069
+ if (!effort) return "";
1194
1070
  if (key === "effort-icon" && noIcons) return "";
1195
1071
  const text = key === "effort-icon" ? "\u{F09D1}" : effort;
1196
- if (noColor) return text;
1072
+ if (noColor2) return text;
1197
1073
  const r = `\x1B[${RESET}m`;
1198
1074
  const effortColors = {
1199
1075
  low: COLORS.green,
@@ -1235,7 +1111,7 @@ function evaluateFsComponent(key, data) {
1235
1111
  return "";
1236
1112
  }
1237
1113
  }
1238
- function evaluateGitComponent(key, noColor = false, noIcons = false) {
1114
+ function evaluateGitComponent(key, noColor2 = false, noIcons = false) {
1239
1115
  switch (key) {
1240
1116
  case "branch":
1241
1117
  return execCommand("git branch --show-current 2>/dev/null");
@@ -1299,7 +1175,7 @@ function evaluateGitComponent(key, noColor = false, noIcons = false) {
1299
1175
  const untracked = parseInt(execCommand("git ls-files --others --exclude-standard 2>/dev/null | wc -l")) || 0;
1300
1176
  if (staged === 0 && modified === 0 && untracked === 0) return "";
1301
1177
  const parts = [];
1302
- if (noColor) {
1178
+ if (noColor2) {
1303
1179
  if (staged > 0) parts.push("+" + staged);
1304
1180
  if (untracked > 0) parts.push("-" + untracked);
1305
1181
  if (modified > 0) parts.push("~" + modified);
@@ -1355,10 +1231,20 @@ function evaluateContextComponent(key, data, args, noIcons = false) {
1355
1231
  case "size":
1356
1232
  return formatTokens(ctx?.context_window_size || 2e5);
1357
1233
  case "bar": {
1358
- const pct = ctx?.used_percentage || 0;
1234
+ const pct = Math.max(0, Math.min(100, ctx?.used_percentage || 0));
1359
1235
  const width = args ? parseInt(args, 10) || 10 : 10;
1360
- const filled = Math.round(pct / 100 * width);
1361
- return "[" + "\u2588".repeat(filled) + "\u2591".repeat(width - filled) + "]";
1236
+ const BLOCKS2 = ["\u2591", "\u258E", "\u258C", "\u258A", "\u2588"];
1237
+ const fillExact = pct / 100 * width;
1238
+ const fullCells = Math.floor(fillExact);
1239
+ const fractional = fillExact - fullCells;
1240
+ const partialIdx = Math.round(fractional * 4);
1241
+ const partialChar = BLOCKS2[partialIdx];
1242
+ const emptyCells = width - fullCells - 1;
1243
+ const barStr = "\u2588".repeat(fullCells) + partialChar + "\u2591".repeat(Math.max(0, emptyCells));
1244
+ if (noColor) return barStr;
1245
+ const color = pct < 50 ? COLORS.green : pct < 75 ? COLORS.yellow : COLORS.red;
1246
+ const bold = pct >= 90 ? `;${COLORS.bold}` : "";
1247
+ return `\x1B[0;${color}${bold}m${barStr}\x1B[${RESET}m`;
1362
1248
  }
1363
1249
  case "icon": {
1364
1250
  if (noIcons) return "";
@@ -1582,7 +1468,7 @@ function evaluateComponent(comp, data, options) {
1582
1468
  result = evaluateEnvComponent(comp.key);
1583
1469
  break;
1584
1470
  case "usage":
1585
- result = evaluateUsageComponent(comp.key, comp.args, options.noColor);
1471
+ result = evaluateUsageComponent(comp.key, data, comp.args, options.noColor);
1586
1472
  break;
1587
1473
  case "account":
1588
1474
  result = evaluateAccountComponent(comp.key);
@@ -1616,14 +1502,14 @@ function evaluateComponent(comp, data, options) {
1616
1502
  }
1617
1503
  case "conditional": {
1618
1504
  if (comp.children && evaluateCondition(comp.key, data)) {
1619
- result = evaluateComponents(comp.children, data, options);
1505
+ result = evaluateComponents(comp.children, data, options, true);
1620
1506
  }
1621
1507
  break;
1622
1508
  }
1623
1509
  }
1624
1510
  return applyStyles(result, comp.styles, options.noColor);
1625
1511
  }
1626
- function evaluateComponents(components, data, options) {
1512
+ function evaluateComponents(components, data, options, preserveEdgeSeparators = false) {
1627
1513
  const evaluated = components.map((comp) => ({
1628
1514
  type: comp.type,
1629
1515
  value: evaluateComponent(comp, data, options)
@@ -1648,7 +1534,11 @@ function evaluateComponents(components, data, options) {
1648
1534
  }
1649
1535
  break;
1650
1536
  }
1651
- if (!hasBefore || !hasAfter) continue;
1537
+ if (preserveEdgeSeparators) {
1538
+ if (!hasBefore && !hasAfter) continue;
1539
+ } else {
1540
+ if (!hasBefore || !hasAfter) continue;
1541
+ }
1652
1542
  }
1653
1543
  filtered.push(entry);
1654
1544
  }
@@ -1656,8 +1546,13 @@ function evaluateComponents(components, data, options) {
1656
1546
  for (let i = 0; i < filtered.length; i++) {
1657
1547
  const entry = filtered[i];
1658
1548
  const prev = filtered[i - 1];
1659
- if (i > 0 && entry.type !== "sep" && entry.type !== "conditional" && prev?.type !== "sep" && entry.value) {
1660
- parts.push(" ");
1549
+ if (i > 0 && entry.type !== "sep" && prev?.type !== "sep" && entry.value) {
1550
+ if (entry.type === "conditional") {
1551
+ const stripped = entry.value.replace(/\x1b\[[0-9;]*m/g, "");
1552
+ if (!stripped.startsWith(" ")) parts.push(" ");
1553
+ } else {
1554
+ parts.push(" ");
1555
+ }
1661
1556
  }
1662
1557
  if (entry.value) {
1663
1558
  parts.push(entry.value);
@@ -1676,7 +1571,7 @@ function evaluateFormat(format, data, options = {}) {
1676
1571
  }
1677
1572
 
1678
1573
  // src/index.ts
1679
- var VERSION = "1.8.0";
1574
+ var VERSION = true ? "1.9.0" : "dev";
1680
1575
  async function readStdin() {
1681
1576
  return new Promise((resolve, reject) => {
1682
1577
  let input = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeline",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Customizable status line generator for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {