claudeline 1.7.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 +235 -173
  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;
@@ -598,7 +598,7 @@ function getSampleDataJson() {
598
598
  import * as path3 from "path";
599
599
  import * as os3 from "os";
600
600
  import * as fs3 from "fs";
601
- import { execSync } from "child_process";
601
+ import { execSync as execSync2 } from "child_process";
602
602
 
603
603
  // src/separators.ts
604
604
  var SEPARATORS = {
@@ -636,53 +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
- var CLUSAGE_API_FILE = path2.join(os2.homedir(), ".claude", "clusage-api.json");
643
- var CLUSAGE_STALE_MS = 10 * 60 * 1e3;
644
- var _clusageCache = null;
645
- function readClusageAPI() {
646
- try {
647
- const raw = fs2.readFileSync(CLUSAGE_API_FILE, "utf8");
648
- const payload = JSON.parse(raw);
649
- if (!payload.updatedAt || !payload.accounts?.length) return null;
650
- const age = Date.now() - new Date(payload.updatedAt).getTime();
651
- if (age > CLUSAGE_STALE_MS) return null;
652
- _clusageCache = payload;
653
- return payload;
654
- } catch {
655
- return null;
656
- }
657
- }
658
- function getClusageAccount() {
659
- const payload = _clusageCache ?? readClusageAPI();
660
- if (!payload || payload.accounts.length === 0) return null;
661
- return payload.accounts[0];
662
- }
663
- function getUsageData() {
664
- const account = getClusageAccount();
665
- if (!account) return null;
666
- return {
667
- five_hour: {
668
- utilization: account.fiveHour.utilization,
669
- resets_at: account.fiveHour.resetsAt
670
- },
671
- seven_day: {
672
- utilization: account.sevenDay.utilization,
673
- resets_at: account.sevenDay.resetsAt
674
- }
675
- };
676
- }
677
- function getProfileData() {
678
- const account = getClusageAccount();
679
- if (!account?.profile) return null;
680
- return { email: account.profile.email };
681
- }
682
- function formatTimeUntil(isoDate) {
683
- const reset = new Date(isoDate).getTime();
639
+ function formatTimeUntil(resetUnix) {
684
640
  const now = Date.now();
685
- let diff = Math.max(0, reset - now);
641
+ let diff = Math.max(0, resetUnix * 1e3 - now);
686
642
  if (diff === 0) return "now";
687
643
  const days = Math.floor(diff / 864e5);
688
644
  diff %= 864e5;
@@ -695,12 +651,12 @@ function formatTimeUntil(isoDate) {
695
651
  }
696
652
  var FIVE_HOUR_MS = 5 * 60 * 60 * 1e3;
697
653
  var SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1e3;
698
- function calculatePace(window, windowDurationMs) {
699
- const resetMs = new Date(window.resets_at).getTime();
654
+ function calculatePace(usedPct, resetUnix, windowDurationMs) {
655
+ const resetMs = resetUnix * 1e3;
700
656
  const remainingMs = Math.max(0, resetMs - Date.now());
701
657
  const elapsedFraction = 1 - remainingMs / windowDurationMs;
702
658
  const target = elapsedFraction * 100;
703
- const delta = window.utilization - target;
659
+ const delta = usedPct - target;
704
660
  return { target, delta };
705
661
  }
706
662
  function paceColor(delta) {
@@ -708,60 +664,67 @@ function paceColor(delta) {
708
664
  if (delta < -10) return "\x1B[0;36m";
709
665
  return "\x1B[0;32m";
710
666
  }
711
- function formatPaceIcon(delta, noColor) {
667
+ function formatPaceIcon(delta, noColor2) {
712
668
  const icon = delta > 10 ? "\uF0E7" : delta < -10 ? "\uF017" : "\uF00C";
713
- if (noColor) return icon;
669
+ if (noColor2) return icon;
714
670
  return `${paceColor(delta)}${icon}\x1B[0m`;
715
671
  }
716
- function formatPaceDelta(delta, noColor) {
672
+ function formatPaceDelta(delta, noColor2) {
717
673
  const rounded = Math.round(delta);
718
674
  let text;
719
675
  if (rounded > 0) text = "+" + rounded + "%";
720
676
  else if (rounded < 0) text = rounded + "%";
721
677
  else text = "\xB10%";
722
- if (noColor) return text;
678
+ if (noColor2) return text;
723
679
  return `${paceColor(delta)}${text}\x1B[0m`;
724
680
  }
725
- function makeBar(pct, width, label) {
726
- const filled = Math.round(pct / 100 * width);
727
- const bar = "\u25B0".repeat(filled) + "\u25B1".repeat(width - filled);
728
- 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";
729
686
  }
730
- function evaluateAccountComponent(key) {
731
- const data = getProfileData();
732
- if (!data) return "";
733
- switch (key) {
734
- case "email":
735
- return data.email;
736
- default:
737
- return "";
738
- }
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;
739
700
  }
740
- function evaluateUsageComponent(key, args, noColor = false) {
741
- const data = getUsageData();
742
- 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;
743
706
  switch (key) {
744
707
  case "5h":
745
- return Math.round(data.five_hour.utilization) + "%";
708
+ return Math.round(fiveHour.used_percentage) + "%";
746
709
  case "week":
747
710
  case "7d":
748
- return Math.round(data.seven_day.utilization) + "%";
711
+ return Math.round(sevenDay.used_percentage) + "%";
749
712
  case "5h-reset":
750
- return formatTimeUntil(data.five_hour.resets_at);
713
+ return formatTimeUntil(fiveHour.resets_at);
751
714
  case "week-reset":
752
715
  case "7d-reset":
753
- return formatTimeUntil(data.seven_day.resets_at);
716
+ return formatTimeUntil(sevenDay.resets_at);
754
717
  case "5h-bar": {
755
718
  const width = args ? parseInt(args, 10) || 10 : 10;
756
- return makeBar(data.five_hour.utilization, width, "H");
719
+ return makeBar(fiveHour.used_percentage, width, noColor2);
757
720
  }
758
721
  case "week-bar":
759
722
  case "7d-bar": {
760
723
  const width = args ? parseInt(args, 10) || 10 : 10;
761
- return makeBar(data.seven_day.utilization, width, "W");
724
+ return makeBar(sevenDay.used_percentage, width, noColor2);
762
725
  }
763
726
  case "5h-icon": {
764
- const pct = data.five_hour.utilization;
727
+ const pct = fiveHour.used_percentage;
765
728
  const icon = "\uF111";
766
729
  if (pct < 50) return `\x1B[0;32m${icon}\x1B[0m`;
767
730
  if (pct < 75) return `\x1B[0;33m${icon}\x1B[0m`;
@@ -770,7 +733,7 @@ function evaluateUsageComponent(key, args, noColor = false) {
770
733
  }
771
734
  case "week-icon":
772
735
  case "7d-icon": {
773
- const pct = data.seven_day.utilization;
736
+ const pct = sevenDay.used_percentage;
774
737
  const icon = "\uF111";
775
738
  if (pct < 50) return `\x1B[0;32m${icon}\x1B[0m`;
776
739
  if (pct < 75) return `\x1B[0;33m${icon}\x1B[0m`;
@@ -778,67 +741,128 @@ function evaluateUsageComponent(key, args, noColor = false) {
778
741
  return `\x1B[0;1;31m${icon}\x1B[0m`;
779
742
  }
780
743
  case "5h-target": {
781
- const { target } = calculatePace(data.five_hour, FIVE_HOUR_MS);
744
+ const { target } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
782
745
  return Math.round(target) + "%";
783
746
  }
784
747
  case "week-target":
785
748
  case "7d-target": {
786
- const { target } = calculatePace(data.seven_day, SEVEN_DAY_MS);
749
+ const { target } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
787
750
  return Math.round(target) + "%";
788
751
  }
789
752
  case "5h-pace": {
790
- const { delta } = calculatePace(data.five_hour, FIVE_HOUR_MS);
791
- return formatPaceDelta(delta, noColor);
753
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
754
+ return formatPaceDelta(delta, noColor2);
792
755
  }
793
756
  case "week-pace":
794
757
  case "7d-pace": {
795
- const { delta } = calculatePace(data.seven_day, SEVEN_DAY_MS);
796
- return formatPaceDelta(delta, noColor);
758
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
759
+ return formatPaceDelta(delta, noColor2);
797
760
  }
798
761
  case "5h-pace-icon": {
799
- const { delta } = calculatePace(data.five_hour, FIVE_HOUR_MS);
800
- return formatPaceIcon(delta, noColor);
762
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
763
+ return formatPaceIcon(delta, noColor2);
801
764
  }
802
765
  case "week-pace-icon":
803
766
  case "7d-pace-icon": {
804
- const { delta } = calculatePace(data.seven_day, SEVEN_DAY_MS);
805
- return formatPaceIcon(delta, noColor);
806
- }
807
- // Clusage momentum/projection data
808
- case "velocity": {
809
- const m = getClusageAccount()?.momentum;
810
- return m ? m.velocity.toFixed(1) + " pp/hr" : "";
811
- }
812
- case "intensity": {
813
- const m = getClusageAccount()?.momentum;
814
- return m?.intensity ?? "";
767
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
768
+ return formatPaceIcon(delta, noColor2);
815
769
  }
816
- case "eta": {
817
- const m = getClusageAccount()?.momentum;
818
- if (!m?.etaToCeiling) return "";
819
- const h = Math.floor(m.etaToCeiling / 3600);
820
- const min = Math.floor(m.etaToCeiling % 3600 / 60);
821
- return h > 0 ? `${h}h ${min}m` : `${min}m`;
822
- }
823
- case "7d-granular": {
824
- const p = getClusageAccount()?.projection;
825
- 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);
826
810
  }
827
- case "7d-projected": {
828
- const p = getClusageAccount()?.projection;
829
- 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);
830
815
  }
831
- case "budget": {
832
- const p = getClusageAccount()?.projection;
833
- 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;
834
834
  }
835
- case "budget-status": {
836
- const p = getClusageAccount()?.projection;
837
- 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 {
838
863
  }
839
- default:
840
- return "";
841
864
  }
865
+ return email ?? "";
842
866
  }
843
867
 
844
868
  // src/nerdfonts.ts
@@ -986,58 +1010,49 @@ function formatDuration(ms) {
986
1010
  }
987
1011
  function execCommand(cmd) {
988
1012
  try {
989
- return execSync(cmd, { encoding: "utf8" }).trim();
1013
+ return execSync2(cmd, { encoding: "utf8" }).trim();
990
1014
  } catch {
991
1015
  return "";
992
1016
  }
993
1017
  }
994
- function applyStyles(text, styles, noColor) {
995
- if (noColor || styles.length === 0 || !text) return text;
1018
+ function applyStyles(text, styles, noColor2) {
1019
+ if (noColor2 || styles.length === 0 || !text) return text;
996
1020
  const codes = styles.map((s) => COLORS[s]).filter(Boolean);
997
1021
  if (codes.length === 0) return text;
998
1022
  return `\x1B[0;${codes.join(";")}m${text}\x1B[${RESET}m`;
999
1023
  }
1000
- var settingsCache = /* @__PURE__ */ new Map();
1001
- var SETTINGS_CACHE_TTL = 1e4;
1002
- function readSettingsFile(filePath) {
1003
- const cached = settingsCache.get(filePath);
1004
- if (cached && Date.now() - cached.ts < SETTINGS_CACHE_TTL) {
1005
- return cached.data;
1006
- }
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) {
1007
1029
  try {
1008
1030
  const data = JSON.parse(fs3.readFileSync(filePath, "utf8"));
1009
- settingsCache.set(filePath, { data, ts: Date.now() });
1010
- return data;
1031
+ return isValidEffort(data.effortLevel) ? data.effortLevel : null;
1011
1032
  } catch {
1012
- return {};
1033
+ return null;
1013
1034
  }
1014
1035
  }
1015
- var VALID_EFFORTS = ["low", "medium", "high"];
1016
1036
  function resolveEffort(data) {
1017
- const stdinEffort = data.model?.reasoning_effort;
1018
- if (stdinEffort && VALID_EFFORTS.includes(stdinEffort)) {
1019
- return stdinEffort;
1020
- }
1021
- let effort = "high";
1022
- const globalPath = path3.join(os3.homedir(), ".claude", "settings.json");
1023
- const globalSettings = readSettingsFile(globalPath);
1024
- if (typeof globalSettings.effortLevel === "string" && VALID_EFFORTS.includes(globalSettings.effortLevel)) {
1025
- effort = globalSettings.effortLevel;
1037
+ if (isValidEffort(data.model?.reasoning_effort)) {
1038
+ return data.model.reasoning_effort;
1026
1039
  }
1040
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path3.join(os3.homedir(), ".claude");
1041
+ let effort = readEffortFromSettings(path3.join(configDir, "settings.json"));
1027
1042
  const projectDir = data.workspace?.project_dir;
1028
1043
  if (projectDir) {
1029
- const projectPath = path3.join(projectDir, ".claude", "settings.json");
1030
- const projectSettings = readSettingsFile(projectPath);
1031
- if (typeof projectSettings.effortLevel === "string" && VALID_EFFORTS.includes(projectSettings.effortLevel)) {
1032
- effort = projectSettings.effortLevel;
1033
- }
1044
+ effort = readEffortFromSettings(path3.join(projectDir, ".claude", "settings.json")) ?? effort;
1034
1045
  }
1035
1046
  return effort;
1036
1047
  }
1037
- function evaluateClaudeComponent(key, data, noColor = false, noIcons = false) {
1048
+ function evaluateClaudeComponent(key, data, noColor2 = false, noIcons = false) {
1038
1049
  switch (key) {
1039
1050
  case "model":
1040
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
+ }
1041
1056
  case "model-id":
1042
1057
  return data.model?.id || "";
1043
1058
  case "model-letter":
@@ -1051,9 +1066,10 @@ function evaluateClaudeComponent(key, data, noColor = false, noIcons = false) {
1051
1066
  case "effort":
1052
1067
  case "effort-icon": {
1053
1068
  const effort = resolveEffort(data);
1069
+ if (!effort) return "";
1054
1070
  if (key === "effort-icon" && noIcons) return "";
1055
1071
  const text = key === "effort-icon" ? "\u{F09D1}" : effort;
1056
- if (noColor) return text;
1072
+ if (noColor2) return text;
1057
1073
  const r = `\x1B[${RESET}m`;
1058
1074
  const effortColors = {
1059
1075
  low: COLORS.green,
@@ -1095,13 +1111,13 @@ function evaluateFsComponent(key, data) {
1095
1111
  return "";
1096
1112
  }
1097
1113
  }
1098
- function evaluateGitComponent(key, noColor = false, noIcons = false) {
1114
+ function evaluateGitComponent(key, noColor2 = false, noIcons = false) {
1099
1115
  switch (key) {
1100
1116
  case "branch":
1101
1117
  return execCommand("git branch --show-current 2>/dev/null");
1102
1118
  case "status":
1103
1119
  try {
1104
- execSync("git diff --quiet 2>/dev/null");
1120
+ execSync2("git diff --quiet 2>/dev/null");
1105
1121
  return "\u2713";
1106
1122
  } catch {
1107
1123
  return "*";
@@ -1109,14 +1125,14 @@ function evaluateGitComponent(key, noColor = false, noIcons = false) {
1109
1125
  case "status-icon":
1110
1126
  if (noIcons) return "";
1111
1127
  try {
1112
- execSync("git diff --quiet 2>/dev/null");
1128
+ execSync2("git diff --quiet 2>/dev/null");
1113
1129
  return getNerdIcon("sparkle");
1114
1130
  } catch {
1115
1131
  return getNerdIcon("commit");
1116
1132
  }
1117
1133
  case "status-word":
1118
1134
  try {
1119
- execSync("git diff --quiet 2>/dev/null");
1135
+ execSync2("git diff --quiet 2>/dev/null");
1120
1136
  return "clean";
1121
1137
  } catch {
1122
1138
  return "dirty";
@@ -1159,7 +1175,7 @@ function evaluateGitComponent(key, noColor = false, noIcons = false) {
1159
1175
  const untracked = parseInt(execCommand("git ls-files --others --exclude-standard 2>/dev/null | wc -l")) || 0;
1160
1176
  if (staged === 0 && modified === 0 && untracked === 0) return "";
1161
1177
  const parts = [];
1162
- if (noColor) {
1178
+ if (noColor2) {
1163
1179
  if (staged > 0) parts.push("+" + staged);
1164
1180
  if (untracked > 0) parts.push("-" + untracked);
1165
1181
  if (modified > 0) parts.push("~" + modified);
@@ -1215,10 +1231,20 @@ function evaluateContextComponent(key, data, args, noIcons = false) {
1215
1231
  case "size":
1216
1232
  return formatTokens(ctx?.context_window_size || 2e5);
1217
1233
  case "bar": {
1218
- const pct = ctx?.used_percentage || 0;
1234
+ const pct = Math.max(0, Math.min(100, ctx?.used_percentage || 0));
1219
1235
  const width = args ? parseInt(args, 10) || 10 : 10;
1220
- const filled = Math.round(pct / 100 * width);
1221
- 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`;
1222
1248
  }
1223
1249
  case "icon": {
1224
1250
  if (noIcons) return "";
@@ -1370,21 +1396,21 @@ function evaluateCondition(condition, data) {
1370
1396
  switch (condition) {
1371
1397
  case "git":
1372
1398
  try {
1373
- execSync("git rev-parse --git-dir 2>/dev/null");
1399
+ execSync2("git rev-parse --git-dir 2>/dev/null");
1374
1400
  return true;
1375
1401
  } catch {
1376
1402
  return false;
1377
1403
  }
1378
1404
  case "dirty":
1379
1405
  try {
1380
- execSync("git diff --quiet 2>/dev/null");
1406
+ execSync2("git diff --quiet 2>/dev/null");
1381
1407
  return false;
1382
1408
  } catch {
1383
1409
  return true;
1384
1410
  }
1385
1411
  case "clean":
1386
1412
  try {
1387
- execSync("git diff --quiet 2>/dev/null");
1413
+ execSync2("git diff --quiet 2>/dev/null");
1388
1414
  return true;
1389
1415
  } catch {
1390
1416
  return false;
@@ -1442,7 +1468,7 @@ function evaluateComponent(comp, data, options) {
1442
1468
  result = evaluateEnvComponent(comp.key);
1443
1469
  break;
1444
1470
  case "usage":
1445
- result = evaluateUsageComponent(comp.key, comp.args, options.noColor);
1471
+ result = evaluateUsageComponent(comp.key, data, comp.args, options.noColor);
1446
1472
  break;
1447
1473
  case "account":
1448
1474
  result = evaluateAccountComponent(comp.key);
@@ -1476,24 +1502,60 @@ function evaluateComponent(comp, data, options) {
1476
1502
  }
1477
1503
  case "conditional": {
1478
1504
  if (comp.children && evaluateCondition(comp.key, data)) {
1479
- result = evaluateComponents(comp.children, data, options);
1505
+ result = evaluateComponents(comp.children, data, options, true);
1480
1506
  }
1481
1507
  break;
1482
1508
  }
1483
1509
  }
1484
1510
  return applyStyles(result, comp.styles, options.noColor);
1485
1511
  }
1486
- function evaluateComponents(components, data, options) {
1512
+ function evaluateComponents(components, data, options, preserveEdgeSeparators = false) {
1513
+ const evaluated = components.map((comp) => ({
1514
+ type: comp.type,
1515
+ value: evaluateComponent(comp, data, options)
1516
+ }));
1517
+ const filtered = [];
1518
+ for (let i = 0; i < evaluated.length; i++) {
1519
+ const entry = evaluated[i];
1520
+ if (entry.type === "sep") {
1521
+ let hasBefore = false;
1522
+ for (let j = i - 1; j >= 0; j--) {
1523
+ if (evaluated[j].type === "sep") continue;
1524
+ if (evaluated[j].value) {
1525
+ hasBefore = true;
1526
+ }
1527
+ break;
1528
+ }
1529
+ let hasAfter = false;
1530
+ for (let j = i + 1; j < evaluated.length; j++) {
1531
+ if (evaluated[j].type === "sep") continue;
1532
+ if (evaluated[j].value) {
1533
+ hasAfter = true;
1534
+ }
1535
+ break;
1536
+ }
1537
+ if (preserveEdgeSeparators) {
1538
+ if (!hasBefore && !hasAfter) continue;
1539
+ } else {
1540
+ if (!hasBefore || !hasAfter) continue;
1541
+ }
1542
+ }
1543
+ filtered.push(entry);
1544
+ }
1487
1545
  const parts = [];
1488
- for (let i = 0; i < components.length; i++) {
1489
- const comp = components[i];
1490
- const prev = components[i - 1];
1491
- const value = evaluateComponent(comp, data, options);
1492
- if (i > 0 && comp.type !== "sep" && comp.type !== "conditional" && prev?.type !== "sep" && value) {
1493
- parts.push(" ");
1546
+ for (let i = 0; i < filtered.length; i++) {
1547
+ const entry = filtered[i];
1548
+ const prev = filtered[i - 1];
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
+ }
1494
1556
  }
1495
- if (value) {
1496
- parts.push(value);
1557
+ if (entry.value) {
1558
+ parts.push(entry.value);
1497
1559
  }
1498
1560
  }
1499
1561
  return parts.join("");
@@ -1509,7 +1571,7 @@ function evaluateFormat(format, data, options = {}) {
1509
1571
  }
1510
1572
 
1511
1573
  // src/index.ts
1512
- var VERSION = "1.7.0";
1574
+ var VERSION = true ? "1.9.0" : "dev";
1513
1575
  async function readStdin() {
1514
1576
  return new Promise((resolve, reject) => {
1515
1577
  let input = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeline",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Customizable status line generator for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {