diff --git a/cli_interface.py b/cli_interface.py index 3a5cbd1..1d23491 100644 --- a/cli_interface.py +++ b/cli_interface.py @@ -6,6 +6,8 @@ CLI Interface - Simple command line interface for the cycling analyzer import asyncio import logging from pathlib import Path +import argparse +import sys from config import Config, load_config, create_sample_config from core_app import CyclingAnalyzerApp @@ -17,11 +19,8 @@ class CLI: def __init__(self): self.app = None - async def run(self): + async def run(self, args): """Main CLI loop""" - print("Cycling Workout Analyzer") - print("=" * 40) - # Setup configuration try: config = self._setup_config() @@ -33,11 +32,13 @@ class CLI: # Initialize app await self.app.initialize() - # Show initial status - self._show_status() - - # Main loop - await self._main_loop() + if args.command: + await self._handle_command(args) + else: + # Show initial status + await self._show_status() + # Main loop + await self._main_loop() except KeyboardInterrupt: print("\nGoodbye!") @@ -67,10 +68,10 @@ class CLI: return config - def _show_status(self): + async def _show_status(self): """Show application status""" print(f"\nStatus:") - print(f"- Available tools: {len(self.app.list_available_tools())}") + print(f"- Available tools: {len(await self.app.list_available_tools())}") print(f"- Available templates: {len(self.app.list_templates())}") print(f"- Cached data keys: {list(self.app.get_cached_data().keys())}") @@ -100,7 +101,7 @@ class CLI: elif choice == "3": await self._enhanced_analysis() elif choice == "4": - self._list_tools() + await self._list_tools() elif choice == "5": self._list_templates() elif choice == "6": @@ -184,9 +185,9 @@ class CLI: print("="*50) print(result) - def _list_tools(self): + async def _list_tools(self): """List available tools""" - tools = self.app.list_available_tools() + tools = await self.app.list_available_tools() if tools: self.app.mcp_client.print_tools() else: @@ -247,10 +248,21 @@ Weekly Structure: f.write(default_rules) return default_rules + async def _handle_command(self, args): + """Handle non-interactive commands""" + if args.command == "analyze_last": + await self._analyze_last_workout() + else: + print(f"Unknown command: {args.command}") + async def main(): """CLI entry point""" + parser = argparse.ArgumentParser(description="Cycling Workout Analyzer") + parser.add_argument('command', nargs='?', help="Command to execute (e.g., analyze_last)") + args = parser.parse_args() + cli = CLI() - await cli.run() + await cli.run(args) if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file diff --git a/core_app.py b/core_app.py index c19d71a..88bd372 100644 --- a/core_app.py +++ b/core_app.py @@ -70,7 +70,7 @@ class CyclingAnalyzerApp: act for act in activities if "cycling" in act.get("activityType", {}).get("typeKey", "").lower() ] - return max(cycling_activities, key=lambda x: x.get("start_time", "")) if cycling_activities else None + return max(cycling_activities, key=lambda x: x.get("startTimeGmt", 0)) if cycling_activities else None # Core functionality methods @@ -80,13 +80,13 @@ class CyclingAnalyzerApp: # Prepare context data context = { - "user_profile": self.cache_manager.get("user_profile", {}), - "recent_activities": self.cache_manager.get("recent_activities", []), - "last_cycling_details": self.cache_manager.get("last_cycling_details", {}), + "user_info": self.cache_manager.get("user_profile", {}), + "activity_summary": self.cache_manager.get("last_cycling_details", {}), **kwargs } # Load and render template + logger.info(f"Rendering template {template_name} with context keys: {list(context.keys())}") prompt = self.template_engine.render(template_name, **context) # Call LLM @@ -122,9 +122,9 @@ class CyclingAnalyzerApp: # Utility methods - def list_available_tools(self) -> list: + async def list_available_tools(self) -> list: """Get list of available MCP tools""" - return self.mcp_client.list_tools() + return await self.mcp_client.list_tools() def list_templates(self) -> list: """Get list of available templates""" @@ -145,13 +145,13 @@ async def main(): await app.initialize() # Example usage - print("Available tools:", len(app.list_available_tools())) + print("Available tools:", len(await app.list_available_tools())) print("Available templates:", len(app.list_templates())) # Run analysis - analysis = await app.analyze_workout("analyze_last_workout", - training_rules="Sample rules") - print("Analysis:", analysis[:200] + "...") + # analysis = await app.analyze_workout("analyze_last_workout", + # training_rules="Sample rules") + # print("Analysis:", analysis[:200] + "...") except Exception as e: logger.error(f"Application error: {e}") diff --git a/rules.yaml b/rules.yaml new file mode 100644 index 0000000..f0435de --- /dev/null +++ b/rules.yaml @@ -0,0 +1,17 @@ + +Training Goals: +- Improve FTP (Functional Threshold Power) +- Build endurance for long rides +- Maintain consistent training + +Power Zones (adjust based on your FTP): +- Zone 1 (Active Recovery): < 55% FTP +- Zone 2 (Endurance): 56-75% FTP +- Zone 3 (Tempo): 76-90% FTP +- Zone 4 (Lactate Threshold): 91-105% FTP +- Zone 5 (VO2 Max): 106-120% FTP + +Weekly Structure: +- 70-80% easy/moderate intensity +- 20-30% high intensity +- At least 1 rest day per week diff --git a/template_engine.py b/template_engine.py index bd11706..92cd169 100644 --- a/template_engine.py +++ b/template_engine.py @@ -122,11 +122,13 @@ class TemplateEngine: try: section_content = self.load_template(section_file) # Render section with same kwargs - section_rendered = section_content.format(**kwargs) + # Recursively render the section content + section_rendered = self.render(section_file, **kwargs) content = content.replace(placeholder, section_rendered) - except (FileNotFoundError, KeyError) as e: + except (FileNotFoundError, KeyError, ValueError) as e: logger.warning(f"Could not process section {section_name}: {e}") - # Leave placeholder as-is if section can't be loaded + # Replace with empty string if section fails + content = content.replace(placeholder, "") return content diff --git a/templates/base/analysis_frameworks/assessment_points.txt b/templates/base/analysis_frameworks/assessment_points.txt new file mode 100644 index 0000000..c69dd2e --- /dev/null +++ b/templates/base/analysis_frameworks/assessment_points.txt @@ -0,0 +1,7 @@ +Please provide: +1. Overall assessment of the workout +2. How well it aligns with my rules and goals +3. Areas for improvement +4. Specific feedback on power, heart rate, duration, and intensity +5. Recovery recommendations +6. Comparison with typical performance metrics \ No newline at end of file diff --git a/templates/base/data_sections/activity_summary.txt b/templates/base/data_sections/activity_summary.txt new file mode 100644 index 0000000..d78ed31 --- /dev/null +++ b/templates/base/data_sections/activity_summary.txt @@ -0,0 +1,2 @@ +ACTIVITY SUMMARY: +Activity ID: {activity_summary[activityId]} \ No newline at end of file diff --git a/templates/base/data_sections/training_rules.txt b/templates/base/data_sections/training_rules.txt new file mode 100644 index 0000000..6613da0 --- /dev/null +++ b/templates/base/data_sections/training_rules.txt @@ -0,0 +1,2 @@ +My training rules and goals: +{training_rules} \ No newline at end of file diff --git a/templates/base/data_sections/user_info.txt b/templates/base/data_sections/user_info.txt new file mode 100644 index 0000000..fb39656 --- /dev/null +++ b/templates/base/data_sections/user_info.txt @@ -0,0 +1,2 @@ +USER INFO: +User ID: {user_info[id]} \ No newline at end of file diff --git a/templates/base/system_prompts/main_agent.txt b/templates/base/system_prompts/main_agent.txt new file mode 100644 index 0000000..2070ffa --- /dev/null +++ b/templates/base/system_prompts/main_agent.txt @@ -0,0 +1,3 @@ +You are an expert cycling coach with access to comprehensive Garmin Connect data. +You analyze cycling workouts, provide performance insights, and give actionable training recommendations. +Use the available tools to gather detailed workout data and provide comprehensive analysis. \ No newline at end of file diff --git a/templates/base/system_prompts/no_tools_analysis.txt b/templates/base/system_prompts/no_tools_analysis.txt new file mode 100644 index 0000000..cdb19cf --- /dev/null +++ b/templates/base/system_prompts/no_tools_analysis.txt @@ -0,0 +1,2 @@ +You are an expert cycling coach. Perform comprehensive analysis using the provided data. +Do not use any tools - all relevant data is included in the prompt. \ No newline at end of file diff --git a/templates/workflows/analyze_last_workout.txt b/templates/workflows/analyze_last_workout.txt new file mode 100644 index 0000000..7e0737b --- /dev/null +++ b/templates/workflows/analyze_last_workout.txt @@ -0,0 +1,11 @@ +Analyze my most recent cycling workout using the provided data. + +{activity_summary_section} + +{user_info_section} + +{training_rules_section} + +{assessment_points} + +Focus on the provided activity details for your analysis. \ No newline at end of file diff --git a/templates/workflows/enhanced_analysis.txt b/templates/workflows/enhanced_analysis.txt new file mode 100644 index 0000000..0cded2d --- /dev/null +++ b/templates/workflows/enhanced_analysis.txt @@ -0,0 +1,5 @@ +Perform enhanced {analysis_type} analysis using all available data and tools. + +Available cached data: {cached_data} + +Use MCP tools as needed to gather additional data for comprehensive analysis. \ No newline at end of file diff --git a/templates/workflows/suggest_next_workout.txt b/templates/workflows/suggest_next_workout.txt new file mode 100644 index 0000000..ce84286 --- /dev/null +++ b/templates/workflows/suggest_next_workout.txt @@ -0,0 +1,10 @@ +Please suggest my next cycling workout based on my recent training history. + +{training_rules_section} + +Please provide: +1. Analysis of my recent training pattern +2. Identified gaps or imbalances in my training +3. Specific workout recommendation for my next session +4. Target zones (power, heart rate, duration) +5. Rationale for the recommendation based on recent performance \ No newline at end of file