claudeline 1.7.0 → 1.8.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 +197 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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 = {
@@ -639,6 +639,8 @@ function getSeparator(name) {
639
639
  import * as fs2 from "fs";
640
640
  import * as path2 from "path";
641
641
  import * as os2 from "os";
642
+ import * as crypto from "crypto";
643
+ import { execSync } from "child_process";
642
644
  var CLUSAGE_API_FILE = path2.join(os2.homedir(), ".claude", "clusage-api.json");
643
645
  var CLUSAGE_STALE_MS = 10 * 60 * 1e3;
644
646
  var _clusageCache = null;
@@ -660,24 +662,162 @@ function getClusageAccount() {
660
662
  if (!payload || payload.accounts.length === 0) return null;
661
663
  return payload.accounts[0];
662
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
+ }
663
799
  function getUsageData() {
664
800
  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
- };
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();
676
814
  }
677
815
  function getProfileData() {
678
816
  const account = getClusageAccount();
679
- if (!account?.profile) return null;
680
- return { email: account.profile.email };
817
+ if (account?.profile) {
818
+ return { email: account.profile.email };
819
+ }
820
+ return getProfileDataDirect();
681
821
  }
682
822
  function formatTimeUntil(isoDate) {
683
823
  const reset = new Date(isoDate).getTime();
@@ -804,7 +944,7 @@ function evaluateUsageComponent(key, args, noColor = false) {
804
944
  const { delta } = calculatePace(data.seven_day, SEVEN_DAY_MS);
805
945
  return formatPaceIcon(delta, noColor);
806
946
  }
807
- // Clusage momentum/projection data
947
+ // Clusage-only momentum/projection data (no direct API fallback)
808
948
  case "velocity": {
809
949
  const m = getClusageAccount()?.momentum;
810
950
  return m ? m.velocity.toFixed(1) + " pp/hr" : "";
@@ -986,7 +1126,7 @@ function formatDuration(ms) {
986
1126
  }
987
1127
  function execCommand(cmd) {
988
1128
  try {
989
- return execSync(cmd, { encoding: "utf8" }).trim();
1129
+ return execSync2(cmd, { encoding: "utf8" }).trim();
990
1130
  } catch {
991
1131
  return "";
992
1132
  }
@@ -1101,7 +1241,7 @@ function evaluateGitComponent(key, noColor = false, noIcons = false) {
1101
1241
  return execCommand("git branch --show-current 2>/dev/null");
1102
1242
  case "status":
1103
1243
  try {
1104
- execSync("git diff --quiet 2>/dev/null");
1244
+ execSync2("git diff --quiet 2>/dev/null");
1105
1245
  return "\u2713";
1106
1246
  } catch {
1107
1247
  return "*";
@@ -1109,14 +1249,14 @@ function evaluateGitComponent(key, noColor = false, noIcons = false) {
1109
1249
  case "status-icon":
1110
1250
  if (noIcons) return "";
1111
1251
  try {
1112
- execSync("git diff --quiet 2>/dev/null");
1252
+ execSync2("git diff --quiet 2>/dev/null");
1113
1253
  return getNerdIcon("sparkle");
1114
1254
  } catch {
1115
1255
  return getNerdIcon("commit");
1116
1256
  }
1117
1257
  case "status-word":
1118
1258
  try {
1119
- execSync("git diff --quiet 2>/dev/null");
1259
+ execSync2("git diff --quiet 2>/dev/null");
1120
1260
  return "clean";
1121
1261
  } catch {
1122
1262
  return "dirty";
@@ -1370,21 +1510,21 @@ function evaluateCondition(condition, data) {
1370
1510
  switch (condition) {
1371
1511
  case "git":
1372
1512
  try {
1373
- execSync("git rev-parse --git-dir 2>/dev/null");
1513
+ execSync2("git rev-parse --git-dir 2>/dev/null");
1374
1514
  return true;
1375
1515
  } catch {
1376
1516
  return false;
1377
1517
  }
1378
1518
  case "dirty":
1379
1519
  try {
1380
- execSync("git diff --quiet 2>/dev/null");
1520
+ execSync2("git diff --quiet 2>/dev/null");
1381
1521
  return false;
1382
1522
  } catch {
1383
1523
  return true;
1384
1524
  }
1385
1525
  case "clean":
1386
1526
  try {
1387
- execSync("git diff --quiet 2>/dev/null");
1527
+ execSync2("git diff --quiet 2>/dev/null");
1388
1528
  return true;
1389
1529
  } catch {
1390
1530
  return false;
@@ -1484,16 +1624,43 @@ function evaluateComponent(comp, data, options) {
1484
1624
  return applyStyles(result, comp.styles, options.noColor);
1485
1625
  }
1486
1626
  function evaluateComponents(components, data, options) {
1627
+ const evaluated = components.map((comp) => ({
1628
+ type: comp.type,
1629
+ value: evaluateComponent(comp, data, options)
1630
+ }));
1631
+ const filtered = [];
1632
+ for (let i = 0; i < evaluated.length; i++) {
1633
+ const entry = evaluated[i];
1634
+ if (entry.type === "sep") {
1635
+ let hasBefore = false;
1636
+ for (let j = i - 1; j >= 0; j--) {
1637
+ if (evaluated[j].type === "sep") continue;
1638
+ if (evaluated[j].value) {
1639
+ hasBefore = true;
1640
+ }
1641
+ break;
1642
+ }
1643
+ let hasAfter = false;
1644
+ for (let j = i + 1; j < evaluated.length; j++) {
1645
+ if (evaluated[j].type === "sep") continue;
1646
+ if (evaluated[j].value) {
1647
+ hasAfter = true;
1648
+ }
1649
+ break;
1650
+ }
1651
+ if (!hasBefore || !hasAfter) continue;
1652
+ }
1653
+ filtered.push(entry);
1654
+ }
1487
1655
  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) {
1656
+ for (let i = 0; i < filtered.length; i++) {
1657
+ const entry = filtered[i];
1658
+ const prev = filtered[i - 1];
1659
+ if (i > 0 && entry.type !== "sep" && entry.type !== "conditional" && prev?.type !== "sep" && entry.value) {
1493
1660
  parts.push(" ");
1494
1661
  }
1495
- if (value) {
1496
- parts.push(value);
1662
+ if (entry.value) {
1663
+ parts.push(entry.value);
1497
1664
  }
1498
1665
  }
1499
1666
  return parts.join("");
@@ -1509,7 +1676,7 @@ function evaluateFormat(format, data, options = {}) {
1509
1676
  }
1510
1677
 
1511
1678
  // src/index.ts
1512
- var VERSION = "1.7.0";
1679
+ var VERSION = "1.8.0";
1513
1680
  async function readStdin() {
1514
1681
  return new Promise((resolve, reject) => {
1515
1682
  let input = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeline",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Customizable status line generator for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {