feat: add --fetch CLI argument for custom model pricing data sources - #51
feat: add --fetch CLI argument for custom model pricing data sources#51iamwrm wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Summary of Changes
Hello @iamwrm, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request implements the requested feature to allow users to override the default model pricing data source. By introducing a new --fetch CLI argument, users can now point the application to a custom URL or a local file containing pricing information, which is particularly useful for users behind corporate proxies or those who wish to use alternative pricing data. The core logic for fetching/loading pricing data has been updated to handle both URL and file inputs, and this new capability is integrated into the main commands and debug features.
Highlights
- New CLI Argument: I've added a new
--fetch(or-f) command-line argument to allow users to specify a custom source for model pricing data. This source can be either an HTTPS URL or a local file path. - Custom Pricing Source Support: The
PricingFetcherclass has been updated to accept an optional source URL or file path in its constructor. If no source is provided, it defaults to the existing LiteLLM pricing URL, maintaining backward compatibility. - Integration Across Commands: The new
--fetchargument is now passed down through thedaily,monthly, andsessioncommands, as well as the debug functionality (detectMismatches), ensuring that custom pricing sources are used consistently when specified.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configureGemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request introduces the --fetch CLI argument, enabling users to specify custom URLs or local file paths for model pricing data. The changes are well-structured and maintain backward compatibility. Consider adding more robust error handling and input validation to enhance the user experience.
There was a problem hiding this comment.
There was a problem hiding this comment.
There was a problem hiding this comment.
There was a problem hiding this comment.
Pull Request Overview
Adds a --fetch CLI argument to allow custom model pricing sources and wires it through all pricing-related commands and utilities.
- Introduces
--fetchargument for custom URLs or file paths - Extends
PricingFetcherto handle HTTP(S) URLs and local files - Updates all commands (
daily,monthly,session,debug) to pass through the fetch option
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/shared-args.internal.ts | Added new fetch CLI option for pricing data source |
| src/pricing-fetcher.ts | Extended constructor, fetch logic, and logging to use the source |
| src/debug.ts | Updated detectMismatches signature and invocation to use fetch |
| src/data-loader.ts | Passed options?.fetch into PricingFetcher in loader functions |
| src/commands/session.ts | Forwarded fetch to session command |
| src/commands/monthly.ts | Forwarded fetch to monthly command |
| src/commands/daily.ts | Forwarded fetch to daily command |
Comments suppressed due to low confidence (2)
src/shared-args.internal.ts:67
- [nitpick] The option name
fetchshadows the globalfetchAPI and may be confusing; consider renaming topricingSourceor a more descriptive name.
fetch: {
src/shared-args.internal.ts:70
- [nitpick] The help text doesn’t mention the default behavior; consider appending that it falls back to the official LiteLLM pricing URL when not specified.
description: 'Custom URL or local file path for model pricing data (supports HTTPS URLs and local filesystem paths)'
There was a problem hiding this comment.
[nitpick] URL detection only checks for 'https://'; consider using a regex like /^https?:\/\//i to also support 'http://' or other schemes explicitly.
| const isUrl = this.pricingSource.startsWith('https://'); | |
| const isUrl = /^https?:\/\//i.test(this.pricingSource); |
There was a problem hiding this comment.
The thrown error hides the original error details; consider including the original error message or stack (e.g., : ${error.message}) to aid debugging.
commit: |
|
@iamwrm Thank you for your contribution The implementation looks good to me! However, I need to add
|
WalkthroughA new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI
participant DataLoader
participant PricingFetcher
User->>CLI: Run ccusage command with --fetch <source>
CLI->>DataLoader: load*UsageData({ fetch: <source>, ... })
DataLoader->>PricingFetcher: new PricingFetcher(<source>)
PricingFetcher->>PricingFetcher: ensurePricingLoaded()
alt Source is HTTPS URL
PricingFetcher->>PricingFetcher: Fetch pricing from URL
else Source is local file
PricingFetcher->>PricingFetcher: Read pricing from file
end
PricingFetcher-->>DataLoader: Return pricing data
DataLoader-->>CLI: Continue cost calculation
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (7)
src/shared-args.internal.ts (1)
73-77: 🛠️ Refactor suggestionValidate
--fetchargument instead of accepting any string
fetchis currently treated as a raw string. An invalid URL (e.g.htp://foo) or a non-existent file will only be caught much later byPricingFetcher, leading to a runtime crash that is hard to trace from the CLI.Consider parsing & validating the flag early, similar to the existing
since/untilvalidation:+import { existsSync } from 'node:fs'; + +function parseFetchArg(value: string): string { + // allow HTTPS URL + if (value.startsWith('https://')) { + return value; + } + + // allow existing local file + if (existsSync(value)) { + return value; + } + + throw new TypeError('The --fetch value must be an https:// URL or an existing file path'); +} … fetch: { - type: 'string', + type: 'custom', short: 'f', - description: 'Custom URL or local file path for model pricing data (supports HTTPS URLs and local filesystem paths)', + description: 'Custom URL or local file path for model pricing data (supports HTTPS URLs and local filesystem paths)', + parse: parseFetchArg, },This keeps failure local, produces an actionable error message, and removes several ESLint “unsafe-argument” warnings downstream.
src/commands/daily.ts (1)
31-32: Same type-safety concern as inmonthly.tsThe
fetchplumb-through looks fine, but strongly typingctxwill silence theno-unsafe-*ESLint errors reported here.🧰 Tools
🪛 ESLint
[error] 31-31: Unsafe assignment of an
anyvalue.(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
src/commands/session.ts (1)
31-32: Same type-safety concern as inmonthly.tsApply the same
ctxgeneric to eliminate the ESLint warnings.🧰 Tools
🪛 ESLint
[error] 31-31: Unsafe assignment of an
anyvalue.(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
src/debug.ts (1)
53-65: WrapPricingFetcherconstruction intry … catchIf the custom pricing source is unreachable or malformed,
new PricingFetcher(fetch)will throw and abort the entire command. Catching the error lets the CLI degrade gracefully (e.g. skip mismatch analysis or exit with a clear message).-// Use PricingFetcher with using statement for automatic cleanup -using fetcher = new PricingFetcher(fetch); +let fetcher: PricingFetcher | undefined; +try { + // Using-statement for automatic cleanup + using _fetcher = new PricingFetcher(fetch); + fetcher = _fetcher; +} catch (err) { + logger.error((err as Error).message); + return { + totalEntries: 0, + entriesWithBoth: 0, + matches: 0, + mismatches: 0, + discrepancies: [], + modelStats: new Map(), + versionStats: new Map(), + }; +}(NB: adapt return object to reuse the existing
statsconstant if preferred.)🧰 Tools
🪛 ESLint
[error] 57-57: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 57-57: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 57-57: Unsafe member access .join on an
errortyped value.(ts/no-unsafe-member-access)
[error] 57-57: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 58-61: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 58-58: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 59-59: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
src/data-loader.ts (2)
457-458: Same risk inloadSessionData
PricingFetcheris created without validation here as well, inheriting the same crash-on-error behaviour noted above.
Please address in both locations to keep CLI execution resilient.
283-284:⚠️ Potential issueStill no error-shielding around
PricingFetcherconstructionPrevious feedback (see bot comments on earlier commits) suggested wrapping the instantiation in a
try / catchso that an invalid URL / unreadable file does not abort the entire data-loading pipeline.
The constructor is still called directly; a single bad source will crash the whole command.src/pricing-fetcher.ts (1)
74-77: 🛠️ Refactor suggestionPropagate the underlying error message for easier debugging
Wrapping the original error obscures the root cause for callers.
Include it (or use thecausefield) so users immediately know why the fetch failed.- throw new Error(`Could not fetch model pricing data from ${this.pricingSource}`); + throw new Error( + `Could not fetch model pricing data from ${this.pricingSource}: ${(error as Error).message}`, + { cause: error as Error } + );🧰 Tools
🪛 ESLint
[error] 75-75: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 75-75: Unsafe member access .error on an
errortyped value.(ts/no-unsafe-member-access)
🧹 Nitpick comments (4)
src/commands/monthly.ts (1)
31-32: Type-safety:ctx.values.fetchisany– propagate the genericThe new field is wired correctly, but the repeated
anyescapes (ctx.values) make ESLint complain and may hide future bugs. Export theArgstype generated bygunshi(or declare an interface) and type thectxparameter:export const monthlyCommand = define<Args>({ … async run(ctx) { // ctx: CommandContext<Args>This removes six
no-unsafe-*warnings in this file alone.🧰 Tools
🪛 ESLint
[error] 31-31: Unsafe assignment of an
anyvalue.(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
src/data-loader.ts (1)
259-259: Naming collides with the globalfetchAPIThe new option key is called
fetch. Even though it’s only accessed asoptions.fetch, the identifier is identical to the global browser / Nodefetchand may cause confusion for maintainers and IDE auto-completion.
Consider a more explicit name such aspricingSourceorpricingUrl.src/pricing-fetcher.ts (1)
40-54: URL detection is too restrictive & case-sensitive
startsWith('https://')ignores:
• uppercaseHTTPS://
• plainhttp://(PR description says “URLs”, not only HTTPS)
• other valid schemes (e.g.file://)A small regex is safer and more explicit:
- const isUrl = this.pricingSource.startsWith('https://'); + const isUrl = /^https?:\/\//i.test(this.pricingSource);🧰 Tools
🪛 ESLint
[error] 43-43: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 43-43: Unsafe member access .warn on an
errortyped value.(ts/no-unsafe-member-access)
[error] 44-44: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 44-44: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 45-45: Unexpected any value in conditional. An explicit comparison or type conversion is required.
(ts/strict-boolean-expressions)
[error] 45-45: Unsafe member access .ok on an
errortyped value.(ts/no-unsafe-member-access)
[error] 46-46: Unsafe member access .statusText on an
errortyped value.(ts/no-unsafe-member-access)
[error] 48-48: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 48-48: Unsafe member access .json on an
errortyped value.(ts/no-unsafe-member-access)
[error] 51-51: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 51-51: Unsafe member access .warn on an
errortyped value.(ts/no-unsafe-member-access)
[error] 52-52: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 52-52: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 53-53: Unsafe argument of type error typed assigned to a parameter of type
string.(ts/no-unsafe-argument)
README.md (1)
224-225: Mixed tabs detected – breaks markdownlint rulesLines 247-252 (JSON snippet) still contain hard tab characters, triggering
MD010.
Run a quickexpand -t4(or your editor’s “convert indentation”) over the new section to keep whitespace consistent.🧰 Tools
🪛 LanguageTool
[uncategorized] ~224-~224: Loose punctuation mark.
Context: ..., Sonnet, etc.) --f, --fetch <source>: Custom URL or local file path for model...(UNLIKELY_OPENING_PUNCTUATION)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📥 Commits
Reviewing files that changed from the base of the PR and between 4264d35 and 6578ef55d56050d693b7708174100f18fd29a422.
📒 Files selected for processing (8)
README.md(2 hunks)src/commands/daily.ts(2 hunks)src/commands/monthly.ts(2 hunks)src/commands/session.ts(2 hunks)src/data-loader.ts(3 hunks)src/debug.ts(2 hunks)src/pricing-fetcher.ts(4 hunks)src/shared-args.internal.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/commands/monthly.ts (1)
src/debug.ts (1)
detectMismatches(53-183)
src/commands/daily.ts (1)
src/debug.ts (1)
detectMismatches(53-183)
src/data-loader.ts (1)
src/pricing-fetcher.ts (1)
PricingFetcher(17-179)
src/commands/session.ts (1)
src/debug.ts (1)
detectMismatches(53-183)
src/pricing-fetcher.ts (1)
src/logger.ts (1)
logger(5-5)
src/debug.ts (1)
src/pricing-fetcher.ts (1)
PricingFetcher(17-179)
🪛 ESLint
src/commands/monthly.ts
[error] 31-31: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 60-60: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 60-60: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 60-60: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 60-60: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
src/commands/daily.ts
[error] 31-31: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 49-49: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 49-49: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 49-49: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 49-49: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
src/commands/session.ts
[error] 31-31: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 49-49: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 49-49: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 49-49: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 49-49: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
src/pricing-fetcher.ts
[error] 43-43: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 43-43: Unsafe member access .warn on an error typed value.
(ts/no-unsafe-member-access)
[error] 44-44: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 44-44: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 45-45: Unexpected any value in conditional. An explicit comparison or type conversion is required.
(ts/strict-boolean-expressions)
[error] 45-45: Unsafe member access .ok on an error typed value.
(ts/no-unsafe-member-access)
[error] 46-46: Unsafe member access .statusText on an error typed value.
(ts/no-unsafe-member-access)
[error] 48-48: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 48-48: Unsafe member access .json on an error typed value.
(ts/no-unsafe-member-access)
[error] 51-51: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 51-51: Unsafe member access .warn on an error typed value.
(ts/no-unsafe-member-access)
[error] 52-52: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 52-52: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 53-53: Unsafe argument of type error typed assigned to a parameter of type string.
(ts/no-unsafe-argument)
[error] 71-71: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 71-71: Unsafe member access .info on an error typed value.
(ts/no-unsafe-member-access)
[error] 75-75: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 75-75: Unsafe member access .error on an error typed value.
(ts/no-unsafe-member-access)
🪛 LanguageTool
README.md
[uncategorized] ~224-~224: Loose punctuation mark.
Context: ..., Sonnet, etc.) - -f, --fetch <source>: Custom URL or local file path for model...
(UNLIKELY_OPENING_PUNCTUATION)
🪛 markdownlint-cli2 (0.17.2)
README.md
247-247: Hard tabs
Column: 1
(MD010, no-hard-tabs)
248-248: Hard tabs
Column: 1
(MD010, no-hard-tabs)
249-249: Hard tabs
Column: 1
(MD010, no-hard-tabs)
250-250: Hard tabs
Column: 1
(MD010, no-hard-tabs)
251-251: Hard tabs
Column: 1
(MD010, no-hard-tabs)
252-252: Hard tabs
Column: 1
(MD010, no-hard-tabs)
Add support for the --fetch flag that allows users to specify custom URLs or local file paths for model pricing data. This addresses issue #12 by enabling users behind corporate proxies to: - Use custom HTTPS URLs for pricing data - Load pricing data from local filesystem paths - Bypass network restrictions when accessing pricing information Changes: - Add --fetch argument to shared CLI arguments - Update PricingFetcher to accept custom data sources - Support both HTTPS URLs and local file paths - Update all commands (daily, monthly, session) to pass fetch parameter - Update debug functionality to use custom pricing sources - Maintain backward compatibility with default LiteLLM pricing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
|
Hi, I have updated accordingly, Sorry for the force push, just to want to make the history cleaner. Have to change some logic interacting with the fetcher, so it throws error and exit when the URL is invalid |
There was a problem hiding this comment.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
src/data-loader.ts (1)
256-263: 🛠️ Refactor suggestion
LoadOptionsnow contains bothfetchandpricingFetcher– risk of contradictory inputSupplying both
fetchand an existingpricingFetcherinstance is ambiguous:
pricingFetchermay already have been initialised with a different source, yet the downstream
code-path (new PricingFetcher(options?.fetch)) assumes the passed in value matchesfetch.
Document which parameter wins, or (preferably) throw if both are provided.
♻️ Duplicate comments (3)
src/commands/session.ts (1)
26-28: Same try/catch safeguard as indaily.ts– instantiatingPricingFetcherwithout validation can crash the whole command on a malformed--fetchsource. Please apply the guarded construction pattern here as well.🧰 Tools
🪛 ESLint
[error] 27-27: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
[error] 27-27: Unsafe argument of type
anyassigned to a parameter of typestring | undefined.(ts/no-unsafe-argument)
[error] 27-27: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
src/commands/monthly.ts (1)
26-28: Same try/catch safeguard as indaily.ts– wrapPricingFetcherinstantiation to surfacePricingSourceErrorgracefully.🧰 Tools
🪛 ESLint
[error] 27-27: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
[error] 27-27: Unsafe argument of type
anyassigned to a parameter of typestring | undefined.(ts/no-unsafe-argument)
[error] 27-27: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
src/data-loader.ts (1)
485-500: Duplicate dispose issue inloadSessionDataSame lifetime problem as noted for
loadDailyUsageData.
🧹 Nitpick comments (6)
src/commands/daily.ts (1)
35-37: Skip redundant parameters indisplaymodeWhen
mode === 'display'neitherfetchnorpricingFetcherare consulted further down the stack, yet they’re still forwarded. Passingundefinedwould avoid a needless branch (and an accidental network hit if someone changes downstream logic).- fetch: ctx.values.fetch, - pricingFetcher, + fetch: ctx.values.mode === 'display' ? undefined : ctx.values.fetch, + pricingFetcher,🧰 Tools
🪛 ESLint
[error] 35-35: Unsafe assignment of an
anyvalue.(ts/no-unsafe-assignment)
[error] 35-35: Unsafe member access .values on an
anyvalue.(ts/no-unsafe-member-access)
README.md (2)
224-251: Replace hard tabs with spaces to satisfy Markdown lint (MD010)Lines 247-252 are indented with tab characters, breaking consistent rendering and failing the linter.
- "model-name": { - "input_cost_per_token": 0.00001, - "output_cost_per_token": 0.00003, - "cache_creation_input_token_cost": 0.000015, - "cache_read_input_token_cost": 0.0000015 - } + "model-name": { + "input_cost_per_token": 0.00001, + "output_cost_per_token": 0.00003, + "cache_creation_input_token_cost": 0.000015, + "cache_read_input_token_cost": 0.0000015 + }🧰 Tools
🪛 LanguageTool
[uncategorized] ~224-~224: Loose punctuation mark.
Context: ..., Sonnet, etc.) --f, --fetch <source>: Custom URL or local file path for model...(UNLIKELY_OPENING_PUNCTUATION)
🪛 markdownlint-cli2 (0.17.2)
247-247: Hard tabs
Column: 1(MD010, no-hard-tabs)
248-248: Hard tabs
Column: 1(MD010, no-hard-tabs)
249-249: Hard tabs
Column: 1(MD010, no-hard-tabs)
250-250: Hard tabs
Column: 1(MD010, no-hard-tabs)
251-251: Hard tabs
Column: 1(MD010, no-hard-tabs)
236-244: Minor punctuation nit – remove stray em-dash before bulletThe list item starts with “etc.) -
-f, --fetch …” which produces a dangling hyphen. Drop the extra “-” for cleaner Markdown rendering.src/data-loader.ts (1)
337-353: Redundant await inside hot path
calculateCostForEntryonly does async work on the first call (pricing load).
Awaiting it for every record forces the loop to run sequentially and can be a bottleneck for
large JSONL files.- cost = await calculateCostForEntry(data, mode, fetcher); + costPromises.push(calculateCostForEntry(data, mode, fetcher)); … - for (const p of costPromises) { … }Even a simple
Promise.allafter the inner loop roughly halves the runtime on large data
sets (~80 k lines). Optional, but easy win.🧰 Tools
🪛 ESLint
[error] 352-352: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 352-352: Unsafe member access .costUSD on an
errortyped value.(ts/no-unsafe-member-access)
src/pricing-fetcher.ts (1)
79-82: Log level nit – fetching is normal information, not a warning
logger.warnsuggests something undesirable is happening.
Considerlogger.infofor normal control-flow messages and reservewarnfor degradations.🧰 Tools
🪛 ESLint
[error] 80-80: Unsafe call of a(n)
errortype typed value.(ts/no-unsafe-call)
[error] 80-80: Unsafe member access .info on an
errortyped value.(ts/no-unsafe-member-access)
src/fetch-cli.test.ts (1)
4-4: Mixingbun:testand Jest helpers
bun:testexports a Jest-like API, but importingjestfrom it is unusual and can confuse
editors & type-checkers. Prefer:import { describe, it, expect, beforeEach, afterEach, vi } from 'bun:test'; const mockFetch = vi.fn();(Bun aliases
vi/viteststyle mocks).This keeps the codebase consistent with Bun’s tooling.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📥 Commits
Reviewing files that changed from the base of the PR and between 6578ef55d56050d693b7708174100f18fd29a422 and bb25424.
📒 Files selected for processing (9)
README.md(2 hunks)src/commands/daily.ts(3 hunks)src/commands/monthly.ts(3 hunks)src/commands/session.ts(3 hunks)src/data-loader.ts(6 hunks)src/debug.ts(1 hunks)src/fetch-cli.test.ts(1 hunks)src/pricing-fetcher.ts(4 hunks)src/shared-args.internal.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/shared-args.internal.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/data-loader.ts (1)
src/pricing-fetcher.ts (2)
PricingFetcher(24-196)PricingSourceError(14-19)
src/debug.ts (1)
src/pricing-fetcher.ts (1)
PricingFetcher(24-196)
src/pricing-fetcher.ts (1)
src/logger.ts (1)
logger(5-5)
🪛 ESLint
src/fetch-cli.test.ts
[error] 9-9: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 10-10: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 11-11: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 15-24: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 15-16: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 15-15: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 15-15: Unsafe member access .fn on an error typed value.
(ts/no-unsafe-member-access)
[error] 16-16: Unsafe member access .mockResolvedValueOnce on an error typed value.
(ts/no-unsafe-member-access)
[error] 26-26: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 36-36: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 36-36: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 36-36: Unsafe member access .toHaveBeenCalledTimes on an error typed value.
(ts/no-unsafe-member-access)
[error] 40-40: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 40-40: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 40-40: Unsafe member access .toHaveBeenCalledTimes on an error typed value.
(ts/no-unsafe-member-access)
[error] 44-44: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 44-44: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 44-44: Unsafe member access .toHaveBeenCalledTimes on an error typed value.
(ts/no-unsafe-member-access)
[error] 47-47: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 51-51: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 52-61: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 52-53: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 52-52: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 52-52: Unsafe member access .fn on an error typed value.
(ts/no-unsafe-member-access)
[error] 53-53: Unsafe member access .mockResolvedValue on an error typed value.
(ts/no-unsafe-member-access)
[error] 63-63: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 71-71: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 71-71: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 71-71: Unsafe member access .toHaveBeenCalledTimes on an error typed value.
(ts/no-unsafe-member-access)
[error] 76-76: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 76-76: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 76-76: Unsafe member access .toHaveBeenCalledTimes on an error typed value.
(ts/no-unsafe-member-access)
[error] 79-79: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 84-84: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 88-88: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 89-89: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 89-89: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 90-90: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 90-90: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 93-93: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 95-95: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 102-102: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 110-110: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 112-126: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 112-112: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 129-129: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 129-129: Unsafe member access .path on an error typed value.
(ts/no-unsafe-member-access)
[error] 134-134: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 134-134: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 134-134: Unsafe member access .toHaveLength on an error typed value.
(ts/no-unsafe-member-access)
[error] 137-137: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 137-137: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 137-137: Unsafe member access .totalCost on an error typed value.
(ts/no-unsafe-member-access)
[error] 137-137: Unsafe member access .toBeCloseTo on an error typed value.
(ts/no-unsafe-member-access)
[error] 140-140: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 148-148: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 150-164: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 150-150: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 167-167: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 167-167: Unsafe member access .path on an error typed value.
(ts/no-unsafe-member-access)
[error] 172-172: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 172-172: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 172-172: Unsafe member access .toHaveLength on an error typed value.
(ts/no-unsafe-member-access)
[error] 175-175: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 175-175: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 175-175: Unsafe member access .totalCost on an error typed value.
(ts/no-unsafe-member-access)
[error] 175-175: Unsafe member access .toBeCloseTo on an error typed value.
(ts/no-unsafe-member-access)
[error] 176-176: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 176-176: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 176-176: Unsafe member access .sessionId on an error typed value.
(ts/no-unsafe-member-access)
[error] 176-176: Unsafe member access .toBe on an error typed value.
(ts/no-unsafe-member-access)
[error] 179-179: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 187-187: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 189-204: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 189-189: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 207-207: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 207-207: Unsafe member access .path on an error typed value.
(ts/no-unsafe-member-access)
[error] 212-212: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 212-212: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 212-212: Unsafe member access .toHaveLength on an error typed value.
(ts/no-unsafe-member-access)
[error] 215-215: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 215-215: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 215-215: Unsafe member access .totalCost on an error typed value.
(ts/no-unsafe-member-access)
[error] 215-215: Unsafe member access .toBe on an error typed value.
(ts/no-unsafe-member-access)
[error] 218-218: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 219-219: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 223-223: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 223-223: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 223-223: Unsafe member access .rejects on an error typed value.
(ts/no-unsafe-member-access)
[error] 226-226: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 227-227: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 227-227: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 230-230: Unsafe argument of type error typed assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 231-231: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 231-231: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 231-231: Unsafe member access .rejects on an error typed value.
(ts/no-unsafe-member-access)
[error] 235-235: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 236-236: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 241-241: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 241-241: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 241-241: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 241-241: Unsafe member access .fn on an error typed value.
(ts/no-unsafe-member-access)
[error] 241-241: Unsafe member access .mockRejectedValue on an error typed value.
(ts/no-unsafe-member-access)
[error] 242-242: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 248-248: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 248-248: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 248-248: Unsafe member access .rejects on an error typed value.
(ts/no-unsafe-member-access)
[error] 249-249: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 249-249: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 249-249: Unsafe member access .toHaveBeenCalledWith on an error typed value.
(ts/no-unsafe-member-access)
[error] 252-252: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 256-256: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 262-262: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 262-262: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 262-262: Unsafe member access .fn on an error typed value.
(ts/no-unsafe-member-access)
[error] 263-263: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 269-269: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 269-269: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 269-269: Unsafe member access .rejects on an error typed value.
(ts/no-unsafe-member-access)
[error] 271-271: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 271-271: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 271-271: Unsafe member access .not on an error typed value.
(ts/no-unsafe-member-access)
[error] 274-274: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 279-279: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 283-283: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 284-284: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 284-284: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 285-285: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 285-285: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 288-288: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 290-290: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 297-297: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 305-305: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 307-322: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 307-307: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 325-325: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 325-325: Unsafe member access .path on an error typed value.
(ts/no-unsafe-member-access)
[error] 330-330: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 330-330: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 330-330: Unsafe member access .toHaveLength on an error typed value.
(ts/no-unsafe-member-access)
[error] 333-333: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 333-333: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 333-333: Unsafe member access .totalCost on an error typed value.
(ts/no-unsafe-member-access)
[error] 333-333: Unsafe member access .toBe on an error typed value.
(ts/no-unsafe-member-access)
[error] 336-336: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 344-344: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 346-361: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 346-346: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 364-364: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 364-364: Unsafe member access .path on an error typed value.
(ts/no-unsafe-member-access)
[error] 369-369: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 369-369: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 369-369: Unsafe member access .toHaveLength on an error typed value.
(ts/no-unsafe-member-access)
[error] 372-372: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 372-372: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 372-372: Unsafe member access .totalCost on an error typed value.
(ts/no-unsafe-member-access)
[error] 372-372: Unsafe member access .toBeCloseTo on an error typed value.
(ts/no-unsafe-member-access)
src/commands/daily.ts
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 27-27: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 30-30: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 30-30: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 31-31: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 32-32: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 32-32: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 33-33: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 34-34: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 35-35: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 35-35: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 54-54: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 54-54: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 54-54: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 54-54: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
src/commands/monthly.ts
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 27-27: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 30-30: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 30-30: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 31-31: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 32-32: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 32-32: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 33-33: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 34-34: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 35-35: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 35-35: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 65-65: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 65-65: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 65-65: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 65-65: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
src/commands/session.ts
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 27-27: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 30-30: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 30-30: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 31-31: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 31-31: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 32-32: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 32-32: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 33-33: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 34-34: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 35-35: Unsafe assignment of an any value.
(ts/no-unsafe-assignment)
[error] 35-35: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 54-54: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 54-54: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 54-54: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 54-54: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
src/data-loader.ts
[error] 352-352: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 352-352: Unsafe member access .costUSD on an error typed value.
(ts/no-unsafe-member-access)
[error] 569-569: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 569-569: Unsafe member access .costUSD on an error typed value.
(ts/no-unsafe-member-access)
src/debug.ts
[error] 58-58: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 58-58: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 58-58: Unsafe member access .join on an error typed value.
(ts/no-unsafe-member-access)
[error] 58-58: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 59-62: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 59-59: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 60-60: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
src/pricing-fetcher.ts
[error] 52-52: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 52-52: Unsafe member access .warn on an error typed value.
(ts/no-unsafe-member-access)
[error] 53-53: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 53-53: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 54-54: Unexpected any value in conditional. An explicit comparison or type conversion is required.
(ts/strict-boolean-expressions)
[error] 54-54: Unsafe member access .ok on an error typed value.
(ts/no-unsafe-member-access)
[error] 55-55: Unsafe member access .statusText on an error typed value.
(ts/no-unsafe-member-access)
[error] 57-57: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 57-57: Unsafe member access .json on an error typed value.
(ts/no-unsafe-member-access)
[error] 60-60: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 60-60: Unsafe member access .warn on an error typed value.
(ts/no-unsafe-member-access)
[error] 61-61: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 61-61: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 62-62: Unsafe argument of type error typed assigned to a parameter of type string.
(ts/no-unsafe-argument)
[error] 80-80: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 80-80: Unsafe member access .info on an error typed value.
(ts/no-unsafe-member-access)
[error] 84-84: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 84-84: Unsafe member access .error on an error typed value.
(ts/no-unsafe-member-access)
🪛 LanguageTool
README.md
[uncategorized] ~224-~224: Loose punctuation mark.
Context: ..., Sonnet, etc.) - -f, --fetch <source>: Custom URL or local file path for model...
(UNLIKELY_OPENING_PUNCTUATION)
🪛 markdownlint-cli2 (0.17.2)
README.md
247-247: Hard tabs
Column: 1
(MD010, no-hard-tabs)
248-248: Hard tabs
Column: 1
(MD010, no-hard-tabs)
249-249: Hard tabs
Column: 1
(MD010, no-hard-tabs)
250-250: Hard tabs
Column: 1
(MD010, no-hard-tabs)
251-251: Hard tabs
Column: 1
(MD010, no-hard-tabs)
252-252: Hard tabs
Column: 1
(MD010, no-hard-tabs)
| // Create a single PricingFetcher instance to reuse across functions | ||
| using pricingFetcher = ctx.values.mode === 'display' ? undefined : new PricingFetcher(ctx.values.fetch); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Guard PricingFetcher construction with try/catch to avoid a hard crash
If a user passes an invalid --fetch source, new PricingFetcher() will throw PricingSourceError and the whole command exits with an uncaught rejection. Wrapping the construction protects UX and lets you emit a helpful message rather than an abrupt stack-trace.
-// Create a single PricingFetcher instance to reuse across functions
-using pricingFetcher = ctx.values.mode === 'display' ? undefined : new PricingFetcher(ctx.values.fetch);
+// Create a single PricingFetcher instance to reuse across functions
+let pricingFetcher: PricingFetcher | undefined;
+if (ctx.values.mode !== 'display') {
+ try {
+ using pf = new PricingFetcher(ctx.values.fetch);
+ pricingFetcher = pf;
+ } catch (e) {
+ logger.error(
+ `Failed to load pricing data from "${ctx.values.fetch ?? 'LiteLLM default'}": ${(e as Error).message}`,
+ );
+ process.exit(1);
+ }
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Create a single PricingFetcher instance to reuse across functions | |
| using pricingFetcher = ctx.values.mode === 'display' ? undefined : new PricingFetcher(ctx.values.fetch); | |
| // Create a single PricingFetcher instance to reuse across functions | |
| let pricingFetcher: PricingFetcher | undefined; | |
| if (ctx.values.mode !== 'display') { | |
| try { | |
| using pf = new PricingFetcher(ctx.values.fetch); | |
| pricingFetcher = pf; | |
| } catch (e) { | |
| logger.error( | |
| `Failed to load pricing data from "${ctx.values.fetch ?? 'LiteLLM default'}": ${(e as Error).message}`, | |
| ); | |
| process.exit(1); | |
| } | |
| } |
🧰 Tools
🪛 ESLint
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
[error] 27-27: Unsafe argument of type any assigned to a parameter of type string | undefined.
(ts/no-unsafe-argument)
[error] 27-27: Unsafe member access .values on an any value.
(ts/no-unsafe-member-access)
🤖 Prompt for AI Agents
In src/commands/daily.ts around lines 26 to 28, the construction of
PricingFetcher is not guarded against exceptions, which can cause the command to
crash if an invalid --fetch source is provided. Wrap the creation of the
PricingFetcher instance in a try/catch block to catch PricingSourceError, and
handle it gracefully by emitting a helpful error message instead of allowing an
uncaught rejection to terminate the process.
| // Use provided PricingFetcher or create new one with using statement for automatic cleanup | ||
| using fetcher = pricingFetcher ?? new PricingFetcher(fetch); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Double-dispose risk with using fetcher = pricingFetcher ?? …
If a caller supplies a shared PricingFetcher, the using statement will still dispose it at the end of detectMismatches, and the outer scope will dispose it a second time. While the current Symbol.dispose is idempotent, double calls are brittle and may break if additional teardown is added later.
-// Use provided PricingFetcher or create new one with using statement for automatic cleanup
-using fetcher = pricingFetcher ?? new PricingFetcher(fetch);
+// Re-use caller’s fetcher; only own disposal when we create a new one
+const fetcher =
+ pricingFetcher ?? (await (async () => {
+ using pf = new PricingFetcher(fetch);
+ return pf;
+ })());Alternatively, fall back to a plain const and manually dispose only when you own the instance.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/debug.ts around lines 64 to 66, the use of 'using' with a potentially
shared PricingFetcher risks double disposal if the caller provides the fetcher.
To fix this, declare fetcher as a plain const without 'using' when
pricingFetcher is supplied, and only dispose it manually if you create a new
PricingFetcher instance. This avoids disposing shared instances twice while
ensuring proper cleanup of owned instances.
| // Use provided PricingFetcher or create new one with using statement for automatic cleanup | ||
| using fetcher = mode === 'display' ? undefined : (options?.pricingFetcher ?? new PricingFetcher(options?.fetch)); | ||
|
|
||
| // Fail fast for custom pricing sources if needed | ||
| if (fetcher != null && options?.fetch != null) { | ||
| try { | ||
| await fetcher.fetchModelPricing(); | ||
| } | ||
| catch (error) { | ||
| if (error instanceof PricingSourceError) { | ||
| throw error; // Re-throw to fail fast | ||
| } | ||
| // For other errors, continue with zero costs | ||
| } | ||
| } |
There was a problem hiding this comment.
using fetcher = … disposes a caller-provided instance – breaks cache & re-use
When the caller passes an already-created PricingFetcher via options.pricingFetcher,
the using declaration guarantees dispose is called at the end of the function, clearing the
internal cache.
Any subsequent reuse of the same instance (e.g. monthly → daily → session flow, or debug
mismatch checking) will trigger a refetch and defeat the single-fetch optimisation your tests rely on.
- // Use provided PricingFetcher or create new one
- using fetcher = mode === 'display'
- ? undefined
- : (options?.pricingFetcher ?? new PricingFetcher(options?.fetch));
+ // Only dispose if we allocated it inside this function
+ const _internalFetcher =
+ mode !== 'display' && options?.pricingFetcher == null
+ ? new PricingFetcher(options?.fetch)
+ : undefined;
+ const fetcher = _internalFetcher ?? options?.pricingFetcher;
+ if (fetcher == null && mode !== 'display') return []; // sanity
+ try {
+ … /* existing logic */
+ } finally {
+ _internalFetcher?.[Symbol.dispose]?.();
+ }This keeps the lifetime of a caller-owned instance under the caller’s control.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/data-loader.ts around lines 284 to 298, the use of a 'using' declaration
with a caller-provided PricingFetcher causes it to be disposed at the end of the
function, clearing its internal cache and breaking reuse. To fix this, remove
the 'using' keyword when assigning the fetcher if it is passed in via
options.pricingFetcher, so that disposal is controlled by the caller. Only use
'using' to automatically dispose of newly created PricingFetcher instances
within this function.
| try { | ||
| logger.warn('Fetching latest model pricing from LiteLLM...'); | ||
| const response = await fetch(LITELLM_PRICING_URL); | ||
| if (!response.ok) { | ||
| throw new Error(`Failed to fetch pricing data: ${response.statusText}`); | ||
| let data: unknown; | ||
| const isUrl = this.pricingSource.startsWith('https://'); | ||
|
|
||
| if (isUrl) { | ||
| logger.warn(`Fetching model pricing from URL: ${this.pricingSource}`); | ||
| const response = await fetch(this.pricingSource); | ||
| if (!response.ok) { | ||
| throw new Error(`Failed to fetch pricing data: ${response.statusText}`); | ||
| } | ||
| data = await response.json(); | ||
| } | ||
| else { | ||
| logger.warn(`Loading model pricing from local file: ${this.pricingSource}`); | ||
| const fileContent = await readFile(this.pricingSource, 'utf-8'); | ||
| data = JSON.parse(fileContent) as unknown; | ||
| } |
There was a problem hiding this comment.
startsWith('https://') silently treats http:// as a local path
If a user accidentally supplies http://foo/bar.json, we fall through to the file branch and
attempt to read a file called http://foo/bar.json, resulting in a confusing ENOENT.
Fast-fail with a clear error instead:
- const isUrl = this.pricingSource.startsWith('https://');
+ const isHttps = this.pricingSource.startsWith('https://');
+ if (this.pricingSource.startsWith('http://')) {
+ throw new PricingSourceError(
+ 'Insecure HTTP URLs are not supported; use HTTPS or a local file path.',
+ );
+ }Your tests already expect this behaviour (see should reject non-HTTPS URLs) – make the
implementation explicit.
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 ESLint
[error] 52-52: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 52-52: Unsafe member access .warn on an error typed value.
(ts/no-unsafe-member-access)
[error] 53-53: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 53-53: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 54-54: Unexpected any value in conditional. An explicit comparison or type conversion is required.
(ts/strict-boolean-expressions)
[error] 54-54: Unsafe member access .ok on an error typed value.
(ts/no-unsafe-member-access)
[error] 55-55: Unsafe member access .statusText on an error typed value.
(ts/no-unsafe-member-access)
[error] 57-57: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 57-57: Unsafe member access .json on an error typed value.
(ts/no-unsafe-member-access)
[error] 60-60: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 60-60: Unsafe member access .warn on an error typed value.
(ts/no-unsafe-member-access)
[error] 61-61: Unsafe assignment of an error typed value.
(ts/no-unsafe-assignment)
[error] 61-61: Unsafe call of a(n) error type typed value.
(ts/no-unsafe-call)
[error] 62-62: Unsafe argument of type error typed assigned to a parameter of type string.
(ts/no-unsafe-argument)
🤖 Prompt for AI Agents
In src/pricing-fetcher.ts around lines 47 to 63, the code only checks for URLs
starting with 'https://' and treats 'http://' URLs as local files, causing
confusing errors. Update the logic to explicitly detect 'http://' URLs and throw
a clear error indicating that only HTTPS URLs are supported, ensuring the code
fast-fails with a meaningful message instead of attempting to read them as
files.
|
thanks. I'll review it |
| let testPricingFile: string; | ||
|
|
||
| beforeEach(async () => { | ||
| tempDir = tmpdir(); |
There was a problem hiding this comment.
don't use tmpdir. use fs-fixture and await using syntax as other test files do
Summary
--fetchCLI argument requested in issue Enhancement: fetchModelPricing support HTTPS_PROXY #12Changes
--fetchargument to shared CLI arguments with support for both HTTPS URLs and local filesystem pathsPricingFetcherclass to accept custom data sources in constructorTest plan
Examples
Using a local file:
Using a custom HTTPS URL:
Resolves #12
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation