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.
@@ -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.
@@ -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](https://img.shields.io/badge/license-MIT-yellow.svg)](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,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](https://img.shields.io/badge/license-MIT-yellow.svg)](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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ """
2
+ Pipman-CLI - Ultimate Python Package Manager
3
+ A powerful, cross-platform CLI tool for managing Python packages with smart features.
4
+ """
5
+
6
+ __version__ = "1.0"
7
+
8
+ # This allows: from pipman_cli import main
9
+ from .__main__ import main
@@ -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](https://img.shields.io/badge/license-MIT-yellow.svg)](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,2 @@
1
+ [console_scripts]
2
+ pipman-cli = pipman_cli.__main__:main
@@ -0,0 +1 @@
1
+ requests>=2.25.0
@@ -0,0 +1,2 @@
1
+ __init__
2
+ __main__