tenseleyflow/shtick / ec1f957

Browse files

stable, interactive mode

Authored by espadonne
SHA
ec1f957251882b1af459d5219023bf48eefcc628
Parents
b2cc963
Tree
899fce8

3 changed files

StatusFile+-
M src/shtick/cli.py 196 138
M src/shtick/config.py 45 27
M src/shtick/generator.py 157 16
src/shtick/cli.pymodified
@@ -9,22 +9,23 @@ import argparse
9
 import os
9
 import os
10
 from typing import Optional
10
 from typing import Optional
11
 
11
 
12
-# Package imports  
12
+# Package imports
13
 from shtick.config import Config
13
 from shtick.config import Config
14
 from shtick.generator import Generator
14
 from shtick.generator import Generator
15
 from shtick.shells import get_supported_shells
15
 from shtick.shells import get_supported_shells
16
 
16
 
17
+
17
 def cmd_generate(args) -> None:
18
 def cmd_generate(args) -> None:
18
     """Generate shell files from config"""
19
     """Generate shell files from config"""
19
     config_path = args.config or Config.get_default_config_path()
20
     config_path = args.config or Config.get_default_config_path()
20
-    
21
+
21
     try:
22
     try:
22
-        config = Config(config_path)
23
+        config = Config(config_path, debug=args.debug)
23
         config.load()
24
         config.load()
24
-        
25
+
25
         generator = Generator()
26
         generator = Generator()
26
-        generator.generate_all(config)
27
+        generator.generate_all(config, interactive=args.interactive)
27
-        
28
+
28
     except FileNotFoundError as e:
29
     except FileNotFoundError as e:
29
         print(f"Error: {e}")
30
         print(f"Error: {e}")
30
         print(f"Create a config file at {config_path} first")
31
         print(f"Create a config file at {config_path} first")
@@ -33,54 +34,58 @@ def cmd_generate(args) -> None:
33
         print(f"Error: {e}")
34
         print(f"Error: {e}")
34
         sys.exit(1)
35
         sys.exit(1)
35
 
36
 
37
+
36
 def cmd_add(args) -> None:
38
 def cmd_add(args) -> None:
37
     """Add an item to the config"""
39
     """Add an item to the config"""
38
-    if '=' not in args.assignment:
40
+    if "=" not in args.assignment:
39
         print("Error: Assignment must be in format key=value")
41
         print("Error: Assignment must be in format key=value")
40
         sys.exit(1)
42
         sys.exit(1)
41
-    
43
+
42
-    key, value = args.assignment.split('=', 1)
44
+    key, value = args.assignment.split("=", 1)
43
     key = key.strip()
45
     key = key.strip()
44
     value = value.strip()
46
     value = value.strip()
45
-    
47
+
46
     if not key or not value:
48
     if not key or not value:
47
         print("Error: Both key and value must be non-empty")
49
         print("Error: Both key and value must be non-empty")
48
         sys.exit(1)
50
         sys.exit(1)
49
-    
51
+
50
     config_path = Config.get_default_config_path()
52
     config_path = Config.get_default_config_path()
51
-    
53
+
52
     try:
54
     try:
53
-        config = Config(config_path)
55
+        config = Config(config_path, debug=getattr(args, "debug", False))
54
         # Try to load existing config, create empty if doesn't exist
56
         # Try to load existing config, create empty if doesn't exist
55
         try:
57
         try:
56
             config.load()
58
             config.load()
57
         except FileNotFoundError:
59
         except FileNotFoundError:
58
             print(f"Creating new config file at {config_path}")
60
             print(f"Creating new config file at {config_path}")
59
-        
61
+
60
         config.add_item(args.type, args.group, key, value)
62
         config.add_item(args.type, args.group, key, value)
61
         config.save()
63
         config.save()
62
-        
64
+
63
         print(f"Added {args.type} '{key}' = '{value}' to group '{args.group}'")
65
         print(f"Added {args.type} '{key}' = '{value}' to group '{args.group}'")
64
-        
66
+
65
     except Exception as e:
67
     except Exception as e:
66
         print(f"Error: {e}")
68
         print(f"Error: {e}")
67
         sys.exit(1)
69
         sys.exit(1)
68
 
70
 
71
+
69
 def cmd_remove(args) -> None:
72
 def cmd_remove(args) -> None:
70
     """Remove an item from the config"""
73
     """Remove an item from the config"""
71
     config_path = Config.get_default_config_path()
74
     config_path = Config.get_default_config_path()
72
-    
75
+
73
     try:
76
     try:
74
-        config = Config(config_path)
77
+        config = Config(config_path, debug=getattr(args, "debug", False))
75
         config.load()
78
         config.load()
76
-        
79
+
77
         # Find matching items
80
         # Find matching items
78
         matches = config.find_items(args.type, args.group, args.search)
81
         matches = config.find_items(args.type, args.group, args.search)
79
-        
82
+
80
         if not matches:
83
         if not matches:
81
-            print(f"No {args.type} items matching '{args.search}' found in group '{args.group}'")
84
+            print(
85
+                f"No {args.type} items matching '{args.search}' found in group '{args.group}'"
86
+            )
82
             return
87
             return
83
-        
88
+
84
         if len(matches) == 1:
89
         if len(matches) == 1:
85
             # Exact match, remove it
90
             # Exact match, remove it
86
             item = matches[0]
91
             item = matches[0]
@@ -94,13 +99,13 @@ def cmd_remove(args) -> None:
94
             print(f"Found {len(matches)} matches:")
99
             print(f"Found {len(matches)} matches:")
95
             for i, item in enumerate(matches, 1):
100
             for i, item in enumerate(matches, 1):
96
                 print(f"  {i}. {item}")
101
                 print(f"  {i}. {item}")
97
-            
102
+
98
             try:
103
             try:
99
                 choice = input("Enter number to remove (or 'q' to quit): ").strip()
104
                 choice = input("Enter number to remove (or 'q' to quit): ").strip()
100
-                if choice.lower() == 'q':
105
+                if choice.lower() == "q":
101
                     print("Cancelled")
106
                     print("Cancelled")
102
                     return
107
                     return
103
-                
108
+
104
                 idx = int(choice) - 1
109
                 idx = int(choice) - 1
105
                 if 0 <= idx < len(matches):
110
                 if 0 <= idx < len(matches):
106
                     item = matches[idx]
111
                     item = matches[idx]
@@ -113,7 +118,7 @@ def cmd_remove(args) -> None:
113
                     print("Invalid choice")
118
                     print("Invalid choice")
114
             except (ValueError, KeyboardInterrupt):
119
             except (ValueError, KeyboardInterrupt):
115
                 print("\nCancelled")
120
                 print("\nCancelled")
116
-    
121
+
117
     except FileNotFoundError:
122
     except FileNotFoundError:
118
         print(f"Config file not found: {config_path}")
123
         print(f"Config file not found: {config_path}")
119
         sys.exit(1)
124
         sys.exit(1)
@@ -121,24 +126,32 @@ def cmd_remove(args) -> None:
121
         print(f"Error: {e}")
126
         print(f"Error: {e}")
122
         sys.exit(1)
127
         sys.exit(1)
123
 
128
 
129
+
124
 def cmd_activate(args) -> None:
130
 def cmd_activate(args) -> None:
125
     """Activate a group"""
131
     """Activate a group"""
126
     config_path = Config.get_default_config_path()
132
     config_path = Config.get_default_config_path()
127
-    
133
+
128
     try:
134
     try:
129
-        config = Config(config_path)
135
+        config = Config(config_path, debug=getattr(args, "debug", False))
130
         config.load()
136
         config.load()
131
-        
137
+
132
-        if args.group == 'persistent':
138
+        if args.group == "persistent":
133
-            print("Error: 'persistent' group is always active and cannot be manually activated")
139
+            print(
140
+                "Error: 'persistent' group is always active and cannot be manually activated"
141
+            )
134
             return
142
             return
135
-        
143
+
136
         if config.activate_group(args.group):
144
         if config.activate_group(args.group):
137
-            # Regenerate loader to include newly activated group
145
+            # Regenerate all files to ensure they exist and are up to date
138
             from shtick.generator import Generator
146
             from shtick.generator import Generator
147
+
139
             generator = Generator()
148
             generator = Generator()
149
+            # Generate shell files for all groups (not just the activated one)
150
+            for group in config.groups:
151
+                generator.generate_for_group(group)
152
+            # Then regenerate the loader to include newly activated group
140
             generator.generate_loader(config)
153
             generator.generate_loader(config)
141
-            
154
+
142
             print(f"Activated group '{args.group}'")
155
             print(f"Activated group '{args.group}'")
143
             print("Changes are now active in new shell sessions")
156
             print("Changes are now active in new shell sessions")
144
         else:
157
         else:
@@ -146,7 +159,7 @@ def cmd_activate(args) -> None:
146
             available = [g.name for g in config.get_regular_groups()]
159
             available = [g.name for g in config.get_regular_groups()]
147
             if available:
160
             if available:
148
                 print(f"Available groups: {', '.join(available)}")
161
                 print(f"Available groups: {', '.join(available)}")
149
-    
162
+
150
     except FileNotFoundError:
163
     except FileNotFoundError:
151
         print(f"Config file not found: {config_path}")
164
         print(f"Config file not found: {config_path}")
152
         print("Run 'shtick generate' first or add some items with 'shtick add'")
165
         print("Run 'shtick generate' first or add some items with 'shtick add'")
@@ -155,29 +168,32 @@ def cmd_activate(args) -> None:
155
         print(f"Error: {e}")
168
         print(f"Error: {e}")
156
         sys.exit(1)
169
         sys.exit(1)
157
 
170
 
171
+
158
 def cmd_deactivate(args) -> None:
172
 def cmd_deactivate(args) -> None:
159
     """Deactivate a group"""
173
     """Deactivate a group"""
160
     config_path = Config.get_default_config_path()
174
     config_path = Config.get_default_config_path()
161
-    
175
+
162
     try:
176
     try:
163
-        config = Config(config_path)
177
+        config = Config(config_path, debug=getattr(args, "debug", False))
164
         config.load()
178
         config.load()
165
-        
179
+
166
-        if args.group == 'persistent':
180
+        if args.group == "persistent":
167
             print("Error: 'persistent' group cannot be deactivated")
181
             print("Error: 'persistent' group cannot be deactivated")
168
             return
182
             return
169
-        
183
+
170
         if config.deactivate_group(args.group):
184
         if config.deactivate_group(args.group):
171
             # Regenerate loader to exclude deactivated group
185
             # Regenerate loader to exclude deactivated group
172
             from shtick.generator import Generator
186
             from shtick.generator import Generator
187
+
173
             generator = Generator()
188
             generator = Generator()
189
+            # Regenerate loader (no need to regenerate all files for deactivation)
174
             generator.generate_loader(config)
190
             generator.generate_loader(config)
175
-            
191
+
176
             print(f"Deactivated group '{args.group}'")
192
             print(f"Deactivated group '{args.group}'")
177
             print("Changes will take effect in new shell sessions")
193
             print("Changes will take effect in new shell sessions")
178
         else:
194
         else:
179
             print(f"Group '{args.group}' was not active")
195
             print(f"Group '{args.group}' was not active")
180
-    
196
+
181
     except FileNotFoundError:
197
     except FileNotFoundError:
182
         print(f"Config file not found: {config_path}")
198
         print(f"Config file not found: {config_path}")
183
         sys.exit(1)
199
         sys.exit(1)
@@ -185,36 +201,40 @@ def cmd_deactivate(args) -> None:
185
         print(f"Error: {e}")
201
         print(f"Error: {e}")
186
         sys.exit(1)
202
         sys.exit(1)
187
 
203
 
204
+
188
 def cmd_list(args) -> None:
205
 def cmd_list(args) -> None:
189
     """List current configuration"""
206
     """List current configuration"""
190
     config_path = Config.get_default_config_path()
207
     config_path = Config.get_default_config_path()
191
-    
208
+
192
     try:
209
     try:
193
-        config = Config(config_path)
210
+        config = Config(config_path, debug=getattr(args, "debug", False))
194
         config.load()
211
         config.load()
195
-        
212
+
196
         if not config.groups:
213
         if not config.groups:
197
             print("No groups configured")
214
             print("No groups configured")
198
             return
215
             return
199
-        
216
+
200
         persistent_group = config.get_persistent_group()
217
         persistent_group = config.get_persistent_group()
201
         regular_groups = config.get_regular_groups()
218
         regular_groups = config.get_regular_groups()
202
         active_groups = config.load_active_groups()
219
         active_groups = config.load_active_groups()
203
-        
220
+
204
         if args.long:
221
         if args.long:
205
             # Long format - detailed line-by-line
222
             # Long format - detailed line-by-line
206
             _print_detailed_list(persistent_group, regular_groups, active_groups)
223
             _print_detailed_list(persistent_group, regular_groups, active_groups)
207
         else:
224
         else:
208
             # Default tabular format
225
             # Default tabular format
209
             _print_tabular_list(persistent_group, regular_groups, active_groups)
226
             _print_tabular_list(persistent_group, regular_groups, active_groups)
210
-    
227
+
211
     except FileNotFoundError:
228
     except FileNotFoundError:
212
         print(f"Config file not found: {config_path}")
229
         print(f"Config file not found: {config_path}")
213
-        print("Use 'shtick add' to create entries or 'shtick generate' with a config file")
230
+        print(
231
+            "Use 'shtick add' to create entries or 'shtick generate' with a config file"
232
+        )
214
     except Exception as e:
233
     except Exception as e:
215
         print(f"Error: {e}")
234
         print(f"Error: {e}")
216
         sys.exit(1)
235
         sys.exit(1)
217
 
236
 
237
+
218
 def _print_detailed_list(persistent_group, regular_groups, active_groups) -> None:
238
 def _print_detailed_list(persistent_group, regular_groups, active_groups) -> None:
219
     """Print detailed line-by-line list format"""
239
     """Print detailed line-by-line list format"""
220
     # Show persistent group first
240
     # Show persistent group first
@@ -233,33 +253,34 @@ def _print_detailed_list(persistent_group, regular_groups, active_groups) -> Non
233
             for key, value in persistent_group.functions.items():
253
             for key, value in persistent_group.functions.items():
234
                 print(f"    {key} = {value}")
254
                 print(f"    {key} = {value}")
235
         print()
255
         print()
236
-    
256
+
237
     # Show regular groups
257
     # Show regular groups
238
     for group in regular_groups:
258
     for group in regular_groups:
239
         status = " (ACTIVE)" if group.name in active_groups else " (inactive)"
259
         status = " (ACTIVE)" if group.name in active_groups else " (inactive)"
240
         print(f"Group: {group.name}{status}")
260
         print(f"Group: {group.name}{status}")
241
-        
261
+
242
         if group.aliases:
262
         if group.aliases:
243
             print(f"  Aliases ({len(group.aliases)}):")
263
             print(f"  Aliases ({len(group.aliases)}):")
244
             for key, value in group.aliases.items():
264
             for key, value in group.aliases.items():
245
                 print(f"    {key} = {value}")
265
                 print(f"    {key} = {value}")
246
-        
266
+
247
         if group.env_vars:
267
         if group.env_vars:
248
             print(f"  Environment Variables ({len(group.env_vars)}):")
268
             print(f"  Environment Variables ({len(group.env_vars)}):")
249
             for key, value in group.env_vars.items():
269
             for key, value in group.env_vars.items():
250
                 print(f"    {key} = {value}")
270
                 print(f"    {key} = {value}")
251
-        
271
+
252
         if group.functions:
272
         if group.functions:
253
             print(f"  Functions ({len(group.functions)}):")
273
             print(f"  Functions ({len(group.functions)}):")
254
             for key, value in group.functions.items():
274
             for key, value in group.functions.items():
255
                 print(f"    {key} = {value}")
275
                 print(f"    {key} = {value}")
256
         print()
276
         print()
257
 
277
 
278
+
258
 def _print_tabular_list(persistent_group, regular_groups, active_groups) -> None:
279
 def _print_tabular_list(persistent_group, regular_groups, active_groups) -> None:
259
     """Print compact tabular list format"""
280
     """Print compact tabular list format"""
260
     # Collect all items for tabular display
281
     # Collect all items for tabular display
261
     items = []
282
     items = []
262
-    
283
+
263
     # Add persistent items
284
     # Add persistent items
264
     if persistent_group:
285
     if persistent_group:
265
         for key, value in persistent_group.aliases.items():
286
         for key, value in persistent_group.aliases.items():
@@ -268,54 +289,58 @@ def _print_tabular_list(persistent_group, regular_groups, active_groups) -> None
268
             items.append(("persistent", "env", key, value, "PERSISTENT"))
289
             items.append(("persistent", "env", key, value, "PERSISTENT"))
269
         for key, value in persistent_group.functions.items():
290
         for key, value in persistent_group.functions.items():
270
             items.append(("persistent", "function", key, value, "PERSISTENT"))
291
             items.append(("persistent", "function", key, value, "PERSISTENT"))
271
-    
292
+
272
     # Add regular group items
293
     # Add regular group items
273
     for group in regular_groups:
294
     for group in regular_groups:
274
         status = "ACTIVE" if group.name in active_groups else "inactive"
295
         status = "ACTIVE" if group.name in active_groups else "inactive"
275
-        
296
+
276
         for key, value in group.aliases.items():
297
         for key, value in group.aliases.items():
277
             items.append((group.name, "alias", key, value, status))
298
             items.append((group.name, "alias", key, value, status))
278
         for key, value in group.env_vars.items():
299
         for key, value in group.env_vars.items():
279
             items.append((group.name, "env", key, value, status))
300
             items.append((group.name, "env", key, value, status))
280
         for key, value in group.functions.items():
301
         for key, value in group.functions.items():
281
             items.append((group.name, "function", key, value, status))
302
             items.append((group.name, "function", key, value, status))
282
-    
303
+
283
     if not items:
304
     if not items:
284
         print("No items configured")
305
         print("No items configured")
285
         return
306
         return
286
-    
307
+
287
     # Calculate column widths
308
     # Calculate column widths
288
     max_group = max(len(item[0]) for item in items)
309
     max_group = max(len(item[0]) for item in items)
289
     max_type = max(len(item[1]) for item in items)
310
     max_type = max(len(item[1]) for item in items)
290
     max_key = max(len(item[2]) for item in items)
311
     max_key = max(len(item[2]) for item in items)
291
     max_value = max(min(len(item[3]), 50) for item in items)  # Limit value column width
312
     max_value = max(min(len(item[3]), 50) for item in items)  # Limit value column width
292
     max_status = max(len(item[4]) for item in items)
313
     max_status = max(len(item[4]) for item in items)
293
-    
314
+
294
     # Ensure minimum widths
315
     # Ensure minimum widths
295
     max_group = max(max_group, 5)  # "Group"
316
     max_group = max(max_group, 5)  # "Group"
296
-    max_type = max(max_type, 4)    # "Type"
317
+    max_type = max(max_type, 4)  # "Type"
297
-    max_key = max(max_key, 3)      # "Key"
318
+    max_key = max(max_key, 3)  # "Key"
298
     max_value = max(max_value, 5)  # "Value"
319
     max_value = max(max_value, 5)  # "Value"
299
-    max_status = max(max_status, 6) # "Status"
320
+    max_status = max(max_status, 6)  # "Status"
300
-    
321
+
301
     # Print header
322
     # Print header
302
     header = f"{'Group':<{max_group}} {'Type':<{max_type}} {'Key':<{max_key}} {'Value':<{max_value}} {'Status':<{max_status}}"
323
     header = f"{'Group':<{max_group}} {'Type':<{max_type}} {'Key':<{max_key}} {'Value':<{max_value}} {'Status':<{max_status}}"
303
     print(header)
324
     print(header)
304
     print("-" * len(header))
325
     print("-" * len(header))
305
-    
326
+
306
     # Print items
327
     # Print items
307
     for group, item_type, key, value, status in items:
328
     for group, item_type, key, value, status in items:
308
         # Truncate long values with ellipsis
329
         # Truncate long values with ellipsis
309
-        display_value = value if len(value) <= max_value else value[:max_value-3] + "..."
330
+        display_value = (
310
-        
331
+            value if len(value) <= max_value else value[: max_value - 3] + "..."
311
-        print(f"{group:<{max_group}} {item_type:<{max_type}} {key:<{max_key}} {display_value:<{max_value}} {status:<{max_status}}")
332
+        )
312
-    
333
+
334
+        print(
335
+            f"{group:<{max_group}} {item_type:<{max_type}} {key:<{max_key}} {display_value:<{max_value}} {status:<{max_status}}"
336
+        )
337
+
313
     # Print summary
338
     # Print summary
314
     print()
339
     print()
315
     total_items = len(items)
340
     total_items = len(items)
316
     active_items = len([item for item in items if item[4] in ["ACTIVE", "PERSISTENT"]])
341
     active_items = len([item for item in items if item[4] in ["ACTIVE", "PERSISTENT"]])
317
     print(f"Total: {total_items} items ({active_items} active)")
342
     print(f"Total: {total_items} items ({active_items} active)")
318
-    
343
+
319
     # Show available commands
344
     # Show available commands
320
     print()
345
     print()
321
     print("Use 'shtick list -l' for detailed view")
346
     print("Use 'shtick list -l' for detailed view")
@@ -324,10 +349,11 @@ def _print_tabular_list(persistent_group, regular_groups, active_groups) -> None
324
         print(f"Activate groups with: shtick activate <group>")
349
         print(f"Activate groups with: shtick activate <group>")
325
         print(f"Inactive groups: {', '.join(sorted(inactive_groups))}")
350
         print(f"Inactive groups: {', '.join(sorted(inactive_groups))}")
326
 
351
 
352
+
327
 def cmd_shells(args) -> None:
353
 def cmd_shells(args) -> None:
328
     """List supported shells"""
354
     """List supported shells"""
329
     shells = sorted(get_supported_shells())
355
     shells = sorted(get_supported_shells())
330
-    
356
+
331
     if args.long:
357
     if args.long:
332
         # Long format - one per line with descriptions
358
         # Long format - one per line with descriptions
333
         print("Supported shells:")
359
         print("Supported shells:")
@@ -337,34 +363,36 @@ def cmd_shells(args) -> None:
337
         # Default columnar format (like ls)
363
         # Default columnar format (like ls)
338
         _print_shells_columns(shells)
364
         _print_shells_columns(shells)
339
 
365
 
366
+
340
 def _print_shells_columns(shells) -> None:
367
 def _print_shells_columns(shells) -> None:
341
     """Print shells in columns like ls output"""
368
     """Print shells in columns like ls output"""
342
     if not shells:
369
     if not shells:
343
         print("No shells configured")
370
         print("No shells configured")
344
         return
371
         return
345
-    
372
+
346
     # Try to get terminal width, fallback to 80
373
     # Try to get terminal width, fallback to 80
347
     try:
374
     try:
348
         import shutil
375
         import shutil
376
+
349
         terminal_width = shutil.get_terminal_size().columns
377
         terminal_width = shutil.get_terminal_size().columns
350
     except:
378
     except:
351
         terminal_width = 80
379
         terminal_width = 80
352
-    
380
+
353
     # Find the longest shell name
381
     # Find the longest shell name
354
     max_shell_length = max(len(shell) for shell in shells)
382
     max_shell_length = max(len(shell) for shell in shells)
355
-    
383
+
356
     # Add some padding
384
     # Add some padding
357
     column_width = max_shell_length + 2
385
     column_width = max_shell_length + 2
358
-    
386
+
359
     # Calculate how many columns we can fit
387
     # Calculate how many columns we can fit
360
     columns = max(1, terminal_width // column_width)
388
     columns = max(1, terminal_width // column_width)
361
-    
389
+
362
     # Calculate number of rows needed
390
     # Calculate number of rows needed
363
     rows = (len(shells) + columns - 1) // columns
391
     rows = (len(shells) + columns - 1) // columns
364
-    
392
+
365
     print(f"Supported shells ({len(shells)} total):")
393
     print(f"Supported shells ({len(shells)} total):")
366
     print()
394
     print()
367
-    
395
+
368
     # Print shells in columns
396
     # Print shells in columns
369
     for row in range(rows):
397
     for row in range(rows):
370
         line = ""
398
         line = ""
@@ -375,52 +403,59 @@ def _print_shells_columns(shells) -> None:
375
                 line += f"{shell:<{column_width}}"
403
                 line += f"{shell:<{column_width}}"
376
         print(line.rstrip())
404
         print(line.rstrip())
377
 
405
 
406
+
378
 def cmd_status(args) -> None:
407
 def cmd_status(args) -> None:
379
     """Show status of groups and active state"""
408
     """Show status of groups and active state"""
380
     config_path = Config.get_default_config_path()
409
     config_path = Config.get_default_config_path()
381
-    
410
+
382
     try:
411
     try:
383
-        config = Config(config_path)
412
+        config = Config(config_path, debug=getattr(args, "debug", False))
384
         config.load()
413
         config.load()
385
-        
414
+
386
         persistent_group = config.get_persistent_group()
415
         persistent_group = config.get_persistent_group()
387
         regular_groups = config.get_regular_groups()
416
         regular_groups = config.get_regular_groups()
388
         active_groups = config.load_active_groups()
417
         active_groups = config.load_active_groups()
389
-        
418
+
390
         print("Shtick Status")
419
         print("Shtick Status")
391
         print("=" * 40)
420
         print("=" * 40)
392
-        
421
+
393
         # Show persistent group
422
         # Show persistent group
394
         if persistent_group:
423
         if persistent_group:
395
-            total_persistent = len(persistent_group.aliases) + len(persistent_group.env_vars) + len(persistent_group.functions)
424
+            total_persistent = (
425
+                len(persistent_group.aliases)
426
+                + len(persistent_group.env_vars)
427
+                + len(persistent_group.functions)
428
+            )
396
             print(f"Persistent (always active): {total_persistent} items")
429
             print(f"Persistent (always active): {total_persistent} items")
397
         else:
430
         else:
398
             print("Persistent: No items")
431
             print("Persistent: No items")
399
-        
432
+
400
         print()
433
         print()
401
-        
434
+
402
         # Show regular groups
435
         # Show regular groups
403
         if regular_groups:
436
         if regular_groups:
404
             print("Available Groups:")
437
             print("Available Groups:")
405
             for group in regular_groups:
438
             for group in regular_groups:
406
                 status = "ACTIVE" if group.name in active_groups else "inactive"
439
                 status = "ACTIVE" if group.name in active_groups else "inactive"
407
-                total_items = len(group.aliases) + len(group.env_vars) + len(group.functions)
440
+                total_items = (
441
+                    len(group.aliases) + len(group.env_vars) + len(group.functions)
442
+                )
408
                 print(f"  {group.name}: {total_items} items ({status})")
443
                 print(f"  {group.name}: {total_items} items ({status})")
409
         else:
444
         else:
410
             print("No regular groups configured")
445
             print("No regular groups configured")
411
-        
446
+
412
         print()
447
         print()
413
-        
448
+
414
         # Show summary
449
         # Show summary
415
         if active_groups:
450
         if active_groups:
416
             print(f"Currently active: {', '.join(active_groups)}")
451
             print(f"Currently active: {', '.join(active_groups)}")
417
         else:
452
         else:
418
             print("No groups currently active")
453
             print("No groups currently active")
419
-        
454
+
420
         print()
455
         print()
421
         print("To activate a group: shtick activate <group>")
456
         print("To activate a group: shtick activate <group>")
422
         print("To deactivate a group: shtick deactivate <group>")
457
         print("To deactivate a group: shtick deactivate <group>")
423
-    
458
+
424
     except FileNotFoundError:
459
     except FileNotFoundError:
425
         print(f"Config file not found: {config_path}")
460
         print(f"Config file not found: {config_path}")
426
         print("No configuration exists yet")
461
         print("No configuration exists yet")
@@ -428,75 +463,98 @@ def cmd_status(args) -> None:
428
         print(f"Error: {e}")
463
         print(f"Error: {e}")
429
         sys.exit(1)
464
         sys.exit(1)
430
 
465
 
466
+
431
 def main():
467
 def main():
432
     """Main CLI entry point"""
468
     """Main CLI entry point"""
433
     parser = argparse.ArgumentParser(
469
     parser = argparse.ArgumentParser(
434
         description="shtick - Generate shell configuration files from TOML"
470
         description="shtick - Generate shell configuration files from TOML"
435
     )
471
     )
436
-    
472
+
437
-    subparsers = parser.add_subparsers(dest='command', help='Available commands')
473
+    # Global flags
438
-    
474
+    parser.add_argument("--debug", action="store_true", help="Enable debug output")
475
+
476
+    subparsers = parser.add_subparsers(dest="command", help="Available commands")
477
+
439
     # Generate command
478
     # Generate command
440
-    gen_parser = subparsers.add_parser('generate', help='Generate shell files from config')
479
+    gen_parser = subparsers.add_parser(
441
-    gen_parser.add_argument('config', nargs='?', help='Path to config TOML file')
480
+        "generate", help="Generate shell files from config"
442
-    
481
+    )
443
-    # Add command  
482
+    gen_parser.add_argument("config", nargs="?", help="Path to config TOML file")
444
-    add_parser = subparsers.add_parser('add', help='Add an item to config')
483
+    gen_parser.add_argument(
445
-    add_parser.add_argument('type', choices=['alias', 'env', 'function'], 
484
+        "-i",
446
-                           help='Type of item to add')
485
+        "--interactive",
447
-    add_parser.add_argument('group', help='Group name')
486
+        action="store_true",
448
-    add_parser.add_argument('assignment', help='Assignment in format key=value')
487
+        help="Interactive shell selection for sourcing instructions",
449
-    
488
+    )
489
+
490
+    # Add command
491
+    add_parser = subparsers.add_parser("add", help="Add an item to config")
492
+    add_parser.add_argument(
493
+        "type", choices=["alias", "env", "function"], help="Type of item to add"
494
+    )
495
+    add_parser.add_argument("group", help="Group name")
496
+    add_parser.add_argument("assignment", help="Assignment in format key=value")
497
+
450
     # Remove command
498
     # Remove command
451
-    rm_parser = subparsers.add_parser('remove', help='Remove an item from config')
499
+    rm_parser = subparsers.add_parser("remove", help="Remove an item from config")
452
-    rm_parser.add_argument('type', choices=['alias', 'env', 'function'],
500
+    rm_parser.add_argument(
453
-                          help='Type of item to remove')
501
+        "type", choices=["alias", "env", "function"], help="Type of item to remove"
454
-    rm_parser.add_argument('group', help='Group name')
502
+    )
455
-    rm_parser.add_argument('search', help='Search term (fuzzy match)')
503
+    rm_parser.add_argument("group", help="Group name")
456
-    
504
+    rm_parser.add_argument("search", help="Search term (fuzzy match)")
505
+
457
     # Activate command
506
     # Activate command
458
-    activate_parser = subparsers.add_parser('activate', help='Activate a group')
507
+    activate_parser = subparsers.add_parser("activate", help="Activate a group")
459
-    activate_parser.add_argument('group', help='Group name to activate')
508
+    activate_parser.add_argument("group", help="Group name to activate")
460
-    
509
+
461
-    # Deactivate command  
510
+    # Deactivate command
462
-    deactivate_parser = subparsers.add_parser('deactivate', help='Deactivate a group')
511
+    deactivate_parser = subparsers.add_parser("deactivate", help="Deactivate a group")
463
-    deactivate_parser.add_argument('group', help='Group name to deactivate')
512
+    deactivate_parser.add_argument("group", help="Group name to deactivate")
464
-    
513
+
465
     # Status command
514
     # Status command
466
-    status_parser = subparsers.add_parser('status', help='Show status of groups')
515
+    status_parser = subparsers.add_parser("status", help="Show status of groups")
467
-    
516
+
468
     # List command
517
     # List command
469
-    list_parser = subparsers.add_parser('list', help='List current configuration')
518
+    list_parser = subparsers.add_parser("list", help="List current configuration")
470
-    list_parser.add_argument('-l', '--long', action='store_true', 
519
+    list_parser.add_argument(
471
-                            help='Show detailed line-by-line format instead of table')
520
+        "-l",
472
-    
521
+        "--long",
522
+        action="store_true",
523
+        help="Show detailed line-by-line format instead of table",
524
+    )
525
+
473
     # Shells command
526
     # Shells command
474
-    shells_parser = subparsers.add_parser('shells', help='List supported shells')
527
+    shells_parser = subparsers.add_parser("shells", help="List supported shells")
475
-    shells_parser.add_argument('-l', '--long', action='store_true',
528
+    shells_parser.add_argument(
476
-                              help='Show one shell per line instead of columns')
529
+        "-l",
477
-    
530
+        "--long",
531
+        action="store_true",
532
+        help="Show one shell per line instead of columns",
533
+    )
534
+
478
     args = parser.parse_args()
535
     args = parser.parse_args()
479
-    
536
+
480
     if not args.command:
537
     if not args.command:
481
         parser.print_help()
538
         parser.print_help()
482
         sys.exit(1)
539
         sys.exit(1)
483
-    
540
+
484
-    if args.command == 'generate':
541
+    if args.command == "generate":
485
         cmd_generate(args)
542
         cmd_generate(args)
486
-    elif args.command == 'add':
543
+    elif args.command == "add":
487
         cmd_add(args)
544
         cmd_add(args)
488
-    elif args.command == 'remove':
545
+    elif args.command == "remove":
489
         cmd_remove(args)
546
         cmd_remove(args)
490
-    elif args.command == 'activate':
547
+    elif args.command == "activate":
491
         cmd_activate(args)
548
         cmd_activate(args)
492
-    elif args.command == 'deactivate':
549
+    elif args.command == "deactivate":
493
         cmd_deactivate(args)
550
         cmd_deactivate(args)
494
-    elif args.command == 'status':
551
+    elif args.command == "status":
495
         cmd_status(args)
552
         cmd_status(args)
496
-    elif args.command == 'list':
553
+    elif args.command == "list":
497
         cmd_list(args)
554
         cmd_list(args)
498
-    elif args.command == 'shells':
555
+    elif args.command == "shells":
499
         cmd_shells(args)
556
         cmd_shells(args)
500
 
557
 
501
-if __name__ == '__main__':
558
+
502
-    main()
559
+if __name__ == "__main__":
560
+    main()
src/shtick/config.pymodified
@@ -22,8 +22,9 @@ class GroupData:
22
 class Config:
22
 class Config:
23
     """Main configuration handler"""
23
     """Main configuration handler"""
24
 
24
 
25
-    def __init__(self, config_path: Optional[str] = None):
25
+    def __init__(self, config_path: Optional[str] = None, debug: bool = False):
26
         self.config_path = config_path or self.get_default_config_path()
26
         self.config_path = config_path or self.get_default_config_path()
27
+        self.debug = debug
27
         self.groups: List[GroupData] = []
28
         self.groups: List[GroupData] = []
28
 
29
 
29
     @staticmethod
30
     @staticmethod
@@ -103,22 +104,28 @@ class Config:
103
         if not os.path.exists(self.config_path):
104
         if not os.path.exists(self.config_path):
104
             raise FileNotFoundError(f"Config file not found: {self.config_path}")
105
             raise FileNotFoundError(f"Config file not found: {self.config_path}")
105
 
106
 
106
-        print(f"Debug: Loading config from: {self.config_path}")
107
+        if self.debug:
108
+            print(f"Debug: Loading config from: {self.config_path}")
107
 
109
 
108
         with open(self.config_path, "rb") as f:
110
         with open(self.config_path, "rb") as f:
109
             data = tomllib.load(f)
111
             data = tomllib.load(f)
110
 
112
 
111
-        print(f"Debug: Raw TOML data keys: {list(data.keys())}")
113
+        if self.debug:
112
-        print(f"Debug: Raw TOML data structure: {data}")
114
+            print(f"Debug: Raw TOML data keys: {list(data.keys())}")
115
+            print(f"Debug: Raw TOML data structure: {data}")
113
 
116
 
114
         self.groups = []
117
         self.groups = []
115
 
118
 
116
         # Parse groups from nested TOML structure
119
         # Parse groups from nested TOML structure
117
         for group_name, group_config in data.items():
120
         for group_name, group_config in data.items():
118
-            print(f"Debug: Processing group '{group_name}' with config: {group_config}")
121
+            if self.debug:
122
+                print(
123
+                    f"Debug: Processing group '{group_name}' with config: {group_config}"
124
+                )
119
 
125
 
120
             if not isinstance(group_config, dict):
126
             if not isinstance(group_config, dict):
121
-                print(f"Debug: Skipping non-dict value for key '{group_name}'")
127
+                if self.debug:
128
+                    print(f"Debug: Skipping non-dict value for key '{group_name}'")
122
                 continue
129
                 continue
123
 
130
 
124
             # Initialize group data
131
             # Initialize group data
@@ -126,27 +133,34 @@ class Config:
126
 
133
 
127
             # Extract each section from the group
134
             # Extract each section from the group
128
             for section_name, section_data in group_config.items():
135
             for section_name, section_data in group_config.items():
129
-                print(
136
+                if self.debug:
130
-                    f"Debug: Processing section '{section_name}' in group '{group_name}': {section_data}"
137
+                    print(
131
-                )
138
+                        f"Debug: Processing section '{section_name}' in group '{group_name}': {section_data}"
139
+                    )
132
 
140
 
133
                 if section_name == "aliases" and isinstance(section_data, dict):
141
                 if section_name == "aliases" and isinstance(section_data, dict):
134
                     group_data["aliases"] = section_data
142
                     group_data["aliases"] = section_data
135
-                    print(f"Debug: Added {len(section_data)} aliases to '{group_name}'")
143
+                    if self.debug:
144
+                        print(
145
+                            f"Debug: Added {len(section_data)} aliases to '{group_name}'"
146
+                        )
136
                 elif section_name == "env_vars" and isinstance(section_data, dict):
147
                 elif section_name == "env_vars" and isinstance(section_data, dict):
137
                     group_data["env_vars"] = section_data
148
                     group_data["env_vars"] = section_data
138
-                    print(
149
+                    if self.debug:
139
-                        f"Debug: Added {len(section_data)} env_vars to '{group_name}'"
150
+                        print(
140
-                    )
151
+                            f"Debug: Added {len(section_data)} env_vars to '{group_name}'"
152
+                        )
141
                 elif section_name == "functions" and isinstance(section_data, dict):
153
                 elif section_name == "functions" and isinstance(section_data, dict):
142
                     group_data["functions"] = section_data
154
                     group_data["functions"] = section_data
143
-                    print(
155
+                    if self.debug:
144
-                        f"Debug: Added {len(section_data)} functions to '{group_name}'"
156
+                        print(
145
-                    )
157
+                            f"Debug: Added {len(section_data)} functions to '{group_name}'"
158
+                        )
146
                 else:
159
                 else:
147
-                    print(
160
+                    if self.debug:
148
-                        f"Debug: Unknown or invalid section '{section_name}' in group '{group_name}'"
161
+                        print(
149
-                    )
162
+                            f"Debug: Unknown or invalid section '{section_name}' in group '{group_name}'"
163
+                        )
150
 
164
 
151
             # Create GroupData object if group has any items
165
             # Create GroupData object if group has any items
152
             total_items = (
166
             total_items = (
@@ -154,7 +168,8 @@ class Config:
154
                 + len(group_data["env_vars"])
168
                 + len(group_data["env_vars"])
155
                 + len(group_data["functions"])
169
                 + len(group_data["functions"])
156
             )
170
             )
157
-            print(f"Debug: Group '{group_name}' has {total_items} total items")
171
+            if self.debug:
172
+                print(f"Debug: Group '{group_name}' has {total_items} total items")
158
 
173
 
159
             if total_items > 0:
174
             if total_items > 0:
160
                 new_group = GroupData(
175
                 new_group = GroupData(
@@ -164,15 +179,18 @@ class Config:
164
                     functions=group_data["functions"],
179
                     functions=group_data["functions"],
165
                 )
180
                 )
166
                 self.groups.append(new_group)
181
                 self.groups.append(new_group)
167
-                print(
182
+                if self.debug:
168
-                    f"Debug: Created group '{group_name}' with {len(group_data['aliases'])} aliases, {len(group_data['env_vars'])} env_vars, {len(group_data['functions'])} functions"
183
+                    print(
169
-                )
184
+                        f"Debug: Created group '{group_name}' with {len(group_data['aliases'])} aliases, {len(group_data['env_vars'])} env_vars, {len(group_data['functions'])} functions"
185
+                    )
170
             else:
186
             else:
171
-                print(f"Warning: Group '{group_name}' has no items, skipping")
187
+                if self.debug:
188
+                    print(f"Warning: Group '{group_name}' has no items, skipping")
172
 
189
 
173
-        print(
190
+        if self.debug:
174
-            f"Debug: Final groups loaded: {[g.name for g in self.groups]} (total: {len(self.groups)})"
191
+            print(
175
-        )
192
+                f"Debug: Final groups loaded: {[g.name for g in self.groups]} (total: {len(self.groups)})"
193
+            )
176
 
194
 
177
     def save(self) -> None:
195
     def save(self) -> None:
178
         """Save the current configuration back to TOML file"""
196
         """Save the current configuration back to TOML file"""
src/shtick/generator.pymodified
@@ -72,7 +72,7 @@ class Generator:
72
 
72
 
73
                     f.write(line)
73
                     f.write(line)
74
 
74
 
75
-    def generate_all(self, config: Config) -> None:
75
+    def generate_all(self, config: Config, interactive: bool = False) -> None:
76
         """Generate shell files for all groups in config"""
76
         """Generate shell files for all groups in config"""
77
         if not config.groups:
77
         if not config.groups:
78
             print("No groups found in configuration")
78
             print("No groups found in configuration")
@@ -87,7 +87,7 @@ class Generator:
87
         self.generate_loader(config)
87
         self.generate_loader(config)
88
 
88
 
89
         print(f"All done! Files generated in {self.output_base_dir}")
89
         print(f"All done! Files generated in {self.output_base_dir}")
90
-        self._print_usage_instructions(config)
90
+        self._print_usage_instructions(config, interactive)
91
 
91
 
92
     def generate_loader(self, config: Config) -> None:
92
     def generate_loader(self, config: Config) -> None:
93
         """Generate dynamic loader files that source persistent + active groups"""
93
         """Generate dynamic loader files that source persistent + active groups"""
@@ -112,9 +112,15 @@ class Generator:
112
                 if persistent_group:
112
                 if persistent_group:
113
                     f.write("# Load persistent configuration\n")
113
                     f.write("# Load persistent configuration\n")
114
                     for item_type in ["alias", "env", "function"]:
114
                     for item_type in ["alias", "env", "function"]:
115
-                        file_path = f"$HOME/.config/shtick/persistent/{item_type}"
115
+                        # Map item_type to the actual filename prefix
116
-                        f.write(f'[ -f "{file_path}/{item_type}s.{shell_name}" ] && ')
116
+                        prefix_map = {
117
-                        f.write(f'source "{file_path}/{item_type}s.{shell_name}"\n')
117
+                            "alias": "aliases",
118
+                            "env": "envvars",
119
+                            "function": "functions",
120
+                        }
121
+                        prefix = prefix_map[item_type]
122
+                        file_path = f"$HOME/.config/shtick/persistent/{item_type}/{prefix}.{shell_name}"
123
+                        f.write(f'[ -f "{file_path}" ] && source "{file_path}"\n')
118
                     f.write("\n")
124
                     f.write("\n")
119
 
125
 
120
                 # Source active groups
126
                 # Source active groups
@@ -123,25 +129,30 @@ class Generator:
123
                     for group_name in active_groups:
129
                     for group_name in active_groups:
124
                         f.write(f"# Group: {group_name}\n")
130
                         f.write(f"# Group: {group_name}\n")
125
                         for item_type in ["alias", "env", "function"]:
131
                         for item_type in ["alias", "env", "function"]:
126
-                            file_path = f"$HOME/.config/shtick/{group_name}/{item_type}"
132
+                            # Map item_type to the actual filename prefix
127
-                            f.write(
133
+                            prefix_map = {
128
-                                f'[ -f "{file_path}/{item_type}s.{shell_name}" ] && '
134
+                                "alias": "aliases",
129
-                            )
135
+                                "env": "envvars",
130
-                            f.write(f'source "{file_path}/{item_type}s.{shell_name}"\n')
136
+                                "function": "functions",
137
+                            }
138
+                            prefix = prefix_map[item_type]
139
+                            file_path = f"$HOME/.config/shtick/{group_name}/{item_type}/{prefix}.{shell_name}"
140
+                            f.write(f'[ -f "{file_path}" ] && source "{file_path}"\n')
131
                         f.write("\n")
141
                         f.write("\n")
132
                 else:
142
                 else:
133
                     f.write("# No active groups\n")
143
                     f.write("# No active groups\n")
134
 
144
 
135
-    def _print_usage_instructions(self, config: Config) -> None:
145
+    def _print_usage_instructions(
146
+        self, config: Config, interactive: bool = False
147
+    ) -> None:
136
         """Print usage instructions for the user"""
148
         """Print usage instructions for the user"""
137
         active_groups = config.load_active_groups()
149
         active_groups = config.load_active_groups()
138
         persistent_group = config.get_persistent_group()
150
         persistent_group = config.get_persistent_group()
139
 
151
 
140
-        print("\nTo use these configurations, add this line to your shell config:")
152
+        if interactive:
141
-        print("  # For bash/zsh (~/.bashrc or ~/.zshrc):")
153
+            self._interactive_shell_setup()
142
-        print("  source ~/.config/shtick/load_active.bash")
154
+        else:
143
-        print("\n  # For fish (~/.config/fish/config.fish):")
155
+            self._print_default_instructions()
144
-        print("  source ~/.config/shtick/load_active.fish")
145
 
156
 
146
         if persistent_group:
157
         if persistent_group:
147
             total_persistent = (
158
             total_persistent = (
@@ -162,6 +173,136 @@ class Generator:
162
         if available_groups:
173
         if available_groups:
163
             print(f"Available groups: {', '.join(available_groups)}")
174
             print(f"Available groups: {', '.join(available_groups)}")
164
 
175
 
176
+    def _print_default_instructions(self) -> None:
177
+        """Print default sourcing instructions"""
178
+        print("\nTo use these configurations, add this line to your shell config:")
179
+        print("  # For bash/zsh (~/.bashrc or ~/.zshrc):")
180
+        print("  source ~/.config/shtick/load_active.bash")
181
+        print("\n  # For fish (~/.config/fish/config.fish):")
182
+        print("  source ~/.config/shtick/load_active.fish")
183
+
184
+    def _interactive_shell_setup(self) -> None:
185
+        """Interactive shell selection for sourcing instructions"""
186
+        from shtick.shells import get_supported_shells
187
+
188
+        print("\nSelect shells to show sourcing instructions for:")
189
+        print("(You can specify multiple shells by number or name, space-separated)")
190
+        print()
191
+
192
+        shells = sorted(get_supported_shells())
193
+        shell_configs = {
194
+            "bash": ("~/.bashrc", "source ~/.config/shtick/load_active.bash"),
195
+            "zsh": ("~/.zshrc", "source ~/.config/shtick/load_active.zsh"),
196
+            "fish": (
197
+                "~/.config/fish/config.fish",
198
+                "source ~/.config/shtick/load_active.fish",
199
+            ),
200
+            "ksh": ("~/.kshrc", "source ~/.config/shtick/load_active.ksh"),
201
+            "mksh": ("~/.mkshrc", "source ~/.config/shtick/load_active.mksh"),
202
+            "yash": ("~/.yashrc", "source ~/.config/shtick/load_active.yash"),
203
+            "dash": ("~/.dashrc", "source ~/.config/shtick/load_active.dash"),
204
+            "csh": ("~/.cshrc", "source ~/.config/shtick/load_active.csh"),
205
+            "tcsh": ("~/.tcshrc", "source ~/.config/shtick/load_active.tcsh"),
206
+            "xonsh": ("~/.xonshrc", "source ~/.config/shtick/load_active.xonsh"),
207
+            "elvish": (
208
+                "~/.elvish/rc.elv",
209
+                "source ~/.config/shtick/load_active.elvish",
210
+            ),
211
+            "nushell": (
212
+                "~/.config/nushell/config.nu",
213
+                "source ~/.config/shtick/load_active.nushell",
214
+            ),
215
+            "powershell": ("$PROFILE", ". ~/.config/shtick/load_active.powershell"),
216
+            "rc": ("~/.rcrc", "source ~/.config/shtick/load_active.rc"),
217
+            "es": ("~/.esrc", "source ~/.config/shtick/load_active.es"),
218
+            "oil": ("~/.oilrc", "source ~/.config/shtick/load_active.oil"),
219
+        }
220
+
221
+        # Display numbered list
222
+        for i, shell in enumerate(shells, 1):
223
+            config_file = shell_configs.get(
224
+                shell, ("~/.profile", f"source ~/.config/shtick/load_active.{shell}")
225
+            )[0]
226
+            print(f"  {i:2d}. {shell:<12} ({config_file})")
227
+
228
+        print(f"  {len(shells)+1:2d}. all          (show all shells)")
229
+        print()
230
+
231
+        try:
232
+            user_input = input(
233
+                "Enter shell numbers or names (space-separated): "
234
+            ).strip()
235
+            if not user_input:
236
+                print("No selection made, showing default instructions.")
237
+                self._print_default_instructions()
238
+                return
239
+
240
+            selected_shells = self._parse_shell_selection(user_input, shells)
241
+
242
+            if not selected_shells:
243
+                print("No valid shells selected, showing default instructions.")
244
+                self._print_default_instructions()
245
+                return
246
+
247
+            print("\nAdd these lines to your shell configuration files:")
248
+            print("=" * 60)
249
+
250
+            for shell in selected_shells:
251
+                config_file, source_line = shell_configs.get(
252
+                    shell,
253
+                    ("~/.profile", f"source ~/.config/shtick/load_active.{shell}"),
254
+                )
255
+
256
+                print(f"\n# {shell.upper()}")
257
+                print(f"# File: {config_file}")
258
+                print(f"{source_line}")
259
+
260
+            print("\n" + "=" * 60)
261
+
262
+        except (KeyboardInterrupt, EOFError):
263
+            print("\nCancelled. Showing default instructions.")
264
+            self._print_default_instructions()
265
+
266
+    def _parse_shell_selection(
267
+        self, user_input: str, available_shells: List[str]
268
+    ) -> List[str]:
269
+        """Parse user input for shell selection (numbers or names)"""
270
+        selected = set()
271
+        tokens = user_input.lower().split()
272
+
273
+        for token in tokens:
274
+            # Check if it's "all"
275
+            if token == "all":
276
+                return available_shells
277
+
278
+            # Check if it's a number
279
+            try:
280
+                num = int(token)
281
+                if 1 <= num <= len(available_shells):
282
+                    selected.add(available_shells[num - 1])
283
+                elif num == len(available_shells) + 1:  # "all" option
284
+                    return available_shells
285
+                else:
286
+                    print(f"Warning: Invalid number {num}, ignoring")
287
+                continue
288
+            except ValueError:
289
+                pass
290
+
291
+            # Check if it's a shell name (fuzzy match)
292
+            matches = [shell for shell in available_shells if token in shell.lower()]
293
+            if len(matches) == 1:
294
+                selected.add(matches[0])
295
+            elif len(matches) > 1:
296
+                print(
297
+                    f"Warning: '{token}' matches multiple shells: {', '.join(matches)}"
298
+                )
299
+                print(f"Using exact match or first match: {matches[0]}")
300
+                selected.add(matches[0])
301
+            else:
302
+                print(f"Warning: No shell matches '{token}', ignoring")
303
+
304
+        return sorted(list(selected))
305
+
165
     def get_shell_files_for_group(self, group_name: str) -> Dict[str, List[str]]:
306
     def get_shell_files_for_group(self, group_name: str) -> Dict[str, List[str]]:
166
         """Get list of generated shell files for a group"""
307
         """Get list of generated shell files for a group"""
167
         files = {"alias": [], "env": [], "function": []}
308
         files = {"alias": [], "env": [], "function": []}