ccusage 0.1.9 → 0.1.11

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 (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +292 -62
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,10 @@ This tool helps you understand the value you're getting from your subscription b
42
42
  - 📄 **JSON Output**: Export data in structured JSON format with `--json`
43
43
  - 💰 **Cost Tracking**: Shows costs in USD for each day/session
44
44
 
45
+ ## Limitations
46
+
47
+ - This tool only reads local JSONL files generated by Claude Code. If you use Claude Code with multiple devices, you need to ensure the JSONL files are synchronized across devices.
48
+
45
49
  ## Installation
46
50
 
47
51
  ### Quick Start (Recommended)
package/dist/index.js CHANGED
@@ -4878,9 +4878,9 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
4878
4878
  if (glob$1 === "" || typeof glob$1 !== "string" && !isState) throw new TypeError("Expected pattern to be a non-empty string");
4879
4879
  const opts = options || {};
4880
4880
  const posix$1 = opts.windows;
4881
- const regex$1 = isState ? picomatch$2.compileRe(glob$1, options) : picomatch$2.makeRe(glob$1, options, false, true);
4882
- const state = regex$1.state;
4883
- delete regex$1.state;
4881
+ const regex$2 = isState ? picomatch$2.compileRe(glob$1, options) : picomatch$2.makeRe(glob$1, options, false, true);
4882
+ const state = regex$2.state;
4883
+ delete regex$2.state;
4884
4884
  let isIgnored = () => false;
4885
4885
  if (opts.ignore) {
4886
4886
  const ignoreOpts = {
@@ -4892,14 +4892,14 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
4892
4892
  isIgnored = picomatch$2(opts.ignore, ignoreOpts, returnState);
4893
4893
  }
4894
4894
  const matcher = (input, returnObject = false) => {
4895
- const { isMatch, match, output } = picomatch$2.test(input, regex$1, options, {
4895
+ const { isMatch, match, output } = picomatch$2.test(input, regex$2, options, {
4896
4896
  glob: glob$1,
4897
4897
  posix: posix$1
4898
4898
  });
4899
4899
  const result = {
4900
4900
  glob: glob$1,
4901
4901
  state,
4902
- regex: regex$1,
4902
+ regex: regex$2,
4903
4903
  posix: posix$1,
4904
4904
  input,
4905
4905
  output,
@@ -4938,7 +4938,7 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
4938
4938
  * @return {Object} Returns an object with matching info.
4939
4939
  * @api public
4940
4940
  */
4941
- picomatch$2.test = (input, regex$1, options, { glob: glob$1, posix: posix$1 } = {}) => {
4941
+ picomatch$2.test = (input, regex$2, options, { glob: glob$1, posix: posix$1 } = {}) => {
4942
4942
  if (typeof input !== "string") throw new TypeError("Expected input to be a string");
4943
4943
  if (input === "") return {
4944
4944
  isMatch: false,
@@ -4952,8 +4952,8 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
4952
4952
  output = format ? format(input) : input;
4953
4953
  match = output === glob$1;
4954
4954
  }
4955
- if (match === false || opts.capture === true) if (opts.matchBase === true || opts.basename === true) match = picomatch$2.matchBase(input, regex$1, options, posix$1);
4956
- else match = regex$1.exec(output);
4955
+ if (match === false || opts.capture === true) if (opts.matchBase === true || opts.basename === true) match = picomatch$2.matchBase(input, regex$2, options, posix$1);
4956
+ else match = regex$2.exec(output);
4957
4957
  return {
4958
4958
  isMatch: Boolean(match),
4959
4959
  match,
@@ -4974,8 +4974,8 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
4974
4974
  * @api public
4975
4975
  */
4976
4976
  picomatch$2.matchBase = (input, glob$1, options) => {
4977
- const regex$1 = glob$1 instanceof RegExp ? glob$1 : picomatch$2.makeRe(glob$1, options);
4978
- return regex$1.test(utils$1.basename(input));
4977
+ const regex$2 = glob$1 instanceof RegExp ? glob$1 : picomatch$2.makeRe(glob$1, options);
4978
+ return regex$2.test(utils$1.basename(input));
4979
4979
  };
4980
4980
  /**
4981
4981
  * Returns true if **any** of the given glob `patterns` match the specified `string`.
@@ -5059,9 +5059,9 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
5059
5059
  const append = opts.contains ? "" : "$";
5060
5060
  let source = `${prepend}(?:${state.output})${append}`;
5061
5061
  if (state && state.negated === true) source = `^(?!${source}).*$`;
5062
- const regex$1 = picomatch$2.toRegex(source, options);
5063
- if (returnState === true) regex$1.state = state;
5064
- return regex$1;
5062
+ const regex$2 = picomatch$2.toRegex(source, options);
5063
+ if (returnState === true) regex$2.state = state;
5064
+ return regex$2;
5065
5065
  };
5066
5066
  /**
5067
5067
  * Create a regular expression from a parsed glob pattern.
@@ -5341,14 +5341,14 @@ function getPartialMatcher(patterns, options) {
5341
5341
  if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true;
5342
5342
  for (let i$1 = 0; i$1 < patterns.length; i$1++) {
5343
5343
  const patternParts = patternsParts[i$1];
5344
- const regex$1 = regexes[i$1];
5344
+ const regex$2 = regexes[i$1];
5345
5345
  const inputPatternCount = inputParts.length;
5346
5346
  const minParts = Math.min(inputPatternCount, patternParts.length);
5347
5347
  let j = 0;
5348
5348
  while (j < minParts) {
5349
5349
  const part = patternParts[j];
5350
5350
  if (part.includes("/")) return true;
5351
- const match = regex$1[j].test(inputParts[j]);
5351
+ const match = regex$2[j].test(inputParts[j]);
5352
5352
  if (!match) break;
5353
5353
  if (part === "**") return true;
5354
5354
  j++;
@@ -5607,6 +5607,32 @@ function _getStandardProps(context) {
5607
5607
  };
5608
5608
  }
5609
5609
  /* @__NO_SIDE_EFFECTS__ */
5610
+ function _isValidObjectKey(object2, key) {
5611
+ return Object.hasOwn(object2, key) && key !== "__proto__" && key !== "prototype" && key !== "constructor";
5612
+ }
5613
+ /* @__NO_SIDE_EFFECTS__ */
5614
+ function _joinExpects(values2, separator) {
5615
+ const list = [...new Set(values2)];
5616
+ if (list.length > 1) return `(${list.join(` ${separator} `)})`;
5617
+ return list[0] ?? "never";
5618
+ }
5619
+ /* @__NO_SIDE_EFFECTS__ */
5620
+ function regex$1(requirement, message2) {
5621
+ return {
5622
+ kind: "validation",
5623
+ type: "regex",
5624
+ reference: regex$1,
5625
+ async: false,
5626
+ expects: `${requirement}`,
5627
+ requirement,
5628
+ message: message2,
5629
+ "~run"(dataset, config2) {
5630
+ if (dataset.typed && !this.requirement.test(dataset.value)) _addIssue(this, "format", dataset, config2);
5631
+ return dataset;
5632
+ }
5633
+ };
5634
+ }
5635
+ /* @__NO_SIDE_EFFECTS__ */
5610
5636
  function getFallback(schema, dataset, config2) {
5611
5637
  return typeof schema.fallback === "function" ? schema.fallback(dataset, config2) : schema.fallback;
5612
5638
  }
@@ -5615,6 +5641,25 @@ function getDefault(schema, dataset, config2) {
5615
5641
  return typeof schema.default === "function" ? schema.default(dataset, config2) : schema.default;
5616
5642
  }
5617
5643
  /* @__NO_SIDE_EFFECTS__ */
5644
+ function boolean(message2) {
5645
+ return {
5646
+ kind: "schema",
5647
+ type: "boolean",
5648
+ reference: boolean,
5649
+ expects: "boolean",
5650
+ async: false,
5651
+ message: message2,
5652
+ get "~standard"() {
5653
+ return /* @__PURE__ */ _getStandardProps(this);
5654
+ },
5655
+ "~run"(dataset, config2) {
5656
+ if (typeof dataset.value === "boolean") dataset.typed = true;
5657
+ else _addIssue(this, "type", dataset, config2);
5658
+ return dataset;
5659
+ }
5660
+ };
5661
+ }
5662
+ /* @__NO_SIDE_EFFECTS__ */
5618
5663
  function number(message2) {
5619
5664
  return {
5620
5665
  kind: "schema",
@@ -5699,6 +5744,99 @@ function object(entries2, message2) {
5699
5744
  };
5700
5745
  }
5701
5746
  /* @__NO_SIDE_EFFECTS__ */
5747
+ function optional(wrapped, default_) {
5748
+ return {
5749
+ kind: "schema",
5750
+ type: "optional",
5751
+ reference: optional,
5752
+ expects: `(${wrapped.expects} | undefined)`,
5753
+ async: false,
5754
+ wrapped,
5755
+ default: default_,
5756
+ get "~standard"() {
5757
+ return /* @__PURE__ */ _getStandardProps(this);
5758
+ },
5759
+ "~run"(dataset, config2) {
5760
+ if (dataset.value === void 0) {
5761
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config2);
5762
+ if (dataset.value === void 0) {
5763
+ dataset.typed = true;
5764
+ return dataset;
5765
+ }
5766
+ }
5767
+ return this.wrapped["~run"](dataset, config2);
5768
+ }
5769
+ };
5770
+ }
5771
+ /* @__NO_SIDE_EFFECTS__ */
5772
+ function record(key, value2, message2) {
5773
+ return {
5774
+ kind: "schema",
5775
+ type: "record",
5776
+ reference: record,
5777
+ expects: "Object",
5778
+ async: false,
5779
+ key,
5780
+ value: value2,
5781
+ message: message2,
5782
+ get "~standard"() {
5783
+ return /* @__PURE__ */ _getStandardProps(this);
5784
+ },
5785
+ "~run"(dataset, config2) {
5786
+ const input = dataset.value;
5787
+ if (input && typeof input === "object") {
5788
+ dataset.typed = true;
5789
+ dataset.value = {};
5790
+ for (const entryKey in input) if (/* @__PURE__ */ _isValidObjectKey(input, entryKey)) {
5791
+ const entryValue = input[entryKey];
5792
+ const keyDataset = this.key["~run"]({ value: entryKey }, config2);
5793
+ if (keyDataset.issues) {
5794
+ const pathItem = {
5795
+ type: "object",
5796
+ origin: "key",
5797
+ input,
5798
+ key: entryKey,
5799
+ value: entryValue
5800
+ };
5801
+ for (const issue of keyDataset.issues) {
5802
+ issue.path = [pathItem];
5803
+ dataset.issues?.push(issue);
5804
+ }
5805
+ if (!dataset.issues) dataset.issues = keyDataset.issues;
5806
+ if (config2.abortEarly) {
5807
+ dataset.typed = false;
5808
+ break;
5809
+ }
5810
+ }
5811
+ const valueDataset = this.value["~run"]({ value: entryValue }, config2);
5812
+ if (valueDataset.issues) {
5813
+ const pathItem = {
5814
+ type: "object",
5815
+ origin: "value",
5816
+ input,
5817
+ key: entryKey,
5818
+ value: entryValue
5819
+ };
5820
+ for (const issue of valueDataset.issues) {
5821
+ if (issue.path) issue.path.unshift(pathItem);
5822
+ else issue.path = [pathItem];
5823
+ dataset.issues?.push(issue);
5824
+ }
5825
+ if (!dataset.issues) dataset.issues = valueDataset.issues;
5826
+ if (config2.abortEarly) {
5827
+ dataset.typed = false;
5828
+ break;
5829
+ }
5830
+ }
5831
+ if (!keyDataset.typed || !valueDataset.typed) dataset.typed = false;
5832
+ if (keyDataset.typed) dataset.value[keyDataset.value] = valueDataset.value;
5833
+ }
5834
+ } else _addIssue(this, "type", dataset, config2);
5835
+ return dataset;
5836
+ }
5837
+ };
5838
+ }
5839
+ /* @__NO_SIDE_EFFECTS__ */
5702
5840
  function string(message2) {
5703
5841
  return {
5704
5842
  kind: "schema",
@@ -5718,6 +5856,72 @@ function string(message2) {
5718
5856
  };
5719
5857
  }
5720
5858
  /* @__NO_SIDE_EFFECTS__ */
5859
+ function _subIssues(datasets) {
5860
+ let issues;
5861
+ if (datasets) for (const dataset of datasets) if (issues) issues.push(...dataset.issues);
5862
+ else issues = dataset.issues;
5863
+ return issues;
5864
+ }
5865
+ /* @__NO_SIDE_EFFECTS__ */
5866
+ function union(options, message2) {
5867
+ return {
5868
+ kind: "schema",
5869
+ type: "union",
5870
+ reference: union,
5871
+ expects: /* @__PURE__ */ _joinExpects(options.map((option) => option.expects), "|"),
5872
+ async: false,
5873
+ options,
5874
+ message: message2,
5875
+ get "~standard"() {
5876
+ return /* @__PURE__ */ _getStandardProps(this);
5877
+ },
5878
+ "~run"(dataset, config2) {
5879
+ let validDataset;
5880
+ let typedDatasets;
5881
+ let untypedDatasets;
5882
+ for (const schema of this.options) {
5883
+ const optionDataset = schema["~run"]({ value: dataset.value }, config2);
5884
+ if (optionDataset.typed) if (optionDataset.issues) if (typedDatasets) typedDatasets.push(optionDataset);
5885
+ else typedDatasets = [optionDataset];
5886
+ else {
5887
+ validDataset = optionDataset;
5888
+ break;
5889
+ }
5890
+ else if (untypedDatasets) untypedDatasets.push(optionDataset);
5891
+ else untypedDatasets = [optionDataset];
5892
+ }
5893
+ if (validDataset) return validDataset;
5894
+ if (typedDatasets) {
5895
+ if (typedDatasets.length === 1) return typedDatasets[0];
5896
+ _addIssue(this, "type", dataset, config2, { issues: /* @__PURE__ */ _subIssues(typedDatasets) });
5897
+ dataset.typed = true;
5898
+ } else if (untypedDatasets?.length === 1) return untypedDatasets[0];
5899
+ else _addIssue(this, "type", dataset, config2, { issues: /* @__PURE__ */ _subIssues(untypedDatasets) });
5900
+ return dataset;
5901
+ }
5902
+ };
5903
+ }
5904
+ /* @__NO_SIDE_EFFECTS__ */
5905
+ function pipe(...pipe2) {
5906
+ return {
5907
+ ...pipe2[0],
5908
+ pipe: pipe2,
5909
+ get "~standard"() {
5910
+ return /* @__PURE__ */ _getStandardProps(this);
5911
+ },
5912
+ "~run"(dataset, config2) {
5913
+ for (const item of pipe2) if (item.kind !== "metadata") {
5914
+ if (dataset.issues && (item.kind === "schema" || item.kind === "transformation")) {
5915
+ dataset.typed = false;
5916
+ break;
5917
+ }
5918
+ if (!dataset.issues || !config2.abortEarly && !config2.abortPipeEarly) dataset = item["~run"](dataset, config2);
5919
+ }
5920
+ return dataset;
5921
+ }
5922
+ };
5923
+ }
5924
+ /* @__NO_SIDE_EFFECTS__ */
5721
5925
  function safeParse(schema, input, config2) {
5722
5926
  const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig(config2));
5723
5927
  return {
@@ -5730,6 +5934,7 @@ function safeParse(schema, input, config2) {
5730
5934
 
5731
5935
  //#endregion
5732
5936
  //#region data-loader.ts
5937
+ const getDefaultClaudePath = () => path.join(homedir(), ".claude");
5733
5938
  const UsageDataSchema = object({
5734
5939
  timestamp: string(),
5735
5940
  message: object({ usage: object({
@@ -5760,7 +5965,8 @@ const formatDate = (dateStr) => {
5760
5965
  return `${year}-${month}-${day}`;
5761
5966
  };
5762
5967
  async function loadUsageData(options) {
5763
- const claudeDir = options?.claudePath ? path.join(options.claudePath, "projects") : path.join(homedir(), ".claude", "projects");
5968
+ const claudePath = options?.claudePath ?? getDefaultClaudePath();
5969
+ const claudeDir = path.join(claudePath, "projects");
5764
5970
  const files = await glob(["**/*.jsonl"], {
5765
5971
  cwd: claudeDir,
5766
5972
  absolute: true
@@ -5802,7 +6008,8 @@ async function loadUsageData(options) {
5802
6008
  });
5803
6009
  }
5804
6010
  async function loadSessionData(options) {
5805
- const claudeDir = options?.claudePath ? path.join(options.claudePath, "projects") : path.join(homedir(), ".claude", "projects");
6011
+ const claudePath = options?.claudePath ?? getDefaultClaudePath();
6012
+ const claudeDir = path.join(claudePath, "projects");
5806
6013
  const files = await glob(["**/*.jsonl"], {
5807
6014
  cwd: claudeDir,
5808
6015
  absolute: true
@@ -6816,7 +7023,7 @@ const consola = createConsola();
6816
7023
  //#endregion
6817
7024
  //#region package.json
6818
7025
  var name = "ccusage";
6819
- var version = "0.1.9";
7026
+ var version = "0.1.11";
6820
7027
  var description = "Usage analysis tool for Claude Code";
6821
7028
 
6822
7029
  //#endregion
@@ -6824,6 +7031,71 @@ var description = "Usage analysis tool for Claude Code";
6824
7031
  const logger = consola.withTag(name);
6825
7032
  const log = (...args) => console.log(...args);
6826
7033
 
7034
+ //#endregion
7035
+ //#region types.ts
7036
+ const ModelSpecSchema = object({
7037
+ max_tokens: optional(union([number(), string()])),
7038
+ max_input_tokens: optional(union([number(), string()])),
7039
+ max_output_tokens: optional(union([number(), string()])),
7040
+ input_cost_per_token: optional(number()),
7041
+ output_cost_per_token: optional(number()),
7042
+ output_cost_per_reasoning_token: optional(number()),
7043
+ litellm_provider: optional(string()),
7044
+ mode: optional(string()),
7045
+ supports_function_calling: optional(boolean()),
7046
+ supports_parallel_function_calling: optional(boolean()),
7047
+ supports_vision: optional(boolean()),
7048
+ supports_audio_input: optional(boolean()),
7049
+ supports_audio_output: optional(boolean()),
7050
+ supports_prompt_caching: optional(boolean()),
7051
+ supports_response_schema: optional(boolean()),
7052
+ supports_system_messages: optional(boolean()),
7053
+ supports_reasoning: optional(boolean()),
7054
+ supports_web_search: optional(boolean()),
7055
+ search_context_cost_per_query: optional(object({
7056
+ search_context_size_low: number(),
7057
+ search_context_size_medium: number(),
7058
+ search_context_size_high: number()
7059
+ })),
7060
+ deprecation_date: optional(string())
7061
+ });
7062
+ const LiteLLMModelPricesSchema = record(string(), ModelSpecSchema);
7063
+ const dateSchema = pipe(string(), regex$1(/^\d{8}$/, "Date must be in YYYYMMDD format"));
7064
+
7065
+ //#endregion
7066
+ //#region shared-args.ts
7067
+ const parseDateArg = (value) => {
7068
+ const result = safeParse(dateSchema, value);
7069
+ if (!result.success) throw new TypeError(result.issues[0].message);
7070
+ return result.output;
7071
+ };
7072
+ const sharedArgs = {
7073
+ since: {
7074
+ type: "custom",
7075
+ short: "s",
7076
+ description: "Filter from date (YYYYMMDD format)",
7077
+ parse: parseDateArg
7078
+ },
7079
+ until: {
7080
+ type: "custom",
7081
+ short: "u",
7082
+ description: "Filter until date (YYYYMMDD format)",
7083
+ parse: parseDateArg
7084
+ },
7085
+ path: {
7086
+ type: "string",
7087
+ short: "p",
7088
+ description: "Custom path to Claude data directory",
7089
+ default: getDefaultClaudePath()
7090
+ },
7091
+ json: {
7092
+ type: "boolean",
7093
+ short: "j",
7094
+ description: "Output in JSON format",
7095
+ default: false
7096
+ }
7097
+ };
7098
+
6827
7099
  //#endregion
6828
7100
  //#region utils.ts
6829
7101
  const formatNumber = (num) => {
@@ -6840,28 +7112,7 @@ var import_picocolors$1 = __toESM(require_picocolors(), 1);
6840
7112
  const dailyCommand = define({
6841
7113
  name: "daily",
6842
7114
  description: "Show usage report grouped by date",
6843
- args: {
6844
- since: {
6845
- type: "string",
6846
- short: "s",
6847
- description: "Filter from date (YYYYMMDD format)"
6848
- },
6849
- until: {
6850
- type: "string",
6851
- short: "u",
6852
- description: "Filter until date (YYYYMMDD format)"
6853
- },
6854
- path: {
6855
- type: "string",
6856
- short: "p",
6857
- description: "Custom path to Claude data directory (default: ~/.claude)"
6858
- },
6859
- json: {
6860
- type: "boolean",
6861
- short: "j",
6862
- description: "Output in JSON format"
6863
- }
6864
- },
7115
+ args: sharedArgs,
6865
7116
  async run(ctx) {
6866
7117
  const options = {
6867
7118
  since: ctx.values.since,
@@ -6952,28 +7203,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
6952
7203
  const sessionCommand = define({
6953
7204
  name: "session",
6954
7205
  description: "Show usage report grouped by conversation session",
6955
- args: {
6956
- since: {
6957
- type: "string",
6958
- short: "s",
6959
- description: "Filter from date (YYYYMMDD format)"
6960
- },
6961
- until: {
6962
- type: "string",
6963
- short: "u",
6964
- description: "Filter until date (YYYYMMDD format)"
6965
- },
6966
- path: {
6967
- type: "string",
6968
- short: "p",
6969
- description: "Custom path to Claude data directory (default: ~/.claude)"
6970
- },
6971
- json: {
6972
- type: "boolean",
6973
- short: "j",
6974
- description: "Output in JSON format"
6975
- }
6976
- },
7206
+ args: sharedArgs,
6977
7207
  async run(ctx) {
6978
7208
  const options = {
6979
7209
  since: ctx.values.since,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccusage",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Usage analysis tool for Claude Code",
5
5
  "homepage": "https://github.com/ryoppippi/ccusage#readme",
6
6
  "bugs": {