From a2899c7f7c106fc9daee78bc41694d6bf005d794 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:16:30 +0100 Subject: [PATCH] Revert "feat!: specify claude dir with `CLAUDE_CONFIG_DIR` and remove `-p`/`--path` option" --- .github/workflows/ci.yaml | 2 - README.md | 30 +++------------ bun.lock | 3 -- package.json | 1 - src/commands/daily.ts | 6 +-- src/commands/mcp.ts | 6 +-- src/commands/monthly.ts | 6 +-- src/commands/session.ts | 6 +-- src/data-loader.test.ts | 74 +------------------------------------ src/data-loader.ts | 18 +-------- src/shared-args.internal.ts | 7 ++++ 11 files changed, 26 insertions(+), 133 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 217659aa..f2ca6271 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,8 +16,6 @@ jobs: - run: bun install --frozen-lockfile - run: bun lint - run: bun typecheck - - name: Create default Claude directory for tests - run: mkdir -p $HOME/.claude - run: bun test npm-publish-dry-run-and-upload-pkg-pr-now: diff --git a/README.md b/README.md index 4edd8429..cf3dfa7f 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,8 @@ ccusage daily # Filter by date range ccusage daily --since 20250525 --until 20250530 -# Set CLAUDE_CONFIG_DIR environment variable for custom data directory -export CLAUDE_CONFIG_DIR="/custom/path/to/.claude" -ccusage daily +# Use custom Claude data directory +ccusage daily --path /custom/path/to/.claude # Output in JSON format ccusage daily --json @@ -165,10 +164,6 @@ ccusage monthly --since 20250101 --until 20250531 # Use custom Claude data directory ccusage monthly --path /custom/path/to/.claude -# Or set CLAUDE_CONFIG_DIR environment variable -export CLAUDE_CONFIG_DIR="/custom/path/to/.claude" -ccusage monthly - # Output in JSON format ccusage monthly --json @@ -196,9 +191,8 @@ ccusage session # Filter sessions by last activity date ccusage session --since 20250525 -# Combine filters with environment variable -export CLAUDE_CONFIG_DIR="/custom/path" -ccusage session --since 20250525 --until 20250530 +# Combine filters +ccusage session --since 20250525 --until 20250530 --path /custom/path # Output in JSON format ccusage session --json @@ -222,6 +216,7 @@ All commands support the following options: - `-s, --since `: Filter from date (YYYYMMDD format) - `-u, --until `: Filter until date (YYYYMMDD format) +- `-p, --path `: Custom path to Claude data directory (default: `~/.claude`) - `-j, --json`: Output results in JSON format instead of table - `-m, --mode `: Cost calculation mode: `auto` (default), `calculate`, or `display` - `-o, --order `: Sort order: `desc` (newest first, default) or `asc` (oldest first). @@ -237,21 +232,6 @@ All commands support the following options: - **`calculate`**: Always calculates costs from token counts using model pricing, ignores any pre-calculated `costUSD` values - **`display`**: Always uses pre-calculated `costUSD` values only, shows $0.00 for entries without pre-calculated costs -#### Environment Variable Support - -The tool supports the `CLAUDE_CONFIG_DIR` environment variable to specify the Claude data directory: - -```bash -# Set the environment variable to use a custom Claude directory -export CLAUDE_CONFIG_DIR="/path/to/custom/claude/directory" -ccusage daily - -# The environment variable determines the Claude data directory -ccusage daily -``` - -The tool will use the path specified in the `CLAUDE_CONFIG_DIR` environment variable, or fall back to the default `~/.claude` directory if not set. - ### MCP (Model Context Protocol) Support Exposes usage data through Model Context Protocol for integration with other tools: diff --git a/bun.lock b/bun.lock index c5818102..d5afd2a5 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,6 @@ "fs-fixture": "^2.8.0", "gunshi": "^0.26.3", "lint-staged": "^16.1.0", - "path-type": "^6.0.0", "picocolors": "^1.1.1", "publint": "^0.3.12", "simple-git-hooks": "^2.13.0", @@ -847,8 +846,6 @@ "path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], - "path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="], - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], diff --git a/package.json b/package.json index dfc54161..4b8e9cfa 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "fs-fixture": "^2.8.0", "gunshi": "^0.26.3", "lint-staged": "^16.1.0", - "path-type": "^6.0.0", "picocolors": "^1.1.1", "publint": "^0.3.12", "simple-git-hooks": "^2.13.0", diff --git a/src/commands/daily.ts b/src/commands/daily.ts index 1ef74abf..cfbf89bc 100644 --- a/src/commands/daily.ts +++ b/src/commands/daily.ts @@ -7,7 +7,7 @@ import { createTotalsObject, getTotalTokens, } from '../calculate-cost.ts'; -import { getDefaultClaudePath, loadDailyUsageData } from '../data-loader.ts'; +import { loadDailyUsageData } from '../data-loader.ts'; import { detectMismatches, printMismatchReport } from '../debug.ts'; import { log, logger } from '../logger.ts'; import { sharedCommandConfig } from '../shared-args.internal.ts'; @@ -25,7 +25,7 @@ export const dailyCommand = define({ const dailyData = await loadDailyUsageData({ since: ctx.values.since, until: ctx.values.until, - claudePath: getDefaultClaudePath(), + claudePath: ctx.values.path, mode: ctx.values.mode, order: ctx.values.order, }); @@ -45,7 +45,7 @@ export const dailyCommand = define({ // Show debug information if requested if (ctx.values.debug && !ctx.values.json) { - const mismatchStats = await detectMismatches(getDefaultClaudePath()); + const mismatchStats = await detectMismatches(ctx.values.path); printMismatchReport(mismatchStats, ctx.values.debugSamples); } diff --git a/src/commands/mcp.ts b/src/commands/mcp.ts index 57502789..85111ac5 100644 --- a/src/commands/mcp.ts +++ b/src/commands/mcp.ts @@ -1,5 +1,4 @@ import { define } from 'gunshi'; -import { getDefaultClaudePath } from '../data-loader.ts'; import { logger } from '../logger.ts'; import { createMcpServer } from '../mcp.ts'; import { sharedArgs } from '../shared-args.internal.ts'; @@ -8,6 +7,7 @@ export const mcpCommand = define({ name: 'mcp', description: 'Show usage report for MCP', args: { + path: sharedArgs.path, mode: sharedArgs.mode, type: { type: 'enum', @@ -23,14 +23,14 @@ export const mcpCommand = define({ }, }, async run(ctx) { - const { type, mode, port } = ctx.values; + const { type, mode, path, port } = ctx.values; // disable info logging if (type === 'stdio') { logger.level = 0; } const server = createMcpServer({ - claudePath: getDefaultClaudePath(), + claudePath: path, mode, }); diff --git a/src/commands/monthly.ts b/src/commands/monthly.ts index 20f19bfe..38919c00 100644 --- a/src/commands/monthly.ts +++ b/src/commands/monthly.ts @@ -7,7 +7,7 @@ import { createTotalsObject, getTotalTokens, } from '../calculate-cost.ts'; -import { getDefaultClaudePath, loadMonthlyUsageData } from '../data-loader.ts'; +import { loadMonthlyUsageData } from '../data-loader.ts'; import { detectMismatches, printMismatchReport } from '../debug.ts'; import { log, logger } from '../logger.ts'; import { sharedCommandConfig } from '../shared-args.internal.ts'; @@ -25,7 +25,7 @@ export const monthlyCommand = define({ const monthlyData = await loadMonthlyUsageData({ since: ctx.values.since, until: ctx.values.until, - claudePath: getDefaultClaudePath(), + claudePath: ctx.values.path, mode: ctx.values.mode, order: ctx.values.order, }); @@ -56,7 +56,7 @@ export const monthlyCommand = define({ // Show debug information if requested if (ctx.values.debug && !ctx.values.json) { - const mismatchStats = await detectMismatches(getDefaultClaudePath()); + const mismatchStats = await detectMismatches(ctx.values.path); printMismatchReport(mismatchStats, ctx.values.debugSamples); } diff --git a/src/commands/session.ts b/src/commands/session.ts index 32c72d12..a40610e2 100644 --- a/src/commands/session.ts +++ b/src/commands/session.ts @@ -7,7 +7,7 @@ import { createTotalsObject, getTotalTokens, } from '../calculate-cost.ts'; -import { getDefaultClaudePath, loadSessionData } from '../data-loader.ts'; +import { loadSessionData } from '../data-loader.ts'; import { detectMismatches, printMismatchReport } from '../debug.ts'; import { log, logger } from '../logger.ts'; import { sharedCommandConfig } from '../shared-args.internal.ts'; @@ -25,7 +25,7 @@ export const sessionCommand = define({ const sessionData = await loadSessionData({ since: ctx.values.since, until: ctx.values.until, - claudePath: getDefaultClaudePath(), + claudePath: ctx.values.path, mode: ctx.values.mode, order: ctx.values.order, }); @@ -45,7 +45,7 @@ export const sessionCommand = define({ // Show debug information if requested if (ctx.values.debug && !ctx.values.json) { - const mismatchStats = await detectMismatches(getDefaultClaudePath()); + const mismatchStats = await detectMismatches(ctx.values.path); printMismatchReport(mismatchStats, ctx.values.debugSamples); } diff --git a/src/data-loader.test.ts b/src/data-loader.test.ts index cf7586f0..a7277d72 100644 --- a/src/data-loader.test.ts +++ b/src/data-loader.test.ts @@ -1,12 +1,8 @@ -import { homedir } from 'node:os'; -import { join } from 'node:path'; -import process from 'node:process'; -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; +import { describe, expect, test } from 'bun:test'; import { createFixture } from 'fs-fixture'; import { calculateCostForEntry, formatDate, - getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, @@ -33,74 +29,6 @@ describe('formatDate', () => { }); }); -describe('getDefaultClaudePath', () => { - const originalEnv = process.env.CLAUDE_CONFIG_DIR; - - beforeEach(() => { - // Clean up env var before each test - delete process.env.CLAUDE_CONFIG_DIR; - }); - - afterEach(() => { - // Restore original environment - if (originalEnv != null) { - process.env.CLAUDE_CONFIG_DIR = originalEnv; - } - else { - delete process.env.CLAUDE_CONFIG_DIR; - } - }); - - test('returns CLAUDE_CONFIG_DIR when environment variable is set', async () => { - await using fixture = await createFixture({ - claude: {}, - }); - process.env.CLAUDE_CONFIG_DIR = fixture.path; - - expect(getDefaultClaudePath()).toBe(fixture.path); - }); - - test('returns default path when CLAUDE_CONFIG_DIR is not set', () => { - // Ensure CLAUDE_CONFIG_DIR is not set - delete process.env.CLAUDE_CONFIG_DIR; - - // Test that it returns the default path (which ends with .claude) - const actualPath = getDefaultClaudePath(); - expect(actualPath).toMatch(/\.claude$/); - expect(actualPath).toContain(homedir()); - }); - - test('returns default path with trimmed CLAUDE_CONFIG_DIR', async () => { - await using fixture = await createFixture({ - claude: {}, - }); - // Test with extra spaces - process.env.CLAUDE_CONFIG_DIR = ` ${fixture.path} `; - - expect(getDefaultClaudePath()).toBe(fixture.path); - }); - - test('throws an error when CLAUDE_CONFIG_DIR is not a directory', async () => { - await using fixture = await createFixture(); - process.env.CLAUDE_CONFIG_DIR = join(fixture.path, 'not-a-directory'); - - expect(() => getDefaultClaudePath()).toThrow(/Claude data directory does not exist/); - }); - - test('throws an error when CLAUDE_CONFIG_DIR does not exist', async () => { - process.env.CLAUDE_CONFIG_DIR = '/nonexistent/path/that/does/not/exist'; - - expect(() => getDefaultClaudePath()).toThrow(/Claude data directory does not exist/); - }); - - test('throws an error when default path does not exist', () => { - // Set to a non-existent path - process.env.CLAUDE_CONFIG_DIR = '/nonexistent/path/.claude'; - - expect(() => getDefaultClaudePath()).toThrow(/Claude data directory does not exist/); - }); -}); - describe('loadDailyUsageData', () => { test('returns empty array when no files found', async () => { await using fixture = await createFixture({ diff --git a/src/data-loader.ts b/src/data-loader.ts index af5bba54..4ef8b758 100644 --- a/src/data-loader.ts +++ b/src/data-loader.ts @@ -2,11 +2,9 @@ import type { CostMode, SortOrder } from './types.internal.ts'; import { readFile } from 'node:fs/promises'; import { homedir } from 'node:os'; import path from 'node:path'; -import process from 'node:process'; import { unreachable } from '@core/errorutil'; import { groupBy } from 'es-toolkit'; // TODO: after node20 is deprecated, switch to native Object.groupBy import { sort } from 'fast-sort'; -import { isDirectorySync } from 'path-type'; import { glob } from 'tinyglobby'; import * as v from 'valibot'; import { logger } from './logger.ts'; @@ -14,22 +12,8 @@ import { PricingFetcher, } from './pricing-fetcher.ts'; -const DEFAULT_CLAUDE_CODE_PATH = path.join(homedir(), '.claude'); - -/** - * Default path for Claude data directory - * Uses environment variable CLAUDE_CONFIG_DIR if set, otherwise defaults to ~/.claude - */ export function getDefaultClaudePath(): string { - const envClaudeCodePath = process.env.CLAUDE_CONFIG_DIR?.trim() ?? DEFAULT_CLAUDE_CODE_PATH; - if (!isDirectorySync(envClaudeCodePath)) { - throw new Error( - ` Claude data directory does not exist: ${envClaudeCodePath}. -Please set CLAUDE_CONFIG_DIR to a valid path, or ensure ${DEFAULT_CLAUDE_CODE_PATH} exists. - `.trim(), - ); - } - return envClaudeCodePath; + return path.join(homedir(), '.claude'); } export const UsageDataSchema = v.object({ diff --git a/src/shared-args.internal.ts b/src/shared-args.internal.ts index 0f382c5c..f5da6e80 100644 --- a/src/shared-args.internal.ts +++ b/src/shared-args.internal.ts @@ -1,6 +1,7 @@ import type { Args } from 'gunshi'; import type { CostMode, SortOrder } from './types.internal.ts'; import * as v from 'valibot'; +import { getDefaultClaudePath } from './data-loader.ts'; import { CostModes, dateSchema, SortOrders } from './types.internal.ts'; function parseDateArg(value: string): string { @@ -24,6 +25,12 @@ export const sharedArgs = { description: 'Filter until date (YYYYMMDD format)', parse: parseDateArg, }, + path: { + type: 'string', + short: 'p', + description: 'Custom path to Claude data directory', + default: getDefaultClaudePath(), + }, json: { type: 'boolean', short: 'j',