diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8367f65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +tmp/ +temp/ + +# Logs +*.log diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..a4c1d52 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,261 @@ +# Bit-Block Sidekick Architecture + +## Overview + +The Bit-Block Sidekick is a lightweight, domain-aware AI agent designed to transform Bit-Block Starter Packs into living, thinking systems. It provides automated infrastructure analysis, configuration, auditing, and optimization capabilities with minimal human intervention. + +## Architecture + +### Core Components + +``` +bit-sidekick/ +├── bit_sidekick/ # Main package +│ ├── __init__.py # Package initialization +│ ├── agent.py # Core AI agent orchestrator +│ ├── cli.py # Command-line interface +│ ├── config.py # Configuration management +│ └── modules/ # Functional modules +│ ├── analyzer.py # Infrastructure analysis +│ ├── configurator.py # Auto-configuration +│ └── auditor.py # Self-auditing +├── examples/ # Example starter packs +│ └── starter-packs/ +│ ├── web-app/ +│ ├── microservices/ +│ └── data-platform/ +└── tests/ # Test suite +``` + +### Module Responsibilities + +#### 1. SidekickAgent (agent.py) +The main orchestrator that coordinates all operations: +- Analyzes infrastructure configurations +- Auto-configures for target environments +- Performs security and optimization audits +- Applies optimizations based on findings +- Transforms starter packs into production-ready systems + +#### 2. InfrastructureAnalyzer (analyzer.py) +Analyzes Bit-Block Starter Packs and infrastructure: +- Parses YAML/JSON configuration files +- Extracts resources and services +- Generates domain-aware insights +- Provides security and optimization recommendations + +#### 3. AutoConfigurator (configurator.py) +Automatically configures infrastructure: +- Environment-specific settings (dev/staging/prod) +- Auto-scaling configuration +- Security group and network settings +- Monitoring and logging setup + +#### 4. SelfAuditor (auditor.py) +Performs comprehensive audits: +- Security vulnerability scanning +- Compliance checks +- Optimization opportunity identification +- Risk scoring and prioritization + +#### 5. SidekickConfig (config.py) +Configuration management: +- Default configuration values +- Custom configuration loading +- Configuration persistence +- Nested key access + +## Workflow + +### Complete Transformation Process + +``` +Starter Pack → Analyze → Configure → Audit → Optimize → Living System +``` + +1. **Analysis Phase**: + - Parse configuration files + - Extract resources and services + - Identify patterns and anti-patterns + +2. **Configuration Phase**: + - Apply environment-specific settings + - Configure auto-scaling + - Set up security policies + - Configure monitoring + +3. **Audit Phase**: + - Security vulnerability scan + - Compliance validation + - Performance analysis + - Cost optimization review + +4. **Optimization Phase**: + - Apply recommended changes + - Implement best practices + - Enable automation features + +## Key Features + +### Domain Awareness +The Sidekick understands common infrastructure patterns: +- Container orchestration (Kubernetes, ECS) +- Database configurations +- Caching layers +- API gateways +- Service meshes +- Data pipelines + +### Security First +Built-in security features: +- Encryption at rest and in transit +- Access control validation +- Secret management checks +- Network security analysis +- Compliance monitoring + +### Environment Intelligence +Smart environment configuration: +- **Development**: Minimal resources, debug enabled +- **Staging**: Production-like, with safety nets +- **Production**: High availability, comprehensive monitoring + +### Self-Auditing +Continuous self-assessment: +- Automated security scans +- Performance monitoring +- Cost optimization +- Risk scoring + +## Command Reference + +### CLI Commands + +```bash +# Analyze infrastructure +bit-sidekick analyze + +# Configure for environment +bit-sidekick configure --environment [dev|staging|prod] + +# Security audit +bit-sidekick audit + +# Optimize infrastructure +bit-sidekick optimize + +# Complete transformation +bit-sidekick transform --environment [env] +``` + +### Python API + +```python +from bit_sidekick import SidekickAgent + +# Initialize agent +agent = SidekickAgent() + +# Analyze +result = agent.analyze_infrastructure("path/to/config.yml") + +# Configure +config = agent.auto_configure("path/to/config.yml", "prod") + +# Audit +audit = agent.self_audit("path/to/config.yml") + +# Transform +transformation = agent.transform_starter_pack("path/to/config.yml", "prod") +``` + +## Configuration Format + +### Starter Pack Structure + +```yaml +name: starter-pack-name +version: 1.0.0 +description: Description of the starter pack + +infrastructure: + compute: + type: container + replicas: 2 + + database: + type: postgresql + version: "14" + +services: + web: + name: web-service + port: 80 + +monitoring: + enabled: true + +security: + encryption_at_rest: true +``` + +## Extensibility + +The Sidekick is designed for easy extension: + +1. **Custom Analyzers**: Add domain-specific analysis logic +2. **Configuration Templates**: Create custom environment profiles +3. **Audit Rules**: Implement custom security/compliance checks +4. **Optimization Strategies**: Define custom optimization rules + +## Future Enhancements + +Potential areas for expansion: +- Machine learning for pattern recognition +- Integration with cloud provider APIs +- Real-time infrastructure monitoring +- Automated remediation actions +- Multi-cloud orchestration +- GitOps integration +- CI/CD pipeline integration +- Cost forecasting and optimization + +## Best Practices + +1. **Start with Analysis**: Always analyze before making changes +2. **Use Environment Progression**: dev → staging → prod +3. **Review Audit Reports**: Address high-severity findings first +4. **Enable Dry-Run**: Test changes before applying +5. **Version Control**: Track infrastructure configurations +6. **Regular Audits**: Schedule periodic security audits +7. **Monitor Changes**: Track optimization impacts + +## Troubleshooting + +### Common Issues + +1. **Configuration not found**: Ensure path exists and is readable +2. **Parse errors**: Validate YAML/JSON syntax +3. **Permission issues**: Check file/directory permissions +4. **Import errors**: Ensure package is installed correctly + +### Debug Mode + +Enable verbose logging: +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +## Contributing + +To contribute: +1. Fork the repository +2. Create a feature branch +3. Add tests for new functionality +4. Ensure all tests pass +5. Submit a pull request + +## License + +MIT License - See LICENSE file for details. diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md new file mode 100644 index 0000000..12bf391 --- /dev/null +++ b/GETTING_STARTED.md @@ -0,0 +1,332 @@ +# Getting Started with Bit-Block Sidekick + +## What is Bit-Block Sidekick? + +Bit-Block Sidekick is an intelligent AI agent that transforms your infrastructure configuration templates (Starter Packs) into production-ready, self-auditing, and auto-configuring systems. Think of it as your DevOps co-pilot that helps you build secure, optimized cloud infrastructure with minimal effort. + +## Quick Start (5 Minutes) + +### 1. Installation + +```bash +# Clone the repository +git clone https://github.com/Bluebit-Innovations/bit-sidekick.git +cd bit-sidekick + +# Install the package +pip install -e . +``` + +### 2. Verify Installation + +```bash +bit-sidekick --version +``` + +You should see: `bit-sidekick, version 0.1.0` + +### 3. Try Your First Analysis + +```bash +bit-sidekick analyze examples/starter-packs/web-app/infrastructure.yml +``` + +This will analyze a sample web application infrastructure and provide insights. + +### 4. Transform a Starter Pack + +```bash +bit-sidekick transform examples/starter-packs/web-app/infrastructure.yml --environment prod +``` + +This runs the complete transformation process: analysis, configuration, audit, and optimization. + +## Understanding the Output + +### Analysis Output + +The analysis shows: +- **Resources Found**: Infrastructure components detected +- **Findings**: Issues or concerns identified +- **Recommendations**: Suggested improvements + +### Audit Output + +The audit provides: +- **Risk Score**: Overall security/compliance score (0-100) +- **Security Findings**: Security vulnerabilities and concerns +- **Optimization Findings**: Performance and cost optimization opportunities +- **Recommendations**: Prioritized action items + +### Configuration Output + +The configuration shows: +- **Environment**: Target environment (dev/staging/prod) +- **Configurations Applied**: Settings applied for the environment + +## Common Use Cases + +### Use Case 1: New Project Setup + +You're starting a new microservices project: + +```bash +# 1. Analyze the microservices starter pack +bit-sidekick analyze examples/starter-packs/microservices/infrastructure.yml + +# 2. Configure for development +bit-sidekick configure examples/starter-packs/microservices/infrastructure.yml --environment dev + +# 3. When ready for production +bit-sidekick transform examples/starter-packs/microservices/infrastructure.yml --environment prod +``` + +### Use Case 2: Security Audit + +You want to audit your existing infrastructure: + +```bash +# Run a comprehensive security audit +bit-sidekick audit path/to/your/infrastructure.yml +``` + +Review the findings and address high-priority security issues first. + +### Use Case 3: Environment Migration + +Moving from staging to production: + +```bash +# 1. Configure for production +bit-sidekick configure path/to/config.yml --environment prod + +# 2. Review the changes +# (Output shows production-optimized settings) + +# 3. Apply optimizations +bit-sidekick optimize path/to/config.yml +``` + +### Use Case 4: Cost Optimization + +Reduce infrastructure costs: + +```bash +# 1. Run analysis to identify optimization opportunities +bit-sidekick analyze path/to/config.yml + +# 2. Review optimization recommendations +# (Look for cost-related suggestions) + +# 3. Apply optimizations +bit-sidekick optimize path/to/config.yml +``` + +## Creating Your Own Starter Pack + +### Basic Structure + +Create a file `my-app.yml`: + +```yaml +name: my-application +version: 1.0.0 +description: My application infrastructure + +infrastructure: + compute: + type: container + image: my-app:latest + replicas: 2 + resources: + cpu: 1 + memory: 2Gi + + database: + type: postgresql + version: "14" + storage: 20Gi + +services: + api: + name: api-service + port: 8080 + health_check: /health + +monitoring: + enabled: true + +security: + encryption_at_rest: true +``` + +### Analyze Your Config + +```bash +bit-sidekick analyze my-app.yml +``` + +The Sidekick will provide recommendations based on your configuration. + +## Using the Python API + +You can also use the Sidekick programmatically: + +```python +from bit_sidekick import SidekickAgent + +# Create an agent +agent = SidekickAgent() + +# Analyze your infrastructure +analysis = agent.analyze_infrastructure("my-app.yml") +print(f"Found {len(analysis['resources'])} resources") + +# Auto-configure for production +config = agent.auto_configure("my-app.yml", "prod") +print(f"Status: {config['status']}") + +# Run security audit +audit = agent.self_audit("my-app.yml") +print(f"Risk Score: {audit['risk_score']}/100") + +# Complete transformation +result = agent.transform_starter_pack("my-app.yml", "prod") +if result['status'] == 'completed': + print("Transformation successful!") +``` + +## Configuration Options + +### Custom Configuration File + +Create `sidekick-config.yml`: + +```yaml +agent: + name: "My Custom Sidekick" + domain_awareness: true + auto_configure: true + +analysis: + security_checks: true + optimization_checks: true + compliance_checks: true + +automation: + auto_fix: false # Set to true to auto-apply fixes + dry_run: true # Simulate changes without applying + require_approval: true # Require approval for changes + +cloud: + providers: + - aws + - azure + - gcp +``` + +Use it with: + +```bash +bit-sidekick --config sidekick-config.yml transform my-app.yml --environment prod +``` + +## Development Workflow + +### Recommended Workflow + +1. **Start Small**: Begin with the `dev` environment + ```bash + bit-sidekick configure my-app.yml --environment dev + ``` + +2. **Iterate**: Make changes based on analysis feedback + ```bash + bit-sidekick analyze my-app.yml + ``` + +3. **Test**: Move to staging when ready + ```bash + bit-sidekick configure my-app.yml --environment staging + ``` + +4. **Audit**: Run security audit before production + ```bash + bit-sidekick audit my-app.yml + ``` + +5. **Deploy**: Transform for production + ```bash + bit-sidekick transform my-app.yml --environment prod + ``` + +## Tips and Best Practices + +### 1. Always Start with Analysis +Before making any changes, understand your current state: +```bash +bit-sidekick analyze +``` + +### 2. Review Audit Findings +Pay special attention to high-severity security findings: +```bash +bit-sidekick audit | grep HIGH +``` + +### 3. Use Environment Progression +Follow the dev → staging → prod progression to catch issues early. + +### 4. Enable Dry-Run Mode +Test changes without applying them: +```yaml +automation: + dry_run: true +``` + +### 5. Track Your Configurations +Keep your starter packs in version control (Git) to track changes over time. + +### 6. Regular Audits +Schedule periodic security audits, especially after major changes. + +## Troubleshooting + +### Problem: "Path does not exist" +**Solution**: Check that the file path is correct and the file exists. + +### Problem: "Failed to parse file" +**Solution**: Validate your YAML/JSON syntax. Use a linter like `yamllint`. + +### Problem: No recommendations shown +**Solution**: Your infrastructure might already be optimized! Try analyzing a different configuration. + +### Problem: Import errors +**Solution**: Ensure the package is installed: +```bash +pip install -e . +``` + +## Next Steps + +1. **Explore Examples**: Check out `examples/starter-packs/` for more complex configurations +2. **Read Architecture**: See `ARCHITECTURE.md` for technical details +3. **Run Tests**: Try `pytest tests/` to understand the test suite +4. **Contribute**: Fork the repo and submit improvements! + +## Getting Help + +- **Documentation**: See `README.md` and `ARCHITECTURE.md` +- **Examples**: Check `examples/starter-packs/` for templates +- **Issues**: Report bugs on GitHub +- **Questions**: Open a discussion on GitHub + +## What's Next? + +Now that you know the basics, try: +- Creating your own starter pack +- Integrating with your CI/CD pipeline +- Customizing the configuration for your needs +- Contributing back to the project + +Happy building! 🚀 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e86c11b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Bluebit Innovations + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d79eafa --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include README.md +include LICENSE +include pyproject.toml +recursive-include bit_sidekick *.py +recursive-include examples *.yml *.yaml *.md +recursive-include tests *.py diff --git a/README.md b/README.md index ac336a0..dca0691 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,207 @@ -# 🧠 Bit-Block Sidekick -### Your Intelligent DevOps, SRE, and Cloud Automation Companion +# Bit-Block Sidekick -**Bit-Block Sidekick** is a lightweight, domain-aware AI agent designed to turn your **Bit-Block Starter Packs** into **self-running, self-auditing, and self-healing systems**. +The Bit-Block Sidekick is a lightweight, domain-aware AI agent built to help startups, engineers, and DevOps teams hit the ground running with secure, self-auditing, and auto-configuring infrastructure. -Instead of starting from static templates, teams can bootstrap their infrastructure with an intelligent Sidekick that **analyzes**, **suggests**, and **automates** best practices across Terraform, Kubernetes, CI/CD, and observability stacks. +Unlike static templates or boilerplate repositories, the Sidekick transforms your Bit-Block Starter Packs into living, thinking systems — capable of analyzing, optimizing, and maintaining your cloud environments with minimal human input. ---- +## Features -## 🚀 Features +🤖 **Domain-Aware AI Agent**: Intelligent understanding of infrastructure patterns and best practices -- **🔍 Secure Smart Audit** - Scans your repo for missing guardrails (NetworkPolicies, IAM boundaries, PDBs, branch protections) and opens pull requests with fixes. +🔒 **Self-Auditing**: Automatic security and compliance checks with actionable recommendations -- **🏗️ Domain Packs** - Pre-tuned best practices for **SaaS APIs**, **AI/ML workloads**, **FinTech**, and **Data/ETL pipelines**. +⚙️ **Auto-Configuration**: Smart configuration for different environments (dev, staging, prod) -- **🧩 Rego-Based Guardrails** - Runs OPA/Conftest policy checks before any change is applied to ensure every modification passes security and compliance gates. +📊 **Infrastructure Analysis**: Deep analysis of starter packs with optimization suggestions -- **💬 Chat-Driven Assistance** - Ask the Sidekick questions directly from your CLI — it explains “why” and “how” using your repo context. +🚀 **Living, Thinking Systems**: Transforms static configurations into adaptive infrastructure -- **📈 Observability Hooks** - Monitors Prometheus metrics or GitHub Actions logs for anomalies and posts remediation suggestions or auto-generated runbooks. +🛡️ **Security First**: Built-in security checks, encryption recommendations, and access control validation ---- +## Installation -## ⚙️ Quickstart +```bash +pip install -e . +``` + +Or with development dependencies: + +```bash +pip install -e ".[dev]" +``` + +## Quick Start + +### 1. Analyze a Starter Pack -### 1️⃣ Initialize for your domain ```bash -bb-sidekick init --domain saas_api -```` +bit-sidekick analyze examples/starter-packs/web-app/infrastructure.yml +``` -### 2️⃣ Run an audit and open a PR with improvements +### 2. Auto-Configure for an Environment ```bash -bb-sidekick audit --open-pr +bit-sidekick configure examples/starter-packs/web-app/infrastructure.yml --environment prod ``` -### 3️⃣ Ask contextual questions +### 3. Audit Your Infrastructure ```bash -bb-sidekick ask "How do I add a staging namespace securely?" +bit-sidekick audit examples/starter-packs/web-app/infrastructure.yml +``` + +### 4. Complete Transformation + +Transform your starter pack into a living, thinking system: + +```bash +bit-sidekick transform examples/starter-packs/web-app/infrastructure.yml --environment prod +``` + +## Usage + +### Command-Line Interface + +The Sidekick provides a comprehensive CLI for all operations: + +```bash +# Show help +bit-sidekick --help + +# Analyze infrastructure +bit-sidekick analyze + +# Configure for environment +bit-sidekick configure --environment [dev|staging|prod] + +# Run security audit +bit-sidekick audit + +# Optimize infrastructure +bit-sidekick optimize + +# Complete transformation +bit-sidekick transform --environment [dev|staging|prod] ``` ---- +### Python API + +You can also use the Sidekick programmatically: + +```python +from bit_sidekick import SidekickAgent, SidekickConfig + +# Initialize the agent +agent = SidekickAgent() + +# Analyze a starter pack +analysis = agent.analyze_infrastructure("path/to/starter-pack.yml") -## 🧩 GitHub Action Integration +# Auto-configure for production +config = agent.auto_configure("path/to/starter-pack.yml", "prod") -Add the following workflow to automate weekly audits and PR reviews: +# Run security audit +audit = agent.self_audit("path/to/starter-pack.yml") + +# Complete transformation +result = agent.transform_starter_pack("path/to/starter-pack.yml", "prod") +``` + +## Configuration + +Create a `sidekick-config.yml` file to customize behavior: ```yaml -# .github/workflows/sidekick.yml -name: Bit-Block Sidekick - -on: - workflow_dispatch: - schedule: - - cron: "0 3 * * 1" # Every Monday 3 AM UTC - pull_request: - -jobs: - run: - permissions: - contents: write - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: Install CLIs - run: | - sudo apt-get update && sudo apt-get install -y conftest - curl -fsSL https://get.terraform.io | sh || true - curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ - - name: Run Sidekick Audit - run: python bitblocks-sidekick/agents/planner.py --audit --pr - - name: Policy Check - run: conftest test -p bitblocks-sidekick/policies/rego . +agent: + name: "My Sidekick" + domain_awareness: true + auto_configure: true + self_audit: true + +analysis: + enabled: true + security_checks: true + optimization_checks: true + compliance_checks: true + +automation: + auto_fix: false + require_approval: true + dry_run: true + +cloud: + providers: + - aws + - azure + - gcp ``` ---- +Then use it with: -## 🔐 Built-In Safety +```bash +bit-sidekick --config sidekick-config.yml transform +``` -* ✅ **Read-Only by Default** — Sidekick never applies directly to production. -* ✅ **Policy-Gated PRs** — All changes are validated via Rego/OPA guardrails. -* ✅ **Human-Approved Merges** — PRs require authorized reviewers. -* ✅ **No Direct Writes** — Everything flows through GitOps or pull requests. +## Starter Pack Examples ---- +The repository includes example starter packs: -## 🧱 Supported Domain Packs +- **web-app**: Basic web application infrastructure +- **microservices**: Microservices architecture with service mesh +- **data-platform**: Data processing and analytics platform -| Domain | Description | Key Capabilities | -| ---------------- | ------------------------------------- | --------------------------------------------------------------- | -| **SaaS API** | Web/API backends and microservices | Auto-adds NetworkPolicies, HPAs, health checks, and rate limits | -| **AI/ML App** | GPU-based inference or model training | Resource guards, cost control, cache optimization | -| **FinTech-Lite** | Payment and compliance apps | TLS enforcement, secrets rotation, PCI-lean policies | -| **Data/ETL** | Pipelines and batch jobs | DLQ setup, retry policies, lifecycle management | +Explore them in `examples/starter-packs/`. ---- +## Development -## 💡 Philosophy +### Setup -> *Move fast, but never break your cloud.* +```bash +# Clone the repository +git clone https://github.com/Bluebit-Innovations/bit-sidekick.git +cd bit-sidekick + +# Install in development mode +pip install -e ".[dev]" +``` -Bit-Block Sidekick empowers startups and engineers to launch faster, safer, and smarter — with production-grade security and reliability from day one. +### Running Tests ---- +```bash +pytest tests/ +``` -## 🧰 Stack Highlights +### Code Formatting -* **Language:** Python 3.11+ -* **Policies:** OPA / Conftest (Rego) -* **Infra Support:** Terraform, Kubernetes -* **Observability:** Prometheus / Grafana -* **CI/CD:** GitHub Actions / GitLab -* **Security:** IAM Boundary / Policy Linter / NetworkPolicy Generator +```bash +black bit_sidekick/ +``` + +### Type Checking + +```bash +mypy bit_sidekick/ +``` ---- +## Architecture -## 🌍 Learn More +The Sidekick is built on three core modules: -**Website:** [https://bluebit.live](https://bluebit.live) -**Docs:** Coming soon → `docs/bitblock-sidekick.md` -**Made by:** [Bluebit Information Technology Services](https://bluebit.live) +1. **Analyzer**: Analyzes infrastructure configurations and generates insights +2. **Configurator**: Auto-configures infrastructure for different environments +3. **Auditor**: Performs security and optimization audits ---- +These modules work together through the main `SidekickAgent` to provide a complete infrastructure management solution. -### 📜 License +## Contributing -This project is part of the **Bit-Block Starter Suite** by **Bluebit IT Services**. -All rights reserved © 2025 Bluebit Information Technology Services. +Contributions are welcome! Please feel free to submit a Pull Request. ---- +## License +MIT License - see LICENSE file for details. +## Support +For issues, questions, or contributions, please visit: +https://github.com/Bluebit-Innovations/bit-sidekick diff --git a/bit_sidekick/__init__.py b/bit_sidekick/__init__.py new file mode 100644 index 0000000..8c641ae --- /dev/null +++ b/bit_sidekick/__init__.py @@ -0,0 +1,14 @@ +""" +Bit-Block Sidekick: A lightweight, domain-aware AI agent for infrastructure automation. + +The Sidekick transforms Bit-Block Starter Packs into living, thinking systems capable of +analyzing, optimizing, and maintaining cloud environments with minimal human input. +""" + +__version__ = "0.1.0" +__author__ = "Bluebit Innovations" + +from bit_sidekick.agent import SidekickAgent +from bit_sidekick.config import SidekickConfig + +__all__ = ["SidekickAgent", "SidekickConfig"] diff --git a/bit_sidekick/agent.py b/bit_sidekick/agent.py new file mode 100644 index 0000000..eb0fa39 --- /dev/null +++ b/bit_sidekick/agent.py @@ -0,0 +1,162 @@ +"""Core AI agent for the Bit-Block Sidekick.""" + +from typing import Dict, Any, Optional +import logging +from bit_sidekick.config import SidekickConfig +from bit_sidekick.modules.analyzer import InfrastructureAnalyzer +from bit_sidekick.modules.configurator import AutoConfigurator +from bit_sidekick.modules.auditor import SelfAuditor + + +logger = logging.getLogger(__name__) + + +class SidekickAgent: + """ + The main AI agent that orchestrates infrastructure management. + + This agent is domain-aware and capable of analyzing, optimizing, + and maintaining cloud environments with minimal human input. + """ + + def __init__(self, config: Optional[SidekickConfig] = None): + """ + Initialize the Sidekick agent. + + Args: + config: Configuration object. If None, uses default configuration. + """ + self.config = config or SidekickConfig() + self.analyzer = InfrastructureAnalyzer(self.config) + self.configurator = AutoConfigurator(self.config) + self.auditor = SelfAuditor(self.config) + logger.info("Bit-Block Sidekick initialized") + + def analyze_infrastructure(self, starter_pack_path: str) -> Dict[str, Any]: + """ + Analyze a Bit-Block Starter Pack and assess the infrastructure. + + Args: + starter_pack_path: Path to the starter pack configuration + + Returns: + Analysis report with findings and recommendations + """ + logger.info(f"Analyzing infrastructure at {starter_pack_path}") + return self.analyzer.analyze(starter_pack_path) + + def auto_configure( + self, starter_pack_path: str, target_environment: str = "dev" + ) -> Dict[str, Any]: + """ + Auto-configure infrastructure based on the starter pack. + + Args: + starter_pack_path: Path to the starter pack configuration + target_environment: Target environment (dev, staging, prod) + + Returns: + Configuration result with applied changes + """ + logger.info(f"Auto-configuring infrastructure for {target_environment}") + return self.configurator.configure(starter_pack_path, target_environment) + + def self_audit(self, infrastructure_path: str) -> Dict[str, Any]: + """ + Perform self-audit of the infrastructure for security and optimization. + + Args: + infrastructure_path: Path to the infrastructure configuration + + Returns: + Audit report with security findings and optimization recommendations + """ + logger.info(f"Running self-audit on {infrastructure_path}") + return self.auditor.audit(infrastructure_path) + + def optimize(self, infrastructure_path: str) -> Dict[str, Any]: + """ + Optimize the infrastructure based on analysis and audit findings. + + Args: + infrastructure_path: Path to the infrastructure configuration + + Returns: + Optimization report with applied changes + """ + logger.info(f"Optimizing infrastructure at {infrastructure_path}") + + # First, analyze the current state + analysis = self.analyzer.analyze(infrastructure_path) + + # Run security and optimization audit + audit = self.auditor.audit(infrastructure_path) + + # Apply optimizations if auto_fix is enabled + optimizations = [] + if self.config.get("automation.auto_fix"): + optimizations = self.configurator.apply_optimizations( + infrastructure_path, audit.get("recommendations", []) + ) + + return { + "status": "completed", + "analysis": analysis, + "audit": audit, + "optimizations_applied": optimizations, + } + + def transform_starter_pack( + self, starter_pack_path: str, target_environment: str = "dev" + ) -> Dict[str, Any]: + """ + Transform a Bit-Block Starter Pack into a living, thinking system. + + This is the main method that orchestrates the complete transformation: + 1. Analyzes the starter pack + 2. Auto-configures for the target environment + 3. Performs self-audit + 4. Applies optimizations + + Args: + starter_pack_path: Path to the starter pack + target_environment: Target environment + + Returns: + Complete transformation report + """ + logger.info(f"Transforming starter pack: {starter_pack_path}") + + result = { + "status": "in_progress", + "starter_pack": starter_pack_path, + "target_environment": target_environment, + } + + try: + # Step 1: Analyze + logger.info("Step 1: Analyzing starter pack") + result["analysis"] = self.analyze_infrastructure(starter_pack_path) + + # Step 2: Auto-configure + logger.info("Step 2: Auto-configuring infrastructure") + result["configuration"] = self.auto_configure(starter_pack_path, target_environment) + + # Step 3: Self-audit + logger.info("Step 3: Running self-audit") + result["audit"] = self.self_audit(starter_pack_path) + + # Step 4: Optimize if enabled + if self.config.get("analysis.optimization_checks"): + logger.info("Step 4: Applying optimizations") + result["optimization"] = self.optimize(starter_pack_path) + + result["status"] = "completed" + logger.info("Transformation completed successfully") + + except Exception as e: + logger.error(f"Transformation failed: {e}") + result["status"] = "failed" + result["error"] = str(e) + + return result diff --git a/bit_sidekick/cli.py b/bit_sidekick/cli.py new file mode 100644 index 0000000..436f486 --- /dev/null +++ b/bit_sidekick/cli.py @@ -0,0 +1,246 @@ +"""Command-line interface for the Bit-Block Sidekick.""" + +import click +import logging +import sys +from bit_sidekick import SidekickAgent, SidekickConfig +from bit_sidekick import __version__ + + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +@click.group() +@click.version_option(version=__version__) +@click.option( + "--config", + "-c", + type=click.Path(exists=True), + help="Path to configuration file", +) +@click.pass_context +def main(ctx: click.Context, config: str) -> None: + """ + Bit-Block Sidekick: A lightweight, domain-aware AI agent for infrastructure automation. + + Transform your Bit-Block Starter Packs into living, thinking systems. + """ + ctx.ensure_object(dict) + ctx.obj["config"] = SidekickConfig(config) if config else SidekickConfig() + ctx.obj["agent"] = SidekickAgent(ctx.obj["config"]) + + +@main.command() +@click.argument("starter_pack_path", type=click.Path(exists=True)) +@click.pass_context +def analyze(ctx: click.Context, starter_pack_path: str) -> None: + """Analyze a Bit-Block Starter Pack or infrastructure configuration.""" + agent = ctx.obj["agent"] + click.echo(f"Analyzing: {starter_pack_path}") + + try: + result = agent.analyze_infrastructure(starter_pack_path) + _display_analysis_result(result) + except Exception as e: + click.echo(f"Error: {e}", err=True) + sys.exit(1) + + +@main.command() +@click.argument("starter_pack_path", type=click.Path(exists=True)) +@click.option( + "--environment", + "-e", + type=click.Choice(["dev", "staging", "prod"]), + default="dev", + help="Target environment", +) +@click.pass_context +def configure(ctx: click.Context, starter_pack_path: str, environment: str) -> None: + """Auto-configure infrastructure for a target environment.""" + agent = ctx.obj["agent"] + click.echo(f"Configuring for {environment} environment: {starter_pack_path}") + + try: + result = agent.auto_configure(starter_pack_path, environment) + _display_configuration_result(result) + except Exception as e: + click.echo(f"Error: {e}", err=True) + sys.exit(1) + + +@main.command() +@click.argument("infrastructure_path", type=click.Path(exists=True)) +@click.pass_context +def audit(ctx: click.Context, infrastructure_path: str) -> None: + """Perform self-audit of infrastructure for security and optimization.""" + agent = ctx.obj["agent"] + click.echo(f"Auditing: {infrastructure_path}") + + try: + result = agent.self_audit(infrastructure_path) + _display_audit_result(result) + except Exception as e: + click.echo(f"Error: {e}", err=True) + sys.exit(1) + + +@main.command() +@click.argument("infrastructure_path", type=click.Path(exists=True)) +@click.pass_context +def optimize(ctx: click.Context, infrastructure_path: str) -> None: + """Optimize infrastructure based on analysis and audit findings.""" + agent = ctx.obj["agent"] + click.echo(f"Optimizing: {infrastructure_path}") + + try: + result = agent.optimize(infrastructure_path) + _display_optimization_result(result) + except Exception as e: + click.echo(f"Error: {e}", err=True) + sys.exit(1) + + +@main.command() +@click.argument("starter_pack_path", type=click.Path(exists=True)) +@click.option( + "--environment", + "-e", + type=click.Choice(["dev", "staging", "prod"]), + default="dev", + help="Target environment", +) +@click.pass_context +def transform(ctx: click.Context, starter_pack_path: str, environment: str) -> None: + """ + Transform a Bit-Block Starter Pack into a living, thinking system. + + This command orchestrates the complete transformation process: + analysis, configuration, audit, and optimization. + """ + agent = ctx.obj["agent"] + click.echo("=" * 60) + click.echo("Bit-Block Sidekick: Transformation Process") + click.echo("=" * 60) + click.echo(f"Starter Pack: {starter_pack_path}") + click.echo(f"Environment: {environment}") + click.echo("=" * 60) + + try: + result = agent.transform_starter_pack(starter_pack_path, environment) + _display_transformation_result(result) + except Exception as e: + click.echo(f"Error: {e}", err=True) + sys.exit(1) + + +def _display_analysis_result(result: dict) -> None: + """Display analysis results in a user-friendly format.""" + click.echo("\n" + "=" * 60) + click.echo("ANALYSIS RESULTS") + click.echo("=" * 60) + + click.echo(f"\nPath: {result.get('path')}") + click.echo(f"Type: {result.get('type', 'unknown')}") + + if result.get("resources"): + click.echo(f"\nResources found: {len(result['resources'])}") + for resource in result["resources"][:5]: # Show first 5 + click.echo(f" - {resource.get('name')} ({resource.get('type')})") + + if result.get("findings"): + click.echo(f"\nFindings: {len(result['findings'])}") + for finding in result["findings"]: + click.echo(f" [{finding.get('type')}] {finding.get('message')}") + + if result.get("recommendations"): + click.echo(f"\nRecommendations: {len(result['recommendations'])}") + for rec in result["recommendations"]: + click.echo(f" - {rec.get('message')}") + + +def _display_configuration_result(result: dict) -> None: + """Display configuration results.""" + click.echo("\n" + "=" * 60) + click.echo("CONFIGURATION RESULTS") + click.echo("=" * 60) + + click.echo(f"\nStatus: {result.get('status')}") + click.echo(f"Environment: {result.get('environment')}") + + if result.get("configurations"): + click.echo(f"\nConfigurations applied: {len(result['configurations'])}") + for config in result["configurations"]: + click.echo(f" - {config.get('type')}") + + +def _display_audit_result(result: dict) -> None: + """Display audit results.""" + click.echo("\n" + "=" * 60) + click.echo("AUDIT RESULTS") + click.echo("=" * 60) + + click.echo(f"\nStatus: {result.get('status')}") + click.echo(f"Risk Score: {result.get('risk_score')}/100") + + if result.get("security_findings"): + click.echo(f"\nSecurity Findings: {len(result['security_findings'])}") + for finding in result["security_findings"]: + severity = finding.get("severity", "unknown").upper() + click.echo(f" [{severity}] {finding.get('message')}") + + if result.get("optimization_findings"): + click.echo(f"\nOptimization Findings: {len(result['optimization_findings'])}") + for finding in result["optimization_findings"]: + click.echo(f" - {finding.get('message')}") + + if result.get("recommendations"): + click.echo(f"\nRecommendations: {len(result['recommendations'])}") + for rec in result["recommendations"][:5]: # Show first 5 + priority = rec.get("priority", "medium").upper() + click.echo(f" [{priority}] {rec.get('action')}") + + +def _display_optimization_result(result: dict) -> None: + """Display optimization results.""" + click.echo("\n" + "=" * 60) + click.echo("OPTIMIZATION RESULTS") + click.echo("=" * 60) + + click.echo(f"\nStatus: {result.get('status')}") + + if result.get("optimizations_applied"): + click.echo(f"\nOptimizations applied: {len(result['optimizations_applied'])}") + for opt in result["optimizations_applied"]: + click.echo(f" - {opt.get('status')}") + + +def _display_transformation_result(result: dict) -> None: + """Display transformation results.""" + click.echo("\n" + "=" * 60) + click.echo("TRANSFORMATION COMPLETE") + click.echo("=" * 60) + + click.echo(f"\nStatus: {result.get('status')}") + + if result.get("status") == "completed": + click.echo("\n✓ Analysis completed") + click.echo("✓ Configuration applied") + click.echo("✓ Security audit completed") + + if result.get("optimization"): + click.echo("✓ Optimizations applied") + + click.echo("\nYour Bit-Block Starter Pack has been transformed into a") + click.echo("living, thinking system ready for deployment!") + elif result.get("status") == "failed": + click.echo(f"\n✗ Transformation failed: {result.get('error')}") + + +if __name__ == "__main__": + main() diff --git a/bit_sidekick/config.py b/bit_sidekick/config.py new file mode 100644 index 0000000..554cc83 --- /dev/null +++ b/bit_sidekick/config.py @@ -0,0 +1,103 @@ +"""Configuration management for the Bit-Block Sidekick.""" + +from typing import Dict, Any, Optional +import yaml +import os + + +class SidekickConfig: + """Configuration manager for the Sidekick agent.""" + + def __init__(self, config_path: Optional[str] = None): + """ + Initialize the configuration manager. + + Args: + config_path: Path to the configuration file. If None, uses default config. + """ + self.config_path = config_path + self.config: Dict[str, Any] = self._load_config() + + def _load_config(self) -> Dict[str, Any]: + """Load configuration from file or use defaults.""" + if self.config_path and os.path.exists(self.config_path): + with open(self.config_path, "r") as f: + return yaml.safe_load(f) or {} + return self._default_config() + + def _default_config(self) -> Dict[str, Any]: + """Return default configuration.""" + return { + "agent": { + "name": "Bit-Block Sidekick", + "version": "0.1.0", + "domain_awareness": True, + "auto_configure": True, + "self_audit": True, + }, + "analysis": { + "enabled": True, + "security_checks": True, + "optimization_checks": True, + "compliance_checks": True, + }, + "automation": { + "auto_fix": False, + "require_approval": True, + "dry_run": True, + }, + "cloud": { + "providers": ["aws", "azure", "gcp"], + "regions": [], + }, + } + + def get(self, key: str, default: Any = None) -> Any: + """ + Get a configuration value. + + Args: + key: Configuration key (supports dot notation, e.g., 'agent.name') + default: Default value if key not found + + Returns: + Configuration value or default + """ + keys = key.split(".") + value = self.config + for k in keys: + if isinstance(value, dict): + value = value.get(k) + if value is None: + return default + else: + return default + return value + + def set(self, key: str, value: Any) -> None: + """ + Set a configuration value. + + Args: + key: Configuration key (supports dot notation) + value: Value to set + """ + keys = key.split(".") + config = self.config + for k in keys[:-1]: + if k not in config: + config[k] = {} + config = config[k] + config[keys[-1]] = value + + def save(self, path: Optional[str] = None) -> None: + """ + Save configuration to file. + + Args: + path: Path to save configuration. If None, uses config_path. + """ + save_path = path or self.config_path + if save_path: + with open(save_path, "w") as f: + yaml.dump(self.config, f, default_flow_style=False) diff --git a/bit_sidekick/modules/__init__.py b/bit_sidekick/modules/__init__.py new file mode 100644 index 0000000..5d9ac9c --- /dev/null +++ b/bit_sidekick/modules/__init__.py @@ -0,0 +1,7 @@ +"""Modules for the Bit-Block Sidekick agent.""" + +from bit_sidekick.modules.analyzer import InfrastructureAnalyzer +from bit_sidekick.modules.configurator import AutoConfigurator +from bit_sidekick.modules.auditor import SelfAuditor + +__all__ = ["InfrastructureAnalyzer", "AutoConfigurator", "SelfAuditor"] diff --git a/bit_sidekick/modules/analyzer.py b/bit_sidekick/modules/analyzer.py new file mode 100644 index 0000000..fa9ff9d --- /dev/null +++ b/bit_sidekick/modules/analyzer.py @@ -0,0 +1,222 @@ +"""Infrastructure analysis module for the Bit-Block Sidekick.""" + +from typing import Dict, List, Any +import os +import yaml +import logging +from bit_sidekick.config import SidekickConfig + + +logger = logging.getLogger(__name__) + + +class InfrastructureAnalyzer: + """Analyzes Bit-Block Starter Packs and infrastructure configurations.""" + + def __init__(self, config: SidekickConfig): + """ + Initialize the analyzer. + + Args: + config: Sidekick configuration + """ + self.config = config + + def analyze(self, starter_pack_path: str) -> Dict[str, Any]: + """ + Analyze a starter pack or infrastructure configuration. + + Args: + starter_pack_path: Path to the configuration + + Returns: + Analysis report with findings + """ + logger.info(f"Starting analysis of {starter_pack_path}") + + result = { + "path": starter_pack_path, + "exists": os.path.exists(starter_pack_path), + "resources": [], + "findings": [], + "recommendations": [], + } + + if not result["exists"]: + result["findings"].append({ + "type": "error", + "message": f"Path does not exist: {starter_pack_path}", + }) + return result + + # Determine if it's a file or directory + if os.path.isfile(starter_pack_path): + result["type"] = "file" + result.update(self._analyze_file(starter_pack_path)) + elif os.path.isdir(starter_pack_path): + result["type"] = "directory" + result.update(self._analyze_directory(starter_pack_path)) + + # Add domain-specific recommendations + result["recommendations"].extend(self._generate_recommendations(result)) + + logger.info(f"Analysis complete. Found {len(result['findings'])} findings") + return result + + def _analyze_file(self, file_path: str) -> Dict[str, Any]: + """Analyze a single configuration file.""" + result: Dict[str, Any] = {"resources": [], "findings": []} + + try: + with open(file_path, "r") as f: + if file_path.endswith((".yml", ".yaml")): + content = yaml.safe_load(f) + result["format"] = "yaml" + result["content"] = content + result["resources"] = self._extract_resources(content) + elif file_path.endswith(".json"): + import json + content = json.load(f) + result["format"] = "json" + result["content"] = content + result["resources"] = self._extract_resources(content) + else: + result["format"] = "unknown" + result["findings"].append({ + "type": "warning", + "message": "Unknown file format", + }) + except Exception as e: + result["findings"].append({ + "type": "error", + "message": f"Failed to parse file: {e}", + }) + + return result + + def _analyze_directory(self, dir_path: str) -> Dict[str, Any]: + """Analyze a directory containing multiple configuration files.""" + result: Dict[str, Any] = {"resources": [], "findings": [], "files": []} + + for root, dirs, files in os.walk(dir_path): + for file in files: + if file.endswith((".yml", ".yaml", ".json")): + file_path = os.path.join(root, file) + file_analysis = self._analyze_file(file_path) + result["files"].append({ + "path": file_path, + "analysis": file_analysis, + }) + result["resources"].extend(file_analysis.get("resources", [])) + result["findings"].extend(file_analysis.get("findings", [])) + + return result + + def _extract_resources(self, content: Any) -> List[Dict[str, Any]]: + """Extract infrastructure resources from configuration.""" + resources = [] + + if isinstance(content, dict): + # Check for common infrastructure patterns + if "resources" in content: + resources.extend(self._parse_resources(content["resources"])) + if "services" in content: + resources.extend(self._parse_services(content["services"])) + if "infrastructure" in content: + resources.extend(self._parse_infrastructure(content["infrastructure"])) + + return resources + + def _parse_resources(self, resources_config: Any) -> List[Dict[str, Any]]: + """Parse resources from configuration.""" + resources = [] + if isinstance(resources_config, dict): + for name, config in resources_config.items(): + resource_type = "unknown" + if isinstance(config, dict): + resource_type = config.get("type", "unknown") + resources.append({ + "name": name, + "type": resource_type, + "config": config, + }) + elif isinstance(resources_config, list): + for item in resources_config: + if isinstance(item, dict): + resources.append({ + "name": item.get("name", "unnamed"), + "type": item.get("type", "unknown"), + "config": item, + }) + return resources + + def _parse_services(self, services_config: Any) -> List[Dict[str, Any]]: + """Parse services from configuration.""" + services = [] + if isinstance(services_config, dict): + for name, config in services_config.items(): + services.append({ + "name": name, + "type": "service", + "config": config, + }) + return services + + def _parse_infrastructure(self, infra_config: Any) -> List[Dict[str, Any]]: + """Parse infrastructure components from configuration.""" + components = [] + if isinstance(infra_config, dict): + for name, config in infra_config.items(): + components.append({ + "name": name, + "type": "infrastructure", + "config": config, + }) + return components + + def _generate_recommendations(self, analysis: Dict[str, Any]) -> List[Dict[str, str]]: + """Generate domain-aware recommendations based on analysis.""" + recommendations = [] + + # Check for security best practices + if self.config.get("analysis.security_checks"): + recommendations.extend(self._security_recommendations(analysis)) + + # Check for optimization opportunities + if self.config.get("analysis.optimization_checks"): + recommendations.extend(self._optimization_recommendations(analysis)) + + return recommendations + + def _security_recommendations(self, analysis: Dict[str, Any]) -> List[Dict[str, str]]: + """Generate security-related recommendations.""" + recommendations = [] + + resources = analysis.get("resources", []) + + # Check for missing encryption + for resource in resources: + config = resource.get("config", {}) + if isinstance(config, dict) and not config.get("encryption"): + recommendations.append({ + "type": "security", + "resource": resource.get("name"), + "message": "Consider enabling encryption for this resource", + }) + + return recommendations + + def _optimization_recommendations(self, analysis: Dict[str, Any]) -> List[Dict[str, str]]: + """Generate optimization recommendations.""" + recommendations = [] + + resources = analysis.get("resources", []) + + # Suggest resource optimization + if len(resources) > 10: + recommendations.append({ + "type": "optimization", + "message": "Consider consolidating resources to reduce complexity", + }) + + return recommendations diff --git a/bit_sidekick/modules/auditor.py b/bit_sidekick/modules/auditor.py new file mode 100644 index 0000000..83ac7d8 --- /dev/null +++ b/bit_sidekick/modules/auditor.py @@ -0,0 +1,279 @@ +"""Self-auditing module for the Bit-Block Sidekick.""" + +from typing import Dict, List, Any +import os +import logging +from bit_sidekick.config import SidekickConfig + + +logger = logging.getLogger(__name__) + + +class SelfAuditor: + """Performs self-audits of infrastructure for security and optimization.""" + + def __init__(self, config: SidekickConfig): + """ + Initialize the auditor. + + Args: + config: Sidekick configuration + """ + self.config = config + + def audit(self, infrastructure_path: str) -> Dict[str, Any]: + """ + Perform a comprehensive audit of the infrastructure. + + Args: + infrastructure_path: Path to infrastructure configuration + + Returns: + Audit report with findings and recommendations + """ + logger.info(f"Starting audit of {infrastructure_path}") + + result = { + "status": "completed", + "path": infrastructure_path, + "security_findings": [], + "optimization_findings": [], + "compliance_findings": [], + "recommendations": [], + "risk_score": 0, + } + + if not os.path.exists(infrastructure_path): + result["status"] = "error" + result["message"] = f"Path does not exist: {infrastructure_path}" + return result + + # Run security checks + if self.config.get("analysis.security_checks"): + result["security_findings"] = self._security_audit(infrastructure_path) + + # Run optimization checks + if self.config.get("analysis.optimization_checks"): + result["optimization_findings"] = self._optimization_audit(infrastructure_path) + + # Run compliance checks + if self.config.get("analysis.compliance_checks"): + result["compliance_findings"] = self._compliance_audit(infrastructure_path) + + # Calculate risk score + result["risk_score"] = self._calculate_risk_score(result) + + # Generate recommendations + result["recommendations"] = self._generate_recommendations(result) + + logger.info(f"Audit complete. Risk score: {result['risk_score']}") + return result + + def _security_audit(self, infrastructure_path: str) -> List[Dict[str, Any]]: + """Perform security audit.""" + findings = [] + + # Check for common security issues + security_checks = [ + self._check_encryption, + self._check_access_controls, + self._check_network_security, + self._check_secrets_management, + ] + + for check in security_checks: + finding = check(infrastructure_path) + if finding: + findings.extend(finding if isinstance(finding, list) else [finding]) + + return findings + + def _check_encryption(self, path: str) -> List[Dict[str, Any]]: + """Check encryption configuration.""" + findings = [] + + # In a real implementation, this would parse the configuration + # and check for encryption settings + findings.append({ + "type": "security", + "severity": "medium", + "category": "encryption", + "message": "Ensure data encryption is enabled for all storage resources", + "recommendation": "Enable encryption at rest for all data storage", + }) + + return findings + + def _check_access_controls(self, path: str) -> List[Dict[str, Any]]: + """Check access control configuration.""" + findings = [] + + findings.append({ + "type": "security", + "severity": "high", + "category": "access_control", + "message": "Review IAM policies for least privilege access", + "recommendation": "Implement role-based access control (RBAC)", + }) + + return findings + + def _check_network_security(self, path: str) -> List[Dict[str, Any]]: + """Check network security configuration.""" + findings = [] + + findings.append({ + "type": "security", + "severity": "medium", + "category": "network", + "message": "Verify network segmentation and firewall rules", + "recommendation": "Implement network isolation for sensitive resources", + }) + + return findings + + def _check_secrets_management(self, path: str) -> List[Dict[str, Any]]: + """Check secrets management.""" + findings = [] + + findings.append({ + "type": "security", + "severity": "high", + "category": "secrets", + "message": "Ensure secrets are stored securely", + "recommendation": "Use a secrets management service for sensitive data", + }) + + return findings + + def _optimization_audit(self, infrastructure_path: str) -> List[Dict[str, Any]]: + """Perform optimization audit.""" + findings = [] + + # Check for optimization opportunities + optimization_checks = [ + self._check_resource_utilization, + self._check_cost_optimization, + self._check_performance, + ] + + for check in optimization_checks: + finding = check(infrastructure_path) + if finding: + findings.extend(finding if isinstance(finding, list) else [finding]) + + return findings + + def _check_resource_utilization(self, path: str) -> List[Dict[str, Any]]: + """Check resource utilization.""" + findings = [] + + findings.append({ + "type": "optimization", + "severity": "low", + "category": "resources", + "message": "Monitor resource utilization for right-sizing opportunities", + "recommendation": "Implement auto-scaling based on actual usage patterns", + }) + + return findings + + def _check_cost_optimization(self, path: str) -> List[Dict[str, Any]]: + """Check cost optimization opportunities.""" + findings = [] + + findings.append({ + "type": "optimization", + "severity": "low", + "category": "cost", + "message": "Review resource costs for optimization opportunities", + "recommendation": "Consider reserved instances or spot instances for cost savings", + }) + + return findings + + def _check_performance(self, path: str) -> List[Dict[str, Any]]: + """Check performance configuration.""" + findings = [] + + findings.append({ + "type": "optimization", + "severity": "low", + "category": "performance", + "message": "Review performance metrics and optimization opportunities", + "recommendation": "Implement caching and CDN for improved performance", + }) + + return findings + + def _compliance_audit(self, infrastructure_path: str) -> List[Dict[str, Any]]: + """Perform compliance audit.""" + findings = [] + + findings.append({ + "type": "compliance", + "severity": "medium", + "category": "logging", + "message": "Ensure audit logging is enabled for compliance", + "recommendation": "Enable comprehensive audit logging for all resources", + }) + + findings.append({ + "type": "compliance", + "severity": "medium", + "category": "backup", + "message": "Verify backup and disaster recovery procedures", + "recommendation": "Implement automated backup and recovery processes", + }) + + return findings + + def _calculate_risk_score(self, audit_result: Dict[str, Any]) -> int: + """Calculate overall risk score based on findings.""" + severity_weights = { + "high": 10, + "medium": 5, + "low": 2, + } + + score = 0 + for finding_type in ["security_findings", "compliance_findings"]: + for finding in audit_result.get(finding_type, []): + severity = finding.get("severity", "low") + score += severity_weights.get(severity, 0) + + return min(score, 100) # Cap at 100 + + def _generate_recommendations(self, audit_result: Dict[str, Any]) -> List[Dict[str, Any]]: + """Generate prioritized recommendations based on audit findings.""" + recommendations = [] + + # Prioritize high-severity security findings + for finding in audit_result.get("security_findings", []): + if finding.get("severity") == "high": + recommendations.append({ + "priority": "high", + "type": "security", + "action": finding.get("recommendation"), + "reason": finding.get("message"), + }) + + # Add optimization recommendations + for finding in audit_result.get("optimization_findings", []): + recommendations.append({ + "priority": "medium", + "type": "optimization", + "action": finding.get("recommendation"), + "reason": finding.get("message"), + }) + + # Add compliance recommendations + for finding in audit_result.get("compliance_findings", []): + recommendations.append({ + "priority": "medium", + "type": "compliance", + "action": finding.get("recommendation"), + "reason": finding.get("message"), + }) + + return recommendations diff --git a/bit_sidekick/modules/configurator.py b/bit_sidekick/modules/configurator.py new file mode 100644 index 0000000..dadd074 --- /dev/null +++ b/bit_sidekick/modules/configurator.py @@ -0,0 +1,217 @@ +"""Auto-configuration module for the Bit-Block Sidekick.""" + +from typing import Dict, List, Any +import os +import logging +from bit_sidekick.config import SidekickConfig + + +logger = logging.getLogger(__name__) + + +class AutoConfigurator: + """Auto-configures infrastructure based on starter packs and environment.""" + + def __init__(self, config: SidekickConfig): + """ + Initialize the auto-configurator. + + Args: + config: Sidekick configuration + """ + self.config = config + + def configure(self, starter_pack_path: str, target_environment: str) -> Dict[str, Any]: + """ + Auto-configure infrastructure for the target environment. + + Args: + starter_pack_path: Path to the starter pack + target_environment: Target environment (dev, staging, prod) + + Returns: + Configuration result + """ + logger.info(f"Configuring for {target_environment} environment") + + result = { + "status": "success", + "environment": target_environment, + "configurations": [], + "changes": [], + } + + # Check if path exists + if not os.path.exists(starter_pack_path): + result["status"] = "error" + result["message"] = f"Path does not exist: {starter_pack_path}" + return result + + # Apply environment-specific configurations + env_config = self._get_environment_config(target_environment) + result["configurations"].append(env_config) + + # Apply auto-scaling if configured + if self.config.get("automation.auto_configure"): + scaling_config = self._configure_auto_scaling(target_environment) + result["configurations"].append(scaling_config) + + # Apply security configurations + security_config = self._configure_security(target_environment) + result["configurations"].append(security_config) + + # Apply monitoring and logging + monitoring_config = self._configure_monitoring(target_environment) + result["configurations"].append(monitoring_config) + + logger.info(f"Configuration complete for {target_environment}") + return result + + def _get_environment_config(self, environment: str) -> Dict[str, Any]: + """Get environment-specific configuration.""" + env_configs = { + "dev": { + "name": "Development Environment", + "instance_type": "small", + "replicas": 1, + "auto_scaling": False, + "monitoring": "basic", + }, + "staging": { + "name": "Staging Environment", + "instance_type": "medium", + "replicas": 2, + "auto_scaling": True, + "monitoring": "standard", + }, + "prod": { + "name": "Production Environment", + "instance_type": "large", + "replicas": 3, + "auto_scaling": True, + "monitoring": "comprehensive", + }, + } + + return { + "type": "environment", + "config": env_configs.get(environment, env_configs["dev"]), + } + + def _configure_auto_scaling(self, environment: str) -> Dict[str, Any]: + """Configure auto-scaling based on environment.""" + scaling_configs = { + "dev": { + "enabled": False, + "min_instances": 1, + "max_instances": 1, + }, + "staging": { + "enabled": True, + "min_instances": 1, + "max_instances": 3, + "target_cpu_utilization": 70, + }, + "prod": { + "enabled": True, + "min_instances": 2, + "max_instances": 10, + "target_cpu_utilization": 60, + }, + } + + return { + "type": "auto_scaling", + "config": scaling_configs.get(environment, scaling_configs["dev"]), + } + + def _configure_security(self, environment: str) -> Dict[str, Any]: + """Configure security settings.""" + return { + "type": "security", + "config": { + "encryption_at_rest": True, + "encryption_in_transit": True, + "network_isolation": True, + "access_logging": True, + "security_groups": self._get_security_groups(environment), + }, + } + + def _get_security_groups(self, environment: str) -> List[Dict[str, Any]]: + """Get security group configurations.""" + base_rules = [ + { + "name": "https", + "protocol": "tcp", + "port": 443, + "source": "0.0.0.0/0", + }, + ] + + if environment == "dev": + base_rules.append({ + "name": "http", + "protocol": "tcp", + "port": 80, + "source": "0.0.0.0/0", + }) + + return base_rules + + def _configure_monitoring(self, environment: str) -> Dict[str, Any]: + """Configure monitoring and logging.""" + monitoring_levels = { + "dev": { + "metrics": ["cpu", "memory"], + "logging_level": "info", + "retention_days": 7, + }, + "staging": { + "metrics": ["cpu", "memory", "disk", "network"], + "logging_level": "info", + "retention_days": 30, + "alerts": ["error_rate", "high_cpu"], + }, + "prod": { + "metrics": ["cpu", "memory", "disk", "network", "requests"], + "logging_level": "warning", + "retention_days": 90, + "alerts": ["error_rate", "high_cpu", "high_memory", "low_availability"], + }, + } + + return { + "type": "monitoring", + "config": monitoring_levels.get(environment, monitoring_levels["dev"]), + } + + def apply_optimizations( + self, infrastructure_path: str, recommendations: List[Dict[str, Any]] + ) -> List[Dict[str, Any]]: + """ + Apply optimization recommendations. + + Args: + infrastructure_path: Path to infrastructure configuration + recommendations: List of optimization recommendations + + Returns: + List of applied optimizations + """ + applied = [] + + if self.config.get("automation.dry_run"): + logger.info("Running in dry-run mode, not applying changes") + return applied + + for recommendation in recommendations: + if recommendation.get("type") == "optimization": + # In a real implementation, this would apply the actual changes + applied.append({ + "recommendation": recommendation, + "status": "applied", + }) + logger.info(f"Applied optimization: {recommendation.get('message')}") + + return applied diff --git a/examples/starter-packs/README.md b/examples/starter-packs/README.md new file mode 100644 index 0000000..6b19dc2 --- /dev/null +++ b/examples/starter-packs/README.md @@ -0,0 +1,26 @@ +# Example Bit-Block Starter Pack + +This directory contains example starter packs that demonstrate how the Bit-Block Sidekick +can transform static configurations into living, thinking systems. + +## Available Starter Packs + +- **web-app**: Basic web application infrastructure +- **microservices**: Microservices architecture with multiple services +- **data-platform**: Data processing and analytics platform + +## Usage + +```bash +# Analyze a starter pack +bit-sidekick analyze examples/starter-packs/web-app/infrastructure.yml + +# Configure for development environment +bit-sidekick configure examples/starter-packs/web-app/infrastructure.yml --environment dev + +# Audit the configuration +bit-sidekick audit examples/starter-packs/web-app/infrastructure.yml + +# Complete transformation +bit-sidekick transform examples/starter-packs/web-app/infrastructure.yml --environment prod +``` diff --git a/examples/starter-packs/data-platform/infrastructure.yml b/examples/starter-packs/data-platform/infrastructure.yml new file mode 100644 index 0000000..61b7694 --- /dev/null +++ b/examples/starter-packs/data-platform/infrastructure.yml @@ -0,0 +1,64 @@ +name: data-platform-starter +version: 1.0.0 +description: Data processing and analytics platform starter pack + +infrastructure: + compute: + type: spark-cluster + workers: 5 + worker_size: large + + storage: + data_lake: + type: s3 + capacity: 1000Gi + lifecycle_policies: true + + warehouse: + type: snowflake + capacity: medium + + processing: + batch: + type: airflow + workers: 3 + + streaming: + type: kafka + partitions: 10 + replication: 3 + +resources: + raw_data_ingestion: + type: data-pipeline + source: external-api + destination: data_lake + schedule: "*/15 * * * *" + + data_transformation: + type: spark-job + input: data_lake/raw + output: data_lake/processed + schedule: "0 2 * * *" + + analytics_pipeline: + type: etl + source: data_lake/processed + destination: warehouse + schedule: "0 4 * * *" + +monitoring: + enabled: true + metrics: + - job_duration + - data_volume + - error_rate + + alerts: + - pipeline_failure + - data_quality_issues + +security: + encryption_at_rest: true + encryption_in_transit: true + access_logging: true diff --git a/examples/starter-packs/microservices/infrastructure.yml b/examples/starter-packs/microservices/infrastructure.yml new file mode 100644 index 0000000..65799db --- /dev/null +++ b/examples/starter-packs/microservices/infrastructure.yml @@ -0,0 +1,68 @@ +name: microservices-starter +version: 1.0.0 +description: Microservices architecture starter pack + +infrastructure: + compute: + type: kubernetes + cluster_size: 3 + + service_mesh: + type: istio + enabled: true + + api_gateway: + type: kong + enabled: true + +services: + user_service: + name: user-service + replicas: 2 + port: 8081 + database: + type: postgresql + name: users_db + + order_service: + name: order-service + replicas: 3 + port: 8082 + database: + type: postgresql + name: orders_db + + inventory_service: + name: inventory-service + replicas: 2 + port: 8083 + database: + type: postgresql + name: inventory_db + + notification_service: + name: notification-service + replicas: 1 + port: 8084 + messaging: + type: rabbitmq + +messaging: + type: rabbitmq + queues: + - order_events + - notification_events + +monitoring: + enabled: true + tracing: true + metrics: + - cpu + - memory + - requests + - latency + +logging: + enabled: true + centralized: true + retention_days: 30 diff --git a/examples/starter-packs/web-app/infrastructure.yml b/examples/starter-packs/web-app/infrastructure.yml new file mode 100644 index 0000000..76d8980 --- /dev/null +++ b/examples/starter-packs/web-app/infrastructure.yml @@ -0,0 +1,56 @@ +name: web-app-starter +version: 1.0.0 +description: Basic web application infrastructure starter pack + +infrastructure: + compute: + type: container + image: nginx:latest + replicas: 2 + resources: + cpu: 1 + memory: 2Gi + + database: + type: postgresql + version: "14" + storage: 20Gi + backup_enabled: true + + cache: + type: redis + version: "7" + memory: 1Gi + + storage: + type: object-storage + capacity: 100Gi + encryption: false + + networking: + load_balancer: + type: application + ports: + - 80 + - 443 + +services: + web: + name: web-application + port: 80 + health_check: /health + + api: + name: api-service + port: 8080 + health_check: /api/health + +monitoring: + enabled: false + metrics: + - cpu + - memory + +logging: + enabled: false + retention_days: 7 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..389232f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,62 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "bit-sidekick" +version = "0.1.0" +description = "A lightweight, domain-aware AI agent for secure, self-auditing, and auto-configuring infrastructure" +readme = "README.md" +requires-python = ">=3.8" +license = {text = "MIT"} +authors = [ + {name = "Bluebit Innovations"} +] +keywords = ["ai", "infrastructure", "devops", "automation", "cloud"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] + +dependencies = [ + "pyyaml>=6.0", + "click>=8.0", + "jinja2>=3.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "pytest-cov>=4.0", + "black>=22.0", + "flake8>=5.0", + "mypy>=1.0", +] + +[project.scripts] +bit-sidekick = "bit_sidekick.cli:main" + +[tool.setuptools] +packages = ["bit_sidekick"] + +[tool.black] +line-length = 100 +target-version = ['py38'] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..e524d85 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +pytest>=7.0 +pytest-cov>=4.0 +black>=22.0 +flake8>=5.0 +mypy>=1.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..58b8a77 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pyyaml>=6.0 +click>=8.0 +jinja2>=3.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6068493 --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..97585e4 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the Bit-Block Sidekick.""" diff --git a/tests/test_agent.py b/tests/test_agent.py new file mode 100644 index 0000000..3d69a3c --- /dev/null +++ b/tests/test_agent.py @@ -0,0 +1,92 @@ +"""Tests for the SidekickAgent.""" + +import pytest +import tempfile +import os +import yaml +from bit_sidekick.agent import SidekickAgent +from bit_sidekick.config import SidekickConfig + + +@pytest.fixture +def agent(): + """Create a test agent.""" + return SidekickAgent() + + +@pytest.fixture +def temp_config_file(): + """Create a temporary configuration file.""" + with tempfile.TemporaryDirectory() as tmpdir: + config_path = os.path.join(tmpdir, "test_config.yml") + config_data = { + "infrastructure": { + "compute": {"type": "container", "replicas": 2}, + "database": {"type": "postgresql"}, + }, + "services": { + "web": {"name": "web-service", "port": 80}, + }, + } + with open(config_path, "w") as f: + yaml.dump(config_data, f) + yield config_path + + +def test_agent_initialization(): + """Test agent initialization.""" + agent = SidekickAgent() + assert agent is not None + assert agent.config is not None + assert agent.analyzer is not None + assert agent.configurator is not None + assert agent.auditor is not None + + +def test_analyze_infrastructure_nonexistent(agent): + """Test analyzing non-existent path.""" + result = agent.analyze_infrastructure("/nonexistent/path") + assert result["exists"] is False + + +def test_analyze_infrastructure_valid(agent, temp_config_file): + """Test analyzing valid configuration.""" + result = agent.analyze_infrastructure(temp_config_file) + assert result["exists"] is True + assert result["type"] == "file" + + +def test_auto_configure(agent, temp_config_file): + """Test auto-configuration.""" + result = agent.auto_configure(temp_config_file, "dev") + assert result["status"] == "success" + assert result["environment"] == "dev" + assert len(result["configurations"]) > 0 + + +def test_self_audit(agent, temp_config_file): + """Test self-audit.""" + result = agent.self_audit(temp_config_file) + assert result["status"] == "completed" + assert "security_findings" in result + assert "optimization_findings" in result + assert "risk_score" in result + + +def test_optimize(agent, temp_config_file): + """Test optimization.""" + result = agent.optimize(temp_config_file) + assert result["status"] == "completed" + assert "analysis" in result + assert "audit" in result + + +def test_transform_starter_pack(agent, temp_config_file): + """Test complete transformation.""" + result = agent.transform_starter_pack(temp_config_file, "dev") + assert result["status"] == "completed" + assert result["starter_pack"] == temp_config_file + assert result["target_environment"] == "dev" + assert "analysis" in result + assert "configuration" in result + assert "audit" in result diff --git a/tests/test_analyzer.py b/tests/test_analyzer.py new file mode 100644 index 0000000..367bbdd --- /dev/null +++ b/tests/test_analyzer.py @@ -0,0 +1,80 @@ +"""Tests for the analyzer module.""" + +import pytest +import tempfile +import os +import yaml +from bit_sidekick.config import SidekickConfig +from bit_sidekick.modules.analyzer import InfrastructureAnalyzer + + +@pytest.fixture +def analyzer(): + """Create a test analyzer.""" + config = SidekickConfig() + return InfrastructureAnalyzer(config) + + +@pytest.fixture +def temp_yaml_file(): + """Create a temporary YAML configuration file.""" + with tempfile.TemporaryDirectory() as tmpdir: + config_path = os.path.join(tmpdir, "test.yml") + config_data = { + "resources": { + "web_server": {"type": "compute", "replicas": 2}, + "database": {"type": "postgresql", "encryption": True}, + }, + } + with open(config_path, "w") as f: + yaml.dump(config_data, f) + yield config_path + + +def test_analyzer_initialization(): + """Test analyzer initialization.""" + config = SidekickConfig() + analyzer = InfrastructureAnalyzer(config) + assert analyzer is not None + assert analyzer.config is not None + + +def test_analyze_nonexistent_path(analyzer): + """Test analyzing non-existent path.""" + result = analyzer.analyze("/nonexistent/path") + assert result["exists"] is False + assert len(result["findings"]) > 0 + + +def test_analyze_yaml_file(analyzer, temp_yaml_file): + """Test analyzing YAML file.""" + result = analyzer.analyze(temp_yaml_file) + assert result["exists"] is True + assert result["type"] == "file" + assert result["format"] == "yaml" + assert len(result["resources"]) > 0 + + +def test_extract_resources(analyzer): + """Test resource extraction.""" + content = { + "resources": { + "server": {"type": "compute"}, + "db": {"type": "database"}, + } + } + resources = analyzer._extract_resources(content) + assert len(resources) == 2 + assert resources[0]["name"] in ["server", "db"] + + +def test_security_recommendations(analyzer): + """Test security recommendations.""" + analysis = { + "resources": [ + {"name": "db", "config": {"encryption": False}}, + ] + } + recommendations = analyzer._security_recommendations(analysis) + assert len(recommendations) > 0 + assert any(r["type"] == "security" for r in recommendations) diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..78aec87 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,56 @@ +"""Tests for the configuration module.""" + +import pytest +import tempfile +import os +from bit_sidekick.config import SidekickConfig + + +def test_default_config(): + """Test default configuration.""" + config = SidekickConfig() + assert config.get("agent.name") == "Bit-Block Sidekick" + assert config.get("agent.domain_awareness") is True + assert config.get("analysis.enabled") is True + + +def test_config_get_nested(): + """Test getting nested configuration values.""" + config = SidekickConfig() + assert config.get("agent.version") == "0.1.0" + assert config.get("automation.dry_run") is True + + +def test_config_get_default(): + """Test getting configuration with default value.""" + config = SidekickConfig() + assert config.get("nonexistent.key", "default") == "default" + + +def test_config_set(): + """Test setting configuration values.""" + config = SidekickConfig() + config.set("agent.name", "Custom Sidekick") + assert config.get("agent.name") == "Custom Sidekick" + + +def test_config_set_nested(): + """Test setting nested configuration values.""" + config = SidekickConfig() + config.set("custom.nested.value", "test") + assert config.get("custom.nested.value") == "test" + + +def test_config_save_load(): + """Test saving and loading configuration.""" + with tempfile.TemporaryDirectory() as tmpdir: + config_path = os.path.join(tmpdir, "config.yml") + + # Create and save config + config1 = SidekickConfig() + config1.set("agent.name", "Test Sidekick") + config1.save(config_path) + + # Load config + config2 = SidekickConfig(config_path) + assert config2.get("agent.name") == "Test Sidekick"