ccstatusline-usage 2.0.27 → 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,13 +50982,19 @@ 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: "cyan" },
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-id", type: "claude-session-id", color: "brightBlack" }
50989
+ { id: "reset-timer", type: "custom-command", color: "brightBlue", commandPath: "$PKG/scripts/usage.sh reset", timeout: 5000 },
50990
+ { id: "sep3", type: "separator" },
50991
+ { id: "model", type: "model", color: "magenta" },
50992
+ { id: "sep4", type: "separator" },
50993
+ { id: "session-id", type: "claude-session-id", color: "cyan" }
50994
+ ],
50995
+ [
50996
+ { id: "context-usage", type: "custom-command", color: "blue", commandPath: "$PKG/scripts/context.sh", timeout: 5000 }
50990
50997
  ],
50991
- [],
50992
50998
  []
50993
50999
  ]),
50994
51000
  flexMode: FlexModeSchema.default("full-minus-40"),
@@ -51444,7 +51450,7 @@ import { execSync as execSync3 } from "child_process";
51444
51450
  import * as fs5 from "fs";
51445
51451
  import * as path4 from "path";
51446
51452
  var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils";
51447
- var PACKAGE_VERSION = "2.0.27";
51453
+ var PACKAGE_VERSION = "2.0.29";
51448
51454
  function getPackageVersion() {
51449
51455
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
51450
51456
  return PACKAGE_VERSION;
@@ -53806,8 +53812,33 @@ var CustomTextEditor = ({ widget, onComplete, onCancel }) => {
53806
53812
  };
53807
53813
  // src/widgets/CustomCommand.tsx
53808
53814
  import { execSync as execSync7 } from "child_process";
53815
+ import * as fs6 from "fs";
53809
53816
  var import_react30 = __toESM(require_react(), 1);
53810
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();
53811
53842
 
53812
53843
  class CustomCommandWidget {
53813
53844
  getDefaultColor() {
@@ -53844,6 +53875,17 @@ class CustomCommandWidget {
53844
53875
  }
53845
53876
  return null;
53846
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
+ }
53847
53889
  render(item, context, settings) {
53848
53890
  if (context.isPreview) {
53849
53891
  return item.commandPath ? `[cmd: ${item.commandPath.substring(0, 20)}${item.commandPath.length > 20 ? "..." : ""}]` : "[No command]";
@@ -53851,7 +53893,8 @@ class CustomCommandWidget {
53851
53893
  try {
53852
53894
  const timeout = item.timeout ?? 1000;
53853
53895
  const jsonInput = JSON.stringify(context.data);
53854
- let output = execSync7(item.commandPath, {
53896
+ const resolvedPath = this.resolveCommandPath(item.commandPath);
53897
+ let output = execSync7(resolvedPath, {
53855
53898
  encoding: "utf8",
53856
53899
  input: jsonInput,
53857
53900
  timeout,
@@ -54272,13 +54315,13 @@ class CurrentWorkingDirWidget {
54272
54315
  supportsColors(item) {
54273
54316
  return true;
54274
54317
  }
54275
- abbreviatePath(path5) {
54318
+ abbreviatePath(path6) {
54276
54319
  const homeDir = os5.homedir();
54277
- const useBackslash = path5.includes("\\") && !path5.includes("/");
54320
+ const useBackslash = path6.includes("\\") && !path6.includes("/");
54278
54321
  const sep = useBackslash ? "\\" : "/";
54279
- let normalizedPath = path5;
54280
- if (path5.startsWith(homeDir)) {
54281
- normalizedPath = "~" + path5.slice(homeDir.length);
54322
+ let normalizedPath = path6;
54323
+ if (path6.startsWith(homeDir)) {
54324
+ normalizedPath = "~" + path6.slice(homeDir.length);
54282
54325
  }
54283
54326
  const parts = normalizedPath.split(/[\\/]+/).filter((part) => part !== "");
54284
54327
  const abbreviated = parts.map((part, index) => {
@@ -58232,42 +58275,42 @@ var StatusJSONSchema = exports_external.looseObject({
58232
58275
  });
58233
58276
 
58234
58277
  // src/utils/jsonl.ts
58235
- import * as fs6 from "fs";
58236
- import path6 from "node:path";
58278
+ import * as fs7 from "fs";
58279
+ import path7 from "node:path";
58237
58280
 
58238
58281
  // node_modules/tinyglobby/dist/index.mjs
58239
- import path5, { posix } from "path";
58282
+ import path6, { posix } from "path";
58240
58283
 
58241
58284
  // node_modules/fdir/dist/index.mjs
58242
58285
  import { createRequire as createRequire2 } from "module";
58243
- 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";
58244
58287
  import * as nativeFs from "fs";
58245
58288
  var __require2 = /* @__PURE__ */ createRequire2(import.meta.url);
58246
- function cleanPath(path5) {
58247
- let normalized = normalize(path5);
58289
+ function cleanPath(path6) {
58290
+ let normalized = normalize(path6);
58248
58291
  if (normalized.length > 1 && normalized[normalized.length - 1] === sep)
58249
58292
  normalized = normalized.substring(0, normalized.length - 1);
58250
58293
  return normalized;
58251
58294
  }
58252
58295
  var SLASHES_REGEX = /[\\/]/g;
58253
- function convertSlashes(path5, separator) {
58254
- return path5.replace(SLASHES_REGEX, separator);
58296
+ function convertSlashes(path6, separator) {
58297
+ return path6.replace(SLASHES_REGEX, separator);
58255
58298
  }
58256
58299
  var WINDOWS_ROOT_DIR_REGEX = /^[a-z]:[\\/]$/i;
58257
- function isRootDirectory(path5) {
58258
- return path5 === "/" || WINDOWS_ROOT_DIR_REGEX.test(path5);
58300
+ function isRootDirectory(path6) {
58301
+ return path6 === "/" || WINDOWS_ROOT_DIR_REGEX.test(path6);
58259
58302
  }
58260
- function normalizePath(path5, options) {
58303
+ function normalizePath(path6, options) {
58261
58304
  const { resolvePaths, normalizePath: normalizePath$1, pathSeparator } = options;
58262
- const pathNeedsCleaning = process.platform === "win32" && path5.includes("/") || path5.startsWith(".");
58305
+ const pathNeedsCleaning = process.platform === "win32" && path6.includes("/") || path6.startsWith(".");
58263
58306
  if (resolvePaths)
58264
- path5 = resolve2(path5);
58307
+ path6 = resolve2(path6);
58265
58308
  if (normalizePath$1 || pathNeedsCleaning)
58266
- path5 = cleanPath(path5);
58267
- if (path5 === ".")
58309
+ path6 = cleanPath(path6);
58310
+ if (path6 === ".")
58268
58311
  return "";
58269
- const needsSeperator = path5[path5.length - 1] !== pathSeparator;
58270
- return convertSlashes(needsSeperator ? path5 + pathSeparator : path5, pathSeparator);
58312
+ const needsSeperator = path6[path6.length - 1] !== pathSeparator;
58313
+ return convertSlashes(needsSeperator ? path6 + pathSeparator : path6, pathSeparator);
58271
58314
  }
58272
58315
  function joinPathWithBasePath(filename, directoryPath) {
58273
58316
  return directoryPath + filename;
@@ -58307,9 +58350,9 @@ var pushDirectory = (directoryPath, paths) => {
58307
58350
  paths.push(directoryPath || ".");
58308
58351
  };
58309
58352
  var pushDirectoryFilter = (directoryPath, paths, filters) => {
58310
- const path5 = directoryPath || ".";
58311
- if (filters.every((filter) => filter(path5, true)))
58312
- paths.push(path5);
58353
+ const path6 = directoryPath || ".";
58354
+ if (filters.every((filter) => filter(path6, true)))
58355
+ paths.push(path6);
58313
58356
  };
58314
58357
  var empty$2 = () => {};
58315
58358
  function build$6(root, options) {
@@ -58366,29 +58409,29 @@ var empty = () => {};
58366
58409
  function build$3(options) {
58367
58410
  return options.group ? groupFiles : empty;
58368
58411
  }
58369
- var resolveSymlinksAsync = function(path5, state, callback$1) {
58370
- const { queue, fs: fs6, options: { suppressErrors } } = state;
58412
+ var resolveSymlinksAsync = function(path6, state, callback$1) {
58413
+ const { queue, fs: fs7, options: { suppressErrors } } = state;
58371
58414
  queue.enqueue();
58372
- fs6.realpath(path5, (error43, resolvedPath) => {
58415
+ fs7.realpath(path6, (error43, resolvedPath) => {
58373
58416
  if (error43)
58374
58417
  return queue.dequeue(suppressErrors ? null : error43, state);
58375
- fs6.stat(resolvedPath, (error$1, stat) => {
58418
+ fs7.stat(resolvedPath, (error$1, stat) => {
58376
58419
  if (error$1)
58377
58420
  return queue.dequeue(suppressErrors ? null : error$1, state);
58378
- if (stat.isDirectory() && isRecursive(path5, resolvedPath, state))
58421
+ if (stat.isDirectory() && isRecursive(path6, resolvedPath, state))
58379
58422
  return queue.dequeue(null, state);
58380
58423
  callback$1(stat, resolvedPath);
58381
58424
  queue.dequeue(null, state);
58382
58425
  });
58383
58426
  });
58384
58427
  };
58385
- var resolveSymlinks = function(path5, state, callback$1) {
58386
- const { queue, fs: fs6, options: { suppressErrors } } = state;
58428
+ var resolveSymlinks = function(path6, state, callback$1) {
58429
+ const { queue, fs: fs7, options: { suppressErrors } } = state;
58387
58430
  queue.enqueue();
58388
58431
  try {
58389
- const resolvedPath = fs6.realpathSync(path5);
58390
- const stat = fs6.statSync(resolvedPath);
58391
- 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))
58392
58435
  return;
58393
58436
  callback$1(stat, resolvedPath);
58394
58437
  } catch (e) {
@@ -58401,10 +58444,10 @@ function build$2(options, isSynchronous) {
58401
58444
  return null;
58402
58445
  return isSynchronous ? resolveSymlinks : resolveSymlinksAsync;
58403
58446
  }
58404
- function isRecursive(path5, resolved, state) {
58447
+ function isRecursive(path6, resolved, state) {
58405
58448
  if (state.options.useRealPaths)
58406
58449
  return isRecursiveUsingRealPaths(resolved, state);
58407
- let parent = dirname2(path5);
58450
+ let parent = dirname3(path6);
58408
58451
  let depth = 1;
58409
58452
  while (parent !== state.root && depth < 2) {
58410
58453
  const resolvedPath = state.symlinks.get(parent);
@@ -58412,9 +58455,9 @@ function isRecursive(path5, resolved, state) {
58412
58455
  if (isSameRoot)
58413
58456
  depth++;
58414
58457
  else
58415
- parent = dirname2(parent);
58458
+ parent = dirname3(parent);
58416
58459
  }
58417
- state.symlinks.set(path5, resolved);
58460
+ state.symlinks.set(path6, resolved);
58418
58461
  return depth > 1;
58419
58462
  }
58420
58463
  function isRecursiveUsingRealPaths(resolved, state) {
@@ -58470,23 +58513,23 @@ var walkAsync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
58470
58513
  state.queue.enqueue();
58471
58514
  if (currentDepth < 0)
58472
58515
  return state.queue.dequeue(null, state);
58473
- const { fs: fs6 } = state;
58516
+ const { fs: fs7 } = state;
58474
58517
  state.visited.push(crawlPath);
58475
58518
  state.counts.directories++;
58476
- fs6.readdir(crawlPath || ".", readdirOpts, (error43, entries = []) => {
58519
+ fs7.readdir(crawlPath || ".", readdirOpts, (error43, entries = []) => {
58477
58520
  callback$1(entries, directoryPath, currentDepth);
58478
58521
  state.queue.dequeue(state.options.suppressErrors ? null : error43, state);
58479
58522
  });
58480
58523
  };
58481
58524
  var walkSync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
58482
- const { fs: fs6 } = state;
58525
+ const { fs: fs7 } = state;
58483
58526
  if (currentDepth < 0)
58484
58527
  return;
58485
58528
  state.visited.push(crawlPath);
58486
58529
  state.counts.directories++;
58487
58530
  let entries = [];
58488
58531
  try {
58489
- entries = fs6.readdirSync(crawlPath || ".", readdirOpts);
58532
+ entries = fs7.readdirSync(crawlPath || ".", readdirOpts);
58490
58533
  } catch (e) {
58491
58534
  if (!state.options.suppressErrors)
58492
58535
  throw e;
@@ -58592,23 +58635,23 @@ var Walker = class {
58592
58635
  const filename = this.joinPath(entry.name, directoryPath);
58593
58636
  this.pushFile(filename, files, this.state.counts, filters);
58594
58637
  } else if (entry.isDirectory()) {
58595
- let path5 = joinDirectoryPath(entry.name, directoryPath, this.state.options.pathSeparator);
58596
- if (exclude && exclude(entry.name, path5))
58638
+ let path6 = joinDirectoryPath(entry.name, directoryPath, this.state.options.pathSeparator);
58639
+ if (exclude && exclude(entry.name, path6))
58597
58640
  continue;
58598
- this.pushDirectory(path5, paths, filters);
58599
- 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);
58600
58643
  } else if (this.resolveSymlink && entry.isSymbolicLink()) {
58601
- let path5 = joinPathWithBasePath(entry.name, directoryPath);
58602
- this.resolveSymlink(path5, this.state, (stat, resolvedPath) => {
58644
+ let path6 = joinPathWithBasePath(entry.name, directoryPath);
58645
+ this.resolveSymlink(path6, this.state, (stat, resolvedPath) => {
58603
58646
  if (stat.isDirectory()) {
58604
58647
  resolvedPath = normalizePath(resolvedPath, this.state.options);
58605
- if (exclude && exclude(entry.name, useRealPaths ? resolvedPath : path5 + pathSeparator))
58648
+ if (exclude && exclude(entry.name, useRealPaths ? resolvedPath : path6 + pathSeparator))
58606
58649
  return;
58607
- 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);
58608
58651
  } else {
58609
- resolvedPath = useRealPaths ? resolvedPath : path5;
58652
+ resolvedPath = useRealPaths ? resolvedPath : path6;
58610
58653
  const filename = basename2(resolvedPath);
58611
- const directoryPath$1 = normalizePath(dirname2(resolvedPath), this.state.options);
58654
+ const directoryPath$1 = normalizePath(dirname3(resolvedPath), this.state.options);
58612
58655
  resolvedPath = this.joinPath(filename, directoryPath$1);
58613
58656
  this.pushFile(resolvedPath, files, this.state.counts, filters);
58614
58657
  }
@@ -58766,7 +58809,7 @@ var Builder = class {
58766
58809
  isMatch = globFn(patterns, ...options);
58767
58810
  this.globCache[patterns.join("\x00")] = isMatch;
58768
58811
  }
58769
- this.options.filters.push((path5) => isMatch(path5));
58812
+ this.options.filters.push((path6) => isMatch(path6));
58770
58813
  return this;
58771
58814
  }
58772
58815
  };
@@ -58845,7 +58888,7 @@ function normalizePattern(pattern, expandDirectories, cwd2, props, isIgnore) {
58845
58888
  if (!result.endsWith("*") && expandDirectories)
58846
58889
  result += "/**";
58847
58890
  const escapedCwd = escapePath(cwd2);
58848
- if (path5.isAbsolute(result.replace(ESCAPING_BACKSLASHES, "")))
58891
+ if (path6.isAbsolute(result.replace(ESCAPING_BACKSLASHES, "")))
58849
58892
  result = posix.relative(escapedCwd, result);
58850
58893
  else
58851
58894
  result = posix.normalize(result);
@@ -58882,7 +58925,7 @@ function normalizePattern(pattern, expandDirectories, cwd2, props, isIgnore) {
58882
58925
  }
58883
58926
  props.depthOffset = newCommonPath.length;
58884
58927
  props.commonPath = newCommonPath;
58885
- props.root = newCommonPath.length > 0 ? path5.posix.join(cwd2, ...newCommonPath) : cwd2;
58928
+ props.root = newCommonPath.length > 0 ? path6.posix.join(cwd2, ...newCommonPath) : cwd2;
58886
58929
  }
58887
58930
  return result;
58888
58931
  }
@@ -59015,18 +59058,18 @@ function globSync(patternsOrOptions, options) {
59015
59058
  ...options,
59016
59059
  patterns: patternsOrOptions
59017
59060
  } : patternsOrOptions;
59018
- 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, "/");
59019
59062
  return crawl(opts, cwd2, true);
59020
59063
  }
59021
59064
 
59022
59065
  // src/utils/jsonl.ts
59023
59066
  import { promisify } from "util";
59024
- var readFile4 = promisify(fs6.readFile);
59025
- var readFileSync4 = fs6.readFileSync;
59026
- var statSync4 = fs6.statSync;
59067
+ var readFile4 = promisify(fs7.readFile);
59068
+ var readFileSync5 = fs7.readFileSync;
59069
+ var statSync4 = fs7.statSync;
59027
59070
  async function getSessionDuration(transcriptPath) {
59028
59071
  try {
59029
- if (!fs6.existsSync(transcriptPath)) {
59072
+ if (!fs7.existsSync(transcriptPath)) {
59030
59073
  return null;
59031
59074
  }
59032
59075
  const content = await readFile4(transcriptPath, "utf-8");
@@ -59078,7 +59121,7 @@ async function getSessionDuration(transcriptPath) {
59078
59121
  }
59079
59122
  async function getTokenMetrics(transcriptPath) {
59080
59123
  try {
59081
- if (!fs6.existsSync(transcriptPath)) {
59124
+ if (!fs7.existsSync(transcriptPath)) {
59082
59125
  return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
59083
59126
  }
59084
59127
  const content = await readFile4(transcriptPath, "utf-8");
@@ -59131,7 +59174,7 @@ function getBlockMetrics() {
59131
59174
  function findMostRecentBlockStartTime(rootDir, sessionDurationHours = 5) {
59132
59175
  const sessionDurationMs = sessionDurationHours * 60 * 60 * 1000;
59133
59176
  const now = new Date;
59134
- const pattern = path6.posix.join(rootDir.replace(/\\/g, "/"), "projects", "**", "*.jsonl");
59177
+ const pattern = path7.posix.join(rootDir.replace(/\\/g, "/"), "projects", "**", "*.jsonl");
59135
59178
  const files = globSync([pattern], {
59136
59179
  absolute: true,
59137
59180
  cwd: rootDir
@@ -59225,7 +59268,7 @@ function findMostRecentBlockStartTime(rootDir, sessionDurationHours = 5) {
59225
59268
  function getAllTimestampsFromFile(filePath) {
59226
59269
  const timestamps = [];
59227
59270
  try {
59228
- const content = readFileSync4(filePath, "utf-8");
59271
+ const content = readFileSync5(filePath, "utf-8");
59229
59272
  const lines = content.trim().split(`
59230
59273
  `).filter((line) => line.length > 0);
59231
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.27",
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