Skip to content

Commit e119408

Browse files
committed
v1.0.8: Major feature — automatic Python interpreter hot-swapping and self-healing environment upgrade
1 parent 914889d commit e119408

File tree

3 files changed

+472
-124
lines changed

3 files changed

+472
-124
lines changed

omnipkg/cli.py

Lines changed: 31 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,50 @@
66
import argparse
77
from .core import omnipkg, ConfigManager
88
from pathlib import Path
9+
import textwrap
910

1011
def print_header(title):
1112
"""Prints a consistent, pretty header for CLI sections."""
1213
print("\n" + "="*60)
1314
print(f" 🚀 {title}")
1415
print("="*60)
1516

16-
import textwrap
17-
1817
def create_parser():
1918
"""Creates and configures the argument parser."""
20-
# This formatter_class is key to making our new help text look good.
21-
# The epilog provides examples of the most important commands.
2219
parser = argparse.ArgumentParser(
2320
prog='omnipkg',
2421
description='The intelligent Python package manager that solves dependency hell.',
25-
formatter_class=argparse.RawTextHelpFormatter, # Prevents argparse from messing up our formatting
22+
formatter_class=argparse.RawTextHelpFormatter,
2623
epilog=textwrap.dedent('''\
2724
2825
Common Commands:
2926
omnipkg install <package> Install a package with downgrade protection.
3027
omnipkg list See all installed packages and their health.
3128
omnipkg status Check the health of your multi-version environment.
3229
omnipkg info <package> Get a detailed dashboard for a specific package.
33-
omnipkg demo Run the interactive showcase to see the magic.
30+
omnipkg stress-test Run the ultimate compatibility stress test.
3431
''')
3532
)
3633

3734
subparsers = parser.add_subparsers(dest='command', help='All available commands:', required=True)
3835

3936
install_parser = subparsers.add_parser('install', help='Install packages (with downgrade protection)')
40-
# Make 'packages' optional (nargs='*') to allow using -r instead
4137
install_parser.add_argument('packages', nargs='*', help='Packages to install (e.g., "requests==2.25.1")')
42-
# Add the new -r/--requirement flag
43-
install_parser.add_argument(
44-
'-r', '--requirement',
45-
help='Install from the given requirements file.',
46-
metavar='FILE'
47-
)
38+
install_parser.add_argument('-r', '--requirement', help='Install from the given requirements file.', metavar='FILE')
4839

4940
uninstall_parser = subparsers.add_parser('uninstall', help='Uninstall packages from main env or bubbles')
50-
uninstall_parser.add_argument('packages', nargs='+', help='Packages to uninstall (e.g., "requests" or "requests==2.25.1")')
41+
uninstall_parser.add_argument('packages', nargs='+', help='Packages to uninstall')
5142
uninstall_parser.add_argument('--yes', '-y', action='store_true', help='Skip confirmation prompts')
5243

53-
info_parser = subparsers.add_parser('info', help='Show detailed package information with interactive version selection')
44+
info_parser = subparsers.add_parser('info', help='Show detailed package information')
5445
info_parser.add_argument('package', help='Package name to inspect')
5546
info_parser.add_argument('--version', default='active', help='Specific version to inspect')
5647

5748
revert_parser = subparsers.add_parser('revert', help="Revert environment to the last known good state")
58-
revert_parser.add_argument('--yes', '-y', action='store_true', help='Skip confirmation and revert immediately')
49+
revert_parser.add_argument('--yes', '-y', action='store_true', help='Skip confirmation')
5950

6051
list_parser = subparsers.add_parser('list', help='List installed packages')
61-
list_parser.add_argument('filter', nargs='?', help='Optional filter pattern for package names')
52+
list_parser.add_argument('filter', nargs='?', help='Optional filter for package names')
6253

6354
status_parser = subparsers.add_parser('status', help='Show multi-version system status')
6455

@@ -69,80 +60,43 @@ def create_parser():
6960
reset_parser = subparsers.add_parser('reset', help='DELETE and rebuild the omnipkg knowledge base in Redis')
7061
reset_parser.add_argument('--yes', '-y', action='store_true', help='Skip confirmation')
7162

72-
rebuild_parser = subparsers.add_parser('rebuild-kb', help='Force a full rebuild of the knowledge base without deleting')
73-
rebuild_parser.add_argument('--force', '-f', action='store_true', help='Ignore cache and force re-processing of all metadata')
63+
rebuild_parser = subparsers.add_parser('rebuild-kb', help='Force a full rebuild of the knowledge base')
64+
rebuild_parser.add_argument('--force', '-f', action='store_true', help='Ignore cache and force re-processing')
7465

7566
return parser
7667

7768
def main():
7869
"""The main entry point for the CLI."""
79-
80-
# Handle the case where 'omnipkg' is run with no arguments
8170
if len(sys.argv) == 1:
8271
cm = ConfigManager()
83-
if not cm.config_path.exists():
84-
cm._first_time_setup()
85-
print("\n" + "="*50)
86-
print("🚀 Welcome to omnipkg! Your setup is complete.")
87-
print("To see the magic in action, we highly recommend running the demo:")
88-
print("\n omnipkg demo\n")
89-
print("="*50)
90-
else:
91-
print("👋 Welcome back to omnipkg!")
92-
print(" Run `omnipkg status` to see your environment.")
93-
print(" Run `omnipkg demo` for a showcase of features.")
94-
print(" Run `omnipkg --help` for all commands.")
72+
# This part for running 'omnipkg' with no arguments is fine.
73+
print("👋 Welcome to omnipkg! Run `omnipkg --help` for commands.")
9574
return 0
9675

9776
parser = create_parser()
9877
args = parser.parse_args()
99-
100-
# First, create the config manager to load/create the config
101-
cm = ConfigManager()
10278

103-
# Now, create the main instance, PASSING IN the loaded config
79+
cm = ConfigManager()
10480
pkg_instance = omnipkg(cm.config)
105-
try:
106-
# In omnipkg/cli.py -> main()
10781

82+
try:
10883
if args.command == 'install':
10984
packages_to_process = []
110-
11185
if args.requirement:
112-
# User provided a requirements file
11386
req_path = Path(args.requirement)
11487
if not req_path.is_file():
11588
print(f"❌ Error: Requirements file not found at '{req_path}'")
11689
return 1
117-
118-
print(f"📄 Reading packages from {req_path.name}...")
119-
# In omnipkg/cli.py -> main()
120-
12190
with open(req_path, 'r') as f:
122-
# Parse the file, handling inline comments and empty lines
123-
packages_to_process = []
124-
for line in f:
125-
# Get the part before any comment and strip whitespace
126-
clean_line = line.split('#')[0].strip()
127-
# Only add it to our list if it's not an empty string
128-
if clean_line:
129-
packages_to_process.append(clean_line)
130-
91+
packages_to_process = [line.split('#')[0].strip() for line in f if line.split('#')[0].strip()]
13192
elif args.packages:
132-
# User provided packages directly on the command line
13393
packages_to_process = args.packages
134-
13594
else:
136-
# No packages or file provided
137-
print("❌ Error: You must either specify packages to install or use the -r flag.")
138-
print(" Example: `omnipkg install requests` or `omnipkg install -r requirements.txt`")
95+
print("❌ Error: You must specify packages or use -r. Ex: `omnipkg install requests`")
13996
return 1
140-
141-
# The magic happens here: pass the list to your existing core logic
14297
return pkg_instance.smart_install(packages_to_process)
14398
elif args.command == 'uninstall':
14499
return pkg_instance.smart_uninstall(args.packages, force=args.yes)
145-
146100
elif args.command == 'revert':
147101
return pkg_instance.revert_to_last_known_good(force=args.yes)
148102
elif args.command == 'info':
@@ -151,21 +105,31 @@ def main():
151105
return pkg_instance.list_packages(args.filter)
152106
elif args.command == 'status':
153107
return pkg_instance.show_multiversion_status()
108+
109+
# ### MODIFIED: Fix for the TypeError ###
154110
elif args.command == 'demo':
155-
from .demo import run_demo
156-
return run_demo()
111+
print_header("Demo Under Construction!")
112+
print("The interactive demo is still being polished.")
113+
print("For now, we'll run the 'stress-test' to showcase omnipkg's power.")
114+
# Fall-through to the stress-test logic
115+
from . import stress_test
116+
if input("\nProceed with the stress test? (y/n): ").lower() != 'y':
117+
print("Stress test cancelled.")
118+
return 0
119+
stress_test.run() # <-- CORRECTED: Pass no arguments
120+
return 0
121+
157122
elif args.command == 'stress-test':
158123
from . import stress_test
159124
print_header("omnipkg Ultimate Stress Test")
160125
print("This test will install, bubble, and test multiple large scientific packages.")
161126
print("\n⚠️ This will download several hundred MB and may take several minutes.")
162-
163127
if input("\nProceed with the stress test? (y/n): ").lower() != 'y':
164128
print("Stress test cancelled.")
165129
return 0
166-
167-
stress_test.run()
130+
stress_test.run() # <-- CORRECTED: Pass no arguments
168131
return 0
132+
169133
elif args.command == 'reset':
170134
return pkg_instance.reset_knowledge_base(force=args.yes)
171135
elif args.command == 'rebuild-kb':

0 commit comments

Comments
 (0)