55import subprocess , shlex , sys , os
66import re
77import shutil
8+ from difflib import get_close_matches
89
910import questionary
1011
@@ -225,7 +226,7 @@ def resolve_item(arg: str):
225226 pkg , item = arg .split ("." , 1 )
226227 contents = list_module_contents (pkg )
227228 if item not in contents :
228- raise FileNotFoundError (f"No item { item !r} in package { pkg !r} " )
229+ raise FileNotFoundError (f"No module { item !r} in package { pkg !r} " )
229230 return pkg , contents [item ]
230231 else :
231232 # search every package for a matching item
@@ -235,10 +236,10 @@ def resolve_item(arg: str):
235236 if arg in contents :
236237 matches .append ((pkg , contents [arg ]))
237238 if not matches :
238- raise FileNotFoundError (f"No item named { arg !r} in any package" )
239+ raise FileNotFoundError (f"No module named { arg !r} in any package" )
239240 if len (matches ) > 1 :
240241 pkgs = ", " .join (p for p ,_ in matches )
241- raise ValueError (f"Ambiguous item { arg !r} found in: { pkgs } " )
242+ raise ValueError (f"Ambiguous module { arg !r} found in: { pkgs } " )
242243 return matches [0 ]
243244
244245def ensure_init_py (directory : Path ):
@@ -432,7 +433,7 @@ async def test_example_agent():
432433
433434def eject (args , extra_args = []):
434435 """
435- Eject primitive modules into the current project.
436+ Eject primitive modules into the current project, with fuzzy suggestions on typos .
436437 """
437438 # If user ran `eject list`, list everything and exit
438439 if args .action == 'list' :
@@ -443,19 +444,38 @@ def eject(args, extra_args=[]):
443444 print (f" • { item } " )
444445 return
445446
446- # Otherwise, perform an eject (interactive or via -m)
447- dest = Path (args .dest ) if args .dest else Path .cwd ()
447+ # Non-interactive mode: try to resolve each module name, suggest on typos
448448 if args .module :
449- # Non-interactive: resolve each module and eject
450- try :
451- items_with_packages = [resolve_item (arg ) for arg in args .module ]
452- eject_items (items_with_packages , dest )
453- except (FileNotFoundError , ValueError ) as e :
454- print (f"Error: { e } " )
455- return
449+ dest = Path (args .dest ) if args .dest else Path .cwd ()
450+
451+ # Build a flat list of all valid names to match against
452+ available = []
453+ for pkg in list_top_level_packages ():
454+ for item in list_module_contents (pkg ).keys ():
455+ available .append (item )
456+ available .append (f"{ pkg } .{ item } " )
457+
458+ items_with_packages = []
459+ for arg in args .module :
460+ try :
461+ items_with_packages .append (resolve_item (arg ))
462+ except FileNotFoundError :
463+ suggestions = get_close_matches (arg , available , n = 3 , cutoff = 0.6 )
464+ if suggestions :
465+ print (f"Error: No module named '{ arg } ' found. Did you mean: { ', ' .join (suggestions )} ?" )
466+ else :
467+ print (f"Error: No module named '{ arg } ' found." )
468+ return
469+ except ValueError as e :
470+ # Ambiguous without a clear pkg.item
471+ print (f"Error: { e } " )
472+ return
473+
474+ eject_items (items_with_packages , dest )
475+
456476 else :
457477 # Interactive mode
458- interactive_eject_mode (dest )
478+ interactive_eject_mode (Path . cwd () )
459479
460480def dev (args , extra_args = []):
461481 """
0 commit comments