ccstatusline-usage 2.0.28 → 2.0.29

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.
@@ -0,0 +1,73 @@
1
+ {
2
+ "version": 3,
3
+ "lines": [
4
+ [
5
+ {
6
+ "id": "session-usage",
7
+ "type": "custom-command",
8
+ "color": "brightBlue",
9
+ "commandPath": "scripts/usage.sh session"
10
+ },
11
+ {
12
+ "id": "sep-session-weekly",
13
+ "type": "separator"
14
+ },
15
+ {
16
+ "id": "weekly-usage",
17
+ "type": "custom-command",
18
+ "color": "brightBlue",
19
+ "commandPath": "scripts/usage.sh weekly"
20
+ },
21
+ {
22
+ "id": "sep-weekly-reset",
23
+ "type": "separator"
24
+ },
25
+ {
26
+ "id": "reset-timer",
27
+ "type": "custom-command",
28
+ "color": "brightBlue",
29
+ "commandPath": "scripts/usage.sh reset"
30
+ },
31
+ {
32
+ "id": "sep-reset-model",
33
+ "type": "separator"
34
+ },
35
+ {
36
+ "id": "model",
37
+ "type": "model",
38
+ "color": "magenta"
39
+ },
40
+ {
41
+ "id": "sep-model-chatid",
42
+ "type": "separator"
43
+ },
44
+ {
45
+ "id": "chat-id",
46
+ "type": "claude-session-id",
47
+ "color": "cyan"
48
+ }
49
+ ],
50
+ [
51
+ {
52
+ "id": "context-usage",
53
+ "type": "custom-command",
54
+ "color": "blue",
55
+ "commandPath": "scripts/context.sh"
56
+ }
57
+ ],
58
+ []
59
+ ],
60
+ "flexMode": "full-minus-40",
61
+ "compactThreshold": 60,
62
+ "colorLevel": 2,
63
+ "inheritSeparatorColors": false,
64
+ "globalBold": false,
65
+ "powerline": {
66
+ "enabled": false,
67
+ "separators": [""],
68
+ "separatorInvertBackground": [false],
69
+ "startCaps": [],
70
+ "endCaps": [],
71
+ "autoAlign": false
72
+ }
73
+ }
@@ -32383,8 +32383,8 @@ var require_utils = __commonJS((exports) => {
32383
32383
  }
32384
32384
  return output;
32385
32385
  };
32386
- exports.basename = (path5, { windows } = {}) => {
32387
- const segs = path5.split(windows ? /[\\/]/ : "/");
32386
+ exports.basename = (path6, { windows } = {}) => {
32387
+ const segs = path6.split(windows ? /[\\/]/ : "/");
32388
32388
  const last = segs[segs.length - 1];
32389
32389
  if (last === "") {
32390
32390
  return segs[segs.length - 2];
@@ -50982,18 +50982,18 @@ var SettingsSchema = exports_external.object({
50982
50982
  version: exports_external.number().default(CURRENT_VERSION),
50983
50983
  lines: exports_external.array(exports_external.array(WidgetItemSchema)).min(1).default([
50984
50984
  [
50985
- { id: "model", type: "model", color: "magenta" },
50985
+ { id: "session-usage", type: "custom-command", color: "brightBlue", commandPath: "$PKG/scripts/usage.sh session", timeout: 5000 },
50986
50986
  { id: "sep1", type: "separator" },
50987
- { id: "context", type: "context-percentage", color: "blue" },
50987
+ { id: "weekly-usage", type: "custom-command", color: "brightBlue", commandPath: "$PKG/scripts/usage.sh weekly", timeout: 5000 },
50988
50988
  { id: "sep2", type: "separator" },
50989
- { id: "session-clock", type: "session-clock", color: "yellow" },
50989
+ { id: "reset-timer", type: "custom-command", color: "brightBlue", commandPath: "$PKG/scripts/usage.sh reset", timeout: 5000 },
50990
50990
  { id: "sep3", type: "separator" },
50991
+ { id: "model", type: "model", color: "magenta" },
50992
+ { id: "sep4", type: "separator" },
50991
50993
  { id: "session-id", type: "claude-session-id", color: "cyan" }
50992
50994
  ],
50993
50995
  [
50994
- { id: "git-branch", type: "git-branch", color: "green" },
50995
- { id: "sep4", type: "separator" },
50996
- { id: "git-changes", type: "git-changes", color: "yellow" }
50996
+ { id: "context-usage", type: "custom-command", color: "blue", commandPath: "$PKG/scripts/context.sh", timeout: 5000 }
50997
50997
  ],
50998
50998
  []
50999
50999
  ]),
@@ -51450,7 +51450,7 @@ import { execSync as execSync3 } from "child_process";
51450
51450
  import * as fs5 from "fs";
51451
51451
  import * as path4 from "path";
51452
51452
  var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils";
51453
- var PACKAGE_VERSION = "2.0.28";
51453
+ var PACKAGE_VERSION = "2.0.29";
51454
51454
  function getPackageVersion() {
51455
51455
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
51456
51456
  return PACKAGE_VERSION;
@@ -53812,8 +53812,33 @@ var CustomTextEditor = ({ widget, onComplete, onCancel }) => {
53812
53812
  };
53813
53813
  // src/widgets/CustomCommand.tsx
53814
53814
  import { execSync as execSync7 } from "child_process";
53815
+ import * as fs6 from "fs";
53815
53816
  var import_react30 = __toESM(require_react(), 1);
53816
53817
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
53818
+ import * as path5 from "path";
53819
+ function getPackageDir() {
53820
+ const scriptPath = process.argv[1];
53821
+ if (scriptPath) {
53822
+ try {
53823
+ const realPath = fs6.realpathSync(scriptPath);
53824
+ let dir = path5.dirname(realPath);
53825
+ for (let i = 0;i < 5; i++) {
53826
+ const pkgJson = path5.join(dir, "package.json");
53827
+ if (fs6.existsSync(pkgJson)) {
53828
+ try {
53829
+ const pkg = JSON.parse(fs6.readFileSync(pkgJson, "utf8"));
53830
+ if (pkg.name === "ccstatusline-usage") {
53831
+ return dir;
53832
+ }
53833
+ } catch {}
53834
+ }
53835
+ dir = path5.dirname(dir);
53836
+ }
53837
+ } catch {}
53838
+ }
53839
+ return "";
53840
+ }
53841
+ var PKG_DIR = getPackageDir();
53817
53842
 
53818
53843
  class CustomCommandWidget {
53819
53844
  getDefaultColor() {
@@ -53850,6 +53875,17 @@ class CustomCommandWidget {
53850
53875
  }
53851
53876
  return null;
53852
53877
  }
53878
+ resolveCommandPath(commandPath) {
53879
+ if (commandPath.startsWith("$PKG/") || commandPath.startsWith("$PACKAGE_DIR/")) {
53880
+ const relativePath = commandPath.replace(/^\$(PKG|PACKAGE_DIR)\//, "");
53881
+ const resolved = path5.join(PKG_DIR, relativePath);
53882
+ return resolved;
53883
+ }
53884
+ if (commandPath.startsWith("$HOME/")) {
53885
+ return commandPath.replace("$HOME", process.env.HOME ?? "~");
53886
+ }
53887
+ return commandPath;
53888
+ }
53853
53889
  render(item, context, settings) {
53854
53890
  if (context.isPreview) {
53855
53891
  return item.commandPath ? `[cmd: ${item.commandPath.substring(0, 20)}${item.commandPath.length > 20 ? "..." : ""}]` : "[No command]";
@@ -53857,7 +53893,8 @@ class CustomCommandWidget {
53857
53893
  try {
53858
53894
  const timeout = item.timeout ?? 1000;
53859
53895
  const jsonInput = JSON.stringify(context.data);
53860
- let output = execSync7(item.commandPath, {
53896
+ const resolvedPath = this.resolveCommandPath(item.commandPath);
53897
+ let output = execSync7(resolvedPath, {
53861
53898
  encoding: "utf8",
53862
53899
  input: jsonInput,
53863
53900
  timeout,
@@ -54278,13 +54315,13 @@ class CurrentWorkingDirWidget {
54278
54315
  supportsColors(item) {
54279
54316
  return true;
54280
54317
  }
54281
- abbreviatePath(path5) {
54318
+ abbreviatePath(path6) {
54282
54319
  const homeDir = os5.homedir();
54283
- const useBackslash = path5.includes("\\") && !path5.includes("/");
54320
+ const useBackslash = path6.includes("\\") && !path6.includes("/");
54284
54321
  const sep = useBackslash ? "\\" : "/";
54285
- let normalizedPath = path5;
54286
- if (path5.startsWith(homeDir)) {
54287
- normalizedPath = "~" + path5.slice(homeDir.length);
54322
+ let normalizedPath = path6;
54323
+ if (path6.startsWith(homeDir)) {
54324
+ normalizedPath = "~" + path6.slice(homeDir.length);
54288
54325
  }
54289
54326
  const parts = normalizedPath.split(/[\\/]+/).filter((part) => part !== "");
54290
54327
  const abbreviated = parts.map((part, index) => {
@@ -58238,42 +58275,42 @@ var StatusJSONSchema = exports_external.looseObject({
58238
58275
  });
58239
58276
 
58240
58277
  // src/utils/jsonl.ts
58241
- import * as fs6 from "fs";
58242
- import path6 from "node:path";
58278
+ import * as fs7 from "fs";
58279
+ import path7 from "node:path";
58243
58280
 
58244
58281
  // node_modules/tinyglobby/dist/index.mjs
58245
- import path5, { posix } from "path";
58282
+ import path6, { posix } from "path";
58246
58283
 
58247
58284
  // node_modules/fdir/dist/index.mjs
58248
58285
  import { createRequire as createRequire2 } from "module";
58249
- import { basename as basename2, dirname as dirname2, normalize, relative, resolve as resolve2, sep } from "path";
58286
+ import { basename as basename2, dirname as dirname3, normalize, relative, resolve as resolve2, sep } from "path";
58250
58287
  import * as nativeFs from "fs";
58251
58288
  var __require2 = /* @__PURE__ */ createRequire2(import.meta.url);
58252
- function cleanPath(path5) {
58253
- let normalized = normalize(path5);
58289
+ function cleanPath(path6) {
58290
+ let normalized = normalize(path6);
58254
58291
  if (normalized.length > 1 && normalized[normalized.length - 1] === sep)
58255
58292
  normalized = normalized.substring(0, normalized.length - 1);
58256
58293
  return normalized;
58257
58294
  }
58258
58295
  var SLASHES_REGEX = /[\\/]/g;
58259
- function convertSlashes(path5, separator) {
58260
- return path5.replace(SLASHES_REGEX, separator);
58296
+ function convertSlashes(path6, separator) {
58297
+ return path6.replace(SLASHES_REGEX, separator);
58261
58298
  }
58262
58299
  var WINDOWS_ROOT_DIR_REGEX = /^[a-z]:[\\/]$/i;
58263
- function isRootDirectory(path5) {
58264
- return path5 === "/" || WINDOWS_ROOT_DIR_REGEX.test(path5);
58300
+ function isRootDirectory(path6) {
58301
+ return path6 === "/" || WINDOWS_ROOT_DIR_REGEX.test(path6);
58265
58302
  }
58266
- function normalizePath(path5, options) {
58303
+ function normalizePath(path6, options) {
58267
58304
  const { resolvePaths, normalizePath: normalizePath$1, pathSeparator } = options;
58268
- const pathNeedsCleaning = process.platform === "win32" && path5.includes("/") || path5.startsWith(".");
58305
+ const pathNeedsCleaning = process.platform === "win32" && path6.includes("/") || path6.startsWith(".");
58269
58306
  if (resolvePaths)
58270
- path5 = resolve2(path5);
58307
+ path6 = resolve2(path6);
58271
58308
  if (normalizePath$1 || pathNeedsCleaning)
58272
- path5 = cleanPath(path5);
58273
- if (path5 === ".")
58309
+ path6 = cleanPath(path6);
58310
+ if (path6 === ".")
58274
58311
  return "";
58275
- const needsSeperator = path5[path5.length - 1] !== pathSeparator;
58276
- return convertSlashes(needsSeperator ? path5 + pathSeparator : path5, pathSeparator);
58312
+ const needsSeperator = path6[path6.length - 1] !== pathSeparator;
58313
+ return convertSlashes(needsSeperator ? path6 + pathSeparator : path6, pathSeparator);
58277
58314
  }
58278
58315
  function joinPathWithBasePath(filename, directoryPath) {
58279
58316
  return directoryPath + filename;
@@ -58313,9 +58350,9 @@ var pushDirectory = (directoryPath, paths) => {
58313
58350
  paths.push(directoryPath || ".");
58314
58351
  };
58315
58352
  var pushDirectoryFilter = (directoryPath, paths, filters) => {
58316
- const path5 = directoryPath || ".";
58317
- if (filters.every((filter) => filter(path5, true)))
58318
- paths.push(path5);
58353
+ const path6 = directoryPath || ".";
58354
+ if (filters.every((filter) => filter(path6, true)))
58355
+ paths.push(path6);
58319
58356
  };
58320
58357
  var empty$2 = () => {};
58321
58358
  function build$6(root, options) {
@@ -58372,29 +58409,29 @@ var empty = () => {};
58372
58409
  function build$3(options) {
58373
58410
  return options.group ? groupFiles : empty;
58374
58411
  }
58375
- var resolveSymlinksAsync = function(path5, state, callback$1) {
58376
- const { queue, fs: fs6, options: { suppressErrors } } = state;
58412
+ var resolveSymlinksAsync = function(path6, state, callback$1) {
58413
+ const { queue, fs: fs7, options: { suppressErrors } } = state;
58377
58414
  queue.enqueue();
58378
- fs6.realpath(path5, (error43, resolvedPath) => {
58415
+ fs7.realpath(path6, (error43, resolvedPath) => {
58379
58416
  if (error43)
58380
58417
  return queue.dequeue(suppressErrors ? null : error43, state);
58381
- fs6.stat(resolvedPath, (error$1, stat) => {
58418
+ fs7.stat(resolvedPath, (error$1, stat) => {
58382
58419
  if (error$1)
58383
58420
  return queue.dequeue(suppressErrors ? null : error$1, state);
58384
- if (stat.isDirectory() && isRecursive(path5, resolvedPath, state))
58421
+ if (stat.isDirectory() && isRecursive(path6, resolvedPath, state))
58385
58422
  return queue.dequeue(null, state);
58386
58423
  callback$1(stat, resolvedPath);
58387
58424
  queue.dequeue(null, state);
58388
58425
  });
58389
58426
  });
58390
58427
  };
58391
- var resolveSymlinks = function(path5, state, callback$1) {
58392
- const { queue, fs: fs6, options: { suppressErrors } } = state;
58428
+ var resolveSymlinks = function(path6, state, callback$1) {
58429
+ const { queue, fs: fs7, options: { suppressErrors } } = state;
58393
58430
  queue.enqueue();
58394
58431
  try {
58395
- const resolvedPath = fs6.realpathSync(path5);
58396
- const stat = fs6.statSync(resolvedPath);
58397
- if (stat.isDirectory() && isRecursive(path5, resolvedPath, state))
58432
+ const resolvedPath = fs7.realpathSync(path6);
58433
+ const stat = fs7.statSync(resolvedPath);
58434
+ if (stat.isDirectory() && isRecursive(path6, resolvedPath, state))
58398
58435
  return;
58399
58436
  callback$1(stat, resolvedPath);
58400
58437
  } catch (e) {
@@ -58407,10 +58444,10 @@ function build$2(options, isSynchronous) {
58407
58444
  return null;
58408
58445
  return isSynchronous ? resolveSymlinks : resolveSymlinksAsync;
58409
58446
  }
58410
- function isRecursive(path5, resolved, state) {
58447
+ function isRecursive(path6, resolved, state) {
58411
58448
  if (state.options.useRealPaths)
58412
58449
  return isRecursiveUsingRealPaths(resolved, state);
58413
- let parent = dirname2(path5);
58450
+ let parent = dirname3(path6);
58414
58451
  let depth = 1;
58415
58452
  while (parent !== state.root && depth < 2) {
58416
58453
  const resolvedPath = state.symlinks.get(parent);
@@ -58418,9 +58455,9 @@ function isRecursive(path5, resolved, state) {
58418
58455
  if (isSameRoot)
58419
58456
  depth++;
58420
58457
  else
58421
- parent = dirname2(parent);
58458
+ parent = dirname3(parent);
58422
58459
  }
58423
- state.symlinks.set(path5, resolved);
58460
+ state.symlinks.set(path6, resolved);
58424
58461
  return depth > 1;
58425
58462
  }
58426
58463
  function isRecursiveUsingRealPaths(resolved, state) {
@@ -58476,23 +58513,23 @@ var walkAsync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
58476
58513
  state.queue.enqueue();
58477
58514
  if (currentDepth < 0)
58478
58515
  return state.queue.dequeue(null, state);
58479
- const { fs: fs6 } = state;
58516
+ const { fs: fs7 } = state;
58480
58517
  state.visited.push(crawlPath);
58481
58518
  state.counts.directories++;
58482
- fs6.readdir(crawlPath || ".", readdirOpts, (error43, entries = []) => {
58519
+ fs7.readdir(crawlPath || ".", readdirOpts, (error43, entries = []) => {
58483
58520
  callback$1(entries, directoryPath, currentDepth);
58484
58521
  state.queue.dequeue(state.options.suppressErrors ? null : error43, state);
58485
58522
  });
58486
58523
  };
58487
58524
  var walkSync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
58488
- const { fs: fs6 } = state;
58525
+ const { fs: fs7 } = state;
58489
58526
  if (currentDepth < 0)
58490
58527
  return;
58491
58528
  state.visited.push(crawlPath);
58492
58529
  state.counts.directories++;
58493
58530
  let entries = [];
58494
58531
  try {
58495
- entries = fs6.readdirSync(crawlPath || ".", readdirOpts);
58532
+ entries = fs7.readdirSync(crawlPath || ".", readdirOpts);
58496
58533
  } catch (e) {
58497
58534
  if (!state.options.suppressErrors)
58498
58535
  throw e;
@@ -58598,23 +58635,23 @@ var Walker = class {
58598
58635
  const filename = this.joinPath(entry.name, directoryPath);
58599
58636
  this.pushFile(filename, files, this.state.counts, filters);
58600
58637
  } else if (entry.isDirectory()) {
58601
- let path5 = joinDirectoryPath(entry.name, directoryPath, this.state.options.pathSeparator);
58602
- if (exclude && exclude(entry.name, path5))
58638
+ let path6 = joinDirectoryPath(entry.name, directoryPath, this.state.options.pathSeparator);
58639
+ if (exclude && exclude(entry.name, path6))
58603
58640
  continue;
58604
- this.pushDirectory(path5, paths, filters);
58605
- this.walkDirectory(this.state, path5, path5, depth - 1, this.walk);
58641
+ this.pushDirectory(path6, paths, filters);
58642
+ this.walkDirectory(this.state, path6, path6, depth - 1, this.walk);
58606
58643
  } else if (this.resolveSymlink && entry.isSymbolicLink()) {
58607
- let path5 = joinPathWithBasePath(entry.name, directoryPath);
58608
- this.resolveSymlink(path5, this.state, (stat, resolvedPath) => {
58644
+ let path6 = joinPathWithBasePath(entry.name, directoryPath);
58645
+ this.resolveSymlink(path6, this.state, (stat, resolvedPath) => {
58609
58646
  if (stat.isDirectory()) {
58610
58647
  resolvedPath = normalizePath(resolvedPath, this.state.options);
58611
- if (exclude && exclude(entry.name, useRealPaths ? resolvedPath : path5 + pathSeparator))
58648
+ if (exclude && exclude(entry.name, useRealPaths ? resolvedPath : path6 + pathSeparator))
58612
58649
  return;
58613
- this.walkDirectory(this.state, resolvedPath, useRealPaths ? resolvedPath : path5 + pathSeparator, depth - 1, this.walk);
58650
+ this.walkDirectory(this.state, resolvedPath, useRealPaths ? resolvedPath : path6 + pathSeparator, depth - 1, this.walk);
58614
58651
  } else {
58615
- resolvedPath = useRealPaths ? resolvedPath : path5;
58652
+ resolvedPath = useRealPaths ? resolvedPath : path6;
58616
58653
  const filename = basename2(resolvedPath);
58617
- const directoryPath$1 = normalizePath(dirname2(resolvedPath), this.state.options);
58654
+ const directoryPath$1 = normalizePath(dirname3(resolvedPath), this.state.options);
58618
58655
  resolvedPath = this.joinPath(filename, directoryPath$1);
58619
58656
  this.pushFile(resolvedPath, files, this.state.counts, filters);
58620
58657
  }
@@ -58772,7 +58809,7 @@ var Builder = class {
58772
58809
  isMatch = globFn(patterns, ...options);
58773
58810
  this.globCache[patterns.join("\x00")] = isMatch;
58774
58811
  }
58775
- this.options.filters.push((path5) => isMatch(path5));
58812
+ this.options.filters.push((path6) => isMatch(path6));
58776
58813
  return this;
58777
58814
  }
58778
58815
  };
@@ -58851,7 +58888,7 @@ function normalizePattern(pattern, expandDirectories, cwd2, props, isIgnore) {
58851
58888
  if (!result.endsWith("*") && expandDirectories)
58852
58889
  result += "/**";
58853
58890
  const escapedCwd = escapePath(cwd2);
58854
- if (path5.isAbsolute(result.replace(ESCAPING_BACKSLASHES, "")))
58891
+ if (path6.isAbsolute(result.replace(ESCAPING_BACKSLASHES, "")))
58855
58892
  result = posix.relative(escapedCwd, result);
58856
58893
  else
58857
58894
  result = posix.normalize(result);
@@ -58888,7 +58925,7 @@ function normalizePattern(pattern, expandDirectories, cwd2, props, isIgnore) {
58888
58925
  }
58889
58926
  props.depthOffset = newCommonPath.length;
58890
58927
  props.commonPath = newCommonPath;
58891
- props.root = newCommonPath.length > 0 ? path5.posix.join(cwd2, ...newCommonPath) : cwd2;
58928
+ props.root = newCommonPath.length > 0 ? path6.posix.join(cwd2, ...newCommonPath) : cwd2;
58892
58929
  }
58893
58930
  return result;
58894
58931
  }
@@ -59021,18 +59058,18 @@ function globSync(patternsOrOptions, options) {
59021
59058
  ...options,
59022
59059
  patterns: patternsOrOptions
59023
59060
  } : patternsOrOptions;
59024
- const cwd2 = opts.cwd ? path5.resolve(opts.cwd).replace(BACKSLASHES, "/") : process.cwd().replace(BACKSLASHES, "/");
59061
+ const cwd2 = opts.cwd ? path6.resolve(opts.cwd).replace(BACKSLASHES, "/") : process.cwd().replace(BACKSLASHES, "/");
59025
59062
  return crawl(opts, cwd2, true);
59026
59063
  }
59027
59064
 
59028
59065
  // src/utils/jsonl.ts
59029
59066
  import { promisify } from "util";
59030
- var readFile4 = promisify(fs6.readFile);
59031
- var readFileSync4 = fs6.readFileSync;
59032
- var statSync4 = fs6.statSync;
59067
+ var readFile4 = promisify(fs7.readFile);
59068
+ var readFileSync5 = fs7.readFileSync;
59069
+ var statSync4 = fs7.statSync;
59033
59070
  async function getSessionDuration(transcriptPath) {
59034
59071
  try {
59035
- if (!fs6.existsSync(transcriptPath)) {
59072
+ if (!fs7.existsSync(transcriptPath)) {
59036
59073
  return null;
59037
59074
  }
59038
59075
  const content = await readFile4(transcriptPath, "utf-8");
@@ -59084,7 +59121,7 @@ async function getSessionDuration(transcriptPath) {
59084
59121
  }
59085
59122
  async function getTokenMetrics(transcriptPath) {
59086
59123
  try {
59087
- if (!fs6.existsSync(transcriptPath)) {
59124
+ if (!fs7.existsSync(transcriptPath)) {
59088
59125
  return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
59089
59126
  }
59090
59127
  const content = await readFile4(transcriptPath, "utf-8");
@@ -59137,7 +59174,7 @@ function getBlockMetrics() {
59137
59174
  function findMostRecentBlockStartTime(rootDir, sessionDurationHours = 5) {
59138
59175
  const sessionDurationMs = sessionDurationHours * 60 * 60 * 1000;
59139
59176
  const now = new Date;
59140
- const pattern = path6.posix.join(rootDir.replace(/\\/g, "/"), "projects", "**", "*.jsonl");
59177
+ const pattern = path7.posix.join(rootDir.replace(/\\/g, "/"), "projects", "**", "*.jsonl");
59141
59178
  const files = globSync([pattern], {
59142
59179
  absolute: true,
59143
59180
  cwd: rootDir
@@ -59231,7 +59268,7 @@ function findMostRecentBlockStartTime(rootDir, sessionDurationHours = 5) {
59231
59268
  function getAllTimestampsFromFile(filePath) {
59232
59269
  const timestamps = [];
59233
59270
  try {
59234
- const content = readFileSync4(filePath, "utf-8");
59271
+ const content = readFileSync5(filePath, "utf-8");
59235
59272
  const lines = content.trim().split(`
59236
59273
  `).filter((line) => line.length > 0);
59237
59274
  for (const line of lines) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline-usage",
3
- "version": "2.0.28",
3
+ "version": "2.0.29",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",
@@ -8,7 +8,11 @@
8
8
  "ccstatusline": "dist/ccstatusline.js"
9
9
  },
10
10
  "files": [
11
- "dist/"
11
+ "dist/",
12
+ "scripts/usage.sh",
13
+ "scripts/context.sh",
14
+ "scripts/setup-enhanced.sh",
15
+ "defaults/"
12
16
  ],
13
17
  "scripts": {
14
18
  "start": "bun run src/ccstatusline.ts",
@@ -0,0 +1,58 @@
1
+ #!/bin/bash
2
+
3
+ CACHE_FILE="$HOME/.claude/.statusline/context.json"
4
+
5
+ make_bar() {
6
+ local pct="$1"
7
+ local width=15
8
+ local filled=$((pct * width / 100))
9
+ local empty=$((width - filled))
10
+ printf "["
11
+ printf "█%.0s" $(seq 1 "$filled")
12
+ printf "░%.0s" $(seq 1 "$empty")
13
+ printf "]"
14
+ }
15
+
16
+ format_tokens() {
17
+ local tokens="$1"
18
+ if [[ $tokens -ge 1000 ]]; then
19
+ echo "$((tokens / 1000))k"
20
+ else
21
+ echo "$tokens"
22
+ fi
23
+ }
24
+
25
+ if [[ ! -f "$CACHE_FILE" ]]; then
26
+ BAR=$(make_bar 0)
27
+ echo "Context: $BAR 0k/200k (0%)"
28
+ exit 0
29
+ fi
30
+
31
+ INPUT=$(cat "$CACHE_FILE")
32
+
33
+ MAX_TOKENS=$(echo "$INPUT" | jq -r '.context_window_size // empty')
34
+ CURRENT_USAGE=$(echo "$INPUT" | jq -r '.current_usage // empty')
35
+
36
+ if [[ -z "$MAX_TOKENS" || "$MAX_TOKENS" == "null" || -z "$CURRENT_USAGE" || "$CURRENT_USAGE" == "null" ]]; then
37
+ BAR=$(make_bar 0)
38
+ echo "Context: $BAR 0k/200k (0%)"
39
+ exit 0
40
+ fi
41
+
42
+ INPUT_TOKENS=$(echo "$INPUT" | jq -r '.current_usage.input_tokens // 0')
43
+ CACHE_CREATE=$(echo "$INPUT" | jq -r '.current_usage.cache_creation_input_tokens // 0')
44
+ CACHE_READ=$(echo "$INPUT" | jq -r '.current_usage.cache_read_input_tokens // 0')
45
+
46
+ CURRENT_TOKENS=$((INPUT_TOKENS + CACHE_CREATE + CACHE_READ))
47
+
48
+ if [[ $MAX_TOKENS -gt 0 ]]; then
49
+ PERCENT=$((CURRENT_TOKENS * 100 / MAX_TOKENS))
50
+ else
51
+ PERCENT=0
52
+ fi
53
+
54
+ CURRENT_FMT=$(format_tokens "$CURRENT_TOKENS")
55
+ MAX_FMT=$(format_tokens "$MAX_TOKENS")
56
+ BAR=$(make_bar "$PERCENT")
57
+
58
+ echo "Context: $BAR ${CURRENT_FMT}/${MAX_FMT} (${PERCENT}%)"
@@ -0,0 +1,98 @@
1
+ #!/bin/bash
2
+ # Setup script for ccstatusline-usage enhanced configuration
3
+ # Installs usage scripts and applies enhanced settings
4
+
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$HOME/.local/share/ccstatusline"
8
+ CONFIG_DIR="$HOME/.config/ccstatusline"
9
+ SETTINGS_FILE="$CONFIG_DIR/settings.json"
10
+
11
+ # Find the package directory (where this script is located)
12
+ PKG_DIR="$(cd "$(dirname "$0")/.." && pwd)"
13
+
14
+ echo "Setting up ccstatusline-usage enhanced configuration..."
15
+
16
+ # Create directories
17
+ mkdir -p "$SCRIPT_DIR"
18
+ mkdir -p "$CONFIG_DIR"
19
+
20
+ # Copy scripts
21
+ if [[ -f "$PKG_DIR/scripts/usage.sh" ]]; then
22
+ cp "$PKG_DIR/scripts/usage.sh" "$SCRIPT_DIR/"
23
+ chmod +x "$SCRIPT_DIR/usage.sh"
24
+ echo "✓ Installed usage.sh to $SCRIPT_DIR/"
25
+ else
26
+ echo "✗ usage.sh not found in package"
27
+ exit 1
28
+ fi
29
+
30
+ if [[ -f "$PKG_DIR/scripts/context.sh" ]]; then
31
+ cp "$PKG_DIR/scripts/context.sh" "$SCRIPT_DIR/"
32
+ chmod +x "$SCRIPT_DIR/context.sh"
33
+ echo "✓ Installed context.sh to $SCRIPT_DIR/"
34
+ else
35
+ echo "✗ context.sh not found in package"
36
+ exit 1
37
+ fi
38
+
39
+ # Create enhanced settings with correct paths
40
+ cat > "$SETTINGS_FILE" << 'EOF'
41
+ {
42
+ "version": 3,
43
+ "lines": [
44
+ [
45
+ {
46
+ "id": "session-usage",
47
+ "type": "custom-command",
48
+ "color": "brightBlue",
49
+ "commandPath": "$HOME/.local/share/ccstatusline/usage.sh session",
50
+ "timeout": 5000
51
+ },
52
+ {"id": "sep1", "type": "separator"},
53
+ {
54
+ "id": "weekly-usage",
55
+ "type": "custom-command",
56
+ "color": "brightBlue",
57
+ "commandPath": "$HOME/.local/share/ccstatusline/usage.sh weekly",
58
+ "timeout": 5000
59
+ },
60
+ {"id": "sep2", "type": "separator"},
61
+ {
62
+ "id": "reset-timer",
63
+ "type": "custom-command",
64
+ "color": "brightBlue",
65
+ "commandPath": "$HOME/.local/share/ccstatusline/usage.sh reset",
66
+ "timeout": 5000
67
+ },
68
+ {"id": "sep3", "type": "separator"},
69
+ {"id": "model", "type": "model", "color": "magenta"},
70
+ {"id": "sep4", "type": "separator"},
71
+ {"id": "session-id", "type": "claude-session-id", "color": "cyan"}
72
+ ],
73
+ [
74
+ {
75
+ "id": "context-usage",
76
+ "type": "custom-command",
77
+ "color": "blue",
78
+ "commandPath": "$HOME/.local/share/ccstatusline/context.sh",
79
+ "timeout": 5000
80
+ }
81
+ ],
82
+ []
83
+ ],
84
+ "flexMode": "full-minus-40",
85
+ "compactThreshold": 60,
86
+ "colorLevel": 2
87
+ }
88
+ EOF
89
+
90
+ # Expand $HOME in the settings file
91
+ sed -i.bak "s|\$HOME|$HOME|g" "$SETTINGS_FILE" && rm -f "$SETTINGS_FILE.bak"
92
+
93
+ echo "✓ Created enhanced settings at $SETTINGS_FILE"
94
+ echo ""
95
+ echo "Setup complete! Run 'npx ccstatusline-usage' to configure."
96
+ echo ""
97
+ echo "Note: The usage widgets require Anthropic API access."
98
+ echo "Make sure you have valid credentials in ~/.claude/credentials.json"
@@ -0,0 +1,147 @@
1
+ #!/bin/bash
2
+
3
+ # Cross-platform usage script for ccstatusline-usage
4
+ # Works on both macOS and Linux
5
+
6
+ CACHE_FILE="$HOME/.cache/ccstatusline-api.json"
7
+ LOCK_FILE="$HOME/.cache/ccstatusline-api.lock"
8
+
9
+ # Detect OS for platform-specific commands
10
+ is_macos() {
11
+ [[ "$(uname)" == "Darwin" ]]
12
+ }
13
+
14
+ # Get file modification time (seconds since epoch)
15
+ get_mtime() {
16
+ if is_macos; then
17
+ stat -f '%m' "$1" 2>/dev/null
18
+ else
19
+ stat -c '%Y' "$1" 2>/dev/null
20
+ fi
21
+ }
22
+
23
+ # Get OAuth token from credentials
24
+ get_token() {
25
+ if is_macos; then
26
+ # macOS: read from keychain
27
+ security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | jq -r '.claudeAiOauth.accessToken // empty'
28
+ else
29
+ # Linux: read from credentials file
30
+ jq -r '.claudeAiOauth.accessToken // empty' ~/.claude/.credentials.json 2>/dev/null
31
+ fi
32
+ }
33
+
34
+ # Parse ISO date to epoch
35
+ parse_iso_date() {
36
+ local iso_date="$1"
37
+ # Remove fractional seconds and Z suffix
38
+ local clean_date="${iso_date%%.*}"
39
+ clean_date="${clean_date%%Z}"
40
+
41
+ if is_macos; then
42
+ TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$clean_date" "+%s" 2>/dev/null
43
+ else
44
+ # Linux: use date -d with ISO format
45
+ date -d "$clean_date" "+%s" 2>/dev/null
46
+ fi
47
+ }
48
+
49
+ fetch_api() {
50
+ local NOW=$(date +%s)
51
+
52
+ # Use cache if < 180 seconds old
53
+ if [[ -f "$CACHE_FILE" ]]; then
54
+ local MTIME=$(get_mtime "$CACHE_FILE")
55
+ if [[ -n "$MTIME" ]]; then
56
+ local CACHE_AGE=$((NOW - MTIME))
57
+ [[ $CACHE_AGE -lt 180 ]] && return 0
58
+ fi
59
+ fi
60
+
61
+ # Rate limit: only try API once per 30 seconds
62
+ if [[ -f "$LOCK_FILE" ]]; then
63
+ local LOCK_MTIME=$(get_mtime "$LOCK_FILE")
64
+ if [[ -n "$LOCK_MTIME" ]]; then
65
+ local LOCK_AGE=$((NOW - LOCK_MTIME))
66
+ if [[ $LOCK_AGE -lt 30 ]]; then
67
+ [[ -f "$CACHE_FILE" ]] && return 0
68
+ return 1
69
+ fi
70
+ fi
71
+ fi
72
+
73
+ touch "$LOCK_FILE"
74
+
75
+ TOKEN=$(get_token)
76
+ [[ -z "$TOKEN" ]] && return 1
77
+
78
+ RESPONSE=$(curl -s --max-time 5 "https://api.anthropic.com/api/oauth/usage" \
79
+ -H "Authorization: Bearer $TOKEN" \
80
+ -H "anthropic-beta: oauth-2025-04-20" 2>/dev/null)
81
+ [[ -z "$RESPONSE" ]] && return 1
82
+
83
+ # Ensure cache directory exists
84
+ mkdir -p "$(dirname "$CACHE_FILE")"
85
+ echo "$RESPONSE" > "$CACHE_FILE"
86
+ return 0
87
+ }
88
+
89
+ make_bar() {
90
+ local pct="$1"
91
+ local width=15
92
+ local filled=$((pct * width / 100))
93
+ local empty=$((width - filled))
94
+ printf "["
95
+ printf "█%.0s" $(seq 1 "$filled")
96
+ printf "░%.0s" $(seq 1 "$empty")
97
+ printf "]"
98
+ }
99
+
100
+ MODE="${1:-all}"
101
+
102
+ fetch_api || { echo "[API Error]"; exit 1; }
103
+
104
+ case "$MODE" in
105
+ session)
106
+ SESSION=$(jq -r '.five_hour.utilization // empty' "$CACHE_FILE" 2>/dev/null)
107
+ [[ -z "$SESSION" ]] && { echo "[Parse Error]"; exit 1; }
108
+ SESSION_INT=${SESSION%.*}
109
+ SESSION_BAR=$(make_bar "$SESSION_INT")
110
+ echo "Session: $SESSION_BAR ${SESSION}%"
111
+ ;;
112
+ weekly)
113
+ WEEKLY=$(jq -r '.seven_day.utilization // empty' "$CACHE_FILE" 2>/dev/null)
114
+ [[ -z "$WEEKLY" ]] && { echo "[Parse Error]"; exit 1; }
115
+ WEEKLY_INT=${WEEKLY%.*}
116
+ WEEKLY_BAR=$(make_bar "$WEEKLY_INT")
117
+ echo "Weekly: $WEEKLY_BAR ${WEEKLY}%"
118
+ ;;
119
+ reset)
120
+ RESETS_AT=$(jq -r '.five_hour.resets_at // empty' "$CACHE_FILE" 2>/dev/null)
121
+ [[ -z "$RESETS_AT" ]] && { echo "[Parse Error]"; exit 1; }
122
+ RESET_EPOCH=$(parse_iso_date "$RESETS_AT")
123
+ NOW_EPOCH=$(date -u +%s)
124
+ if [[ -z "$RESET_EPOCH" ]]; then
125
+ echo "[Date Error]"
126
+ exit 1
127
+ fi
128
+ DIFF=$((RESET_EPOCH - NOW_EPOCH))
129
+ if [[ $DIFF -le 0 ]]; then
130
+ echo "Reset now"
131
+ else
132
+ HOURS=$((DIFF / 3600))
133
+ MINUTES=$(((DIFF % 3600) / 60))
134
+ echo "${HOURS}:$(printf '%02d' $MINUTES) hr"
135
+ fi
136
+ ;;
137
+ *)
138
+ SESSION=$(jq -r '.five_hour.utilization // empty' "$CACHE_FILE" 2>/dev/null)
139
+ WEEKLY=$(jq -r '.seven_day.utilization // empty' "$CACHE_FILE" 2>/dev/null)
140
+ [[ -z "$SESSION" || -z "$WEEKLY" ]] && { echo "[Parse Error]"; exit 1; }
141
+ SESSION_INT=${SESSION%.*}
142
+ WEEKLY_INT=${WEEKLY%.*}
143
+ SESSION_BAR=$(make_bar "$SESSION_INT")
144
+ WEEKLY_BAR=$(make_bar "$WEEKLY_INT")
145
+ echo "Session: $SESSION_BAR ${SESSION}% | Weekly: $WEEKLY_BAR ${WEEKLY}%"
146
+ ;;
147
+ esac