pipman-cli 1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pipman_cli-1.0/LICENSE.txt +21 -0
- pipman_cli-1.0/PKG-INFO +104 -0
- pipman_cli-1.0/README.md +78 -0
- pipman_cli-1.0/pyproject.toml +38 -0
- pipman_cli-1.0/setup.cfg +4 -0
- pipman_cli-1.0/src/__init__.py +9 -0
- pipman_cli-1.0/src/__main__.py +955 -0
- pipman_cli-1.0/src/pipman_cli.egg-info/PKG-INFO +104 -0
- pipman_cli-1.0/src/pipman_cli.egg-info/SOURCES.txt +11 -0
- pipman_cli-1.0/src/pipman_cli.egg-info/dependency_links.txt +1 -0
- pipman_cli-1.0/src/pipman_cli.egg-info/entry_points.txt +2 -0
- pipman_cli-1.0/src/pipman_cli.egg-info/requires.txt +1 -0
- pipman_cli-1.0/src/pipman_cli.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Irfan Haider
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pipman_cli-1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pipman-cli
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Ultimate Python Package Manager - Cross-Platform CLI tool for managing Python packages with smart features
|
|
5
|
+
Author-email: Irfan Haider <irfanhaider.expert@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Irfanh-dev/Pipman-CLI
|
|
8
|
+
Project-URL: Repository, https://github.com/Irfanh-dev/Pipman-CLI.git
|
|
9
|
+
Keywords: pip,package-manager,cli,python-packages,updater
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE.txt
|
|
24
|
+
Requires-Dist: requests>=2.25.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Pipman-CLI ( Ultimate Python Package Manager) By Irfan
|
|
28
|
+
|
|
29
|
+
A powerful, cross-platform command-line tool for managing **Python packages** with `intelligent scanning`, `smart updates`, and `beautiful terminal output`. Specially helpful for "vibe coders" and developers who want a faster way to handle dependencies without the headache of manual pip commands. Just run, scan, and update.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## ✨ Features
|
|
33
|
+
|
|
34
|
+
* **Intelligent Scanning:** Fast, concurrent package scanning with real-time progress tracking.
|
|
35
|
+
* **Smart Updates:** Supports fuzzy matching and multi-select package updates for maximum efficiency.
|
|
36
|
+
* **Modern Terminal GUI:** Features a colorful ANSI-coded interface so managing packages won't feel static or boring.
|
|
37
|
+
* **Batch Operations:** Update multiple specific packages or entire ranges (e.g., 1-4) in one go.
|
|
38
|
+
* **Cross-Platform:** Works 100% on Windows, macOS, and Linux.
|
|
39
|
+
* **Interactive Mode:** User-friendly CLI with loading animations and interruptible scans.
|
|
40
|
+
|
|
41
|
+
## 🖼️ Preview
|
|
42
|
+
<img src="pipman-cli-scan.png" width="700" />
|
|
43
|
+
|
|
44
|
+
<img src="pipman-cli-update.png" width="700" />
|
|
45
|
+
|
|
46
|
+
## 🚀 Installation
|
|
47
|
+
|
|
48
|
+
1. Ensure you have **Python 3.6+** and **pip** installed on your system.
|
|
49
|
+
2. Download project and run `pipman-cli.py`
|
|
50
|
+
<!-- 3. write pip install pipman-cli in terminal -->
|
|
51
|
+
## 📦 How to Usage
|
|
52
|
+
### Available Commands
|
|
53
|
+
|
|
54
|
+
| Command | Description |
|
|
55
|
+
|---------|-------------|
|
|
56
|
+
| scan | Scan all installed packages for updates (press 'q' to stop early) |
|
|
57
|
+
| update <name/pattern> | Smart update with fuzzy matching and multi-select |
|
|
58
|
+
| list | Show all installed packages |
|
|
59
|
+
| help | Display available commands |
|
|
60
|
+
| legend | Show color legend |
|
|
61
|
+
| exit | Exit the program |
|
|
62
|
+
|
|
63
|
+
<!-- * **Live on Github** [Click here!](https://github.com/Irfanh-dev/pipman-cli) -->
|
|
64
|
+
|
|
65
|
+
**Instructions;**
|
|
66
|
+
**1.** type `Scan` after running pipman-cli.py, to scan the packages installed on your system.
|
|
67
|
+
**2.** type `legend` to understand what each color means.
|
|
68
|
+
**3.** type `update all` to update all out updated libraries.
|
|
69
|
+
**4.** type `update {package_name}` to update specific one. (e.g. update ytdlp)
|
|
70
|
+
## ⚙️ Development & Technology
|
|
71
|
+
|
|
72
|
+
This tool is built for speed and reliability using:
|
|
73
|
+
|
|
74
|
+
* **Python 3.6+:** The core engine for package management.
|
|
75
|
+
* **Requests Library:** Handles version checking and dependency data.
|
|
76
|
+
* **Subprocess & Threading:** Powers the concurrent scanning and background tasks.
|
|
77
|
+
* **ANSI Styling:** For the attractive, color-coded terminal interface.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## Acknowledgments
|
|
81
|
+
|
|
82
|
+
- Built with Python's subprocess and
|
|
83
|
+
requests libraries
|
|
84
|
+
- Inspired by the need for a better pip user experience
|
|
85
|
+
- Thanks to the Python community for excellent package management tools
|
|
86
|
+
|
|
87
|
+
## Issues & Support
|
|
88
|
+
|
|
89
|
+
If you encounter any issues or have suggestions:
|
|
90
|
+
|
|
91
|
+
1. Check the [Issues](https://github.com/Irfanh-dev/pipman-cli/issues) page
|
|
92
|
+
2. Create a new issue with detailed information
|
|
93
|
+
3. Include your Python version, OS, and error messages
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# 📜 [](LICENSE)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
* This project is open source and distributed under the **MIT License**.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
Made with 💙 by Irfan ([@Irfanh-dev](https://github.com/Irfanh-dev)). Assisted with AI || 🚀 Stay connected and check profile for More Awesome upcoming projects.
|
pipman_cli-1.0/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Pipman-CLI ( Ultimate Python Package Manager) By Irfan
|
|
2
|
+
|
|
3
|
+
A powerful, cross-platform command-line tool for managing **Python packages** with `intelligent scanning`, `smart updates`, and `beautiful terminal output`. Specially helpful for "vibe coders" and developers who want a faster way to handle dependencies without the headache of manual pip commands. Just run, scan, and update.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## ✨ Features
|
|
7
|
+
|
|
8
|
+
* **Intelligent Scanning:** Fast, concurrent package scanning with real-time progress tracking.
|
|
9
|
+
* **Smart Updates:** Supports fuzzy matching and multi-select package updates for maximum efficiency.
|
|
10
|
+
* **Modern Terminal GUI:** Features a colorful ANSI-coded interface so managing packages won't feel static or boring.
|
|
11
|
+
* **Batch Operations:** Update multiple specific packages or entire ranges (e.g., 1-4) in one go.
|
|
12
|
+
* **Cross-Platform:** Works 100% on Windows, macOS, and Linux.
|
|
13
|
+
* **Interactive Mode:** User-friendly CLI with loading animations and interruptible scans.
|
|
14
|
+
|
|
15
|
+
## 🖼️ Preview
|
|
16
|
+
<img src="pipman-cli-scan.png" width="700" />
|
|
17
|
+
|
|
18
|
+
<img src="pipman-cli-update.png" width="700" />
|
|
19
|
+
|
|
20
|
+
## 🚀 Installation
|
|
21
|
+
|
|
22
|
+
1. Ensure you have **Python 3.6+** and **pip** installed on your system.
|
|
23
|
+
2. Download project and run `pipman-cli.py`
|
|
24
|
+
<!-- 3. write pip install pipman-cli in terminal -->
|
|
25
|
+
## 📦 How to Usage
|
|
26
|
+
### Available Commands
|
|
27
|
+
|
|
28
|
+
| Command | Description |
|
|
29
|
+
|---------|-------------|
|
|
30
|
+
| scan | Scan all installed packages for updates (press 'q' to stop early) |
|
|
31
|
+
| update <name/pattern> | Smart update with fuzzy matching and multi-select |
|
|
32
|
+
| list | Show all installed packages |
|
|
33
|
+
| help | Display available commands |
|
|
34
|
+
| legend | Show color legend |
|
|
35
|
+
| exit | Exit the program |
|
|
36
|
+
|
|
37
|
+
<!-- * **Live on Github** [Click here!](https://github.com/Irfanh-dev/pipman-cli) -->
|
|
38
|
+
|
|
39
|
+
**Instructions;**
|
|
40
|
+
**1.** type `Scan` after running pipman-cli.py, to scan the packages installed on your system.
|
|
41
|
+
**2.** type `legend` to understand what each color means.
|
|
42
|
+
**3.** type `update all` to update all out updated libraries.
|
|
43
|
+
**4.** type `update {package_name}` to update specific one. (e.g. update ytdlp)
|
|
44
|
+
## ⚙️ Development & Technology
|
|
45
|
+
|
|
46
|
+
This tool is built for speed and reliability using:
|
|
47
|
+
|
|
48
|
+
* **Python 3.6+:** The core engine for package management.
|
|
49
|
+
* **Requests Library:** Handles version checking and dependency data.
|
|
50
|
+
* **Subprocess & Threading:** Powers the concurrent scanning and background tasks.
|
|
51
|
+
* **ANSI Styling:** For the attractive, color-coded terminal interface.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Acknowledgments
|
|
55
|
+
|
|
56
|
+
- Built with Python's subprocess and
|
|
57
|
+
requests libraries
|
|
58
|
+
- Inspired by the need for a better pip user experience
|
|
59
|
+
- Thanks to the Python community for excellent package management tools
|
|
60
|
+
|
|
61
|
+
## Issues & Support
|
|
62
|
+
|
|
63
|
+
If you encounter any issues or have suggestions:
|
|
64
|
+
|
|
65
|
+
1. Check the [Issues](https://github.com/Irfanh-dev/pipman-cli/issues) page
|
|
66
|
+
2. Create a new issue with detailed information
|
|
67
|
+
3. Include your Python version, OS, and error messages
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# 📜 [](LICENSE)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
* This project is open source and distributed under the **MIT License**.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
Made with 💙 by Irfan ([@Irfanh-dev](https://github.com/Irfanh-dev)). Assisted with AI || 🚀 Stay connected and check profile for More Awesome upcoming projects.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pipman-cli"
|
|
7
|
+
version = "1.0"
|
|
8
|
+
description = "Ultimate Python Package Manager - Cross-Platform CLI tool for managing Python packages with smart features"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Irfan Haider", email = "irfanhaider.expert@gmail.com"}
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Utilities",
|
|
26
|
+
]
|
|
27
|
+
keywords = ["pip", "package-manager", "cli", "python-packages", "updater"]
|
|
28
|
+
requires-python = ">=3.8"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"requests>=2.25.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/Irfanh-dev/Pipman-CLI"
|
|
35
|
+
Repository = "https://github.com/Irfanh-dev/Pipman-CLI.git"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
pipman-cli = "pipman_cli.__main__:main"
|
pipman_cli-1.0/setup.cfg
ADDED
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Ultimate Python Package Manager (Pipman-CLI)
|
|
4
|
+
Version 1.0 - Cross-Platform Edition
|
|
5
|
+
Compatible with Windows, macOS, and Linux
|
|
6
|
+
Author: Irfan Haider
|
|
7
|
+
Email: irfanhaider.expert@gmail.com
|
|
8
|
+
GitHub: https://github.com/Irfanh-dev/Pipman-CLI
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import subprocess
|
|
12
|
+
import json
|
|
13
|
+
import requests
|
|
14
|
+
import sys
|
|
15
|
+
import concurrent.futures
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
import select
|
|
19
|
+
import re
|
|
20
|
+
import os
|
|
21
|
+
import platform
|
|
22
|
+
import argparse
|
|
23
|
+
from difflib import get_close_matches
|
|
24
|
+
|
|
25
|
+
# Store outdated packages globally
|
|
26
|
+
outdated_packages_global = []
|
|
27
|
+
# tool version; changing this updates banner, user-agent, etc.
|
|
28
|
+
__version__ = "1.0"
|
|
29
|
+
# global indentation used for nicer layout
|
|
30
|
+
INDENT = " "
|
|
31
|
+
|
|
32
|
+
class Colors:
|
|
33
|
+
"""ANSI color codes for terminal output."""
|
|
34
|
+
GREEN = '\033[92m'
|
|
35
|
+
RED = '\033[91m'
|
|
36
|
+
YELLOW = '\033[93m'
|
|
37
|
+
BLUE = '\033[94m'
|
|
38
|
+
CYAN = '\033[96m'
|
|
39
|
+
WHITE = '\033[97m'
|
|
40
|
+
MAGENTA = '\033[95m'
|
|
41
|
+
BOLD = '\033[1m'
|
|
42
|
+
UNDERLINE = '\033[4m'
|
|
43
|
+
RESET = '\033[0m'
|
|
44
|
+
|
|
45
|
+
class PackageScanner:
|
|
46
|
+
"""Manages package scanning with interrupt capability."""
|
|
47
|
+
def __init__(self):
|
|
48
|
+
self.scanning = False
|
|
49
|
+
self.stop_requested = False
|
|
50
|
+
self.results = {}
|
|
51
|
+
self.lock = threading.Lock()
|
|
52
|
+
self.scanned_count = 0
|
|
53
|
+
self.total_packages = 0
|
|
54
|
+
|
|
55
|
+
class LoadingAnimation:
|
|
56
|
+
"""Display loading animation with moon phases."""
|
|
57
|
+
def __init__(self, print_lock=None):
|
|
58
|
+
self.moon_phases = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"]
|
|
59
|
+
self.spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
60
|
+
self.current = 0
|
|
61
|
+
self.running = False
|
|
62
|
+
self.thread = None
|
|
63
|
+
self.print_lock = print_lock or threading.Lock()
|
|
64
|
+
self.message = ""
|
|
65
|
+
|
|
66
|
+
def _animate(self):
|
|
67
|
+
"""Internal animation loop."""
|
|
68
|
+
while self.running:
|
|
69
|
+
moon = self.moon_phases[self.current % len(self.moon_phases)]
|
|
70
|
+
spinner = self.spinner[self.current % len(self.spinner)]
|
|
71
|
+
with self.print_lock:
|
|
72
|
+
sys.stdout.write(f"\r{Colors.MAGENTA}{spinner} {self.message} {moon} {Colors.RESET}")
|
|
73
|
+
sys.stdout.flush()
|
|
74
|
+
self.current += 1
|
|
75
|
+
time.sleep(0.1)
|
|
76
|
+
|
|
77
|
+
def start(self, message="Loading"):
|
|
78
|
+
"""Start loading animation."""
|
|
79
|
+
self.message = message
|
|
80
|
+
self.running = True
|
|
81
|
+
self.thread = threading.Thread(target=self._animate)
|
|
82
|
+
self.thread.daemon = True
|
|
83
|
+
self.thread.start()
|
|
84
|
+
|
|
85
|
+
def stop(self, message=""):
|
|
86
|
+
"""Stop loading animation."""
|
|
87
|
+
self.running = False
|
|
88
|
+
if self.thread:
|
|
89
|
+
self.thread.join(timeout=0.5)
|
|
90
|
+
with self.print_lock:
|
|
91
|
+
sys.stdout.write("\r" + " " * 80 + "\r")
|
|
92
|
+
sys.stdout.flush()
|
|
93
|
+
if message:
|
|
94
|
+
print(f"{message}")
|
|
95
|
+
|
|
96
|
+
def check_pip_availability():
|
|
97
|
+
"""Check if pip is available in the system."""
|
|
98
|
+
try:
|
|
99
|
+
# Try to run pip --version
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
[sys.executable, "-m", "pip", "--version"],
|
|
102
|
+
capture_output=True,
|
|
103
|
+
text=True,
|
|
104
|
+
timeout=5
|
|
105
|
+
)
|
|
106
|
+
return result.returncode == 0
|
|
107
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
def check_and_install_dependencies(verbose=False):
|
|
111
|
+
"""Check for required dependencies and install if missing."""
|
|
112
|
+
required_packages = {
|
|
113
|
+
'requests': 'requests'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
missing_packages = []
|
|
117
|
+
|
|
118
|
+
# Check each required package
|
|
119
|
+
for package_name, import_name in required_packages.items():
|
|
120
|
+
try:
|
|
121
|
+
__import__(import_name)
|
|
122
|
+
except ImportError:
|
|
123
|
+
missing_packages.append(package_name)
|
|
124
|
+
|
|
125
|
+
if not missing_packages:
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
# If packages are missing, try to install them
|
|
129
|
+
if verbose:
|
|
130
|
+
print(f"\n{INDENT}{Colors.YELLOW}Missing dependencies detected:{Colors.RESET}")
|
|
131
|
+
for pkg in missing_packages:
|
|
132
|
+
print(f"{INDENT}{Colors.RED}✗ {pkg}{Colors.RESET}")
|
|
133
|
+
print(f"\n{INDENT}{Colors.BLUE}Attempting automatic installation...{Colors.RESET}")
|
|
134
|
+
|
|
135
|
+
loader = LoadingAnimation()
|
|
136
|
+
|
|
137
|
+
for package in missing_packages:
|
|
138
|
+
if verbose:
|
|
139
|
+
loader.start(f"Installing {package}")
|
|
140
|
+
try:
|
|
141
|
+
result = subprocess.run(
|
|
142
|
+
[sys.executable, "-m", "pip", "install", package],
|
|
143
|
+
capture_output=True,
|
|
144
|
+
text=True,
|
|
145
|
+
timeout=60
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if result.returncode == 0:
|
|
149
|
+
if verbose:
|
|
150
|
+
loader.stop(f"{INDENT}{Colors.GREEN}✓ {package} installed successfully{Colors.RESET}")
|
|
151
|
+
else:
|
|
152
|
+
if verbose:
|
|
153
|
+
loader.stop()
|
|
154
|
+
print(f"{INDENT}{Colors.RED}✗ Failed to install {package}{Colors.RESET}")
|
|
155
|
+
if result.stderr:
|
|
156
|
+
error_msg = result.stderr.split('\n')[0][:100]
|
|
157
|
+
print(f"{INDENT}{Colors.YELLOW}Error: {error_msg}{Colors.RESET}")
|
|
158
|
+
return False
|
|
159
|
+
except subprocess.TimeoutExpired:
|
|
160
|
+
if verbose:
|
|
161
|
+
loader.stop()
|
|
162
|
+
print(f"{INDENT}{Colors.RED}✗ Installation timeout for {package}{Colors.RESET}")
|
|
163
|
+
return False
|
|
164
|
+
except Exception as e:
|
|
165
|
+
if verbose:
|
|
166
|
+
loader.stop()
|
|
167
|
+
print(f"{INDENT}{Colors.RED}✗ Error installing {package}: {str(e)}{Colors.RESET}")
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
if verbose:
|
|
171
|
+
print(f"\n{INDENT}{Colors.GREEN}✓ All dependencies installed successfully!{Colors.RESET}")
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
def get_installed_packages():
|
|
175
|
+
"""Return dict of installed packages with current versions."""
|
|
176
|
+
try:
|
|
177
|
+
# Use sys.executable to ensure we use the correct Python interpreter
|
|
178
|
+
result = subprocess.run(
|
|
179
|
+
[sys.executable, "-m", "pip", "list", "--format=json"],
|
|
180
|
+
capture_output=True,
|
|
181
|
+
text=True,
|
|
182
|
+
timeout=30
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if result.returncode != 0:
|
|
186
|
+
print(f"{INDENT}{Colors.RED}Error running pip: {result.stderr[:100]}{Colors.RESET}")
|
|
187
|
+
return {}
|
|
188
|
+
|
|
189
|
+
packages = json.loads(result.stdout)
|
|
190
|
+
return {pkg["name"]: pkg["version"] for pkg in packages}
|
|
191
|
+
except json.JSONDecodeError as e:
|
|
192
|
+
print(f"{INDENT}{Colors.RED}Failed to parse pip output: {e}{Colors.RESET}")
|
|
193
|
+
return {}
|
|
194
|
+
except Exception as e:
|
|
195
|
+
print(f"{INDENT}{Colors.RED}Error getting installed packages: {e}{Colors.RESET}")
|
|
196
|
+
return {}
|
|
197
|
+
|
|
198
|
+
def normalize_package_name(name):
|
|
199
|
+
"""Normalize package name for fuzzy matching."""
|
|
200
|
+
# Remove common separators and convert to lowercase
|
|
201
|
+
name = name.lower()
|
|
202
|
+
for sep in ['-', '_', '.', ' ', '=', '+']:
|
|
203
|
+
name = name.replace(sep, '')
|
|
204
|
+
return name
|
|
205
|
+
|
|
206
|
+
def find_similar_packages(all_packages, search_term, max_results=15):
|
|
207
|
+
"""Find packages similar to search term using intelligent matching."""
|
|
208
|
+
if not all_packages:
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
search_lower = search_term.lower()
|
|
212
|
+
search_normalized = normalize_package_name(search_term)
|
|
213
|
+
|
|
214
|
+
# Score each package based on match quality
|
|
215
|
+
scored_packages = []
|
|
216
|
+
|
|
217
|
+
for pkg in all_packages:
|
|
218
|
+
pkg_lower = pkg.lower()
|
|
219
|
+
pkg_normalized = normalize_package_name(pkg)
|
|
220
|
+
score = 0
|
|
221
|
+
|
|
222
|
+
# 1. Exact match (highest priority)
|
|
223
|
+
if pkg_lower == search_lower:
|
|
224
|
+
score = 1000
|
|
225
|
+
|
|
226
|
+
# 2. Package name starts with search term
|
|
227
|
+
elif pkg_lower.startswith(search_lower):
|
|
228
|
+
score = 900
|
|
229
|
+
|
|
230
|
+
# 3. Package name ends with search term
|
|
231
|
+
elif pkg_lower.endswith(search_lower):
|
|
232
|
+
score = 800
|
|
233
|
+
|
|
234
|
+
# 4. Package contains search term as whole word
|
|
235
|
+
words = re.split(r'[-_.]', pkg_lower)
|
|
236
|
+
if search_lower in words:
|
|
237
|
+
score = 700
|
|
238
|
+
|
|
239
|
+
# 5. Package contains search term (not necessarily whole word)
|
|
240
|
+
elif search_lower in pkg_lower:
|
|
241
|
+
score = 500
|
|
242
|
+
|
|
243
|
+
# 6. Normalized package contains normalized search term
|
|
244
|
+
elif search_normalized and search_normalized in pkg_normalized:
|
|
245
|
+
score = 300
|
|
246
|
+
|
|
247
|
+
if score > 0:
|
|
248
|
+
scored_packages.append((score, pkg))
|
|
249
|
+
|
|
250
|
+
# Sort by score (descending)
|
|
251
|
+
scored_packages.sort(reverse=True, key=lambda x: x[0])
|
|
252
|
+
|
|
253
|
+
# Get top matches
|
|
254
|
+
top_matches = [pkg for score, pkg in scored_packages[:max_results]]
|
|
255
|
+
|
|
256
|
+
# If we don't have enough matches and search term is reasonable length,
|
|
257
|
+
# add some fuzzy matches as last resort
|
|
258
|
+
if len(top_matches) < 3 and len(search_lower) > 2:
|
|
259
|
+
remaining = [p for p in all_packages if p not in top_matches]
|
|
260
|
+
if remaining:
|
|
261
|
+
fuzzy = get_close_matches(
|
|
262
|
+
search_lower,
|
|
263
|
+
remaining,
|
|
264
|
+
n=2,
|
|
265
|
+
cutoff=0.7
|
|
266
|
+
)
|
|
267
|
+
for pkg in fuzzy:
|
|
268
|
+
if pkg not in top_matches:
|
|
269
|
+
top_matches.append(pkg)
|
|
270
|
+
|
|
271
|
+
return top_matches[:max_results]
|
|
272
|
+
|
|
273
|
+
def get_package_info_simple(package_name):
|
|
274
|
+
"""Get only basic package info - faster than full metadata."""
|
|
275
|
+
try:
|
|
276
|
+
# Add timeout and better error handling
|
|
277
|
+
response = requests.get(
|
|
278
|
+
f"https://pypi.org/pypi/{package_name}/json",
|
|
279
|
+
timeout=5,
|
|
280
|
+
headers={'User-Agent': f'pipman-cli/{__version__}'}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if response.status_code == 200:
|
|
284
|
+
info = response.json()
|
|
285
|
+
latest_version = info["info"]["version"]
|
|
286
|
+
|
|
287
|
+
# Get approximate size from first available release file
|
|
288
|
+
releases = info["releases"].get(latest_version, [])
|
|
289
|
+
size_bytes = 0
|
|
290
|
+
|
|
291
|
+
for release in releases:
|
|
292
|
+
if release.get("size"):
|
|
293
|
+
size_bytes = release.get("size")
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
size_mb = round(size_bytes / (1024 * 1024), 1) if size_bytes > 0 else 0
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
"latest": latest_version,
|
|
300
|
+
"size": size_mb,
|
|
301
|
+
"success": True,
|
|
302
|
+
"summary": info["info"].get("summary", "")
|
|
303
|
+
}
|
|
304
|
+
elif response.status_code == 404:
|
|
305
|
+
return {"latest": None, "size": 0, "success": False, "error": "Package not found on PyPI"}
|
|
306
|
+
except requests.exceptions.Timeout:
|
|
307
|
+
return {"latest": None, "size": 0, "success": False, "error": "Request timeout"}
|
|
308
|
+
except requests.exceptions.ConnectionError:
|
|
309
|
+
return {"latest": None, "size": 0, "success": False, "error": "Network connection error"}
|
|
310
|
+
except requests.RequestException as e:
|
|
311
|
+
return {"latest": None, "size": 0, "success": False, "error": str(e)}
|
|
312
|
+
|
|
313
|
+
return {"latest": None, "size": 0, "success": False, "error": "Unknown error"}
|
|
314
|
+
|
|
315
|
+
def scan_package_batch(package_names, scanner):
|
|
316
|
+
"""Scan a batch of packages with progress reporting."""
|
|
317
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
|
318
|
+
future_to_package = {
|
|
319
|
+
executor.submit(get_package_info_simple, name): name
|
|
320
|
+
for name in package_names
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
for future in concurrent.futures.as_completed(future_to_package):
|
|
324
|
+
if scanner.stop_requested:
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
package_name = future_to_package[future]
|
|
328
|
+
try:
|
|
329
|
+
result = future.result(timeout=5)
|
|
330
|
+
with scanner.lock:
|
|
331
|
+
scanner.results[package_name] = result
|
|
332
|
+
scanner.scanned_count += 1
|
|
333
|
+
except Exception as e:
|
|
334
|
+
with scanner.lock:
|
|
335
|
+
scanner.results[package_name] = {
|
|
336
|
+
"latest": None,
|
|
337
|
+
"size": 0,
|
|
338
|
+
"success": False,
|
|
339
|
+
"error": str(e)
|
|
340
|
+
}
|
|
341
|
+
scanner.scanned_count += 1
|
|
342
|
+
|
|
343
|
+
def show_packages_with_progress(installed, scanner):
|
|
344
|
+
"""Display packages with real-time progress and interrupt capability."""
|
|
345
|
+
print(f"\n{Colors.BOLD}{'Package':<30}{'Current':<15}{'Latest':<15}{'Size':<8}{'Status':<10}{Colors.RESET}")
|
|
346
|
+
print(f"{Colors.CYAN}{'-'*88}{Colors.RESET}")
|
|
347
|
+
|
|
348
|
+
# Store the lines that will be printed during scanning
|
|
349
|
+
scan_output_lines = []
|
|
350
|
+
scan_output_lines.append(f"\n{Colors.BOLD}{'Package':<30}{'Current':<15}{'Latest':<15}{'Size':<8}{'Status':<10}{Colors.RESET}")
|
|
351
|
+
scan_output_lines.append(f"{Colors.CYAN}{'-'*88}{Colors.RESET}")
|
|
352
|
+
|
|
353
|
+
# Start scanning in background thread
|
|
354
|
+
scanner.scanning = True
|
|
355
|
+
scanner.total_packages = len(installed)
|
|
356
|
+
|
|
357
|
+
# Start scanning thread
|
|
358
|
+
scan_thread = threading.Thread(
|
|
359
|
+
target=scan_package_batch,
|
|
360
|
+
args=(list(installed.keys()), scanner)
|
|
361
|
+
)
|
|
362
|
+
scan_thread.daemon = True
|
|
363
|
+
scan_thread.start()
|
|
364
|
+
|
|
365
|
+
outdated = {}
|
|
366
|
+
displayed_packages = set()
|
|
367
|
+
|
|
368
|
+
# Create a shared print lock for animation and package printing
|
|
369
|
+
print_lock = threading.Lock()
|
|
370
|
+
|
|
371
|
+
# Start loading animation
|
|
372
|
+
loader = LoadingAnimation(print_lock=print_lock)
|
|
373
|
+
loader.start(f"Scanning packages [0/{scanner.total_packages}]")
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
while scanner.scanning and not scanner.stop_requested:
|
|
377
|
+
# Display newly scanned packages
|
|
378
|
+
with scanner.lock:
|
|
379
|
+
current_scanned = scanner.scanned_count
|
|
380
|
+
# Update animation message with progress
|
|
381
|
+
loader.message = f"Scanning packages [{current_scanned}/{scanner.total_packages}]"
|
|
382
|
+
|
|
383
|
+
# Display newly scanned packages
|
|
384
|
+
for name, current in installed.items():
|
|
385
|
+
if name in scanner.results and name not in displayed_packages:
|
|
386
|
+
result = scanner.results[name]
|
|
387
|
+
latest = result.get("latest")
|
|
388
|
+
size = result.get("size", 0)
|
|
389
|
+
# Determine color and status
|
|
390
|
+
if latest is None:
|
|
391
|
+
name_color = Colors.YELLOW
|
|
392
|
+
status = "unknown"
|
|
393
|
+
elif latest == current:
|
|
394
|
+
name_color = Colors.GREEN
|
|
395
|
+
status = "updated"
|
|
396
|
+
else:
|
|
397
|
+
name_color = Colors.RED
|
|
398
|
+
status = "outdated"
|
|
399
|
+
outdated[name] = (latest, size)
|
|
400
|
+
latest_display = latest if latest else '-'
|
|
401
|
+
size_display = f"{size:.1f}M" if size > 0 else '-'
|
|
402
|
+
status_display = status.upper()
|
|
403
|
+
|
|
404
|
+
# Stop animation temporarily to print package line
|
|
405
|
+
loader.stop()
|
|
406
|
+
|
|
407
|
+
line = (f"{name_color}{name:<30}{Colors.RESET}"
|
|
408
|
+
f"{Colors.WHITE}{current:<15}{Colors.RESET}"
|
|
409
|
+
f"{Colors.CYAN}{latest_display:<15}{Colors.RESET}"
|
|
410
|
+
f"{Colors.YELLOW}{size_display:<8}{Colors.RESET}"
|
|
411
|
+
f"{name_color}{status_display:<10}{Colors.RESET}")
|
|
412
|
+
print(line)
|
|
413
|
+
scan_output_lines.append(line)
|
|
414
|
+
|
|
415
|
+
# Restart animation
|
|
416
|
+
loader.start(f"Scanning packages [{current_scanned}/{scanner.total_packages}]")
|
|
417
|
+
|
|
418
|
+
displayed_packages.add(name)
|
|
419
|
+
|
|
420
|
+
# Check for user interrupt (non-blocking)
|
|
421
|
+
try:
|
|
422
|
+
if sys.stdin in select.select([sys.stdin], [], [], 0.1)[0]:
|
|
423
|
+
line = sys.stdin.readline().strip()
|
|
424
|
+
if line and line.lower() == 'q':
|
|
425
|
+
loader.stop()
|
|
426
|
+
with print_lock:
|
|
427
|
+
print(f"\n{INDENT}{Colors.YELLOW}Scan interrupted by user.{Colors.RESET}")
|
|
428
|
+
scanner.stop_requested = True
|
|
429
|
+
break
|
|
430
|
+
except:
|
|
431
|
+
pass
|
|
432
|
+
|
|
433
|
+
# Check if scanning is complete
|
|
434
|
+
if scanner.scanned_count >= scanner.total_packages:
|
|
435
|
+
scanner.scanning = False
|
|
436
|
+
break
|
|
437
|
+
|
|
438
|
+
# Small sleep to prevent CPU spinning
|
|
439
|
+
time.sleep(0.1)
|
|
440
|
+
|
|
441
|
+
except KeyboardInterrupt:
|
|
442
|
+
loader.stop()
|
|
443
|
+
with print_lock:
|
|
444
|
+
print(f"\n{INDENT}{Colors.YELLOW}Scan interrupted by Ctrl+C.{Colors.RESET}")
|
|
445
|
+
scanner.stop_requested = True
|
|
446
|
+
finally:
|
|
447
|
+
# Stop loading animation
|
|
448
|
+
loader.stop()
|
|
449
|
+
|
|
450
|
+
# Wait for scan thread to finish
|
|
451
|
+
scanner.scanning = False
|
|
452
|
+
scan_thread.join(timeout=2)
|
|
453
|
+
|
|
454
|
+
# Clear all scan output lines from terminal before final summary
|
|
455
|
+
# +2 for the header lines
|
|
456
|
+
for _ in range(len(scan_output_lines)):
|
|
457
|
+
with print_lock:
|
|
458
|
+
sys.stdout.write("\033[F\033[2K") # Move up and clear line
|
|
459
|
+
sys.stdout.flush()
|
|
460
|
+
|
|
461
|
+
return outdated
|
|
462
|
+
|
|
463
|
+
def show_packages_final(installed, outdated_info):
|
|
464
|
+
"""Display final package list after scanning."""
|
|
465
|
+
print(f"\n{INDENT}{Colors.BOLD}FINAL RESULTS:{Colors.RESET}")
|
|
466
|
+
print(f"{Colors.BOLD}{'Package':<30}{'Current':<15}{'Latest':<15}{'Size':<8}{'Status':<10}{Colors.RESET}")
|
|
467
|
+
print(f"{Colors.CYAN}{'-'*88}{Colors.RESET}")
|
|
468
|
+
|
|
469
|
+
outdated = {}
|
|
470
|
+
|
|
471
|
+
for name, current in installed.items():
|
|
472
|
+
result = outdated_info.get(name, {"latest": None, "size": 0})
|
|
473
|
+
latest = result.get("latest")
|
|
474
|
+
size = result.get("size", 0)
|
|
475
|
+
|
|
476
|
+
if latest and latest != current:
|
|
477
|
+
outdated[name] = (latest, size)
|
|
478
|
+
name_color = Colors.RED
|
|
479
|
+
status = "OUTDATED"
|
|
480
|
+
elif latest == current:
|
|
481
|
+
name_color = Colors.GREEN
|
|
482
|
+
status = "UPDATED"
|
|
483
|
+
else:
|
|
484
|
+
name_color = Colors.YELLOW
|
|
485
|
+
status = "UNKNOWN"
|
|
486
|
+
|
|
487
|
+
latest_display = latest if latest else '-'
|
|
488
|
+
size_display = f"{size:.1f}M" if size > 0 else '-'
|
|
489
|
+
|
|
490
|
+
print(f"{name_color}{name:<30}{Colors.RESET}"
|
|
491
|
+
f"{Colors.WHITE}{current:<15}{Colors.RESET}"
|
|
492
|
+
f"{Colors.CYAN}{latest_display:<15}{Colors.RESET}"
|
|
493
|
+
f"{Colors.YELLOW}{size_display:<8}{Colors.RESET}"
|
|
494
|
+
f"{name_color}{status:<10}{Colors.RESET}")
|
|
495
|
+
|
|
496
|
+
return outdated
|
|
497
|
+
|
|
498
|
+
def update_packages(packages_to_update):
|
|
499
|
+
"""Update packages using pip, handle pip specially."""
|
|
500
|
+
if not packages_to_update:
|
|
501
|
+
print(f"{INDENT}{Colors.YELLOW}No packages to update.{Colors.RESET}")
|
|
502
|
+
return
|
|
503
|
+
|
|
504
|
+
print(f"\n{INDENT}{Colors.BLUE}Preparing to update {len(packages_to_update)} package(s)...{Colors.RESET}")
|
|
505
|
+
|
|
506
|
+
for i, name in enumerate(packages_to_update, 1):
|
|
507
|
+
print(f"\n{INDENT}{Colors.BLUE}[{i}/{len(packages_to_update)}] {Colors.BOLD}{name}{Colors.RESET}")
|
|
508
|
+
|
|
509
|
+
# Create loading animation for this package update
|
|
510
|
+
loader = LoadingAnimation()
|
|
511
|
+
loader.start(f"Updating {name}")
|
|
512
|
+
|
|
513
|
+
try:
|
|
514
|
+
if name.lower() == "pip":
|
|
515
|
+
result = subprocess.run(
|
|
516
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "pip"],
|
|
517
|
+
capture_output=True,
|
|
518
|
+
text=True
|
|
519
|
+
)
|
|
520
|
+
else:
|
|
521
|
+
result = subprocess.run(
|
|
522
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", name],
|
|
523
|
+
capture_output=True,
|
|
524
|
+
text=True
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
loader.stop()
|
|
528
|
+
|
|
529
|
+
if result.returncode == 0:
|
|
530
|
+
print(f"{INDENT}{Colors.GREEN}✓ Successfully updated {name}{Colors.RESET}")
|
|
531
|
+
else:
|
|
532
|
+
print(f"{INDENT}{Colors.RED}✗ Failed to update {name}{Colors.RESET}")
|
|
533
|
+
if result.stderr:
|
|
534
|
+
error_msg = result.stderr.split('\n')[0][:100]
|
|
535
|
+
print(f"{INDENT}{Colors.YELLOW}Error: {error_msg}...{Colors.RESET}")
|
|
536
|
+
except Exception as e:
|
|
537
|
+
loader.stop()
|
|
538
|
+
print(f"{INDENT}{Colors.RED}✗ Error updating {name}: {str(e)[:50]}{Colors.RESET}")
|
|
539
|
+
|
|
540
|
+
print(f"\n{INDENT}{Colors.GREEN}✅ Update finished!{Colors.RESET}")
|
|
541
|
+
|
|
542
|
+
def parse_selection_input(selection_str, max_number):
|
|
543
|
+
"""Parse user selection input like '1,3,5' or '1-5' or 'all'."""
|
|
544
|
+
selection_str = selection_str.strip().lower()
|
|
545
|
+
|
|
546
|
+
if selection_str == 'all':
|
|
547
|
+
return list(range(1, max_number + 1))
|
|
548
|
+
|
|
549
|
+
selected_numbers = set()
|
|
550
|
+
parts = selection_str.split(',')
|
|
551
|
+
|
|
552
|
+
for part in parts:
|
|
553
|
+
part = part.strip()
|
|
554
|
+
if not part:
|
|
555
|
+
continue
|
|
556
|
+
|
|
557
|
+
if '-' in part:
|
|
558
|
+
range_parts = part.split('-')
|
|
559
|
+
if len(range_parts) == 2:
|
|
560
|
+
try:
|
|
561
|
+
start = int(range_parts[0].strip())
|
|
562
|
+
end = int(range_parts[1].strip())
|
|
563
|
+
if 1 <= start <= end <= max_number:
|
|
564
|
+
selected_numbers.update(range(start, end + 1))
|
|
565
|
+
else:
|
|
566
|
+
return None
|
|
567
|
+
except ValueError:
|
|
568
|
+
return None
|
|
569
|
+
else:
|
|
570
|
+
try:
|
|
571
|
+
num = int(part)
|
|
572
|
+
if 1 <= num <= max_number:
|
|
573
|
+
selected_numbers.add(num)
|
|
574
|
+
else:
|
|
575
|
+
return None
|
|
576
|
+
except ValueError:
|
|
577
|
+
return None
|
|
578
|
+
|
|
579
|
+
return sorted(selected_numbers)
|
|
580
|
+
|
|
581
|
+
def select_packages_from_matches(matches, search_term):
|
|
582
|
+
"""Let user select one or multiple packages from matches."""
|
|
583
|
+
if not matches:
|
|
584
|
+
print(f"{Colors.RED}No packages found matching '{search_term}'.{Colors.RESET}")
|
|
585
|
+
return None
|
|
586
|
+
|
|
587
|
+
# if the top match is exactly the search term, select it immediately
|
|
588
|
+
if matches[0].lower() == search_term.lower():
|
|
589
|
+
print(f"{Colors.GREEN}Auto-selected exact match: {matches[0]}{Colors.RESET}")
|
|
590
|
+
return [matches[0]]
|
|
591
|
+
|
|
592
|
+
if len(matches) == 1:
|
|
593
|
+
print(f"{Colors.GREEN}Found: {matches[0]}{Colors.RESET}")
|
|
594
|
+
# Automatically proceed when only one match
|
|
595
|
+
return [matches[0]]
|
|
596
|
+
|
|
597
|
+
print(f"\n{Colors.BLUE}Found {len(matches)} packages matching '{search_term}':{Colors.RESET}")
|
|
598
|
+
|
|
599
|
+
# Show matches with explanation
|
|
600
|
+
for i, pkg in enumerate(matches, 1):
|
|
601
|
+
pkg_lower = pkg.lower()
|
|
602
|
+
if pkg_lower == search_term.lower():
|
|
603
|
+
reason = "Exact match"
|
|
604
|
+
elif pkg_lower.startswith(search_term.lower()):
|
|
605
|
+
reason = f"Starts with '{search_term}'"
|
|
606
|
+
elif pkg_lower.endswith(search_term.lower()):
|
|
607
|
+
reason = f"Ends with '{search_term}'"
|
|
608
|
+
elif search_term.lower() in re.split(r'[-_.]', pkg_lower):
|
|
609
|
+
reason = f"Contains '{search_term}' as a word"
|
|
610
|
+
elif search_term.lower() in pkg_lower:
|
|
611
|
+
reason = f"Contains '{search_term}'"
|
|
612
|
+
else:
|
|
613
|
+
reason = "Similar match"
|
|
614
|
+
|
|
615
|
+
print(f" {Colors.CYAN}{i:>2}.{Colors.RESET} {Colors.BOLD}{pkg:<30}{Colors.RESET} ({Colors.YELLOW}{reason}{Colors.RESET})")
|
|
616
|
+
|
|
617
|
+
print(f"\n{Colors.BOLD}Selection Options:{Colors.RESET}")
|
|
618
|
+
print(f" {Colors.CYAN}Single:{Colors.RESET} Enter a single number (e.g., 1)")
|
|
619
|
+
print(f" {Colors.CYAN}Multiple:{Colors.RESET} Enter comma-separated numbers (e.g., 1,3,5)")
|
|
620
|
+
print(f" {Colors.CYAN}Range:{Colors.RESET} Enter a range (e.g., 1-5)")
|
|
621
|
+
print(f" {Colors.CYAN}All:{Colors.RESET} Enter 'all' to select all {len(matches)} packages")
|
|
622
|
+
print(f" {Colors.CYAN}Cancel:{Colors.RESET} Enter '0' or 'cancel'")
|
|
623
|
+
|
|
624
|
+
while True:
|
|
625
|
+
try:
|
|
626
|
+
choice = input(f"\n{Colors.BOLD}Select packages (1-{len(matches)}): {Colors.RESET}").strip()
|
|
627
|
+
|
|
628
|
+
if choice.lower() in ['0', 'cancel', 'exit', 'quit']:
|
|
629
|
+
print(f"{Colors.YELLOW}Cancelled.{Colors.RESET}")
|
|
630
|
+
return None
|
|
631
|
+
|
|
632
|
+
selected_numbers = parse_selection_input(choice, len(matches))
|
|
633
|
+
|
|
634
|
+
if selected_numbers is None:
|
|
635
|
+
print(f"{Colors.RED}Invalid selection. Please enter valid numbers between 1 and {len(matches)}.{Colors.RESET}")
|
|
636
|
+
continue
|
|
637
|
+
|
|
638
|
+
if not selected_numbers:
|
|
639
|
+
print(f"{Colors.YELLOW}No packages selected.{Colors.RESET}")
|
|
640
|
+
return None
|
|
641
|
+
|
|
642
|
+
selected_packages = [matches[i-1] for i in selected_numbers]
|
|
643
|
+
|
|
644
|
+
print(f"\n{Colors.BLUE}Selected {len(selected_packages)} package(s):{Colors.RESET}")
|
|
645
|
+
for i, pkg in enumerate(selected_packages, 1):
|
|
646
|
+
print(f" {Colors.CYAN}{i}.{Colors.RESET} {pkg}")
|
|
647
|
+
|
|
648
|
+
# Automatically proceed with the selection without asking for confirmation
|
|
649
|
+
return selected_packages
|
|
650
|
+
|
|
651
|
+
except KeyboardInterrupt:
|
|
652
|
+
print(f"\n{Colors.YELLOW}Cancelled.{Colors.RESET}")
|
|
653
|
+
return None
|
|
654
|
+
except Exception as e:
|
|
655
|
+
print(f"{Colors.RED}Error: {e}{Colors.RESET}")
|
|
656
|
+
return None
|
|
657
|
+
|
|
658
|
+
def print_color_legend():
|
|
659
|
+
"""Print legend explaining the color codes."""
|
|
660
|
+
print(f"\n{Colors.BOLD}Color Legend:{Colors.RESET}")
|
|
661
|
+
print(f"{Colors.GREEN}Green{Colors.RESET} → Package is up-to-date")
|
|
662
|
+
print(f"{Colors.RED}Red{Colors.RESET} → Update available")
|
|
663
|
+
print(f"{Colors.YELLOW}Yellow{Colors.RESET} → Status unknown")
|
|
664
|
+
print(f"{Colors.CYAN}Cyan{Colors.RESET} → Latest version")
|
|
665
|
+
print(f"{Colors.WHITE}White{Colors.RESET} → Current version")
|
|
666
|
+
print(f"{Colors.BLUE}Blue{Colors.RESET} → Action/Progress")
|
|
667
|
+
print(f"{Colors.MAGENTA}Magenta{Colors.RESET} → Loading animation")
|
|
668
|
+
print(f"{Colors.CYAN}{'-'*40}{Colors.RESET}")
|
|
669
|
+
|
|
670
|
+
def quick_update_specific(package_name):
|
|
671
|
+
"""Quickly update a specific package without full scan."""
|
|
672
|
+
print(f"\n{INDENT}{Colors.BLUE}Quick update for: {Colors.BOLD}{package_name}{Colors.RESET}")
|
|
673
|
+
|
|
674
|
+
# Create loading animation
|
|
675
|
+
loader = LoadingAnimation()
|
|
676
|
+
loader.start("Checking current version")
|
|
677
|
+
|
|
678
|
+
installed = get_installed_packages()
|
|
679
|
+
|
|
680
|
+
loader.stop()
|
|
681
|
+
|
|
682
|
+
if package_name not in installed:
|
|
683
|
+
print(f"{INDENT}{Colors.RED}Package '{package_name}' is not installed.{Colors.RESET}")
|
|
684
|
+
return False
|
|
685
|
+
|
|
686
|
+
current_version = installed[package_name]
|
|
687
|
+
print(f"{INDENT}{Colors.WHITE}Current version: {current_version}{Colors.RESET}")
|
|
688
|
+
|
|
689
|
+
# Get latest version with loading animation
|
|
690
|
+
loader.start("Fetching latest version")
|
|
691
|
+
result = get_package_info_simple(package_name)
|
|
692
|
+
loader.stop()
|
|
693
|
+
|
|
694
|
+
if result["success"]:
|
|
695
|
+
latest_version = result["latest"]
|
|
696
|
+
size_mb = result["size"]
|
|
697
|
+
|
|
698
|
+
if latest_version == current_version:
|
|
699
|
+
print(f"{INDENT}{Colors.GREEN}✓ {package_name} is already up-to-date ({current_version}){Colors.RESET}")
|
|
700
|
+
return True
|
|
701
|
+
else:
|
|
702
|
+
print(f"{INDENT}{Colors.CYAN}Latest version: {latest_version}{Colors.RESET}")
|
|
703
|
+
print(f"{INDENT}{Colors.YELLOW}Download size: ~{size_mb:.1f} MB{Colors.RESET}")
|
|
704
|
+
|
|
705
|
+
# Automatically perform the update without asking
|
|
706
|
+
update_packages([package_name])
|
|
707
|
+
return True
|
|
708
|
+
else:
|
|
709
|
+
print(f"{INDENT}{Colors.RED}Could not fetch information for {package_name}{Colors.RESET}")
|
|
710
|
+
if result.get("error"):
|
|
711
|
+
print(f"{INDENT}{Colors.YELLOW}Error: {result['error']}{Colors.RESET}")
|
|
712
|
+
return False
|
|
713
|
+
|
|
714
|
+
def batch_update_packages(package_names):
|
|
715
|
+
"""Update multiple packages without asking for confirmation."""
|
|
716
|
+
if not package_names:
|
|
717
|
+
print(f"{INDENT}{Colors.YELLOW}No packages selected.{Colors.RESET}")
|
|
718
|
+
return False
|
|
719
|
+
|
|
720
|
+
print(f"\n{INDENT}{Colors.BLUE}Batch update for {len(package_names)} packages:{Colors.RESET}")
|
|
721
|
+
for i, pkg in enumerate(package_names, 1):
|
|
722
|
+
print(f"{INDENT}{Colors.CYAN}{i}.{Colors.RESET} {pkg}")
|
|
723
|
+
|
|
724
|
+
# Proceed directly
|
|
725
|
+
update_packages(package_names)
|
|
726
|
+
return True
|
|
727
|
+
|
|
728
|
+
def smart_update_command(user_input):
|
|
729
|
+
"""Handle smart update command with intelligent matching."""
|
|
730
|
+
search_term = user_input[7:].strip()
|
|
731
|
+
|
|
732
|
+
if not search_term:
|
|
733
|
+
print(f"{INDENT}{Colors.RED}Please specify a package name or pattern.{Colors.RESET}")
|
|
734
|
+
return
|
|
735
|
+
|
|
736
|
+
if search_term.lower() == "all":
|
|
737
|
+
global outdated_packages_global
|
|
738
|
+
if not outdated_packages_global:
|
|
739
|
+
print(f"{INDENT}{Colors.YELLOW}To update all outdated packages, please run 'scan' first.{Colors.RESET}")
|
|
740
|
+
print(f"{INDENT}{Colors.BLUE}Or use 'update <pattern>' to update all packages matching a pattern.{Colors.RESET}")
|
|
741
|
+
return
|
|
742
|
+
print(f"{INDENT}{Colors.BLUE}Updating all {len(outdated_packages_global)} outdated packages...{Colors.RESET}")
|
|
743
|
+
batch_update_packages(outdated_packages_global)
|
|
744
|
+
return
|
|
745
|
+
|
|
746
|
+
# Create loading animation
|
|
747
|
+
loader = LoadingAnimation()
|
|
748
|
+
loader.start("Searching installed packages")
|
|
749
|
+
|
|
750
|
+
installed = get_installed_packages()
|
|
751
|
+
|
|
752
|
+
loader.stop()
|
|
753
|
+
|
|
754
|
+
if not installed:
|
|
755
|
+
print(f"{INDENT}{Colors.YELLOW}No packages installed.{Colors.RESET}")
|
|
756
|
+
return
|
|
757
|
+
|
|
758
|
+
# Find similar packages using intelligent matching
|
|
759
|
+
matches = find_similar_packages(list(installed.keys()), search_term)
|
|
760
|
+
|
|
761
|
+
if not matches:
|
|
762
|
+
print(f"{INDENT}{Colors.RED}No packages found matching '{search_term}'.{Colors.RESET}")
|
|
763
|
+
print(f"{INDENT}{Colors.YELLOW}Try a different search term or use 'list' to see all packages.{Colors.RESET}")
|
|
764
|
+
return
|
|
765
|
+
|
|
766
|
+
# Let user select packages
|
|
767
|
+
selected_packages = select_packages_from_matches(matches, search_term)
|
|
768
|
+
|
|
769
|
+
if selected_packages:
|
|
770
|
+
if len(selected_packages) == 1:
|
|
771
|
+
quick_update_specific(selected_packages[0])
|
|
772
|
+
else:
|
|
773
|
+
batch_update_packages(selected_packages)
|
|
774
|
+
|
|
775
|
+
def print_banner():
|
|
776
|
+
"""Print the application banner with left-aligned text."""
|
|
777
|
+
border = '=' * 60
|
|
778
|
+
banner = f"""
|
|
779
|
+
{Colors.CYAN}{border}{Colors.RESET}
|
|
780
|
+
{Colors.BOLD}ULTIMATE PYTHON PACKAGE MANAGER (Pipman-CLI){Colors.RESET}
|
|
781
|
+
{Colors.WHITE}Version {__version__} | Cross-Platform Edition{Colors.RESET}
|
|
782
|
+
{Colors.YELLOW}Author: Irfan Haider{Colors.RESET}
|
|
783
|
+
{Colors.BLUE}GitHub: https://github.com/Irfanh-dev/Pipman-CLI{Colors.RESET}
|
|
784
|
+
{Colors.CYAN}{border}{Colors.RESET}
|
|
785
|
+
"""
|
|
786
|
+
print(banner)
|
|
787
|
+
|
|
788
|
+
def print_commands():
|
|
789
|
+
"""Print available commands."""
|
|
790
|
+
print(f"\n{INDENT}{Colors.BOLD}Smart Commands:{Colors.RESET}")
|
|
791
|
+
print(f"{INDENT}{Colors.GREEN}{'scan':<22}{Colors.RESET}→ Scan all packages (press 'q' to stop early)")
|
|
792
|
+
print(f"{INDENT}{Colors.CYAN}{'update <name/pattern>':<22}{Colors.RESET}→ Smart update with multi-select")
|
|
793
|
+
print(f"{INDENT}{Colors.BLUE}{'list':<22}{Colors.RESET}→ Show installed packages only")
|
|
794
|
+
print(f"{INDENT}{Colors.YELLOW}{'exit':<22}{Colors.RESET}→ Exit program")
|
|
795
|
+
print(f"{INDENT}{Colors.WHITE}{'help':<22}{Colors.RESET}→ Show help")
|
|
796
|
+
print(f"{INDENT}{Colors.MAGENTA}{'legend':<22}{Colors.RESET}→ Show color legend")
|
|
797
|
+
|
|
798
|
+
print(f"\n{INDENT}{Colors.BOLD}Selection Examples:{Colors.RESET}")
|
|
799
|
+
print(f"{INDENT}{Colors.CYAN}{'update pyqt':<22}{Colors.RESET}→ Shows all PyQt packages")
|
|
800
|
+
print(f"{INDENT}{'Then enter:':<22}{Colors.RESET}{Colors.GREEN}{'all':<8}{Colors.RESET}→ Update all matching")
|
|
801
|
+
print(f"{INDENT}{'Or enter:':<22}{Colors.RESET}{Colors.GREEN}{'1,3,5':<8}{Colors.RESET}→ Update packages 1, 3, and 5")
|
|
802
|
+
print(f"{INDENT}{'Or enter:':<22}{Colors.RESET}{Colors.GREEN}{'1-4':<8}{Colors.RESET}→ Update packages 1 through 4")
|
|
803
|
+
|
|
804
|
+
def main():
|
|
805
|
+
"""Main function."""
|
|
806
|
+
# simple CLI parsing
|
|
807
|
+
parser = argparse.ArgumentParser(prog="pipman-cli", description="Ultimate Python Package Manager (Pipman-CLI)")
|
|
808
|
+
parser.add_argument("--version", "-V", action="store_true", help="Show version and exit")
|
|
809
|
+
parser.add_argument("command", nargs=argparse.REMAINDER, help="Command to run (scan, update, etc.)")
|
|
810
|
+
args = parser.parse_args()
|
|
811
|
+
|
|
812
|
+
if args.version:
|
|
813
|
+
print(__version__)
|
|
814
|
+
return
|
|
815
|
+
|
|
816
|
+
# Clear screen
|
|
817
|
+
os.system('cls' if platform.system() == 'Windows' else 'clear')
|
|
818
|
+
|
|
819
|
+
# Print banner
|
|
820
|
+
print_banner()
|
|
821
|
+
|
|
822
|
+
# Check if pip is available
|
|
823
|
+
print(f"{INDENT}{Colors.BLUE}Checking system compatibility...{Colors.RESET}")
|
|
824
|
+
|
|
825
|
+
loader = LoadingAnimation()
|
|
826
|
+
loader.start("Checking pip availability")
|
|
827
|
+
|
|
828
|
+
if not check_pip_availability():
|
|
829
|
+
loader.stop(f"{INDENT}{Colors.RED}✗ pip is not available!{Colors.RESET}")
|
|
830
|
+
print(f"\n{Colors.YELLOW}Please ensure pip is installed and in your PATH.{Colors.RESET}")
|
|
831
|
+
print(f"{Colors.WHITE}You can install pip with: {Colors.CYAN}python -m ensurepip --upgrade{Colors.RESET}")
|
|
832
|
+
return
|
|
833
|
+
|
|
834
|
+
loader.stop(f"{INDENT}{Colors.GREEN}✓ pip is available{Colors.RESET}")
|
|
835
|
+
|
|
836
|
+
# Check and install required dependencies (silently in background)
|
|
837
|
+
check_and_install_dependencies(verbose=False)
|
|
838
|
+
|
|
839
|
+
# Show OS info
|
|
840
|
+
print(f"\n{INDENT}{Colors.WHITE}System: {Colors.CYAN}{platform.system()} {platform.release()}{Colors.RESET}")
|
|
841
|
+
print(f"{INDENT}{Colors.WHITE}Python: {Colors.CYAN}{sys.version.split()[0]}{Colors.RESET}")
|
|
842
|
+
|
|
843
|
+
# Print commands
|
|
844
|
+
print_commands()
|
|
845
|
+
|
|
846
|
+
# if a command was passed via CLI arguments, execute it and exit
|
|
847
|
+
if args.command:
|
|
848
|
+
user_input = " ".join(args.command).strip()
|
|
849
|
+
run_command(user_input)
|
|
850
|
+
return
|
|
851
|
+
|
|
852
|
+
# otherwise enter interactive prompt
|
|
853
|
+
while True:
|
|
854
|
+
try:
|
|
855
|
+
user_input = input(f"\n{Colors.BOLD}{Colors.CYAN}pipman-cli>{Colors.RESET} ").strip()
|
|
856
|
+
except KeyboardInterrupt:
|
|
857
|
+
print(f"\n{Colors.YELLOW}\nExiting...{Colors.RESET}")
|
|
858
|
+
break
|
|
859
|
+
except EOFError:
|
|
860
|
+
print(f"\n{Colors.YELLOW}\nExiting...{Colors.RESET}")
|
|
861
|
+
break
|
|
862
|
+
|
|
863
|
+
if not user_input:
|
|
864
|
+
continue
|
|
865
|
+
|
|
866
|
+
if not run_command(user_input):
|
|
867
|
+
break
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def run_command(user_input):
|
|
871
|
+
"""Process a single command string; return False if the loop should exit."""
|
|
872
|
+
if not user_input:
|
|
873
|
+
return True
|
|
874
|
+
cmd = user_input.strip().lower()
|
|
875
|
+
|
|
876
|
+
if cmd == "exit":
|
|
877
|
+
print(f"{INDENT}{Colors.YELLOW}Exiting...{Colors.RESET}")
|
|
878
|
+
return False
|
|
879
|
+
elif cmd == "help":
|
|
880
|
+
print_commands()
|
|
881
|
+
elif cmd == "legend":
|
|
882
|
+
print_color_legend()
|
|
883
|
+
elif cmd == "list":
|
|
884
|
+
loader = LoadingAnimation()
|
|
885
|
+
loader.start("Fetching installed packages")
|
|
886
|
+
installed = get_installed_packages()
|
|
887
|
+
loader.stop()
|
|
888
|
+
|
|
889
|
+
if not installed:
|
|
890
|
+
print(f"{INDENT}{Colors.YELLOW}No packages installed.{Colors.RESET}")
|
|
891
|
+
else:
|
|
892
|
+
print(f"\n{INDENT}{Colors.BOLD}{'Package':<30}{'Version':<20}{Colors.RESET}")
|
|
893
|
+
print(f"{INDENT}{Colors.WHITE}{'-'*50}{Colors.RESET}")
|
|
894
|
+
for name, version in installed.items():
|
|
895
|
+
print(f"{INDENT}{Colors.WHITE}{name:<30}{version:<20}{Colors.RESET}")
|
|
896
|
+
print(f"\n{INDENT}{Colors.GREEN}Total: {len(installed)} packages{Colors.RESET}")
|
|
897
|
+
elif cmd.startswith("update "):
|
|
898
|
+
raw = user_input[7:]
|
|
899
|
+
terms = [t.strip().strip(' .;') for t in raw.split(',') if t.strip()]
|
|
900
|
+
if len(terms) > 1:
|
|
901
|
+
for term in terms:
|
|
902
|
+
smart_update_command(f"update {term}")
|
|
903
|
+
else:
|
|
904
|
+
smart_update_command(user_input)
|
|
905
|
+
elif cmd == "scan":
|
|
906
|
+
loader = LoadingAnimation()
|
|
907
|
+
loader.start("Fetching installed packages")
|
|
908
|
+
installed = get_installed_packages()
|
|
909
|
+
loader.stop()
|
|
910
|
+
|
|
911
|
+
if not installed:
|
|
912
|
+
print(f"{INDENT}{Colors.YELLOW}No packages installed.{Colors.RESET}")
|
|
913
|
+
else:
|
|
914
|
+
scanner = PackageScanner()
|
|
915
|
+
print(f"\n{INDENT}{Colors.YELLOW}Starting scan of {len(installed)} packages...{Colors.RESET}")
|
|
916
|
+
print(f"{INDENT}{Colors.BLUE}Press 'q' during scan to stop early{Colors.RESET}\n")
|
|
917
|
+
try:
|
|
918
|
+
outdated = show_packages_with_progress(installed, scanner)
|
|
919
|
+
outdated_info = scanner.results
|
|
920
|
+
outdated = show_packages_final(installed, outdated_info)
|
|
921
|
+
total = len(installed)
|
|
922
|
+
outdated_count = len(outdated)
|
|
923
|
+
scanned_count = scanner.scanned_count
|
|
924
|
+
print(f"\n{INDENT}{Colors.BOLD}Scan Summary:{Colors.RESET}")
|
|
925
|
+
print(f"{INDENT}{Colors.GREEN}✓ Up-to-date: {total - outdated_count}{Colors.RESET}")
|
|
926
|
+
print(f"{INDENT}{Colors.RED}↻ Outdated: {outdated_count}{Colors.RESET}")
|
|
927
|
+
print(f"{INDENT}{Colors.CYAN}📦 Scanned: {scanned_count}/{total}{Colors.RESET}")
|
|
928
|
+
print(f"{INDENT}{Colors.YELLOW}⚡ Scan interrupted: {'Yes' if scanner.stop_requested else 'No'}{Colors.RESET}")
|
|
929
|
+
global outdated_packages_global
|
|
930
|
+
if outdated:
|
|
931
|
+
print(f"\n{INDENT}{Colors.BOLD}Update Commands:{Colors.RESET}")
|
|
932
|
+
outdated_list = list(outdated.keys())
|
|
933
|
+
print(f"{INDENT}{Colors.GREEN}update all{Colors.RESET} → Update all {len(outdated)} outdated packages")
|
|
934
|
+
print(f"{INDENT}{Colors.CYAN}update <name/pattern>{Colors.RESET} → Smart update with multi-select")
|
|
935
|
+
if outdated_list:
|
|
936
|
+
print(f"{INDENT}Example: {Colors.BLUE}update {outdated_list[0]}{Colors.RESET}")
|
|
937
|
+
# Store outdated packages globally for update all
|
|
938
|
+
outdated_packages_global = outdated_list
|
|
939
|
+
else:
|
|
940
|
+
outdated_packages_global = []
|
|
941
|
+
except Exception as e:
|
|
942
|
+
print(f"{INDENT}{Colors.RED}Scan error: {e}{Colors.RESET}")
|
|
943
|
+
else:
|
|
944
|
+
print(f"{INDENT}{Colors.RED}Unknown command. Type 'help' for available commands.{Colors.RESET}")
|
|
945
|
+
return True
|
|
946
|
+
|
|
947
|
+
if __name__ == "__main__":
|
|
948
|
+
try:
|
|
949
|
+
main()
|
|
950
|
+
except KeyboardInterrupt:
|
|
951
|
+
print(f"\n{Colors.YELLOW}\nProgram interrupted. Exiting...{Colors.RESET}")
|
|
952
|
+
sys.exit(0)
|
|
953
|
+
except Exception as e:
|
|
954
|
+
print(f"{Colors.RED}Fatal error: {e}{Colors.RESET}")
|
|
955
|
+
sys.exit(1)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pipman-cli
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Ultimate Python Package Manager - Cross-Platform CLI tool for managing Python packages with smart features
|
|
5
|
+
Author-email: Irfan Haider <irfanhaider.expert@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Irfanh-dev/Pipman-CLI
|
|
8
|
+
Project-URL: Repository, https://github.com/Irfanh-dev/Pipman-CLI.git
|
|
9
|
+
Keywords: pip,package-manager,cli,python-packages,updater
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE.txt
|
|
24
|
+
Requires-Dist: requests>=2.25.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Pipman-CLI ( Ultimate Python Package Manager) By Irfan
|
|
28
|
+
|
|
29
|
+
A powerful, cross-platform command-line tool for managing **Python packages** with `intelligent scanning`, `smart updates`, and `beautiful terminal output`. Specially helpful for "vibe coders" and developers who want a faster way to handle dependencies without the headache of manual pip commands. Just run, scan, and update.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## ✨ Features
|
|
33
|
+
|
|
34
|
+
* **Intelligent Scanning:** Fast, concurrent package scanning with real-time progress tracking.
|
|
35
|
+
* **Smart Updates:** Supports fuzzy matching and multi-select package updates for maximum efficiency.
|
|
36
|
+
* **Modern Terminal GUI:** Features a colorful ANSI-coded interface so managing packages won't feel static or boring.
|
|
37
|
+
* **Batch Operations:** Update multiple specific packages or entire ranges (e.g., 1-4) in one go.
|
|
38
|
+
* **Cross-Platform:** Works 100% on Windows, macOS, and Linux.
|
|
39
|
+
* **Interactive Mode:** User-friendly CLI with loading animations and interruptible scans.
|
|
40
|
+
|
|
41
|
+
## 🖼️ Preview
|
|
42
|
+
<img src="pipman-cli-scan.png" width="700" />
|
|
43
|
+
|
|
44
|
+
<img src="pipman-cli-update.png" width="700" />
|
|
45
|
+
|
|
46
|
+
## 🚀 Installation
|
|
47
|
+
|
|
48
|
+
1. Ensure you have **Python 3.6+** and **pip** installed on your system.
|
|
49
|
+
2. Download project and run `pipman-cli.py`
|
|
50
|
+
<!-- 3. write pip install pipman-cli in terminal -->
|
|
51
|
+
## 📦 How to Usage
|
|
52
|
+
### Available Commands
|
|
53
|
+
|
|
54
|
+
| Command | Description |
|
|
55
|
+
|---------|-------------|
|
|
56
|
+
| scan | Scan all installed packages for updates (press 'q' to stop early) |
|
|
57
|
+
| update <name/pattern> | Smart update with fuzzy matching and multi-select |
|
|
58
|
+
| list | Show all installed packages |
|
|
59
|
+
| help | Display available commands |
|
|
60
|
+
| legend | Show color legend |
|
|
61
|
+
| exit | Exit the program |
|
|
62
|
+
|
|
63
|
+
<!-- * **Live on Github** [Click here!](https://github.com/Irfanh-dev/pipman-cli) -->
|
|
64
|
+
|
|
65
|
+
**Instructions;**
|
|
66
|
+
**1.** type `Scan` after running pipman-cli.py, to scan the packages installed on your system.
|
|
67
|
+
**2.** type `legend` to understand what each color means.
|
|
68
|
+
**3.** type `update all` to update all out updated libraries.
|
|
69
|
+
**4.** type `update {package_name}` to update specific one. (e.g. update ytdlp)
|
|
70
|
+
## ⚙️ Development & Technology
|
|
71
|
+
|
|
72
|
+
This tool is built for speed and reliability using:
|
|
73
|
+
|
|
74
|
+
* **Python 3.6+:** The core engine for package management.
|
|
75
|
+
* **Requests Library:** Handles version checking and dependency data.
|
|
76
|
+
* **Subprocess & Threading:** Powers the concurrent scanning and background tasks.
|
|
77
|
+
* **ANSI Styling:** For the attractive, color-coded terminal interface.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## Acknowledgments
|
|
81
|
+
|
|
82
|
+
- Built with Python's subprocess and
|
|
83
|
+
requests libraries
|
|
84
|
+
- Inspired by the need for a better pip user experience
|
|
85
|
+
- Thanks to the Python community for excellent package management tools
|
|
86
|
+
|
|
87
|
+
## Issues & Support
|
|
88
|
+
|
|
89
|
+
If you encounter any issues or have suggestions:
|
|
90
|
+
|
|
91
|
+
1. Check the [Issues](https://github.com/Irfanh-dev/pipman-cli/issues) page
|
|
92
|
+
2. Create a new issue with detailed information
|
|
93
|
+
3. Include your Python version, OS, and error messages
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# 📜 [](LICENSE)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
* This project is open source and distributed under the **MIT License**.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
Made with 💙 by Irfan ([@Irfanh-dev](https://github.com/Irfanh-dev)). Assisted with AI || 🚀 Stay connected and check profile for More Awesome upcoming projects.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/__init__.py
|
|
5
|
+
src/__main__.py
|
|
6
|
+
src/pipman_cli.egg-info/PKG-INFO
|
|
7
|
+
src/pipman_cli.egg-info/SOURCES.txt
|
|
8
|
+
src/pipman_cli.egg-info/dependency_links.txt
|
|
9
|
+
src/pipman_cli.egg-info/entry_points.txt
|
|
10
|
+
src/pipman_cli.egg-info/requires.txt
|
|
11
|
+
src/pipman_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25.0
|