claudeline 1.8.0 → 1.10.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 +225 -323
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -166,7 +166,7 @@ function listComponents() {
166
166
  {
167
167
  name: "Claude/Session",
168
168
  prefix: "claude",
169
- items: ["model", "model-id", "model-letter", "effort", "effort-icon", "version", "session", "session-full", "style"]
169
+ items: ["model", "model-id", "model-letter", "model-family", "model-short", "effort", "effort-icon", "version", "session", "session-full", "style"]
170
170
  },
171
171
  {
172
172
  name: "Context Window",
@@ -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: "claude:model-family claude:effort-icon dim:sep:middot nerd:nf-md-source_branch git:repo sep:none text:: sep:none git:branch if:subdir(sep:none text:/ sep:none fs:relative) if:dirty(dim:sep:middot git:dirty) dim:sep:middot dim: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 "
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,150 +651,220 @@ 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
- function paceColor(delta) {
847
- if (delta > 10) return "\x1B[0;33m";
848
- if (delta < -10) return "\x1B[0;36m";
849
- return "\x1B[0;32m";
850
- }
851
- function formatPaceIcon(delta, noColor) {
662
+ function formatPaceIcon(delta, noColor2) {
852
663
  const icon = delta > 10 ? "\uF0E7" : delta < -10 ? "\uF017" : "\uF00C";
853
- if (noColor) return icon;
854
- return `${paceColor(delta)}${icon}\x1B[0m`;
664
+ if (noColor2) return icon;
665
+ const { fg, bold } = barColorFromDelta(delta);
666
+ if (fg === null) return icon;
667
+ return `\x1B[0;${bold ? "1;" : ""}${fg}m${icon}\x1B[0m`;
855
668
  }
856
- function formatPaceDelta(delta, noColor) {
669
+ function formatPaceDelta(delta, noColor2) {
857
670
  const rounded = Math.round(delta);
858
671
  let text;
859
672
  if (rounded > 0) text = "+" + rounded + "%";
860
673
  else if (rounded < 0) text = rounded + "%";
861
674
  else text = "\xB10%";
862
- if (noColor) return text;
863
- return `${paceColor(delta)}${text}\x1B[0m`;
675
+ if (noColor2) return text;
676
+ const { fg, bold } = barColorFromDelta(delta);
677
+ if (fg === null) return text;
678
+ return `\x1B[0;${bold ? "1;" : ""}${fg}m${text}\x1B[0m`;
864
679
  }
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;
680
+ function barColorFromDelta(delta) {
681
+ if (delta > 15) return { fg: 31, bold: true };
682
+ if (delta > 5) return { fg: 31, bold: false };
683
+ return { fg: null, bold: false };
869
684
  }
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 "";
685
+ function makeBar(pct, width, noColor2, delta = 0, label) {
686
+ const clamped = Math.max(0, Math.min(100, pct));
687
+ const filled = Math.round(clamped / 100 * width);
688
+ const empty = width - filled;
689
+ const filledStr = "\u2588".repeat(filled);
690
+ const emptyStr = "\u2591".repeat(empty);
691
+ if (noColor2) return (label || "") + filledStr + emptyStr;
692
+ const { fg, bold } = barColorFromDelta(delta);
693
+ if (fg === null) {
694
+ const bar2 = `${filledStr}\x1B[2m${emptyStr}\x1B[0m`;
695
+ return label ? label + bar2 : bar2;
878
696
  }
697
+ const boldStr = bold ? "1;" : "";
698
+ const fillColor = `\x1B[0;${boldStr}${fg}m`;
699
+ const dimColor = `\x1B[0;2;${fg}m`;
700
+ const bar = `${fillColor}${filledStr}\x1B[0m${dimColor}${emptyStr}\x1B[0m`;
701
+ return label ? label + bar : bar;
879
702
  }
880
- function evaluateUsageComponent(key, args, noColor = false) {
881
- const data = getUsageData();
882
- if (!data) return "";
703
+ function evaluateUsageComponent(key, data, args, noColor2 = false) {
704
+ const limits = data.rate_limits;
705
+ if (!limits) return "";
706
+ const fiveHour = limits.five_hour;
707
+ const sevenDay = limits.seven_day;
883
708
  switch (key) {
884
709
  case "5h":
885
- return Math.round(data.five_hour.utilization) + "%";
710
+ return Math.round(fiveHour.used_percentage) + "%";
886
711
  case "week":
887
712
  case "7d":
888
- return Math.round(data.seven_day.utilization) + "%";
713
+ return Math.round(sevenDay.used_percentage) + "%";
889
714
  case "5h-reset":
890
- return formatTimeUntil(data.five_hour.resets_at);
715
+ return formatTimeUntil(fiveHour.resets_at);
891
716
  case "week-reset":
892
717
  case "7d-reset":
893
- return formatTimeUntil(data.seven_day.resets_at);
718
+ return formatTimeUntil(sevenDay.resets_at);
894
719
  case "5h-bar": {
895
720
  const width = args ? parseInt(args, 10) || 10 : 10;
896
- return makeBar(data.five_hour.utilization, width, "H");
721
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
722
+ return makeBar(fiveHour.used_percentage, width, noColor2, delta);
897
723
  }
898
724
  case "week-bar":
899
725
  case "7d-bar": {
900
726
  const width = args ? parseInt(args, 10) || 10 : 10;
901
- return makeBar(data.seven_day.utilization, width, "W");
727
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
728
+ return makeBar(sevenDay.used_percentage, width, noColor2, delta);
902
729
  }
903
730
  case "5h-icon": {
904
- const pct = data.five_hour.utilization;
731
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
732
+ const { fg, bold } = barColorFromDelta(delta);
905
733
  const icon = "\uF111";
906
- if (pct < 50) return `\x1B[0;32m${icon}\x1B[0m`;
907
- if (pct < 75) return `\x1B[0;33m${icon}\x1B[0m`;
908
- if (pct < 90) return `\x1B[0;31m${icon}\x1B[0m`;
909
- return `\x1B[0;1;31m${icon}\x1B[0m`;
734
+ if (noColor2 || fg === null) return icon;
735
+ return `\x1B[0;${bold ? "1;" : ""}${fg}m${icon}\x1B[0m`;
910
736
  }
911
737
  case "week-icon":
912
738
  case "7d-icon": {
913
- const pct = data.seven_day.utilization;
739
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
740
+ const { fg, bold } = barColorFromDelta(delta);
914
741
  const icon = "\uF111";
915
- if (pct < 50) return `\x1B[0;32m${icon}\x1B[0m`;
916
- if (pct < 75) return `\x1B[0;33m${icon}\x1B[0m`;
917
- if (pct < 90) return `\x1B[0;31m${icon}\x1B[0m`;
918
- return `\x1B[0;1;31m${icon}\x1B[0m`;
742
+ if (noColor2 || fg === null) return icon;
743
+ return `\x1B[0;${bold ? "1;" : ""}${fg}m${icon}\x1B[0m`;
919
744
  }
920
745
  case "5h-target": {
921
- const { target } = calculatePace(data.five_hour, FIVE_HOUR_MS);
746
+ const { target } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
922
747
  return Math.round(target) + "%";
923
748
  }
924
749
  case "week-target":
925
750
  case "7d-target": {
926
- const { target } = calculatePace(data.seven_day, SEVEN_DAY_MS);
751
+ const { target } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
927
752
  return Math.round(target) + "%";
928
753
  }
929
754
  case "5h-pace": {
930
- const { delta } = calculatePace(data.five_hour, FIVE_HOUR_MS);
931
- return formatPaceDelta(delta, noColor);
755
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
756
+ return formatPaceDelta(delta, noColor2);
932
757
  }
933
758
  case "week-pace":
934
759
  case "7d-pace": {
935
- const { delta } = calculatePace(data.seven_day, SEVEN_DAY_MS);
936
- return formatPaceDelta(delta, noColor);
760
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
761
+ return formatPaceDelta(delta, noColor2);
937
762
  }
938
763
  case "5h-pace-icon": {
939
- const { delta } = calculatePace(data.five_hour, FIVE_HOUR_MS);
940
- return formatPaceIcon(delta, noColor);
764
+ const { delta } = calculatePace(fiveHour.used_percentage, fiveHour.resets_at, FIVE_HOUR_MS);
765
+ return formatPaceIcon(delta, noColor2);
941
766
  }
942
767
  case "week-pace-icon":
943
768
  case "7d-pace-icon": {
944
- const { delta } = calculatePace(data.seven_day, SEVEN_DAY_MS);
945
- return formatPaceIcon(delta, noColor);
769
+ const { delta } = calculatePace(sevenDay.used_percentage, sevenDay.resets_at, SEVEN_DAY_MS);
770
+ return formatPaceIcon(delta, noColor2);
946
771
  }
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" : "";
951
- }
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) + "%" : "";
772
+ default:
773
+ return "";
774
+ }
775
+ }
776
+
777
+ // src/account.ts
778
+ import * as fs2 from "fs";
779
+ import * as path2 from "path";
780
+ import * as os2 from "os";
781
+ import * as crypto from "crypto";
782
+ import { execSync } from "child_process";
783
+ var PROFILE_CACHE_TTL_MS = 60 * 60 * 1e3;
784
+ function extractToken(raw) {
785
+ try {
786
+ const parsed = JSON.parse(raw);
787
+ if (typeof parsed === "string") return parsed;
788
+ if (parsed.claudeAiOauth?.accessToken) return parsed.claudeAiOauth.accessToken;
789
+ if (parsed.accessToken) return parsed.accessToken;
790
+ if (parsed.access_token) return parsed.access_token;
791
+ } catch {
792
+ }
793
+ return null;
794
+ }
795
+ function getKeychainService() {
796
+ const configDir = process.env.CLAUDE_CONFIG_DIR;
797
+ if (configDir) {
798
+ const suffix = crypto.createHash("sha256").update(configDir).digest("hex").slice(0, 8);
799
+ return `Claude Code-credentials-${suffix}`;
800
+ }
801
+ return "Claude Code-credentials";
802
+ }
803
+ function getOAuthToken() {
804
+ try {
805
+ if (process.platform === "darwin") {
806
+ const service = getKeychainService();
807
+ const raw = execSync(
808
+ `security find-generic-password -s "${service}" -w 2>/dev/null`,
809
+ { encoding: "utf8" }
810
+ ).trim();
811
+ return extractToken(raw);
966
812
  }
967
- case "7d-projected": {
968
- const p = getClusageAccount()?.projection;
969
- return p ? Math.round(p.projectedAtReset) + "%" : "";
813
+ if (process.platform === "linux") {
814
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude");
815
+ const raw = fs2.readFileSync(path2.join(configDir, ".credentials.json"), "utf8").trim();
816
+ return extractToken(raw);
970
817
  }
971
- case "budget": {
972
- const p = getClusageAccount()?.projection;
973
- return p ? p.dailyBudget.toFixed(1) + " pp/day" : "";
818
+ } catch {
819
+ }
820
+ return null;
821
+ }
822
+ function getCacheFile(token) {
823
+ const hash = crypto.createHash("sha256").update(token).digest("hex").slice(0, 12);
824
+ const dir = path2.join(getClaudeConfigDir(), "cache");
825
+ try {
826
+ fs2.mkdirSync(dir, { recursive: true });
827
+ } catch {
828
+ }
829
+ return path2.join(dir, `claudeline-profile-${hash}.json`);
830
+ }
831
+ function readCachedProfile(cacheFile) {
832
+ try {
833
+ const cached = JSON.parse(fs2.readFileSync(cacheFile, "utf8"));
834
+ if (Date.now() - cached.fetched_at < PROFILE_CACHE_TTL_MS) {
835
+ return cached.email;
974
836
  }
975
- case "budget-status": {
976
- const p = getClusageAccount()?.projection;
977
- return p?.status ?? "";
837
+ } catch {
838
+ }
839
+ return null;
840
+ }
841
+ function fetchEmail(token) {
842
+ try {
843
+ const result = execSync(
844
+ `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"`,
845
+ { encoding: "utf8", env: { ...process.env, __CLAUDE_TOKEN: token } }
846
+ ).trim();
847
+ const data = JSON.parse(result);
848
+ return data.account?.email ?? null;
849
+ } catch {
850
+ return null;
851
+ }
852
+ }
853
+ function evaluateAccountComponent(key) {
854
+ if (key !== "email") return "";
855
+ const token = getOAuthToken();
856
+ if (!token) return "";
857
+ const cacheFile = getCacheFile(token);
858
+ const cached = readCachedProfile(cacheFile);
859
+ if (cached) return cached;
860
+ const email = fetchEmail(token);
861
+ if (email) {
862
+ try {
863
+ fs2.writeFileSync(cacheFile, JSON.stringify({ email, fetched_at: Date.now() }));
864
+ } catch {
978
865
  }
979
- default:
980
- return "";
981
866
  }
867
+ return email ?? "";
982
868
  }
983
869
 
984
870
  // src/nerdfonts.ts
@@ -1131,57 +1017,55 @@ function execCommand(cmd) {
1131
1017
  return "";
1132
1018
  }
1133
1019
  }
1134
- function applyStyles(text, styles, noColor) {
1135
- if (noColor || styles.length === 0 || !text) return text;
1020
+ function applyStyles(text, styles, noColor2) {
1021
+ if (noColor2 || styles.length === 0 || !text) return text;
1136
1022
  const codes = styles.map((s) => COLORS[s]).filter(Boolean);
1137
1023
  if (codes.length === 0) return text;
1138
1024
  return `\x1B[0;${codes.join(";")}m${text}\x1B[${RESET}m`;
1139
1025
  }
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
- }
1026
+ var VALID_EFFORTS = ["low", "medium", "high"];
1027
+ function isValidEffort(value) {
1028
+ return typeof value === "string" && VALID_EFFORTS.includes(value);
1029
+ }
1030
+ function readEffortFromSettings(filePath) {
1147
1031
  try {
1148
1032
  const data = JSON.parse(fs3.readFileSync(filePath, "utf8"));
1149
- settingsCache.set(filePath, { data, ts: Date.now() });
1150
- return data;
1033
+ return isValidEffort(data.effortLevel) ? data.effortLevel : null;
1151
1034
  } catch {
1152
- return {};
1035
+ return null;
1153
1036
  }
1154
1037
  }
1155
- var VALID_EFFORTS = ["low", "medium", "high"];
1156
1038
  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;
1039
+ if (isValidEffort(data.model?.reasoning_effort)) {
1040
+ return data.model.reasoning_effort;
1166
1041
  }
1042
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path3.join(os3.homedir(), ".claude");
1043
+ let effort = readEffortFromSettings(path3.join(configDir, "settings.json"));
1167
1044
  const projectDir = data.workspace?.project_dir;
1168
1045
  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
- }
1046
+ effort = readEffortFromSettings(path3.join(projectDir, ".claude", "settings.json")) ?? effort;
1174
1047
  }
1175
1048
  return effort;
1176
1049
  }
1177
- function evaluateClaudeComponent(key, data, noColor = false, noIcons = false) {
1050
+ function evaluateClaudeComponent(key, data, noColor2 = false, noIcons = false) {
1178
1051
  switch (key) {
1179
1052
  case "model":
1180
1053
  return data.model?.display_name || "Claude";
1054
+ case "model-short": {
1055
+ const name = data.model?.display_name || "Claude";
1056
+ return name.replace(/\s*\(.*\)$/, "");
1057
+ }
1181
1058
  case "model-id":
1182
1059
  return data.model?.id || "";
1183
1060
  case "model-letter":
1184
1061
  return (data.model?.display_name || "C")[0].toUpperCase();
1062
+ case "model-family": {
1063
+ const name = (data.model?.display_name || "").toLowerCase();
1064
+ if (name.includes("opus")) return "Opus";
1065
+ if (name.includes("sonnet")) return "Sonnet";
1066
+ if (name.includes("haiku")) return "Haiku";
1067
+ return data.model?.display_name || "Claude";
1068
+ }
1185
1069
  case "version":
1186
1070
  return data.version || "";
1187
1071
  case "session":
@@ -1191,17 +1075,18 @@ function evaluateClaudeComponent(key, data, noColor = false, noIcons = false) {
1191
1075
  case "effort":
1192
1076
  case "effort-icon": {
1193
1077
  const effort = resolveEffort(data);
1078
+ if (!effort) return "";
1194
1079
  if (key === "effort-icon" && noIcons) return "";
1195
- const text = key === "effort-icon" ? "\u{F09D1}" : effort;
1196
- if (noColor) return text;
1197
- const r = `\x1B[${RESET}m`;
1198
- const effortColors = {
1199
- low: COLORS.green,
1200
- medium: COLORS.yellow,
1201
- high: COLORS.red
1202
- };
1203
- const code = effortColors[effort] || "";
1204
- return code ? `\x1B[0;${code}m${text}${r}` : text;
1080
+ if (key === "effort-icon") {
1081
+ const icons = {
1082
+ low: "\u25CB",
1083
+ medium: "\u25D1",
1084
+ high: "\u25CF",
1085
+ max: "\u25C9"
1086
+ };
1087
+ return icons[effort] || "\u25CF";
1088
+ }
1089
+ return effort;
1205
1090
  }
1206
1091
  case "style":
1207
1092
  return data.output_style?.name || "default";
@@ -1235,7 +1120,7 @@ function evaluateFsComponent(key, data) {
1235
1120
  return "";
1236
1121
  }
1237
1122
  }
1238
- function evaluateGitComponent(key, noColor = false, noIcons = false) {
1123
+ function evaluateGitComponent(key, noColor2 = false, noIcons = false) {
1239
1124
  switch (key) {
1240
1125
  case "branch":
1241
1126
  return execCommand("git branch --show-current 2>/dev/null");
@@ -1299,7 +1184,7 @@ function evaluateGitComponent(key, noColor = false, noIcons = false) {
1299
1184
  const untracked = parseInt(execCommand("git ls-files --others --exclude-standard 2>/dev/null | wc -l")) || 0;
1300
1185
  if (staged === 0 && modified === 0 && untracked === 0) return "";
1301
1186
  const parts = [];
1302
- if (noColor) {
1187
+ if (noColor2) {
1303
1188
  if (staged > 0) parts.push("+" + staged);
1304
1189
  if (untracked > 0) parts.push("-" + untracked);
1305
1190
  if (modified > 0) parts.push("~" + modified);
@@ -1355,10 +1240,18 @@ function evaluateContextComponent(key, data, args, noIcons = false) {
1355
1240
  case "size":
1356
1241
  return formatTokens(ctx?.context_window_size || 2e5);
1357
1242
  case "bar": {
1358
- const pct = ctx?.used_percentage || 0;
1243
+ const pct = Math.max(0, Math.min(100, ctx?.used_percentage || 0));
1359
1244
  const width = args ? parseInt(args, 10) || 10 : 10;
1360
1245
  const filled = Math.round(pct / 100 * width);
1361
- return "[" + "\u2588".repeat(filled) + "\u2591".repeat(width - filled) + "]";
1246
+ const empty = width - filled;
1247
+ const filledStr = "\u2588".repeat(filled);
1248
+ const emptyStr = "\u2591".repeat(empty);
1249
+ if (noColor) return filledStr + emptyStr;
1250
+ const color = pct < 50 ? COLORS.green : pct < 75 ? COLORS.yellow : COLORS.red;
1251
+ const bold = pct >= 90 ? `;${COLORS.bold}` : "";
1252
+ const fillColor = `\x1B[0;${color}${bold}m`;
1253
+ const dimColor = `\x1B[0;2;${color}m`;
1254
+ return `${fillColor}${filledStr}\x1B[${RESET}m${dimColor}${emptyStr}\x1B[${RESET}m`;
1362
1255
  }
1363
1256
  case "icon": {
1364
1257
  if (noIcons) return "";
@@ -1582,7 +1475,7 @@ function evaluateComponent(comp, data, options) {
1582
1475
  result = evaluateEnvComponent(comp.key);
1583
1476
  break;
1584
1477
  case "usage":
1585
- result = evaluateUsageComponent(comp.key, comp.args, options.noColor);
1478
+ result = evaluateUsageComponent(comp.key, data, comp.args, options.noColor);
1586
1479
  break;
1587
1480
  case "account":
1588
1481
  result = evaluateAccountComponent(comp.key);
@@ -1616,14 +1509,14 @@ function evaluateComponent(comp, data, options) {
1616
1509
  }
1617
1510
  case "conditional": {
1618
1511
  if (comp.children && evaluateCondition(comp.key, data)) {
1619
- result = evaluateComponents(comp.children, data, options);
1512
+ result = evaluateComponents(comp.children, data, options, true);
1620
1513
  }
1621
1514
  break;
1622
1515
  }
1623
1516
  }
1624
1517
  return applyStyles(result, comp.styles, options.noColor);
1625
1518
  }
1626
- function evaluateComponents(components, data, options) {
1519
+ function evaluateComponents(components, data, options, preserveEdgeSeparators = false) {
1627
1520
  const evaluated = components.map((comp) => ({
1628
1521
  type: comp.type,
1629
1522
  value: evaluateComponent(comp, data, options)
@@ -1648,7 +1541,11 @@ function evaluateComponents(components, data, options) {
1648
1541
  }
1649
1542
  break;
1650
1543
  }
1651
- if (!hasBefore || !hasAfter) continue;
1544
+ if (preserveEdgeSeparators) {
1545
+ if (!hasBefore && !hasAfter) continue;
1546
+ } else {
1547
+ if (!hasBefore || !hasAfter) continue;
1548
+ }
1652
1549
  }
1653
1550
  filtered.push(entry);
1654
1551
  }
@@ -1656,8 +1553,13 @@ function evaluateComponents(components, data, options) {
1656
1553
  for (let i = 0; i < filtered.length; i++) {
1657
1554
  const entry = filtered[i];
1658
1555
  const prev = filtered[i - 1];
1659
- if (i > 0 && entry.type !== "sep" && entry.type !== "conditional" && prev?.type !== "sep" && entry.value) {
1660
- parts.push(" ");
1556
+ if (i > 0 && entry.type !== "sep" && prev?.type !== "sep" && entry.value) {
1557
+ if (entry.type === "conditional") {
1558
+ const stripped = entry.value.replace(/\x1b\[[0-9;]*m/g, "");
1559
+ if (!stripped.startsWith(" ")) parts.push(" ");
1560
+ } else {
1561
+ parts.push(" ");
1562
+ }
1661
1563
  }
1662
1564
  if (entry.value) {
1663
1565
  parts.push(entry.value);
@@ -1676,7 +1578,7 @@ function evaluateFormat(format, data, options = {}) {
1676
1578
  }
1677
1579
 
1678
1580
  // src/index.ts
1679
- var VERSION = "1.8.0";
1581
+ var VERSION = true ? "1.10.0" : "dev";
1680
1582
  async function readStdin() {
1681
1583
  return new Promise((resolve, reject) => {
1682
1584
  let input = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeline",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Customizable status line generator for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {