tunacode-cli 0.0.67__py3-none-any.whl → 0.0.68__py3-none-any.whl

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.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (38) hide show
  1. tunacode/cli/commands/__init__.py +2 -0
  2. tunacode/cli/commands/implementations/__init__.py +2 -0
  3. tunacode/cli/commands/implementations/command_reload.py +48 -0
  4. tunacode/cli/commands/implementations/quickstart.py +43 -0
  5. tunacode/cli/commands/registry.py +131 -1
  6. tunacode/cli/commands/slash/__init__.py +32 -0
  7. tunacode/cli/commands/slash/command.py +157 -0
  8. tunacode/cli/commands/slash/loader.py +134 -0
  9. tunacode/cli/commands/slash/processor.py +294 -0
  10. tunacode/cli/commands/slash/types.py +93 -0
  11. tunacode/cli/commands/slash/validator.py +399 -0
  12. tunacode/cli/main.py +4 -1
  13. tunacode/cli/repl.py +25 -0
  14. tunacode/configuration/defaults.py +1 -0
  15. tunacode/constants.py +1 -1
  16. tunacode/core/agents/agent_components/agent_helpers.py +14 -13
  17. tunacode/core/agents/main.py +1 -1
  18. tunacode/core/agents/utils.py +4 -3
  19. tunacode/core/setup/config_setup.py +231 -6
  20. tunacode/core/setup/coordinator.py +13 -5
  21. tunacode/core/setup/git_safety_setup.py +5 -1
  22. tunacode/exceptions.py +119 -5
  23. tunacode/setup.py +5 -2
  24. tunacode/tools/glob.py +9 -46
  25. tunacode/tools/grep.py +9 -51
  26. tunacode/tools/xml_helper.py +83 -0
  27. tunacode/tutorial/__init__.py +9 -0
  28. tunacode/tutorial/content.py +98 -0
  29. tunacode/tutorial/manager.py +182 -0
  30. tunacode/tutorial/steps.py +124 -0
  31. tunacode/ui/output.py +1 -1
  32. tunacode/utils/user_configuration.py +45 -0
  33. tunacode_cli-0.0.68.dist-info/METADATA +192 -0
  34. {tunacode_cli-0.0.67.dist-info → tunacode_cli-0.0.68.dist-info}/RECORD +37 -24
  35. tunacode_cli-0.0.67.dist-info/METADATA +0 -327
  36. {tunacode_cli-0.0.67.dist-info → tunacode_cli-0.0.68.dist-info}/WHEEL +0 -0
  37. {tunacode_cli-0.0.67.dist-info → tunacode_cli-0.0.68.dist-info}/entry_points.txt +0 -0
  38. {tunacode_cli-0.0.67.dist-info → tunacode_cli-0.0.68.dist-info}/licenses/LICENSE +0 -0
@@ -37,12 +37,16 @@ class ConfigSetup(BaseSetup):
37
37
  """Config setup should always run to load and merge configuration."""
38
38
  return True
39
39
 
40
- async def execute(self, force_setup: bool = False) -> None:
40
+ async def execute(self, force_setup: bool = False, wizard_mode: bool = False) -> None:
41
41
  """Setup configuration and run onboarding if needed, with config fingerprint fast path."""
42
42
  import hashlib
43
43
 
44
44
  self.state_manager.session.device_id = system.get_device_id()
45
45
  loaded_config = user_configuration.load_config()
46
+
47
+ # Initialize first-time user settings if needed
48
+ user_configuration.initialize_first_time_user(self.state_manager)
49
+
46
50
  # Fast path: if config fingerprint matches last loaded and config is already present, skip reprocessing
47
51
  new_fp = None
48
52
  if loaded_config:
@@ -52,6 +56,7 @@ class ConfigSetup(BaseSetup):
52
56
  if (
53
57
  loaded_config
54
58
  and not force_setup
59
+ and not wizard_mode
55
60
  and new_fp
56
61
  and last_fp == new_fp
57
62
  and getattr(self.state_manager, "_config_valid", False)
@@ -68,13 +73,17 @@ class ConfigSetup(BaseSetup):
68
73
  await self._handle_cli_config(loaded_config)
69
74
  return
70
75
 
71
- if loaded_config and not force_setup:
76
+ if loaded_config and not force_setup and not wizard_mode:
72
77
  # Silent loading
73
78
  # Merge loaded config with defaults to ensure all required keys exist
74
79
  self.state_manager.session.user_config = self._merge_with_defaults(loaded_config)
75
80
  else:
76
- if force_setup:
77
- await ui.muted("Running setup process, resetting config")
81
+ if force_setup or wizard_mode:
82
+ if wizard_mode:
83
+ await ui.muted("Running interactive setup wizard")
84
+ else:
85
+ await ui.muted("Running setup process, resetting config")
86
+
78
87
  # Ensure user_config is properly initialized
79
88
  if (
80
89
  not hasattr(self.state_manager.session, "user_config")
@@ -89,7 +98,11 @@ class ConfigSetup(BaseSetup):
89
98
  except ConfigurationError as e:
90
99
  await ui.error(str(e))
91
100
  raise
92
- await self._onboarding()
101
+
102
+ if wizard_mode:
103
+ await self._wizard_onboarding()
104
+ else:
105
+ await self._onboarding()
93
106
  else:
94
107
  # No config found - show CLI usage instead of onboarding
95
108
  from tunacode.ui.console import console
@@ -107,9 +120,10 @@ class ConfigSetup(BaseSetup):
107
120
  "--key 'your-key' --baseurl 'https://openrouter.ai/api/v1'[/green]"
108
121
  )
109
122
  console.print("\n[yellow]Run 'tunacode --help' for more options[/yellow]\n")
123
+ console.print("\n[cyan]Or use --wizard for guided setup[/cyan]\n")
110
124
 
111
125
  raise ConfigurationError(
112
- "No configuration found. Please use CLI flags to configure."
126
+ "No configuration found. Please use CLI flags to configure or --wizard for guided setup."
113
127
  )
114
128
 
115
129
  if not self.state_manager.session.user_config.get("default_model"):
@@ -339,3 +353,214 @@ class ConfigSetup(BaseSetup):
339
353
  await ui.success(f"Configuration saved to: {self.config_file}")
340
354
  except ConfigurationError as e:
341
355
  await ui.error(str(e))
356
+
357
+ async def _wizard_onboarding(self):
358
+ """Run enhanced wizard-style onboarding process for new users."""
359
+ initial_config = json.dumps(self.state_manager.session.user_config, sort_keys=True)
360
+
361
+ # Welcome message with provider guidance
362
+ await ui.panel(
363
+ "Welcome to TunaCode Setup Wizard!",
364
+ "This guided setup will help you configure TunaCode in under 5 minutes.\n"
365
+ "We'll help you choose a provider, set up your API keys, and configure your preferred model.",
366
+ border_style=UI_COLORS["primary"],
367
+ )
368
+
369
+ # Step 1: Provider selection with detailed guidance
370
+ await self._wizard_step1_provider_selection()
371
+
372
+ # Step 2: API key setup with provider-specific guidance
373
+ await self._wizard_step2_api_key_setup()
374
+
375
+ # Step 3: Model selection with smart recommendations
376
+ await self._wizard_step3_model_selection()
377
+
378
+ # Step 4: Optional settings configuration
379
+ await self._wizard_step4_optional_settings()
380
+
381
+ # Save configuration and finish
382
+ current_config = json.dumps(self.state_manager.session.user_config, sort_keys=True)
383
+ if initial_config != current_config:
384
+ try:
385
+ user_configuration.save_config(self.state_manager)
386
+ await ui.panel(
387
+ "Setup Complete!",
388
+ f"Configuration saved to: [bold]{self.config_file}[/bold]\n\n"
389
+ "You're ready to start using TunaCode!\n"
390
+ "Use [green]/quickstart[/green] anytime for a tutorial.",
391
+ border_style=UI_COLORS["success"],
392
+ )
393
+ except ConfigurationError as e:
394
+ await ui.error(str(e))
395
+
396
+ async def _wizard_step1_provider_selection(self):
397
+ """Wizard step 1: Provider selection with detailed explanations."""
398
+ provider_info = {
399
+ "1": {
400
+ "name": "OpenRouter",
401
+ "description": "Access to multiple models (GPT-4, Claude, Gemini, etc.)",
402
+ "signup": "https://openrouter.ai/",
403
+ "key_name": "OPENROUTER_API_KEY",
404
+ },
405
+ "2": {
406
+ "name": "OpenAI",
407
+ "description": "GPT-4 models",
408
+ "signup": "https://platform.openai.com/signup",
409
+ "key_name": "OPENAI_API_KEY",
410
+ },
411
+ "3": {
412
+ "name": "Anthropic",
413
+ "description": "Claude-3 models",
414
+ "signup": "https://console.anthropic.com/",
415
+ "key_name": "ANTHROPIC_API_KEY",
416
+ },
417
+ "4": {
418
+ "name": "Google",
419
+ "description": "Gemini models",
420
+ "signup": "https://ai.google.dev/",
421
+ "key_name": "GEMINI_API_KEY",
422
+ },
423
+ }
424
+
425
+ message = "Choose your AI provider:\n\n"
426
+ for key, info in provider_info.items():
427
+ message += f" {key} - {info['name']}: {info['description']}\n"
428
+
429
+ await ui.panel("Provider Selection", message, border_style=UI_COLORS["primary"])
430
+
431
+ while True:
432
+ choice = await ui.input(
433
+ "wizard_provider",
434
+ pretext=" Choose provider (1-4): ",
435
+ state_manager=self.state_manager,
436
+ )
437
+
438
+ if choice.strip() in provider_info:
439
+ selected = provider_info[choice.strip()]
440
+ self._wizard_selected_provider = selected
441
+
442
+ await ui.success(f"Selected: {selected['name']}")
443
+ await ui.info(f"Sign up at: {selected['signup']}")
444
+ break
445
+ else:
446
+ await ui.error("Please enter 1, 2, 3, or 4")
447
+
448
+ async def _wizard_step2_api_key_setup(self):
449
+ """Wizard step 2: API key setup with provider-specific guidance."""
450
+ provider = self._wizard_selected_provider
451
+
452
+ message = f"Enter your {provider['name']} API key:\n\n"
453
+ message += f"Get your key from: {provider['signup']}\n"
454
+ message += "Your key will be stored securely in your local config"
455
+
456
+ await ui.panel(f"{provider['name']} API Key", message, border_style=UI_COLORS["primary"])
457
+
458
+ while True:
459
+ api_key = await ui.input(
460
+ "wizard_api_key",
461
+ pretext=f" {provider['name']} API Key: ",
462
+ is_password=True,
463
+ state_manager=self.state_manager,
464
+ )
465
+
466
+ if api_key.strip():
467
+ # Ensure env dict exists
468
+ if "env" not in self.state_manager.session.user_config:
469
+ self.state_manager.session.user_config["env"] = {}
470
+
471
+ self.state_manager.session.user_config["env"][provider["key_name"]] = (
472
+ api_key.strip()
473
+ )
474
+ await ui.success("API key saved successfully!")
475
+ break
476
+ else:
477
+ await ui.error("API key cannot be empty")
478
+
479
+ async def _wizard_step3_model_selection(self):
480
+ """Wizard step 3: Model selection with smart recommendations."""
481
+ provider = self._wizard_selected_provider
482
+
483
+ # Provide smart recommendations based on provider
484
+ recommendations = {
485
+ "OpenAI": [
486
+ ("openai:gpt-4o", "GPT-4o flagship multimodal model (recommended)"),
487
+ ("openai:gpt-4.1", "Latest GPT-4.1 with enhanced coding"),
488
+ ("openai:o3", "Advanced reasoning model for complex tasks"),
489
+ ],
490
+ "Anthropic": [
491
+ ("anthropic:claude-sonnet-4", "Claude Sonnet 4 latest generation (recommended)"),
492
+ ("anthropic:claude-opus-4.1", "Most capable Claude with extended thinking"),
493
+ ("anthropic:claude-3.5-sonnet", "Claude 3.5 Sonnet proven performance"),
494
+ ],
495
+ "OpenRouter": [
496
+ (
497
+ "openrouter:anthropic/claude-sonnet-4",
498
+ "Claude Sonnet 4 via OpenRouter (recommended)",
499
+ ),
500
+ ("openrouter:openai/gpt-4o", "GPT-4o multimodal via OpenRouter"),
501
+ ("openrouter:google/gemini-2.5-flash", "Google Gemini 2.5 Flash latest"),
502
+ ],
503
+ "Google": [
504
+ (
505
+ "google:gemini-2.5-pro",
506
+ "Gemini 2.5 Pro with thinking capabilities (recommended)",
507
+ ),
508
+ ("google:gemini-2.5-flash", "Gemini 2.5 Flash best price-performance"),
509
+ ("google:gemini-2.0-flash", "Gemini 2.0 Flash with native tool use"),
510
+ ],
511
+ }
512
+
513
+ models = recommendations.get(provider["name"], [])
514
+ message = f"Choose your default {provider['name']} model:\n\n"
515
+
516
+ for i, (model_id, description) in enumerate(models, 1):
517
+ message += f" {i} - {description}\n"
518
+
519
+ message += "\nYou can change this later with [green]/model[/green]"
520
+
521
+ await ui.panel("Model Selection", message, border_style=UI_COLORS["primary"])
522
+
523
+ while True:
524
+ choice = await ui.input(
525
+ "wizard_model",
526
+ pretext=f" Choose model (1-{len(models)}): ",
527
+ state_manager=self.state_manager,
528
+ )
529
+
530
+ try:
531
+ index = int(choice.strip()) - 1
532
+ if 0 <= index < len(models):
533
+ selected_model = models[index][0]
534
+ self.state_manager.session.user_config["default_model"] = selected_model
535
+ await ui.success(f"Selected: {selected_model}")
536
+ break
537
+ else:
538
+ await ui.error(f"Please enter a number between 1 and {len(models)}")
539
+ except ValueError:
540
+ await ui.error("Please enter a valid number")
541
+
542
+ async def _wizard_step4_optional_settings(self):
543
+ """Wizard step 4: Optional settings configuration."""
544
+ message = "Configure optional settings:\n\n"
545
+ message += "• Tutorial: Enable interactive tutorial for new users\n"
546
+ message += "\nSkip this step to use recommended defaults"
547
+
548
+ await ui.panel("Optional Settings", message, border_style=UI_COLORS["primary"])
549
+
550
+ # Ask about tutorial
551
+ tutorial_choice = await ui.input(
552
+ "wizard_tutorial",
553
+ pretext=" Enable tutorial for new users? [Y/n]: ",
554
+ state_manager=self.state_manager,
555
+ )
556
+
557
+ enable_tutorial = tutorial_choice.strip().lower() not in ["n", "no", "false"]
558
+
559
+ if "settings" not in self.state_manager.session.user_config:
560
+ self.state_manager.session.user_config["settings"] = {}
561
+
562
+ self.state_manager.session.user_config["settings"]["enable_tutorial"] = enable_tutorial
563
+
564
+ # Streaming is always enabled - no user choice needed
565
+
566
+ await ui.info("Optional settings configured!")
@@ -22,8 +22,8 @@ class SetupCoordinator:
22
22
  """Register a setup step to be run."""
23
23
  self.setup_steps.append(step)
24
24
 
25
- async def run_setup(self, force_setup: bool = False) -> None:
26
- """Run all registered setup steps concurrently if possible."""
25
+ async def run_setup(self, force_setup: bool = False, wizard_mode: bool = False) -> None:
26
+ """Run all registered setup steps with proper dependency order."""
27
27
  # Run should_run checks sequentially (they may depend on order)
28
28
  steps_to_run = []
29
29
  for step in self.setup_steps:
@@ -35,11 +35,19 @@ class SetupCoordinator:
35
35
  f"Setup failed at step '{getattr(step, 'name', repr(step))}': {str(e)}"
36
36
  )
37
37
  raise
38
- # Run all .execute(force_setup) in parallel where possible (independent steps)
39
- from asyncio import gather
40
38
 
41
39
  try:
42
- await gather(*(step.execute(force_setup) for step in steps_to_run))
40
+ # Run steps sequentially to respect dependencies (ConfigSetup must complete before EnvironmentSetup)
41
+ for step in steps_to_run:
42
+ # Check if the step's execute method supports wizard_mode
43
+ import inspect
44
+
45
+ sig = inspect.signature(step.execute)
46
+ if "wizard_mode" in sig.parameters:
47
+ await step.execute(force_setup, wizard_mode=wizard_mode)
48
+ else:
49
+ await step.execute(force_setup)
50
+
43
51
  # Now validate all sequentially: if any fail, raise error
44
52
  for step in steps_to_run:
45
53
  if not await step.validate():
@@ -37,8 +37,12 @@ class GitSafetySetup(BaseSetup):
37
37
  # Always run unless user has explicitly disabled it
38
38
  return not self.state_manager.session.user_config.get("skip_git_safety", False)
39
39
 
40
- async def execute(self, _force: bool = False) -> None:
40
+ async def execute(self, _force: bool = False, wizard_mode: bool = False) -> None:
41
41
  """Create a safety branch for TunaCode operations."""
42
+ # Skip git safety during wizard mode to avoid UI interference
43
+ if wizard_mode:
44
+ return
45
+
42
46
  try:
43
47
  # Check if git is installed
44
48
  result = subprocess.run(
tunacode/exceptions.py CHANGED
@@ -18,7 +18,18 @@ class TunaCodeError(Exception):
18
18
  class ConfigurationError(TunaCodeError):
19
19
  """Raised when there's a configuration issue."""
20
20
 
21
- pass
21
+ def __init__(self, message: str, suggested_fix: str = None, help_url: str = None):
22
+ self.suggested_fix = suggested_fix
23
+ self.help_url = help_url
24
+
25
+ # Build enhanced error message with actionable guidance
26
+ full_message = message
27
+ if suggested_fix:
28
+ full_message += f"\n\n💡 Suggested fix: {suggested_fix}"
29
+ if help_url:
30
+ full_message += f"\n📖 More help: {help_url}"
31
+
32
+ super().__init__(full_message)
22
33
 
23
34
 
24
35
  # User Interaction Exceptions
@@ -31,7 +42,19 @@ class UserAbortError(TunaCodeError):
31
42
  class ValidationError(TunaCodeError):
32
43
  """Raised when input validation fails."""
33
44
 
34
- pass
45
+ def __init__(self, message: str, suggested_fix: str = None, valid_examples: list = None):
46
+ self.suggested_fix = suggested_fix
47
+ self.valid_examples = valid_examples or []
48
+
49
+ # Build enhanced error message with actionable guidance
50
+ full_message = f"Validation failed: {message}"
51
+ if suggested_fix:
52
+ full_message += f"\n\n💡 Suggested fix: {suggested_fix}"
53
+ if valid_examples:
54
+ examples_text = "\n".join(f" • {example}" for example in valid_examples)
55
+ full_message += f"\n\n✅ Valid examples:\n{examples_text}"
56
+
57
+ super().__init__(full_message)
35
58
 
36
59
 
37
60
  # Tool and Agent Exceptions
@@ -39,17 +62,47 @@ class ToolExecutionError(TunaCodeError):
39
62
  """Raised when a tool fails to execute."""
40
63
 
41
64
  def __init__(
42
- self, tool_name: ToolName, message: ErrorMessage, original_error: OriginalError = None
65
+ self,
66
+ tool_name: ToolName,
67
+ message: ErrorMessage,
68
+ original_error: OriginalError = None,
69
+ suggested_fix: str = None,
70
+ recovery_commands: list = None,
43
71
  ):
44
72
  self.tool_name = tool_name
45
73
  self.original_error = original_error
46
- super().__init__(f"Tool '{tool_name}' failed: {message}")
74
+ self.suggested_fix = suggested_fix
75
+ self.recovery_commands = recovery_commands or []
76
+
77
+ # Build enhanced error message
78
+ full_message = f"Tool '{tool_name}' failed: {message}"
79
+ if suggested_fix:
80
+ full_message += f"\n\n💡 Suggested fix: {suggested_fix}"
81
+ if recovery_commands:
82
+ commands_text = "\n".join(f" • {cmd}" for cmd in recovery_commands)
83
+ full_message += f"\n\n🔧 Recovery commands:\n{commands_text}"
84
+
85
+ super().__init__(full_message)
47
86
 
48
87
 
49
88
  class AgentError(TunaCodeError):
50
89
  """Raised when agent operations fail."""
51
90
 
52
- pass
91
+ def __init__(self, message: str, suggested_fix: str = None, troubleshooting_steps: list = None):
92
+ self.suggested_fix = suggested_fix
93
+ self.troubleshooting_steps = troubleshooting_steps or []
94
+
95
+ # Build enhanced error message
96
+ full_message = f"Agent error: {message}"
97
+ if suggested_fix:
98
+ full_message += f"\n\n💡 Suggested fix: {suggested_fix}"
99
+ if troubleshooting_steps:
100
+ steps_text = "\n".join(
101
+ f" {i + 1}. {step}" for i, step in enumerate(troubleshooting_steps)
102
+ )
103
+ full_message += f"\n\n🔍 Troubleshooting steps:\n{steps_text}"
104
+
105
+ super().__init__(full_message)
53
106
 
54
107
 
55
108
  # State Management Exceptions
@@ -103,6 +156,67 @@ class FileOperationError(TunaCodeError):
103
156
  super().__init__(f"File {operation} failed for '{path}': {message}")
104
157
 
105
158
 
159
+ # Additional specialized exception classes for onboarding scenarios
160
+ class OnboardingError(TunaCodeError):
161
+ """Raised when onboarding process encounters issues."""
162
+
163
+ def __init__(
164
+ self, message: str, step: str = None, suggested_fix: str = None, help_command: str = None
165
+ ):
166
+ self.step = step
167
+ self.suggested_fix = suggested_fix
168
+ self.help_command = help_command
169
+
170
+ # Build enhanced error message
171
+ full_message = f"Onboarding failed: {message}"
172
+ if step:
173
+ full_message = f"Onboarding failed at step '{step}': {message}"
174
+ if suggested_fix:
175
+ full_message += f"\n\n💡 Suggested fix: {suggested_fix}"
176
+ if help_command:
177
+ full_message += f"\n🆘 For help: {help_command}"
178
+
179
+ super().__init__(full_message)
180
+
181
+
182
+ class ModelConfigurationError(ConfigurationError):
183
+ """Raised when model configuration is invalid."""
184
+
185
+ def __init__(self, model: str, issue: str, valid_models: list = None):
186
+ self.model = model
187
+ self.issue = issue
188
+ self.valid_models = valid_models or []
189
+
190
+ suggested_fix = "Use --wizard for guided setup or --model with a valid model name"
191
+ help_url = "https://docs.anthropic.com/en/docs/claude-code"
192
+
193
+ message = f"Model '{model}' configuration error: {issue}"
194
+ if valid_models:
195
+ examples = valid_models[:3] # Show first 3 examples
196
+ suggested_fix += f"\n\nValid examples: {', '.join(examples)}"
197
+
198
+ super().__init__(message, suggested_fix=suggested_fix, help_url=help_url)
199
+
200
+
201
+ class SetupValidationError(ValidationError):
202
+ """Raised when setup validation fails."""
203
+
204
+ def __init__(self, validation_type: str, details: str, quick_fixes: list = None):
205
+ self.validation_type = validation_type
206
+ self.details = details
207
+ self.quick_fixes = quick_fixes or []
208
+
209
+ suggested_fix = "Run 'tunacode --wizard' for guided setup"
210
+ if quick_fixes:
211
+ suggested_fix = f"Try these quick fixes: {', '.join(quick_fixes)}"
212
+
213
+ super().__init__(
214
+ f"{validation_type} validation failed: {details}",
215
+ suggested_fix=suggested_fix,
216
+ valid_examples=["tunacode --wizard", "tunacode --setup", "tunacode --help"],
217
+ )
218
+
219
+
106
220
  class TooBroadPatternError(ToolExecutionError):
107
221
  """Raised when a search pattern is too broad and times out."""
108
222
 
tunacode/setup.py CHANGED
@@ -18,7 +18,9 @@ from tunacode.core.setup import (
18
18
  from tunacode.core.state import StateManager
19
19
 
20
20
 
21
- async def setup(run_setup: bool, state_manager: StateManager, cli_config: dict = None) -> None:
21
+ async def setup(
22
+ run_setup: bool, state_manager: StateManager, cli_config: dict = None, wizard_mode: bool = False
23
+ ) -> None:
22
24
  """
23
25
  Setup TunaCode on startup using the new setup coordinator.
24
26
 
@@ -26,6 +28,7 @@ async def setup(run_setup: bool, state_manager: StateManager, cli_config: dict =
26
28
  run_setup (bool): If True, force run the setup process, resetting current config.
27
29
  state_manager (StateManager): The state manager instance.
28
30
  cli_config (dict): Optional CLI configuration with baseurl, model, and key.
31
+ wizard_mode (bool): If True, run interactive setup wizard.
29
32
  """
30
33
  coordinator = SetupCoordinator(state_manager)
31
34
 
@@ -39,7 +42,7 @@ async def setup(run_setup: bool, state_manager: StateManager, cli_config: dict =
39
42
  coordinator.register_step(GitSafetySetup(state_manager))
40
43
 
41
44
  # Run all setup steps
42
- await coordinator.run_setup(force_setup=run_setup)
45
+ await coordinator.run_setup(force_setup=run_setup, wizard_mode=wizard_mode)
43
46
 
44
47
 
45
48
  async def setup_agent(agent: Optional[Any], state_manager: StateManager) -> None:
tunacode/tools/glob.py CHANGED
@@ -14,11 +14,10 @@ from functools import lru_cache
14
14
  from pathlib import Path
15
15
  from typing import Any, Dict, List, Optional, Set, Union
16
16
 
17
- import defusedxml.ElementTree as ET
18
-
19
17
  from tunacode.core.code_index import CodeIndex
20
18
  from tunacode.exceptions import ToolExecutionError
21
19
  from tunacode.tools.base import BaseTool
20
+ from tunacode.tools.xml_helper import load_parameters_schema_from_xml, load_prompt_from_xml
22
21
 
23
22
  # Configuration
24
23
  MAX_RESULTS = 5000 # Maximum files to return
@@ -70,17 +69,10 @@ class GlobTool(BaseTool):
70
69
  Returns:
71
70
  str: The loaded prompt from XML or a default prompt
72
71
  """
73
- try:
74
- # Load prompt from XML file
75
- prompt_file = Path(__file__).parent / "prompts" / "glob_prompt.xml"
76
- if prompt_file.exists():
77
- tree = ET.parse(prompt_file)
78
- root = tree.getroot()
79
- description = root.find("description")
80
- if description is not None:
81
- return description.text.strip()
82
- except Exception:
83
- pass # Fall through to default
72
+ # Try to load from XML helper
73
+ prompt = load_prompt_from_xml("glob")
74
+ if prompt:
75
+ return prompt
84
76
 
85
77
  # Fallback to default prompt
86
78
  return """Fast file pattern matching tool
@@ -92,39 +84,10 @@ class GlobTool(BaseTool):
92
84
  @lru_cache(maxsize=1)
93
85
  def _get_parameters_schema(self) -> Dict[str, Any]:
94
86
  """Get the parameters schema for the glob tool."""
95
- # Try to load from XML first
96
- try:
97
- prompt_file = Path(__file__).parent / "prompts" / "glob_prompt.xml"
98
- if prompt_file.exists():
99
- tree = ET.parse(prompt_file)
100
- root = tree.getroot()
101
- parameters = root.find("parameters")
102
- if parameters is not None:
103
- schema: Dict[str, Any] = {"type": "object", "properties": {}, "required": []}
104
- required_fields: List[str] = []
105
-
106
- for param in parameters.findall("parameter"):
107
- name = param.get("name")
108
- required = param.get("required", "false").lower() == "true"
109
- param_type = param.find("type")
110
- description = param.find("description")
111
-
112
- if name and param_type is not None:
113
- prop = {
114
- "type": param_type.text.strip(),
115
- "description": description.text.strip()
116
- if description is not None
117
- else "",
118
- }
119
-
120
- schema["properties"][name] = prop
121
- if required:
122
- required_fields.append(name)
123
-
124
- schema["required"] = required_fields
125
- return schema
126
- except Exception:
127
- pass # Fall through to hardcoded schema
87
+ # Try to load from XML helper
88
+ schema = load_parameters_schema_from_xml("glob")
89
+ if schema:
90
+ return schema
128
91
 
129
92
  # Fallback to hardcoded schema
130
93
  return {