Skip to content

Add package manager & Update build process & Update npm lib process installation#18

Merged
Maciek-roboblog merged 35 commits intomainfrom
uv
Jun 22, 2025
Merged

Add package manager & Update build process & Update npm lib process installation#18
Maciek-roboblog merged 35 commits intomainfrom
uv

Conversation

@Maciek-roboblog
Copy link
Owner

@Maciek-roboblog Maciek-roboblog commented Jun 19, 2025

Many Thanks for @adawalli @taylorwilsdon

Summary by CodeRabbit

  • New Features

    • Added robust dependency checks for Node.js and npx to ensure required tools are available before running the monitor.
    • Enhanced terminal input handling for improved display and user experience.
    • Automatic switching of token limits and improved notifications for usage thresholds.
  • Documentation

    • Major updates and additions to user guides, troubleshooting, contributing, development, changelog, and release process documentation.
    • Expanded installation, troubleshooting, and usage instructions for better onboarding and support.
  • Chores

    • Introduced configuration files for code quality tools, pre-commit hooks, and editor settings.
    • Added comprehensive .gitignore and project packaging configuration.
    • Implemented automated GitHub Actions workflows for linting, release, and version bumping.

Adam Wallis and others added 11 commits June 19, 2025 14:55
Add modern Python packaging with uv tool installation support:

- Add pyproject.toml with console script entry points (ccusage-monitor, claude-monitor)

- Update README.md to prioritize uv installation over legacy methods

- Add CLAUDE.md development documentation

- Add comprehensive .gitignore for Python project

Users can now install with: uv tool install claude-usage-monitor

Maintains full backwards compatibility with existing installation methods
Update installation docs to use local directory instead of PyPI:

- Change from 'uv tool install claude-usage-monitor' to 'uv tool install .'

- Add git clone step since package not yet published to PyPI

- Clarify platform-specific uv installation commands

Fixes installation errors for users trying to install from PyPI
feat: add uv package manager support
- Add comprehensive .ruff.toml configuration with essential linting rules
- Set up pre-commit hooks for automated code quality checks
- Create GitHub Actions CI workflow for ruff validation
- Configure VS Code settings for real-time ruff integration
- Format existing codebase to comply with ruff standards
- Update documentation with ruff usage instructions
- Update Python requirement from 3.6+ to 3.7+ (ruff minimum)
- Replace outdated tools (black, flake8) with modern ruff tooling

Resolves #15
feat: integrate ruff for code quality and formatting
@Maciek-roboblog
Copy link
Owner Author

@taylorwilsdon We should merge it changes with #11

What do you think?

@adawalli

@Maciek-roboblog Maciek-roboblog self-assigned this Jun 21, 2025
@Maciek-roboblog Maciek-roboblog requested a review from adawalli June 21, 2025 14:02
@adawalli
Copy link

@taylorwilsdon We should merge it changes with #11

What do you think?

@adawalli

I say we merge this one in and then cherry pick what we want from #11

@Maciek-roboblog
Copy link
Owner Author

All future releases will be much smaller, but this one was a bit hard for me to split up.

@Maciek-roboblog Maciek-roboblog changed the title Uv Add package manager & Update build process & Update npm lib process installation Jun 21, 2025
@Maciek-roboblog
Copy link
Owner Author

@taylorwilsdon

@adawalli
Copy link

looks great, consider adding @coderabbitai to help review these complicated/big ones

Copy link

@adawalli adawalli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but definitely hard to spot problems in something so big :)

@Maciek-roboblog Maciek-roboblog requested a review from kory- June 22, 2025 06:31
@coderabbitai
Copy link

coderabbitai bot commented Jun 22, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces a modernized project structure for the Claude Code Usage Monitor, adding comprehensive documentation, automated code quality tools, robust dependency checks, and improved packaging. The main script is refactored for better terminal and dependency handling. New GitHub Actions workflows automate linting, versioning, and release processes, while development and troubleshooting guides are expanded.

Changes

File(s) Change Summary
.gitignore, .vscode/settings.json, .coderabbit.yaml, .pre-commit-config.yaml, .ruff.toml, pyproject.toml Added configuration files for editor, code quality, pre-commit hooks, linter/formatter, packaging, and CodeRabbit integration.
.github/workflows/lint.yml, .github/workflows/release.yml, .github/workflows/version-bump.yml Added GitHub Actions workflows for linting, automated release, and version bumping with changelog/PR automation.
claude_monitor.py New script implementing a real-time terminal monitor for Claude token usage with enhanced terminal handling, burn rate calculation, multi-plan support, and robust error handling.
check_dependency.py New script: checks Node.js and npx presence and versions, with error reporting and exit on failure.
CHANGELOG.md, CLAUDE.md, CONTRIBUTING.md, DEVELOPMENT.md, README.md, RELEASE.md, TROUBLESHOOTING.md Added or updated documentation: changelog, architecture, contributing, development, release process, troubleshooting, and usage guides.

Sequence Diagram(s)

Main Dependency and Monitoring Flow

sequenceDiagram
    participant User
    participant claude_monitor.py
    participant check_dependency.py
    participant Terminal
    participant npx/ccusage

    User->>claude_monitor.py: Run monitor
    claude_monitor.py->>check_dependency.py: test_node()
    check_dependency.py-->>claude_monitor.py: Node.js check result
    claude_monitor.py->>check_dependency.py: test_npx()
    check_dependency.py-->>claude_monitor.py: npx check result
    claude_monitor.py->>Terminal: Setup raw mode
    claude_monitor.py->>npx/ccusage: Run "npx ccusage blocks --json"
    npx/ccusage-->>claude_monitor.py: Usage data (JSON)
    claude_monitor.py->>Terminal: Display usage, progress bars, status
    claude_monitor.py->>Terminal: Restore settings on exit/error
Loading

Poem

In the warren where code bunnies dwell,
New scripts and docs ring the bell!
With Ruff and pre-commit, the carrots align,
Node and npx checked—oh how fine!
GitHub hops in with workflows anew,
While the monitor blinks in terminal view.
🥕 Cheers to each hop—release is in queue!


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 585559e and 29142b9.

📒 Files selected for processing (2)
  • claude_monitor.py (1 hunks)
  • pyproject.toml (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

♻️ Duplicate comments (1)
.vscode/settings.json (1)

1-17: Well-configured VS Code settings for Python development.

The configuration properly sets up Ruff as the primary linter and formatter with appropriate save actions.

Note: This conflicts with the .gitignore exclusion of .vscode/ directory as mentioned in the .gitignore review.

🧹 Nitpick comments (10)
CLAUDE.md (1)

173-178: Add language identifier to code block

Add toml language identifier for proper syntax highlighting.

 ### Console Script Entry Points
 The `pyproject.toml` defines two console commands:
-```
+```toml
 [project.scripts]
 claude-monitor = "claude_monitor:main"

</blockquote></details>
<details>
<summary>claude_monitor.py (2)</summary><blockquote>

`394-397`: **Simplify token limit update logic**

Use `max()` for more concise code as suggested by static analysis.

```diff
 new_limit = get_token_limit("custom_max", data["blocks"])
-if new_limit > token_limit:
-    token_limit = new_limit
+token_limit = max(token_limit, new_limit)

317-541: Consider refactoring main() to reduce complexity

The main() function has grown quite complex with 39 local variables, 22 branches, and 118 statements. Consider extracting logical sections into separate functions for better maintainability:

  • Session data processing
  • Display formatting and output
  • Token limit detection and switching
  • Time calculations

This would improve readability and make the code easier to test and maintain.

TROUBLESHOOTING.md (7)

11-13: Quick fixes table formatting
The new rows render correctly, but consider replacing the tilde in ~/.local/bin with $HOME/.local/bin for clarity and consistency. Also verify that the table alignment displays properly in all Markdown renderers.


15-15: Clarify “Permission denied” quick fix
Wrap the chmod +x claude_monitor.py snippet in backticks for consistency, e.g.:

chmod +x claude_monitor.py

And consider adding a note that this only applies when running from source.


22-28: Specify language for the error code block
The fenced block showing the externally-managed-environment error should include a language tag (e.g., bash or text) to satisfy lint rules and improve readability:

error: externally-managed-environment  
× This environment is externally managed

30-31: Use a heading instead of bold emphasis
The line

**This is common on Ubuntu 23.04+, Debian 12+, Fedora 38+**

should be a proper Markdown heading (e.g., ### or ####) for semantic consistency.


118-118: Add language to fenced code block
The fence before the Failed to get usage data snippet is missing a language tag. Add bash (or text) after the opening backticks:

Failed to get usage data: [Errno 2] No such file or directory: 'ccusage'

173-173: Mark error snippet with language
Fenced code showing the permission error should specify a language:

Permission denied: ./claude_monitor.py

177-177: Convert emphasis to heading
The line

**This only applies when running from source**

functions as a subheading—switch to a Markdown header (e.g., ####) for clarity.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48f64d6 and 2f6e67b.

📒 Files selected for processing (15)
  • .coderabbit.yaml (1 hunks)
  • .github/workflows/lint.yml (1 hunks)
  • .gitignore (1 hunks)
  • .pre-commit-config.yaml (1 hunks)
  • .ruff.toml (1 hunks)
  • .vscode/settings.json (1 hunks)
  • CHANGELOG.md (1 hunks)
  • CLAUDE.md (1 hunks)
  • CONTRIBUTING.md (9 hunks)
  • DEVELOPMENT.md (5 hunks)
  • README.md (21 hunks)
  • TROUBLESHOOTING.md (14 hunks)
  • claude_monitor.py (5 hunks)
  • init_dependency.py (1 hunks)
  • pyproject.toml (1 hunks)
🧰 Additional context used
🪛 Pylint (3.3.7)
claude_monitor.py

[refactor] 273-274: Consider using 'max_tokens = max(max_tokens, tokens)' instead of unnecessary if block

(R1731)


[refactor] 317-317: Too many local variables (39/15)

(R0914)


[refactor] 395-396: Consider using 'token_limit = max(token_limit, new_limit)' instead of unnecessary if block

(R1731)


[refactor] 317-317: Too many branches (22/12)

(R0912)


[refactor] 317-317: Too many statements (118/50)

(R0915)

🪛 LanguageTool
README.md

[uncategorized] ~133-~133: Possible missing preposition found.
Context: ...e above) - it's safer and easier > 2. Use a virtual environment - `python3 -m v...

(AI_HYDRA_LEO_MISSING_TO)


[uncategorized] ~232-~232: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... need your help!** This is a new release and we want to ensure it works perfectly on...

(COMMA_COMPOUND_SENTENCE)


[style] ~249-~249: The wording of this phrase can be improved.
Context: ...issues/new) > > Thank you for helping make this tool better! 🚀 ## ✨ Features & How It Works #...

(MAKE_STYLE_BETTER)

🪛 markdownlint-cli2 (0.17.2)
README.md

454-454: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


562-562: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


701-701: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

TROUBLESHOOTING.md

25-25: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


30-30: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


118-118: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


173-173: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


177-177: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)

🔇 Additional comments (23)
.ruff.toml (1)

1-12: LGTM! Well-configured Ruff setup.

The configuration follows best practices with appropriate rule selection, reasonable line length, and proper separation of concerns between linting and formatting.

.coderabbit.yaml (1)

1-16: LGTM! Proper CodeRabbit configuration.

The configuration is valid according to the schema and uses sensible settings for automated code reviews with a balanced "chill" profile.

CONTRIBUTING.md (5)

96-96: Approve PEP 8 example update
The “Bad: Unclear abbreviations” example is clear and aligns with the new style guide.


108-108: Docstring formatting is consistent
The adjusted indentation and spacing in the example docstring maintain readability.


335-335: Coverage targets updated
Lowering ML components coverage from 95% to 90% is reasonable given the complexity.


346-346: Python versions matrix updated
Adding support for 3.12 and removing 3.6 reflects current compatibility requirements.


389-389: Session duration preference entry
The new bullet for session duration preferences enhances the research section.

DEVELOPMENT.md (5)

9-9: ML Status label added
Marking the ML-Powered Auto Mode as “In Active Development” correctly sets expectations for contributors.


136-136: Docker section introduction
The new “🐳 Docker Image” heading outlines upcoming containerization work.


317-323: Code Quality Tools section
The Ruff and pre-commit installation instructions are clear; ensure the uv tool install pre-commit command installs the correct plugin.


355-355: Development workflow update
Noting that code is auto-formatted and linted via hooks replaces manual PEP 8 steps accurately.


395-400: Developer contact details
The updated email and GitHub contact points are precise for contributor questions.

.pre-commit-config.yaml (2)

5-14: Ruff hooks pinned to v0.8.4
Pinning ensures reproducible CI but verify that v0.8.4 supports all current Python versions (3.7–3.12).


15-25: Standard pre-commit-hooks configuration
The selected hooks cover whitespace, EOL, YAML/TOML validation, and merge conflict detection. Looks good.

.github/workflows/lint.yml (2)

27-32: Ruff checks and formatting
Running both ruff check and ruff format --check ensures code style consistency. Good integration.


47-57: Pre-commit job enforcement
The pre-commit run with diff-on-failure instructs contributors to fix locally—this will keep the codebase clean.

CHANGELOG.md (5)

1-7: Version 1.0.8 entry added
Documenting the automatic Node.js installation support is accurate and clear.


8-15: Version 1.0.7 changes
The enhancements to init_dependency.py are thoroughly summarized.


16-30: Version 1.0.6 additions
The extensive packaging and tooling improvements are well documented.


31-37: “Changed” section for v1.0.6
Accurate reflection of renaming and command updates.


38-42: “Fixed” section for v1.0.6
The terminal corruption fixes are clearly listed.

claude_monitor.py (2)

282-315: Well-implemented terminal handling

The terminal management functions properly handle raw mode setup and restoration, with appropriate fallbacks for systems without termios support. The flush_input() function effectively prevents display corruption from stray keyboard input.


425-427: Fix incorrect command name in comment

The comment references ccusage-monitor but the actual command defined in pyproject.toml is claude-monitor.

 # Press Ctrl+C after checking status
-# ccusage-monitor
+# claude-monitor

Likely an incorrect or invalid review comment.

@coderabbitai
Copy link

coderabbitai bot commented Jun 22, 2025

✅ Actions performed

Reviews resumed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
init_dependency.py (1)

58-67: Security concern: Downloaded files lack integrity verification.

The script downloads Node.js archives without verifying checksums or signatures, which poses a security risk. Malicious actors could potentially serve compromised downloads.

Consider adding checksum verification:

+import hashlib

 filename = f"node-v{NODE_VERSION}-{system}-{arch}.tar.xz"
 url = f"{NODE_DIST_URL}/v{NODE_VERSION}/{filename}"
+checksum_url = f"{url}.sha256"

 print(f"Downloading Node.js from {url}")
 urllib.request.urlretrieve(url, filename)
+urllib.request.urlretrieve(checksum_url, f"{filename}.sha256")
+
+# Verify checksum
+with open(f"{filename}.sha256", 'r') as f:
+    expected = f.read().split()[0]
+with open(filename, 'rb') as f:
+    actual = hashlib.sha256(f.read()).hexdigest()
+if actual != expected:
+    print("❌ Checksum verification failed!")
+    sys.exit(1)
+print("✓ Checksum verified")
🧹 Nitpick comments (5)
init_dependency.py (3)

78-187: Refactor to reduce function complexity.

This function has 84 statements, exceeding the recommended 50. The extensive error handling and user interaction logic could be broken down into smaller, more focused functions.

Consider extracting helper functions:

+def get_user_consent_for_installation():
+    """Get user consent for Node.js installation with detailed prompts."""
+    # Move lines 86-116 here
+    
+def download_node_installer(url, filename):
+    """Download Node.js MSI installer with error handling."""
+    # Move lines 121-139 here
+    
+def run_msi_installer(filename):
+    """Run MSI installer with comprehensive error handling."""
+    # Move lines 144-180 here

 def install_node_windows():
     """Install Node.js on Windows using MSI installer with user consent."""
+    if not get_user_consent_for_installation():
+        sys.exit(0)
+    
+    filename = download_node_installer(url, filename)
+    run_msi_installer(filename)
     # Rest of function

190-268: Improve error handling granularity.

The function has 59 statements and handles multiple types of errors. Consider breaking down error handling to be more specific and actionable.

Extract specific error handling functions:

+def handle_ccusage_installation_errors(global_error, local_error=None):
+    """Provide specific troubleshooting for ccusage installation failures."""
+    print("\n❌ ccusage installation failed.")
+    # Move error analysis logic here
+    
+def attempt_ccusage_installation():
+    """Attempt ccusage installation with fallback strategies."""
+    # Move installation logic here
+    
 def ensure_ccusage_available():
     """Ensure ccusage is available via npx."""
+    if is_ccusage_available():
+        return
+    attempt_ccusage_installation()

10-11: Consider making Node.js version configurable.

The hardcoded Node.js version might become outdated. Consider making it configurable or adding a mechanism to use the latest LTS version.

-NODE_VERSION = "18.17.1"
-NODE_DIST_URL = "https://nodejs.org/dist"
+NODE_VERSION = os.environ.get("NODE_VERSION", "18.17.1")
+NODE_DIST_URL = os.environ.get("NODE_DIST_URL", "https://nodejs.org/dist")

Or add a function to fetch the latest LTS version from Node.js API.

claude_monitor.py (1)

271-274: Minor optimization opportunity.

Static analysis suggests using max() instead of the if block for finding maximum tokens.

-            if tokens > max_tokens:
-                max_tokens = tokens
+            max_tokens = max(max_tokens, tokens)
CLAUDE.md (1)

12-12: Update line count to match current file size.

The documentation states 545 lines, but claude_monitor.py currently has 541 lines based on the provided code.

-This is a single-file Python application (545 lines) with modern packaging:
+This is a single-file Python application (541 lines) with modern packaging:
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6e67b and 31f0617.

📒 Files selected for processing (8)
  • .github/workflows/lint.yml (1 hunks)
  • .gitignore (1 hunks)
  • CLAUDE.md (1 hunks)
  • CONTRIBUTING.md (9 hunks)
  • README.md (21 hunks)
  • claude_monitor.py (5 hunks)
  • init_dependency.py (1 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • CONTRIBUTING.md
  • .gitignore
  • .github/workflows/lint.yml
  • pyproject.toml
🧰 Additional context used
🪛 Pylint (3.3.7)
init_dependency.py

[refactor] 95-115: Unnecessary "elif" after "break", remove the leading "el" from "elif"

(R1723)


[refactor] 78-78: Too many statements (84/50)

(R0915)


[refactor] 190-190: Too many statements (59/50)

(R0915)

claude_monitor.py

[refactor] 273-274: Consider using 'max_tokens = max(max_tokens, tokens)' instead of unnecessary if block

(R1731)


[refactor] 317-317: Too many local variables (36/15)

(R0914)


[refactor] 395-396: Consider using 'token_limit = max(token_limit, new_limit)' instead of unnecessary if block

(R1731)


[refactor] 317-317: Too many branches (22/12)

(R0912)


[refactor] 317-317: Too many statements (114/50)

(R0915)

🪛 LanguageTool
README.md

[uncategorized] ~133-~133: Possible missing preposition found.
Context: ...e above) - it's safer and easier > 2. Use a virtual environment - `python3 -m v...

(AI_HYDRA_LEO_MISSING_TO)


[uncategorized] ~232-~232: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... need your help!** This is a new release and we want to ensure it works perfectly on...

(COMMA_COMPOUND_SENTENCE)


[style] ~249-~249: The wording of this phrase can be improved.
Context: ...issues/new) > > Thank you for helping make this tool better! 🚀 ## ✨ Features & How It Works #...

(MAKE_STYLE_BETTER)

🪛 markdownlint-cli2 (0.17.2)
README.md

454-454: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


562-562: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


701-701: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

🔇 Additional comments (9)
claude_monitor.py (3)

13-13: Good integration with dependency management.

The import of ensure_node_installed properly integrates with the new dependency management system, ensuring Node.js and ccusage are available before the monitor starts.


27-32: Excellent update to use npx for ccusage execution.

The change from direct ccusage to npx ccusage is a smart improvement that leverages npm's package execution capabilities and aligns with the new dependency management approach.


282-315: Well-implemented terminal handling with proper error handling.

The terminal setup and restoration functions properly handle the case where termios is not available (e.g., on Windows) and include appropriate exception handling for edge cases.

CLAUDE.md (2)

1-191: Comprehensive documentation with accurate technical details.

The documentation provides excellent coverage of the project architecture, installation methods, development commands, and technical implementation details. The structure is well-organized and user-friendly.


25-27: Verify function line numbers for accuracy.

The line numbers for key functions should be verified against the actual file to ensure developers can easily locate them.

#!/bin/bash
# Check actual line numbers for key functions in claude_monitor.py
echo "Checking function locations:"
grep -n "def run_ccusage" claude_monitor.py
grep -n "def calculate_hourly_burn_rate" claude_monitor.py  
grep -n "def main" claude_monitor.py
README.md (4)

60-876: Comprehensive and well-structured documentation update.

The README provides excellent coverage of installation methods, usage examples, troubleshooting, and development setup. The addition of automatic dependency installation explanation and modern installation methods (uv, pipx) significantly improves the user experience.


587-589: Update Python version requirement.

The documentation mentions Python 3.7+, but earlier in the README (line 4) it shows Python 3.7+ in the badge. Ensure this matches the actual requirement in pyproject.toml.

#!/bin/bash
# Check Python version requirement in pyproject.toml
if [ -f pyproject.toml ]; then
    echo "Python version requirement in pyproject.toml:"
    grep -A 5 "requires-python" pyproject.toml || echo "Python version requirement not found"
fi

3-4: Verify PyPI package availability.

The README references a PyPI package claude-monitor, but this should be verified to ensure the package actually exists and the installation commands work.

#!/bin/bash
# Check if PyPI package exists and installation commands are valid
echo "Checking PyPI package availability:"
curl -s "https://pypi.org/pypi/claude-monitor/json" | jq -r '.info.name, .info.version' 2>/dev/null || echo "Package not found on PyPI"

# Check if the package name matches what's in pyproject.toml
if [ -f pyproject.toml ]; then
    echo "Package name in pyproject.toml:"
    grep -A 2 "^\[project\]" pyproject.toml | grep "name" || echo "Name not found in pyproject.toml"
fi

122-127: Package name inconsistency in pip installation.

The pip installation command uses claude-usage-monitor while other commands use claude-monitor. This inconsistency could confuse users.

-pip install claude-usage-monitor
+pip install claude-monitor

Ensure all package references use the same name consistently throughout the documentation.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
init_dependency.py (2)

34-47: Simplify control flow by removing unnecessary else block.

The else block is unnecessary after the return statement. This improves code readability.

-        if computed_checksum == expected_checksum:
-            print("✓ Checksum verification passed")
-            return True
-        else:
-            print("✗ Checksum verification failed!")
-            print(f"Expected: {expected_checksum}")
-            print(f"Computed: {computed_checksum}")
-            return False
+        if computed_checksum == expected_checksum:
+            print("✓ Checksum verification passed")
+            return True
+        
+        print("✗ Checksum verification failed!")
+        print(f"Expected: {expected_checksum}")
+        print(f"Computed: {computed_checksum}")
+        return False

145-166: Simplify control flow by replacing elif with if after break.

Since the first condition breaks out of the loop, the subsequent elif statements can be simplified to if statements.

         if choice in ["y", "yes"]:
             break
-        elif choice in ["n", "no"]:
+        if choice in ["n", "no"]:
             print("\nInstallation cancelled. Alternative installation options:")
             print("1. Download and install Node.js manually from: https://nodejs.org/")
             print("2. Use a package manager like Chocolatey: choco install nodejs")
             print("3. Use Scoop: scoop install nodejs")
             print("4. Use Windows Package Manager: winget install OpenJS.NodeJS")
             print("\nAfter installing Node.js, please run this script again.")
             sys.exit(0)
-        elif choice == "help":
+        if choice == "help":
             print("\nAutomatic installation details:")
             print("- Downloads Node.js MSI installer from official source")
             print("- Runs: msiexec /i node-installer.msi /quiet /norestart")
             print("- Requires administrator privileges (UAC prompt may appear)")
             print("- Installs to: C:\\Program Files\\nodejs")
             continue
-        else:
-            print(
-                "Please enter 'y' for yes, 'n' for no, or 'help' for more information."
-            )
+        
+        print(
+            "Please enter 'y' for yes, 'n' for no, or 'help' for more information."
+        )
RELEASE.md (1)

19-22: Format bare URLs as proper Markdown links.

Bare URLs should be formatted as Markdown links for better accessibility and consistency.

For line 20:

-   - Generate at: https://pypi.org/manage/account/token/
+   - Generate at: [https://pypi.org/manage/account/token/](https://pypi.org/manage/account/token/)

For line 104:

-1. Go to: https://github.com/Maciek-roboblog/Claude-Code-Usage-Monitor/releases/new
+1. Go to: [https://github.com/Maciek-roboblog/Claude-Code-Usage-Monitor/releases/new](https://github.com/Maciek-roboblog/Claude-Code-Usage-Monitor/releases/new)

For line 126:

-1. Check PyPI: https://pypi.org/project/claude-monitor/
+1. Check PyPI: [https://pypi.org/project/claude-monitor/](https://pypi.org/project/claude-monitor/)

Also applies to: 104-104, 126-126

.github/workflows/release.yml (1)

19-24: Consider using a TOML parser for more robust version extraction.

Using grep and sed to parse TOML files is fragile and may break if the file format changes (e.g., spaces around the equals sign, different quotes).

Consider using a proper TOML parser:

       - name: Extract version from pyproject.toml
         id: extract
         run: |
-          VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
+          VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
           echo "version=$VERSION" >> $GITHUB_OUTPUT
           echo "Version: $VERSION"

Note: tomllib is available in Python 3.11+. For older versions, you might need to use pip install toml and use the toml package instead.

.github/workflows/version-bump.yml (1)

40-45: Use TOML parser for robust version extraction and updating.

Similar to the release workflow, using grep/sed for TOML parsing is fragile. Additionally, using sed to update the version might not handle all edge cases correctly.

Consider using Python with a TOML library for both reading and writing:

       - name: Extract current version
         id: current
         run: |
-          CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
+          CURRENT_VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
           echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
           echo "Current version: $CURRENT_VERSION"

For updating the version:

       - name: Update pyproject.toml
         run: |
           NEW_VERSION="${{ steps.new.outputs.version }}"
-          sed -i "s/^version = .*/version = \"$NEW_VERSION\"/" pyproject.toml
+          python3 -c "
+import toml
+with open('pyproject.toml', 'r') as f:
+    data = toml.load(f)
+data['project']['version'] = '$NEW_VERSION'
+with open('pyproject.toml', 'w') as f:
+    toml.dump(data, f)
+"
           echo "Updated pyproject.toml to version $NEW_VERSION"

Also applies to: 76-80

README.md (3)

454-454: Consider using proper headings instead of bold emphasis.

Using emphasis for section headers reduces document structure clarity and accessibility.

For line 454:

-**Start with Default (Recommended for New Users)**
+##### Start with Default (Recommended for New Users)

For line 562:

-**Sprint Development**
+##### Sprint Development

Also applies to: 562-562


701-704: Add language specification to fenced code block.

Fenced code blocks should specify the language for proper syntax highlighting.

-```
+```text
 error: externally-managed-environment
 × This environment is externally managed

232-232: Add comma before 'and' for better readability.

In formal writing, a comma before 'and' helps separate independent clauses.

-> **We need your help!** This is a new release and we want to ensure it works perfectly on all systems.
+> **We need your help!** This is a new release, and we want to ensure it works perfectly on all systems.
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31f0617 and d1b443c.

📒 Files selected for processing (5)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/version-bump.yml (1 hunks)
  • README.md (21 hunks)
  • RELEASE.md (1 hunks)
  • init_dependency.py (1 hunks)
🧰 Additional context used
🪛 Pylint (3.3.7)
init_dependency.py

[refactor] 34-41: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 145-165: Unnecessary "elif" after "break", remove the leading "el" from "elif"

(R1723)


[refactor] 128-128: Too many statements (84/50)

(R0915)


[refactor] 240-240: Too many statements (59/50)

(R0915)

🪛 LanguageTool
README.md

[uncategorized] ~133-~133: Possible missing preposition found.
Context: ...e above) - it's safer and easier > 2. Use a virtual environment - `python3 -m v...

(AI_HYDRA_LEO_MISSING_TO)


[uncategorized] ~232-~232: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... need your help!** This is a new release and we want to ensure it works perfectly on...

(COMMA_COMPOUND_SENTENCE)


[style] ~249-~249: The wording of this phrase can be improved.
Context: ...issues/new) > > Thank you for helping make this tool better! 🚀 ## ✨ Features & How It Works #...

(MAKE_STYLE_BETTER)

🪛 markdownlint-cli2 (0.17.2)
README.md

454-454: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


562-562: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


701-701: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

RELEASE.md

20-20: Bare URL used
null

(MD034, no-bare-urls)


104-104: Bare URL used
null

(MD034, no-bare-urls)


126-126: Bare URL used
null

(MD034, no-bare-urls)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
init_dependency.py (2)

36-43: Improve code style by removing unnecessary "else" after "return"

The Pylint suggestion is valid - the else clause after a return statement is unnecessary and can be simplified.

 if computed_checksum == expected_checksum:
     print("✓ Checksum verification passed")
     return True
-else:
-    print("✗ Checksum verification failed!")
-    print(f"Expected: {expected_checksum}")
-    print(f"Computed: {computed_checksum}")
-    return False
+
+print("✗ Checksum verification failed!")
+print(f"Expected: {expected_checksum}")
+print(f"Computed: {computed_checksum}")
+return False

147-167: Minor code style improvement for user input handling

The user input validation logic can be simplified per Pylint's suggestion.

 if choice in ["y", "yes"]:
     break
-elif choice in ["n", "no"]:
+if choice in ["n", "no"]:
     print("\nInstallation cancelled. Alternative installation options:")
     print("1. Download and install Node.js manually from: https://nodejs.org/")
     print("2. Use a package manager like Chocolatey: choco install nodejs")
     print("3. Use Scoop: scoop install nodejs")
     print("4. Use Windows Package Manager: winget install OpenJS.NodeJS")
     print("\nAfter installing Node.js, please run this script again.")
     sys.exit(0)
-elif choice == "help":
+if choice == "help":
     print("\nAutomatic installation details:")
     print("- Downloads Node.js MSI installer from official source")
     print("- Runs: msiexec /i node-installer.msi /quiet /norestart")
     print("- Requires administrator privileges (UAC prompt may appear)")
     print("- Installs to: C:\\Program Files\\nodejs")
     continue
-else:
-    print(
-        "Please enter 'y' for yes, 'n' for no, or 'help' for more information."
-    )
+
+print(
+    "Please enter 'y' for yes, 'n' for no, or 'help' for more information."
+)
README.md (1)

701-704: Minor formatting improvement needed

The fenced code block should specify a language for better rendering and accessibility.

-```
+```text
 error: externally-managed-environment
 × This environment is externally managed

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d1b443cb46dcf48308fec7d3f1694d30bc20d6a7 and 4a1109825095909bfbad3ecf58421e087c1d9431.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `README.md` (21 hunks)
* `init_dependency.py` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>README.md</summary>

[uncategorized] ~133-~133: Possible missing preposition found.
Context: ...e above) - it's safer and easier > 2. **Use a virtual environment** - `python3 -m v...

(AI_HYDRA_LEO_MISSING_TO)

---

[uncategorized] ~232-~232: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... need your help!** This is a new release and we want to ensure it works perfectly on...

(COMMA_COMPOUND_SENTENCE)

---

[style] ~249-~249: The wording of this phrase can be improved.
Context: ...issues/new) > > **Thank you for helping make this tool better! 🚀**   ## ✨ Features & How It Works  #...

(MAKE_STYLE_BETTER)

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>README.md</summary>

454-454: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)

---

562-562: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)

---

701-701: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>
<details>
<summary>🪛 Pylint (3.3.7)</summary>

<details>
<summary>init_dependency.py</summary>

[refactor] 36-43: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)

---

[refactor] 147-167: Unnecessary "elif" after "break", remove the leading "el" from "elif"

(R1723)

---

[refactor] 130-130: Too many statements (84/50)

(R0915)

---

[refactor] 242-242: Too many statements (59/50)

(R0915)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>init_dependency.py (3)</summary>

`14-20`: **✓ macOS checksums successfully added**

Great improvement! The NODE_CHECKSUMS dictionary now includes all required checksums for both Linux and macOS platforms, addressing the previous review concern about missing macOS support.

---

`242-321`: **Comprehensive npm installation handling**

Excellent implementation with proper fallback strategy (global → local installation) and detailed error handling. The extensive troubleshooting guidance will help users resolve common npm permission and installation issues.

---

`323-356`: **Well-orchestrated dependency management**

The main `ensure_node_installed()` function provides excellent orchestration of the entire dependency installation process. The logical flow from Node.js → npm → npx → ccusage is clear and handles all major platforms appropriately.

</details>
<details>
<summary>README.md (3)</summary>

`71-157`: **Excellent modernization of installation instructions**

The installation section has been significantly improved with:
- Clear promotion of `uv` as the recommended method with compelling benefits
- Proper handling of "externally-managed-environment" warnings
- Comprehensive alternative installation methods
- Good progression from simple to complex installation scenarios

This addresses modern Python packaging challenges effectively.

---

`158-567`: **Comprehensive and consistent usage documentation**

The usage examples are well-structured with:
- Consistent use of `claude-monitor` command throughout
- Logical progression from basic to advanced scenarios
- Real-world workflow examples
- Good integration of timezone and scheduling options

The command consistency issues from previous reviews have been properly addressed.

---

`230-250`: **Excellent community engagement approach**

The "Please Help Test This Release" section shows great community engagement practices:
- Clear instructions for testing and feedback
- Proper issue reporting format guidance
- Fallback to develop branch for immediate fixes
- Appreciative tone that encourages participation

This will help ensure release quality and build community involvement.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
init_dependency.py (1)

36-43: Simplify conditional logic.

The else clause after return is unnecessary and can be simplified.

     if computed_checksum == expected_checksum:
         print("✓ Checksum verification passed")
         return True
-    else:
-        print("✗ Checksum verification failed!")
-        print(f"Expected: {expected_checksum}")
-        print(f"Computed: {computed_checksum}")
-        return False
+    
+    print("✗ Checksum verification failed!")
+    print(f"Expected: {expected_checksum}")
+    print(f"Computed: {computed_checksum}")
+    return False
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a11098 and 5ca83eb.

📒 Files selected for processing (2)
  • README.md (21 hunks)
  • init_dependency.py (1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[uncategorized] ~139-~139: Possible missing preposition found.
Context: ...e above) - it's safer and easier > 2. Use a virtual environment - `python3 -m v...

(AI_HYDRA_LEO_MISSING_TO)


[uncategorized] ~238-~238: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... need your help!** This is a new release and we want to ensure it works perfectly on...

(COMMA_COMPOUND_SENTENCE)


[style] ~255-~255: The wording of this phrase can be improved.
Context: ...issues/new) > > Thank you for helping make this tool better! 🚀 ## ✨ Features & How It Works #...

(MAKE_STYLE_BETTER)

🪛 markdownlint-cli2 (0.17.2)
README.md

460-460: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


568-568: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)


707-707: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

🪛 Pylint (3.3.7)
init_dependency.py

[refactor] 36-43: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 147-167: Unnecessary "elif" after "break", remove the leading "el" from "elif"

(R1723)


[refactor] 130-130: Too many statements (84/50)

(R0915)


[refactor] 244-380: Too many nested blocks (6/5)

(R1702)


[refactor] 244-380: Too many nested blocks (6/5)

(R1702)


[refactor] 242-242: Too many branches (25/12)

(R0912)


[refactor] 242-242: Too many statements (88/50)

(R0915)

🔇 Additional comments (6)
README.md (6)

3-4: LGTM! Modern badge and Python version update.

Good update to reflect the actual PyPI package name and bumping the minimum Python version to 3.7+, which aligns with modern Python practices.


56-69: Excellent addition of automatic dependency installation documentation.

The clear explanation of automatic dependency installation addresses a major user pain point and sets proper expectations. This aligns well with the new init_dependency.py functionality.


71-118: Outstanding modernization with uv installation method.

The addition of uv as the recommended installation method is excellent. It addresses the "externally-managed-environment" issues that plague modern Linux distributions and provides a much better user experience.


236-256: Great addition of testing request section.

This proactive approach to getting user feedback and testing on the develop branch shows good project management. The clear instructions for issue reporting will help improve the tool's reliability.


704-746: Comprehensive troubleshooting for modern Linux distributions.

Excellent coverage of the "externally-managed-environment" error with multiple solution approaches prioritized correctly (uv first, then pipx, then virtual env). This will save users significant frustration.


873-879: Nice addition of contributors section.

Good recognition of contributors and encouragement for future contributions. This helps build community around the project.

Comment on lines +130 to +240
def install_node_windows():
"""Install Node.js on Windows using MSI installer with user consent and return the installation path."""
print("\nNode.js is required but not found on your system.")
print(
"This script can automatically install Node.js, but it requires administrator privileges."
)
print(f"Node.js version {NODE_VERSION} will be downloaded and installed.")

while True:
choice = (
input(
"\nDo you want to proceed with the automatic installation? (y/n/help): "
)
.lower()
.strip()
)

if choice in ["y", "yes"]:
break
elif choice in ["n", "no"]:
print("\nInstallation cancelled. Alternative installation options:")
print("1. Download and install Node.js manually from: https://nodejs.org/")
print("2. Use a package manager like Chocolatey: choco install nodejs")
print("3. Use Scoop: scoop install nodejs")
print("4. Use Windows Package Manager: winget install OpenJS.NodeJS")
print("\nAfter installing Node.js, please run this script again.")
sys.exit(0)
elif choice == "help":
print("\nAutomatic installation details:")
print("- Downloads Node.js MSI installer from official source")
print("- Runs: msiexec /i node-installer.msi /quiet /norestart")
print("- Requires administrator privileges (UAC prompt may appear)")
print("- Installs to: C:\\Program Files\\nodejs")
continue
else:
print(
"Please enter 'y' for yes, 'n' for no, or 'help' for more information."
)

print("Downloading Node.js MSI installer for Windows...")
filename = os.path.join(tempfile.gettempdir(), "node-installer.msi")
url = f"{NODE_DIST_URL}/v{NODE_VERSION}/node-v{NODE_VERSION}-x64.msi"

try:
urllib.request.urlretrieve(url, filename)
print(f"✓ Downloaded installer ({os.path.getsize(filename)} bytes)")
except urllib.error.URLError as e:
print(f"\n❌ Failed to download Node.js installer from {url}")
print(f"Network error: {e}")
print("\n🔧 Next steps:")
print("1. Check your internet connection")
print("2. Download Node.js manually from: https://nodejs.org/")
print("3. Try again later if the server is temporarily unavailable")
sys.exit(1)
except OSError as e:
print(f"\n❌ Failed to save installer file to {filename}")
print(f"File system error: {e}")
print("\n🔧 Next steps:")
print("1. Check disk space and permissions")
print("2. Try running as administrator")
print("3. Download Node.js manually from: https://nodejs.org/")
sys.exit(1)

print(
"Running installer (administrator privileges required - UAC prompt may appear)..."
)
try:
subprocess.run(
["msiexec", "/i", filename, "/quiet", "/norestart"],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
print("\n❌ Node.js installation failed.")
print("\nCommon causes and solutions:")
print("• Insufficient administrator privileges - Run as administrator")
print("• Windows Installer service issues - Restart Windows Installer service")
print("• MSI file corruption - Try downloading again")
print("• Conflicting existing installation - Uninstall old Node.js first")

if e.stderr:
print(f"\nInstaller error output: {e.stderr.strip()}")
if e.stdout:
print(f"Installer output: {e.stdout.strip()}")
print(f"Return code: {e.returncode}")

print("\n🔧 Next steps:")
print("1. Download and install Node.js manually from: https://nodejs.org/")
print("2. Or use Windows Package Manager: winget install OpenJS.NodeJS")
print("3. Or use Chocolatey: choco install nodejs")
print("4. Restart your terminal after installation")
sys.exit(1)
except FileNotFoundError:
print("\n❌ Windows Installer (msiexec) not found.")
print("This indicates a serious Windows system issue.")
print("\nPlease install Node.js manually from: https://nodejs.org/")
sys.exit(1)
except PermissionError:
print("\n❌ Permission denied when running installer.")
print("Please run this script as administrator or install Node.js manually.")
print("\nManual installation: https://nodejs.org/")
sys.exit(1)

print("Node.js installed successfully.")
node_path = "C:\\Program Files\\nodejs"
os.environ["PATH"] = node_path + os.pathsep + os.environ["PATH"]

print("Node.js installed successfully and added to PATH.")
return node_path

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor the Windows installation function to reduce complexity.

The install_node_windows function is quite large (84 statements) and handles multiple concerns. Consider breaking it down into smaller, focused functions for better maintainability.

+def _get_user_consent_for_installation():
+    """Get user consent for Windows Node.js installation."""
+    print("\nNode.js is required but not found on your system.")
+    print("This script can automatically install Node.js, but it requires administrator privileges.")
+    print(f"Node.js version {NODE_VERSION} will be downloaded and installed.")
+    
+    while True:
+        choice = input("\nDo you want to proceed with the automatic installation? (y/n/help): ").lower().strip()
+        if choice in ["y", "yes"]:
+            return True
+        elif choice in ["n", "no"]:
+            print("\nInstallation cancelled. Alternative installation options:")
+            print("1. Download and install Node.js manually from: https://nodejs.org/")
+            print("2. Use a package manager like Chocolatey: choco install nodejs")
+            print("3. Use Scoop: scoop install nodejs")
+            print("4. Use Windows Package Manager: winget install OpenJS.NodeJS")
+            print("\nAfter installing Node.js, please run this script again.")
+            return False
+        elif choice == "help":
+            print("\nAutomatic installation details:")
+            print("- Downloads Node.js MSI installer from official source")
+            print("- Runs: msiexec /i node-installer.msi /quiet /norestart")
+            print("- Requires administrator privileges (UAC prompt may appear)")
+            print("- Installs to: C:\\Program Files\\nodejs")
+        else:
+            print("Please enter 'y' for yes, 'n' for no, or 'help' for more information.")

+def _download_windows_installer():
+    """Download the Windows Node.js installer."""
+    filename = os.path.join(tempfile.gettempdir(), "node-installer.msi")
+    url = f"{NODE_DIST_URL}/v{NODE_VERSION}/node-v{NODE_VERSION}-x64.msi"
+    
+    try:
+        urllib.request.urlretrieve(url, filename)
+        print(f"✓ Downloaded installer ({os.path.getsize(filename)} bytes)")
+        return filename
+    except urllib.error.URLError as e:
+        print(f"\n❌ Failed to download Node.js installer from {url}")
+        print(f"Network error: {e}")
+        print("\n🔧 Next steps:")
+        print("1. Check your internet connection")
+        print("2. Download Node.js manually from: https://nodejs.org/")
+        print("3. Try again later if the server is temporarily unavailable")
+        sys.exit(1)
+    except OSError as e:
+        print(f"\n❌ Failed to save installer file to {filename}")
+        print(f"File system error: {e}")
+        print("\n🔧 Next steps:")
+        print("1. Check disk space and permissions")
+        print("2. Try running as administrator")
+        print("3. Download Node.js manually from: https://nodejs.org/")
+        sys.exit(1)

+def _run_windows_installer(filename):
+    """Run the Windows Node.js installer."""
+    print("Running installer (administrator privileges required - UAC prompt may appear)...")
+    try:
+        subprocess.run(
+            ["msiexec", "/i", filename, "/quiet", "/norestart"],
+            check=True,
+            capture_output=True,
+            text=True,
+        )
+        print("Node.js installed successfully.")
+    except subprocess.CalledProcessError as e:
+        print("\n❌ Node.js installation failed.")
+        print("\nCommon causes and solutions:")
+        print("• Insufficient administrator privileges - Run as administrator")
+        print("• Windows Installer service issues - Restart Windows Installer service")
+        print("• MSI file corruption - Try downloading again")
+        print("• Conflicting existing installation - Uninstall old Node.js first")
+        
+        if e.stderr:
+            print(f"\nInstaller error output: {e.stderr.strip()}")
+        if e.stdout:
+            print(f"Installer output: {e.stdout.strip()}")
+        print(f"Return code: {e.returncode}")
+        
+        print("\n🔧 Next steps:")
+        print("1. Download and install Node.js manually from: https://nodejs.org/")
+        print("2. Or use Windows Package Manager: winget install OpenJS.NodeJS")
+        print("3. Or use Chocolatey: choco install nodejs")
+        print("4. Restart your terminal after installation")
+        sys.exit(1)
+    except (FileNotFoundError, PermissionError) as e:
+        # Handle specific exceptions with targeted messages
+        if isinstance(e, FileNotFoundError):
+            print("\n❌ Windows Installer (msiexec) not found.")
+            print("This indicates a serious Windows system issue.")
+        else:
+            print("\n❌ Permission denied when running installer.")
+            print("Please run this script as administrator or install Node.js manually.")
+        print("\nManual installation: https://nodejs.org/")
+        sys.exit(1)

 def install_node_windows():
     """Install Node.js on Windows using MSI installer with user consent and return the installation path."""
-    print("\nNode.js is required but not found on your system.")
-    print("This script can automatically install Node.js, but it requires administrator privileges.")
-    print(f"Node.js version {NODE_VERSION} will be downloaded and installed.")
-    
-    while True:
-        choice = input("\nDo you want to proceed with the automatic installation? (y/n/help): ").lower().strip()
-        
-        if choice in ["y", "yes"]:
-            break
-        elif choice in ["n", "no"]:
-            print("\nInstallation cancelled. Alternative installation options:")
-            print("1. Download and install Node.js manually from: https://nodejs.org/")
-            print("2. Use a package manager like Chocolatey: choco install nodejs")
-            print("3. Use Scoop: scoop install nodejs")
-            print("4. Use Windows Package Manager: winget install OpenJS.NodeJS")
-            print("\nAfter installing Node.js, please run this script again.")
-            sys.exit(0)
-        elif choice == "help":
-            print("\nAutomatic installation details:")
-            print("- Downloads Node.js MSI installer from official source")
-            print("- Runs: msiexec /i node-installer.msi /quiet /norestart")
-            print("- Requires administrator privileges (UAC prompt may appear)")
-            print("- Installs to: C:\\Program Files\\nodejs")
-            continue
-        else:
-            print("Please enter 'y' for yes, 'n' for no, or 'help' for more information.")
-    
-    print("Downloading Node.js MSI installer for Windows...")
-    filename = os.path.join(tempfile.gettempdir(), "node-installer.msi")
-    url = f"{NODE_DIST_URL}/v{NODE_VERSION}/node-v{NODE_VERSION}-x64.msi"
-    
-    try:
-        urllib.request.urlretrieve(url, filename)
-        print(f"✓ Downloaded installer ({os.path.getsize(filename)} bytes)")
-    except urllib.error.URLError as e:
-        print(f"\n❌ Failed to download Node.js installer from {url}")
-        print(f"Network error: {e}")
-        print("\n🔧 Next steps:")
-        print("1. Check your internet connection")
-        print("2. Download Node.js manually from: https://nodejs.org/")
-        print("3. Try again later if the server is temporarily unavailable")
-        sys.exit(1)
-    except OSError as e:
-        print(f"\n❌ Failed to save installer file to {filename}")
-        print(f"File system error: {e}")
-        print("\n🔧 Next steps:")
-        print("1. Check disk space and permissions")
-        print("2. Try running as administrator")
-        print("3. Download Node.js manually from: https://nodejs.org/")
-        sys.exit(1)
-    
-    print("Running installer (administrator privileges required - UAC prompt may appear)...")
-    try:
-        subprocess.run(
-            ["msiexec", "/i", filename, "/quiet", "/norestart"],
-            check=True,
-            capture_output=True,
-            text=True,
-        )
-    except subprocess.CalledProcessError as e:
-        print("\n❌ Node.js installation failed.")
-        print("\nCommon causes and solutions:")
-        print("• Insufficient administrator privileges - Run as administrator")
-        print("• Windows Installer service issues - Restart Windows Installer service")
-        print("• MSI file corruption - Try downloading again")
-        print("• Conflicting existing installation - Uninstall old Node.js first")
-        
-        if e.stderr:
-            print(f"\nInstaller error output: {e.stderr.strip()}")
-        if e.stdout:
-            print(f"Installer output: {e.stdout.strip()}")
-        print(f"Return code: {e.returncode}")
-        
-        print("\n🔧 Next steps:")
-        print("1. Download and install Node.js manually from: https://nodejs.org/")
-        print("2. Or use Windows Package Manager: winget install OpenJS.NodeJS")
-        print("3. Or use Chocolatey: choco install nodejs")
-        print("4. Restart your terminal after installation")
-        sys.exit(1)
-    except FileNotFoundError:
-        print("\n❌ Windows Installer (msiexec) not found.")
-        print("This indicates a serious Windows system issue.")
-        print("\nPlease install Node.js manually from: https://nodejs.org/")
-        sys.exit(1)
-    except PermissionError:
-        print("\n❌ Permission denied when running installer.")
-        print("Please run this script as administrator or install Node.js manually.")
-        print("\nManual installation: https://nodejs.org/")
-        sys.exit(1)
-    
-    print("Node.js installed successfully.")
+    if not _get_user_consent_for_installation():
+        sys.exit(0)
+    
+    print("Downloading Node.js MSI installer for Windows...")
+    filename = _download_windows_installer()
+    _run_windows_installer(filename)
+    
     node_path = "C:\\Program Files\\nodejs"
     os.environ["PATH"] = node_path + os.pathsep + os.environ["PATH"]
-    
     print("Node.js installed successfully and added to PATH.")
     return node_path
📝 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.

Suggested change
def install_node_windows():
"""Install Node.js on Windows using MSI installer with user consent and return the installation path."""
print("\nNode.js is required but not found on your system.")
print(
"This script can automatically install Node.js, but it requires administrator privileges."
)
print(f"Node.js version {NODE_VERSION} will be downloaded and installed.")
while True:
choice = (
input(
"\nDo you want to proceed with the automatic installation? (y/n/help): "
)
.lower()
.strip()
)
if choice in ["y", "yes"]:
break
elif choice in ["n", "no"]:
print("\nInstallation cancelled. Alternative installation options:")
print("1. Download and install Node.js manually from: https://nodejs.org/")
print("2. Use a package manager like Chocolatey: choco install nodejs")
print("3. Use Scoop: scoop install nodejs")
print("4. Use Windows Package Manager: winget install OpenJS.NodeJS")
print("\nAfter installing Node.js, please run this script again.")
sys.exit(0)
elif choice == "help":
print("\nAutomatic installation details:")
print("- Downloads Node.js MSI installer from official source")
print("- Runs: msiexec /i node-installer.msi /quiet /norestart")
print("- Requires administrator privileges (UAC prompt may appear)")
print("- Installs to: C:\\Program Files\\nodejs")
continue
else:
print(
"Please enter 'y' for yes, 'n' for no, or 'help' for more information."
)
print("Downloading Node.js MSI installer for Windows...")
filename = os.path.join(tempfile.gettempdir(), "node-installer.msi")
url = f"{NODE_DIST_URL}/v{NODE_VERSION}/node-v{NODE_VERSION}-x64.msi"
try:
urllib.request.urlretrieve(url, filename)
print(f"✓ Downloaded installer ({os.path.getsize(filename)} bytes)")
except urllib.error.URLError as e:
print(f"\n❌ Failed to download Node.js installer from {url}")
print(f"Network error: {e}")
print("\n🔧 Next steps:")
print("1. Check your internet connection")
print("2. Download Node.js manually from: https://nodejs.org/")
print("3. Try again later if the server is temporarily unavailable")
sys.exit(1)
except OSError as e:
print(f"\n❌ Failed to save installer file to {filename}")
print(f"File system error: {e}")
print("\n🔧 Next steps:")
print("1. Check disk space and permissions")
print("2. Try running as administrator")
print("3. Download Node.js manually from: https://nodejs.org/")
sys.exit(1)
print(
"Running installer (administrator privileges required - UAC prompt may appear)..."
)
try:
subprocess.run(
["msiexec", "/i", filename, "/quiet", "/norestart"],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
print("\n❌ Node.js installation failed.")
print("\nCommon causes and solutions:")
print("• Insufficient administrator privileges - Run as administrator")
print("• Windows Installer service issues - Restart Windows Installer service")
print("• MSI file corruption - Try downloading again")
print("• Conflicting existing installation - Uninstall old Node.js first")
if e.stderr:
print(f"\nInstaller error output: {e.stderr.strip()}")
if e.stdout:
print(f"Installer output: {e.stdout.strip()}")
print(f"Return code: {e.returncode}")
print("\n🔧 Next steps:")
print("1. Download and install Node.js manually from: https://nodejs.org/")
print("2. Or use Windows Package Manager: winget install OpenJS.NodeJS")
print("3. Or use Chocolatey: choco install nodejs")
print("4. Restart your terminal after installation")
sys.exit(1)
except FileNotFoundError:
print("\n❌ Windows Installer (msiexec) not found.")
print("This indicates a serious Windows system issue.")
print("\nPlease install Node.js manually from: https://nodejs.org/")
sys.exit(1)
except PermissionError:
print("\n❌ Permission denied when running installer.")
print("Please run this script as administrator or install Node.js manually.")
print("\nManual installation: https://nodejs.org/")
sys.exit(1)
print("Node.js installed successfully.")
node_path = "C:\\Program Files\\nodejs"
os.environ["PATH"] = node_path + os.pathsep + os.environ["PATH"]
print("Node.js installed successfully and added to PATH.")
return node_path
# Add these helper functions above install_node_windows()
def _get_user_consent_for_installation():
"""Get user consent for Windows Node.js installation."""
print("\nNode.js is required but not found on your system.")
print("This script can automatically install Node.js, but it requires administrator privileges.")
print(f"Node.js version {NODE_VERSION} will be downloaded and installed.")
while True:
choice = input("\nDo you want to proceed with the automatic installation? (y/n/help): ").lower().strip()
if choice in ["y", "yes"]:
return True
elif choice in ["n", "no"]:
print("\nInstallation cancelled. Alternative installation options:")
print("1. Download and install Node.js manually from: https://nodejs.org/")
print("2. Use a package manager like Chocolatey: choco install nodejs")
print("3. Use Scoop: scoop install nodejs")
print("4. Use Windows Package Manager: winget install OpenJS.NodeJS")
print("\nAfter installing Node.js, please run this script again.")
return False
elif choice == "help":
print("\nAutomatic installation details:")
print("- Downloads Node.js MSI installer from official source")
print("- Runs: msiexec /i node-installer.msi /quiet /norestart")
print("- Requires administrator privileges (UAC prompt may appear)")
print("- Installs to: C:\\Program Files\\nodejs")
else:
print("Please enter 'y' for yes, 'n' for no, or 'help' for more information.")
def _download_windows_installer():
"""Download the Windows Node.js installer."""
filename = os.path.join(tempfile.gettempdir(), "node-installer.msi")
url = f"{NODE_DIST_URL}/v{NODE_VERSION}/node-v{NODE_VERSION}-x64.msi"
try:
urllib.request.urlretrieve(url, filename)
print(f"✓ Downloaded installer ({os.path.getsize(filename)} bytes)")
return filename
except urllib.error.URLError as e:
print(f"\n❌ Failed to download Node.js installer from {url}")
print(f"Network error: {e}")
print("\n🔧 Next steps:")
print("1. Check your internet connection")
print("2. Download Node.js manually from: https://nodejs.org/")
print("3. Try again later if the server is temporarily unavailable")
sys.exit(1)
except OSError as e:
print(f"\n❌ Failed to save installer file to {filename}")
print(f"File system error: {e}")
print("\n🔧 Next steps:")
print("1. Check disk space and permissions")
print("2. Try running as administrator")
print("3. Download Node.js manually from: https://nodejs.org/")
sys.exit(1)
def _run_windows_installer(filename):
"""Run the Windows Node.js installer."""
print("Running installer (administrator privileges required - UAC prompt may appear)...")
try:
subprocess.run(
["msiexec", "/i", filename, "/quiet", "/norestart"],
check=True,
capture_output=True,
text=True,
)
print("Node.js installed successfully.")
except subprocess.CalledProcessError as e:
print("\n❌ Node.js installation failed.")
print("\nCommon causes and solutions:")
print("• Insufficient administrator privileges - Run as administrator")
print("• Windows Installer service issues - Restart Windows Installer service")
print("• MSI file corruption - Try downloading again")
print("• Conflicting existing installation - Uninstall old Node.js first")
if e.stderr:
print(f"\nInstaller error output: {e.stderr.strip()}")
if e.stdout:
print(f"Installer output: {e.stdout.strip()}")
print(f"Return code: {e.returncode}")
print("\n🔧 Next steps:")
print("1. Download and install Node.js manually from: https://nodejs.org/")
print("2. Or use Windows Package Manager: winget install OpenJS.NodeJS")
print("3. Or use Chocolatey: choco install nodejs")
print("4. Restart your terminal after installation")
sys.exit(1)
except (FileNotFoundError, PermissionError) as e:
if isinstance(e, FileNotFoundError):
print("\n❌ Windows Installer (msiexec) not found.")
print("This indicates a serious Windows system issue.")
else:
print("\n❌ Permission denied when running installer.")
print("Please run this script as administrator or install Node.js manually.")
print("\nManual installation: https://nodejs.org/")
sys.exit(1)
# Refactored install_node_windows
def install_node_windows():
"""Install Node.js on Windows using MSI installer with user consent and return the installation path."""
if not _get_user_consent_for_installation():
sys.exit(0)
print("Downloading Node.js MSI installer for Windows...")
filename = _download_windows_installer()
_run_windows_installer(filename)
node_path = "C:\\Program Files\\nodejs"
os.environ["PATH"] = node_path + os.pathsep + os.environ["PATH"]
print("Node.js installed successfully and added to PATH.")
return node_path
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 147-167: Unnecessary "elif" after "break", remove the leading "el" from "elif"

(R1723)


[refactor] 130-130: Too many statements (84/50)

(R0915)

🤖 Prompt for AI Agents
In init_dependency.py between lines 130 and 240, the install_node_windows
function is too large and handles multiple concerns, making it complex. Refactor
by splitting it into smaller functions such as one for user consent and input
handling, one for downloading the installer, one for running the installer, and
one for error handling and messaging. This modular approach will improve
readability and maintainability.

Comment on lines +242 to +381
def ensure_ccusage_available():
"""Ensure ccusage is available via npx."""
try:
# Find npm and npx commands - try multiple locations
npm_cmd = shutil.which("npm")
npx_cmd = shutil.which("npx")

# If not found in PATH, check common installation locations
if not npm_cmd:
common_paths = [
os.path.join(os.environ.get("HOME", ""), ".local", "bin", "npm"),
"/usr/local/bin/npm",
"/usr/bin/npm",
"C:\\Program Files\\nodejs\\npm.cmd"
if platform.system() == "Windows"
else None,
]
# Also check in the directory where we might have just installed Node.js
if os.path.exists("nodejs"):
for subdir in os.listdir("nodejs"):
if subdir.startswith("node-v"):
npm_path = os.path.join("nodejs", subdir, "bin", "npm")
if os.path.exists(npm_path):
common_paths.insert(0, os.path.abspath(npm_path))

for path in common_paths:
if path and os.path.exists(path):
npm_cmd = path
break

if not npx_cmd:
common_paths = [
os.path.join(os.environ.get("HOME", ""), ".local", "bin", "npx"),
"/usr/local/bin/npx",
"/usr/bin/npx",
"C:\\Program Files\\nodejs\\npx.cmd"
if platform.system() == "Windows"
else None,
]
# Also check in the directory where we might have just installed Node.js
if os.path.exists("nodejs"):
for subdir in os.listdir("nodejs"):
if subdir.startswith("node-v"):
npx_path = os.path.join("nodejs", subdir, "bin", "npx")
if os.path.exists(npx_path):
common_paths.insert(0, os.path.abspath(npx_path))

for path in common_paths:
if path and os.path.exists(path):
npx_cmd = path
break

if not npm_cmd or not npx_cmd:
print("\n❌ npm or npx command not found even after installation.")
print("PATH environment variable may not be updated in current process.")
print("\n🔧 Please try:")
print("1. Restart your terminal and run the command again")
print("2. Or manually install ccusage: npm install -g ccusage")
sys.exit(1)

# Check if ccusage is available
result = subprocess.run(
[npx_cmd, "--no-install", "ccusage", "--version"],
capture_output=True,
text=True,
env=os.environ.copy(), # Use current environment
)
if result.returncode == 0:
print("✓ ccusage is available")
return # ccusage is available

print("Installing ccusage...")
# Try global installation first
try:
result = subprocess.run(
[npm_cmd, "install", "-g", "ccusage"],
check=True,
capture_output=True,
text=True,
env=os.environ.copy(), # Use current environment
)
print("✓ ccusage installed globally")
except subprocess.CalledProcessError as e:
print("⚠️ Global installation failed, trying local installation...")
if e.stderr:
print(f"Global install error: {e.stderr.strip()}")

# If global fails, install locally
try:
result = subprocess.run(
[npm_cmd, "install", "ccusage"],
check=True,
capture_output=True,
text=True,
env=os.environ.copy(), # Use current environment
)
print("✓ ccusage installed locally")
except subprocess.CalledProcessError as local_e:
print("\n❌ Both global and local ccusage installation failed.")
print("\nGlobal installation error:")
if e.stderr:
print(f" {e.stderr.strip()}")
print(f" Return code: {e.returncode}")

print("\nLocal installation error:")
if local_e.stderr:
print(f" {local_e.stderr.strip()}")
print(f" Return code: {local_e.returncode}")

print("\n🔧 Troubleshooting steps:")
print("1. Check npm permissions: npm config get prefix")
print("2. Try with sudo (Linux/Mac): sudo npm install -g ccusage")
print("3. Check npm registry: npm config get registry")
print("4. Clear npm cache: npm cache clean --force")
print("5. Manual install: npm install -g ccusage")
sys.exit(1)
except FileNotFoundError:
print("\n❌ npm command not found.")
print("Node.js and npm must be installed first.")
print("This should not happen if Node.js installation succeeded.")
sys.exit(1)
except PermissionError:
print("\n❌ Permission denied when running npm.")
print("Try running with elevated privileges or check npm permissions.")
print("\nOn Linux/Mac: sudo npm install -g ccusage")
print("On Windows: Run as administrator")
sys.exit(1)
except FileNotFoundError:
print("\n❌ npx command not found.")
print("Node.js and npm must be installed and available in PATH.")
print("Please restart your terminal or check your Node.js installation.")
sys.exit(1)
except Exception as e:
print(f"\n❌ Unexpected error during ccusage installation: {e}")
print("\n🔧 Manual installation steps:")
print("1. npm install -g ccusage")
print("2. Or use npx: npx ccusage --version")
print("3. Check Node.js installation: node --version")
sys.exit(1)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor the ccusage installation function to reduce complexity.

The ensure_ccusage_available function is extremely complex (88 statements, 25 branches, 6 nested levels) and handles too many responsibilities. Consider breaking it into smaller, focused functions.

+def _find_npm_npx_commands():
+    """Find npm and npx commands in common locations."""
+    npm_cmd = shutil.which("npm")
+    npx_cmd = shutil.which("npx")
+    
+    common_npm_paths = [
+        os.path.join(os.environ.get("HOME", ""), ".local", "bin", "npm"),
+        "/usr/local/bin/npm",
+        "/usr/bin/npm",
+        "C:\\Program Files\\nodejs\\npm.cmd" if platform.system() == "Windows" else None,
+    ]
+    
+    common_npx_paths = [
+        os.path.join(os.environ.get("HOME", ""), ".local", "bin", "npx"),
+        "/usr/local/bin/npx", 
+        "/usr/bin/npx",
+        "C:\\Program Files\\nodejs\\npx.cmd" if platform.system() == "Windows" else None,
+    ]
+    
+    # Check local nodejs installation directory
+    if os.path.exists("nodejs"):
+        for subdir in os.listdir("nodejs"):
+            if subdir.startswith("node-v"):
+                npm_path = os.path.join("nodejs", subdir, "bin", "npm")
+                npx_path = os.path.join("nodejs", subdir, "bin", "npx")
+                if os.path.exists(npm_path):
+                    common_npm_paths.insert(0, os.path.abspath(npm_path))
+                if os.path.exists(npx_path):
+                    common_npx_paths.insert(0, os.path.abspath(npx_path))
+    
+    # Find npm command
+    if not npm_cmd:
+        for path in common_npm_paths:
+            if path and os.path.exists(path):
+                npm_cmd = path
+                break
+    
+    # Find npx command  
+    if not npx_cmd:
+        for path in common_npx_paths:
+            if path and os.path.exists(path):
+                npx_cmd = path
+                break
+                
+    return npm_cmd, npx_cmd

+def _check_ccusage_available(npx_cmd):
+    """Check if ccusage is already available."""
+    result = subprocess.run(
+        [npx_cmd, "--no-install", "ccusage", "--version"],
+        capture_output=True,
+        text=True,
+        env=os.environ.copy(),
+    )
+    return result.returncode == 0

+def _install_ccusage(npm_cmd):
+    """Install ccusage globally or locally."""
+    print("Installing ccusage...")
+    
+    # Try global installation first
+    try:
+        subprocess.run(
+            [npm_cmd, "install", "-g", "ccusage"],
+            check=True,
+            capture_output=True,
+            text=True,
+            env=os.environ.copy(),
+        )
+        print("✓ ccusage installed globally")
+        return
+    except subprocess.CalledProcessError as e:
+        print("⚠️  Global installation failed, trying local installation...")
+        if e.stderr:
+            print(f"Global install error: {e.stderr.strip()}")
+    
+    # Try local installation
+    try:
+        subprocess.run(
+            [npm_cmd, "install", "ccusage"],
+            check=True,
+            capture_output=True,
+            text=True,
+            env=os.environ.copy(),
+        )
+        print("✓ ccusage installed locally")
+    except subprocess.CalledProcessError as local_e:
+        print("\n❌ Both global and local ccusage installation failed.")
+        print(f"\nGlobal installation error: {e.stderr.strip() if e.stderr else 'Unknown'}")
+        print(f"Local installation error: {local_e.stderr.strip() if local_e.stderr else 'Unknown'}")
+        print("\n🔧 Troubleshooting steps:")
+        print("1. Check npm permissions: npm config get prefix")
+        print("2. Try with sudo (Linux/Mac): sudo npm install -g ccusage")
+        print("3. Check npm registry: npm config get registry")
+        print("4. Clear npm cache: npm cache clean --force")
+        print("5. Manual install: npm install -g ccusage")
+        sys.exit(1)

 def ensure_ccusage_available():
     """Ensure ccusage is available via npx."""
     try:
-        # Find npm and npx commands - try multiple locations
-        npm_cmd = shutil.which("npm")
-        npx_cmd = shutil.which("npx")
-        
-        # If not found in PATH, check common installation locations
-        if not npm_cmd:
-            common_paths = [
-                os.path.join(os.environ.get("HOME", ""), ".local", "bin", "npm"),
-                "/usr/local/bin/npm",
-                "/usr/bin/npm",
-                "C:\\Program Files\\nodejs\\npm.cmd" if platform.system() == "Windows" else None,
-            ]
-            # Also check in the directory where we might have just installed Node.js
-            if os.path.exists("nodejs"):
-                for subdir in os.listdir("nodejs"):
-                    if subdir.startswith("node-v"):
-                        npm_path = os.path.join("nodejs", subdir, "bin", "npm")
-                        if os.path.exists(npm_path):
-                            common_paths.insert(0, os.path.abspath(npm_path))
-            
-            for path in common_paths:
-                if path and os.path.exists(path):
-                    npm_cmd = path
-                    break
-        
-        if not npx_cmd:
-            common_paths = [
-                os.path.join(os.environ.get("HOME", ""), ".local", "bin", "npx"),
-                "/usr/local/bin/npx",
-                "/usr/bin/npx",
-                "C:\\Program Files\\nodejs\\npx.cmd" if platform.system() == "Windows" else None,
-            ]
-            # Also check in the directory where we might have just installed Node.js
-            if os.path.exists("nodejs"):
-                for subdir in os.listdir("nodejs"):
-                    if subdir.startswith("node-v"):
-                        npx_path = os.path.join("nodejs", subdir, "bin", "npx")
-                        if os.path.exists(npx_path):
-                            common_paths.insert(0, os.path.abspath(npx_path))
-            
-            for path in common_paths:
-                if path and os.path.exists(path):
-                    npx_cmd = path
-                    break
+        npm_cmd, npx_cmd = _find_npm_npx_commands()
         
         if not npm_cmd or not npx_cmd:
             print("\n❌ npm or npx command not found even after installation.")
@@ -299,66 +350,13 @@ def ensure_ccusage_available():
             print("2. Or manually install ccusage: npm install -g ccusage")
             sys.exit(1)
         
-        # Check if ccusage is available
-        result = subprocess.run(
-            [npx_cmd, "--no-install", "ccusage", "--version"],
-            capture_output=True,
-            text=True,
-            env=os.environ.copy(),  # Use current environment
-        )
-        if result.returncode == 0:
+        if _check_ccusage_available(npx_cmd):
             print("✓ ccusage is available")
-            return  # ccusage is available
+            return
         
-        print("Installing ccusage...")
-        # Try global installation first
-        try:
-            result = subprocess.run(
-                [npm_cmd, "install", "-g", "ccusage"],
-                check=True,
-                capture_output=True,
-                text=True,
-                env=os.environ.copy(),  # Use current environment
-            )
-            print("✓ ccusage installed globally")
-        except subprocess.CalledProcessError as e:
-            print("⚠️  Global installation failed, trying local installation...")
-            if e.stderr:
-                print(f"Global install error: {e.stderr.strip()}")
-            
-            # If global fails, install locally
-            try:
-                result = subprocess.run(
-                    [npm_cmd, "install", "ccusage"],
-                    check=True,
-                    capture_output=True,
-                    text=True,
-                    env=os.environ.copy(),  # Use current environment
-                )
-                print("✓ ccusage installed locally")
-            except subprocess.CalledProcessError as local_e:
-                print("\n❌ Both global and local ccusage installation failed.")
-                print("\nGlobal installation error:")
-                if e.stderr:
-                    print(f"  {e.stderr.strip()}")
-                print(f"  Return code: {e.returncode}")
-                
-                print("\nLocal installation error:")
-                if local_e.stderr:
-                    print(f"  {local_e.stderr.strip()}")
-                print(f"  Return code: {local_e.returncode}")
-                
-                print("\n🔧 Troubleshooting steps:")
-                print("1. Check npm permissions: npm config get prefix")
-                print("2. Try with sudo (Linux/Mac): sudo npm install -g ccusage")
-                print("3. Check npm registry: npm config get registry")
-                print("4. Clear npm cache: npm cache clean --force")
-                print("5. Manual install: npm install -g ccusage")
-                sys.exit(1)
-        except FileNotFoundError:
-            print("\n❌ npm command not found.")
-            print("Node.js and npm must be installed first.")
-            print("This should not happen if Node.js installation succeeded.")
-            sys.exit(1)
-        except PermissionError:
-            print("\n❌ Permission denied when running npm.")
-            print("Try running with elevated privileges or check npm permissions.")
-            print("\nOn Linux/Mac: sudo npm install -g ccusage")
-            print("On Windows: Run as administrator")
-            sys.exit(1)
+        _install_ccusage(npm_cmd)
+        
+    except FileNotFoundError:
+        print("\n❌ npm command not found.")
+        print("Node.js and npm must be installed first.")
+        print("This should not happen if Node.js installation succeeded.")
+        sys.exit(1)
+    except PermissionError:
+        print("\n❌ Permission denied when running npm.")
+        print("Try running with elevated privileges or check npm permissions.")
+        print("\nOn Linux/Mac: sudo npm install -g ccusage")
+        print("On Windows: Run as administrator")
+        sys.exit(1)
     except FileNotFoundError:
         print("\n❌ npx command not found.")
         print("Node.js and npm must be installed and available in PATH.")
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 244-380: Too many nested blocks (6/5)

(R1702)


[refactor] 244-380: Too many nested blocks (6/5)

(R1702)


[refactor] 242-242: Too many branches (25/12)

(R0912)


[refactor] 242-242: Too many statements (88/50)

(R0915)

🤖 Prompt for AI Agents
In init_dependency.py from lines 242 to 381, the ensure_ccusage_available
function is overly complex with many nested branches and responsibilities.
Refactor by splitting it into smaller functions such as locating npm and npx
commands, checking ccusage availability, performing global installation, and
handling local installation fallback. Each function should have a single
responsibility to improve readability and maintainability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
claude_monitor.py (1)

317-541: Note: Main function complexity remains high.

As mentioned in previous reviews, the main function still has high complexity (36 local variables, 22 branches, 115 statements). While the functionality works well, this should be addressed in a future refactoring as previously discussed.

The refactoring to extract logical sections into separate functions should be prioritized in future development to improve maintainability and testability.

🧹 Nitpick comments (2)
check_dependency.py (2)

16-19: Consider improving error message consistency.

The error message uses a checkmark format (✗) which is good for user feedback, but consider adding more context about what the user should do next.

-        print("✗ node not found on PATH")
+        print("✗ node not found on PATH")
+        print("  Please install Node.js from https://nodejs.org/")

50-53: Consistent error message pattern.

Similar to the node check, consider adding installation guidance for npx as well.

-        print("✗ npx not found on PATH")
+        print("✗ npx not found on PATH")
+        print("  npx should be included with Node.js 8.2+")
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ca83eb and 585559e.

📒 Files selected for processing (4)
  • CHANGELOG.md (1 hunks)
  • check_dependency.py (1 hunks)
  • claude_monitor.py (5 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml
🧰 Additional context used
🪛 Pylint (3.3.7)
claude_monitor.py

[refactor] 273-274: Consider using 'max_tokens = max(max_tokens, tokens)' instead of unnecessary if block

(R1731)


[refactor] 317-317: Too many local variables (36/15)

(R0914)


[refactor] 396-397: Consider using 'token_limit = max(token_limit, new_limit)' instead of unnecessary if block

(R1731)


[refactor] 317-317: Too many branches (22/12)

(R0912)


[refactor] 317-317: Too many statements (115/50)

(R0915)

🔇 Additional comments (20)
check_dependency.py (4)

1-9: LGTM: Clean module structure with appropriate dependencies.

The imports and constants are well-defined. The minimum Node.js version 8.2 is reasonable as it includes npx by default.


20-34: Robust version parsing with good error handling.

The version parsing logic correctly handles the 'v' prefix and compares major/minor versions appropriately. The fallback for missing minor version (line 27) is a good defensive practice.


35-42: Comprehensive error handling covers edge cases.

The exception handling covers both subprocess errors and unexpected exceptions, providing clear feedback to users.


54-68: Good npx validation approach.

The npx test correctly suppresses stdout while capturing stderr for meaningful error reporting. The error handling pattern is consistent with the test_node() function.

claude_monitor.py (15)

3-10: Good import organization with new dependencies.

The imports are well-organized and the new dependencies (subprocess, sys, threading, datetime) support the enhanced functionality.


13-13: Proper dependency checking integration.

The import of dependency checking functions from the new module is clean and follows good separation of concerns.


15-22: Robust cross-platform terminal handling.

The conditional import of termios with fallback for systems without it is a good approach for cross-platform compatibility.


27-32: Updated subprocess call for npx compatibility.

The change from direct ccusage to npx ccusage improves compatibility as mentioned in the PR objectives.


56-66: Enhanced progress bar styling with proper color handling.

The progress bar improvements with consistent color codes and proper reset handling are well implemented.


282-297: Well-implemented terminal raw mode setup.

The terminal setup function properly handles the case where termios is not available and includes good error handling.


299-306: Proper terminal cleanup on exit.

The restore function ensures terminal settings are properly reset even if errors occur.


308-315: Input flushing prevents display corruption.

The flush_input function addresses the terminal corruption issue mentioned in past reviews and changelog.


319-320: Dependency validation at startup.

Calling the dependency check functions at the start of main() ensures the environment is properly validated before proceeding.


323-327: Threading event improves timing control.

Using a threading event instead of time.sleep provides better control over the refresh timing and allows for clean shutdown.


341-342: Proper cursor management for better UX.

Hiding the cursor during monitoring and the clear screen approach improves the user experience.


345-346: Input flushing prevents interference.

The flush_input call before each refresh prevents typed characters from interfering with the display.


363-387: Improved no-active-session display.

The enhanced display for when no session is active provides better user feedback than before.


393-398: Smart token limit auto-switching.

The automatic switch from pro to custom_max when limits are exceeded is a user-friendly feature.


521-537: Comprehensive cleanup on exit and error.

The exception handling properly restores cursor and terminal settings in both keyboard interrupt and error cases.

CHANGELOG.md (1)

1-59: Well-structured changelog following best practices.

The changelog follows the "Keep a Changelog" format with clear sections for Added, Changed, and Fixed items. The version links at the bottom are properly formatted and provide easy access to releases.

The documented changes accurately reflect the code changes observed in this PR:

  • Replacement of init_dependency.py with check_dependency.py
  • Terminal handling improvements ✓
  • Script renaming from ccusage_monitor.py to claude_monitor.py
  • npx usage instead of direct ccusage calls ✓
  • Modern packaging with pyproject.toml ✓

@Maciek-roboblog Maciek-roboblog merged commit bdf41a5 into main Jun 22, 2025
10 of 11 checks passed
@adawalli
Copy link

💪

AlexLoar pushed a commit to AlexLoar/Claude-Code-Usage-Monitor that referenced this pull request Jul 4, 2025
Add package manager & Update build process & Update npm lib process installation
@coderabbitai coderabbitai bot mentioned this pull request Jul 13, 2025
@Maciek-roboblog Maciek-roboblog deleted the uv branch July 15, 2025 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recommend Ruff for Linting, Formatting, and Type Checking Use proper package manager Please publish as a real package

2 participants