diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..489d0f7 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,21 @@ +# Grimlock Backend Configuration + +# API Keys +ANTHROPIC_API_KEY=your-anthropic-api-key-here + +# Server Configuration +HOST=0.0.0.0 +PORT=8000 +DEBUG=true + +# Context Configuration +CONTEXT_PATH=/app/context +REPOS_PATH=/app/repos + +# AI Configuration +AI_MODEL=claude-sonnet-4-5-20250514 +AI_MAX_TOKENS=4096 +AI_TEMPERATURE=0.7 + +# Logging +LOG_LEVEL=INFO diff --git a/backend/api/chat.py b/backend/api/chat.py new file mode 100644 index 0000000..fa65dbc --- /dev/null +++ b/backend/api/chat.py @@ -0,0 +1,137 @@ +""" +Chat API - Main endpoint for interacting with Grimlock +""" + +from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import StreamingResponse +from pydantic import BaseModel +from typing import List, Optional +import logging + +from core.context_manager import ContextManager +from core.ai_client import AIClient +import main + +logger = logging.getLogger(__name__) + +router = APIRouter() + +class Message(BaseModel): + role: str + content: str + +class ChatRequest(BaseModel): + messages: List[Message] + role: Optional[str] = None + stream: bool = False + +class ChatResponse(BaseModel): + response: str + context_used: bool + +@router.post("/", response_model=ChatResponse) +async def chat( + request: ChatRequest, + context_manager: ContextManager = Depends(main.get_context_manager), + ai_client: AIClient = Depends(main.get_ai_client) +): + """ + Chat with Grimlock + + Args: + request: Chat request with messages and optional role + + Returns: + ChatResponse with AI response + """ + try: + # Get the last user message for context + user_message = None + for msg in reversed(request.messages): + if msg.role == "user": + user_message = msg.content + break + + # Get relevant context + context = "" + if user_message: + context = context_manager.get_context_for_query( + user_message, + role=request.role + ) + + # Build system prompt + system_prompt = context_manager.get_system_prompt(role=request.role) + + # Add context to system prompt if available + if context: + system_prompt += f"\n\n# Company Context\n{context}" + + # Convert messages to API format + api_messages = [ + {"role": msg.role, "content": msg.content} + for msg in request.messages + ] + + # Get response from AI + response = await ai_client.chat( + messages=api_messages, + system_prompt=system_prompt + ) + + return ChatResponse( + response=response, + context_used=bool(context) + ) + + except Exception as e: + logger.error(f"Error in chat endpoint: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.post("/stream") +async def chat_stream( + request: ChatRequest, + context_manager: ContextManager = Depends(main.get_context_manager), + ai_client: AIClient = Depends(main.get_ai_client) +): + """Stream chat response from Grimlock""" + + try: + # Get context + user_message = None + for msg in reversed(request.messages): + if msg.role == "user": + user_message = msg.content + break + + context = "" + if user_message: + context = context_manager.get_context_for_query( + user_message, + role=request.role + ) + + # Build system prompt + system_prompt = context_manager.get_system_prompt(role=request.role) + if context: + system_prompt += f"\n\n# Company Context\n{context}" + + # Convert messages + api_messages = [ + {"role": msg.role, "content": msg.content} + for msg in request.messages + ] + + # Stream response + async def generate(): + async for chunk in ai_client.chat_stream( + messages=api_messages, + system_prompt=system_prompt + ): + yield chunk + + return StreamingResponse(generate(), media_type="text/plain") + + except Exception as e: + logger.error(f"Error in chat stream: {e}") + raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/context/projects/grimlock.md b/backend/context/projects/grimlock.md new file mode 100644 index 0000000..477478b --- /dev/null +++ b/backend/context/projects/grimlock.md @@ -0,0 +1,339 @@ +# Grimlock + +> **AI-Native Company Operating System** +> *Your team's AI companion. One interface. All your context. Infinite intelligence.* + +--- + +## What is Grimlock? + +Grimlock is an AI-powered operating system for modern companies. It's like having Jarvis (from Iron Man) for your entire organization - an intelligent assistant that knows everything about your company, can access all your internal systems, and generates any artifact on-demand. + +**The Problem:** +- Knowledge workers waste 60-80% of their time finding files, asking for updates, manual data entry, scheduling, and formatting documents +- Current tools (Slack, Notion, ChatGPT) are disconnected and lack company context +- AI assistants are generic and can't access internal systems +- Companies are locked into Microsoft/Google ecosystems + +**The Solution:** +One AI interface that: +- ✅ Knows everything about your company +- ✅ Connects to all your internal systems +- ✅ Generates documents, spreadsheets, code, presentations on-demand +- ✅ Works for everyone (BD, engineering, ops, finance, assistants) +- ✅ Self-hosted for data sovereignty +- ✅ Platform-agnostic (not locked to MS/Google) + +--- + +## Key Features + +### 🤖 AI-Native Interface +- Natural language interaction with company-aware AI +- Context preserved across conversations +- Role-based responses (BD gets BD-focused, engineers get technical) + +### 🔌 Universal Connectors +- Git repositories (GitHub, GitLab, Gitea) +- Databases (PostgreSQL, MySQL, MongoDB) +- File storage (MinIO, S3, local filesystems) +- Calendar systems +- Project management tools +- Custom internal tools via plugin architecture + +### 📄 Artifact Generation +- Documents (Markdown, PDF) +- Spreadsheets (CSV, JSON - no Excel dependency) +- Code (any language) +- Presentations +- Reports and dashboards + +### 🏢 Multi-Tenant Architecture +- Self-hosted on customer infrastructure +- Complete data sovereignty +- Role-based access control +- SSO integration + +### 🔒 Security First +- All data stays on customer infrastructure +- Encrypted at rest and in transit +- Audit logs for compliance +- SOC2, HIPAA-ready architecture + +--- + +## Use Cases + +### For Engineers +``` +Engineer: @grimlock I need to build a payment processing system for construction invoices +Grimlock: Based on Vector Zulu patterns, this maps to the UTILEN + Finance Infrastructure patterns: + + Core Stack: FastAPI + PostgreSQL + Redis + Celery + Key Patterns: Multi-tenant isolation, background job processing, audit trails + Cost Estimate: ~$3-5/transaction for AI processing + Timeline: 30 days to working prototype + + Should I generate the project scaffold? +``` + +### For Business Development +``` +BD: @grimlock create a capabilities deck for a prospective cyber range client +Grimlock: [Generates PDF] + Created: VectorZulu-CyberRange-Capabilities-2026-02.pdf + Download: [link] +``` + +### For Executives +``` +Exec: @grimlock summarize all active projects and their status +Grimlock: Active Projects Summary: + + UTILEN (Document Management) - Production, 499 docs processed + Vector Zulu Platform (Cyber Range) - K3s deployment in progress + Blockchain Platform - Architecture phase + + Full report: [link to generated document] +``` + +### For Operations +``` +Ops: @grimlock generate January infrastructure cost report +Grimlock: [Generates spreadsheet with cost breakdown] + Total: $2,847 + Breakdown: AI APIs ($1,200), Hosting ($897), Network ($750) + Recommendations: Consider tier upgrade for AI API (ROI positive) +``` + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ Grimlock Web Interface │ +│ (React/Next.js - ai.yourcompany.com) │ +└─────────────────┬───────────────────────────────┘ + │ +┌─────────────────▼───────────────────────────────┐ +│ Grimlock Core │ +│ (FastAPI Backend) │ +│ │ +│ ┌─────────────┐ ┌──────────────┐ │ +│ │ AI Layer │ │ Context │ │ +│ │ (Claude) │ │ Manager │ │ +│ └─────────────┘ └──────────────┘ │ +│ │ +│ ┌─────────────┐ ┌──────────────┐ │ +│ │ Artifact │ │ Connector │ │ +│ │ Generator │ │ Engine │ │ +│ └─────────────┘ └──────────────┘ │ +└─────────────────┬───────────────────────────────┘ + │ +┌─────────────────▼───────────────────────────────┐ +│ Company Data Sources │ +│ (via Connectors) │ +│ │ +│ • Git Repos • Databases • File Storage │ +│ • Calendars • Project Mgmt • Custom APIs │ +└─────────────────────────────────────────────────┘ +``` + +--- + +## Tech Stack + +**Backend:** +- FastAPI (Python) - API server +- PostgreSQL - Metadata and user data +- Redis - Caching and task queue +- Celery - Background job processing +- Anthropic Claude API - AI reasoning + +**Frontend:** +- React/Next.js - Web interface +- TailwindCSS - Styling +- WebSocket - Real-time updates + +**Deployment:** +- Docker/Docker Compose - Containerization +- Kubernetes - Production orchestration (optional) +- Self-hosted on customer infrastructure + +**Connectors:** +- Plugin architecture for extensibility +- Pre-built connectors for common systems +- Custom connector SDK + +--- + +## Getting Started + +### Quick Start (Docker Compose) + +```bash +# Clone the repository +git clone https://gittea.979labs.com/amitis55/grimlock.git +cd grimlock + +# Set up environment variables +cp .env.example .env +# Edit .env with your configuration + +# Start Grimlock +docker-compose up -d + +# Access at http://localhost:3000 +``` + +### Manual Installation + +```bash +# Backend +cd backend +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python main.py + +# Frontend +cd frontend +npm install +npm run dev +``` + +See [Installation Guide](docs/installation.md) for detailed instructions. + +--- + +## Project Structure + +``` +grimlock/ +├── backend/ # FastAPI backend +│ ├── api/ # API endpoints +│ ├── core/ # Core business logic +│ ├── connectors/ # Data source connectors +│ ├── ai/ # AI integration layer +│ └── artifacts/ # Artifact generation +├── frontend/ # React frontend +│ ├── components/ # UI components +│ ├── pages/ # Next.js pages +│ └── lib/ # Utilities +├── connectors/ # Connector plugins +│ ├── git/ # Git connector +│ ├── database/ # Database connector +│ └── ... +├── docs/ # Documentation +├── docker/ # Docker configurations +└── scripts/ # Deployment scripts +``` + +--- + +## Roadmap + +### Phase 1: MVP (Current - Month 3) +- [x] Repository setup +- [ ] Core backend (FastAPI + Claude integration) +- [ ] Basic web interface +- [ ] Git connector (read-only) +- [ ] Document generation (Markdown, PDF) +- [ ] Spreadsheet generation (CSV, JSON) +- [ ] Deploy internally at Vector Zulu + +### Phase 2: Beta (Month 4-6) +- [ ] Mobile apps (iOS, Android) +- [ ] Desktop app (Electron) +- [ ] Connector architecture framework +- [ ] 10+ pre-built connectors +- [ ] Admin dashboard +- [ ] Multi-tenant support +- [ ] 5-10 beta customers + +### Phase 3: Production (Month 7-12) +- [ ] Advanced artifact generation +- [ ] Workflow automation +- [ ] SSO integrations +- [ ] Compliance features (SOC2, HIPAA) +- [ ] Self-service onboarding +- [ ] Public launch + +### Phase 4: Scale (Year 2+) +- [ ] White-label options +- [ ] Enterprise features +- [ ] API marketplace +- [ ] AI model flexibility +- [ ] Advanced analytics + +--- + +## Business Model + +**Self-Hosted License:** +- $50-150/user/month +- Deploy on customer infrastructure +- Include support and updates + +**Managed Hosting:** +- $100-200/user/month +- We host on Vector Zulu infrastructure +- Full management and support + +**Professional Services:** +- Custom connector development +- Implementation and training +- Enterprise support + +--- + +## Why Grimlock? + +**The name:** Grimlock is a powerful Transformer - representing transformation of how companies operate. Just as Grimlock is a leader among Transformers, this platform transforms company operations. + +**The mission:** Eliminate bullshit work. Empower knowledge workers. Make AI-native operations accessible to every company. + +**The vision:** Every company runs on AI-native infrastructure where the AI is the interface to everything. + +--- + +## Contributing + +We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. + +--- + +## License + +[License TBD - Likely dual license: OSS for self-hosted, commercial for managed hosting] + +--- + +## Contact + +**Project Owner:** JA @ Vector Zulu LLC +**Repository:** https://gittea.979labs.com/amitis55/grimlock +**Company:** Vector Zulu LLC + +--- + +## Case Study: Vector Zulu + +Grimlock was born from Vector Zulu's need to eliminate operational overhead. We built UTILEN (an enterprise document management system) in 30 days using AI-assisted development - a project others quoted at 18 months. We realized the AI workflows we developed could transform how any company operates. + +Vector Zulu serves as Grimlock's pilot customer, validating every feature in a real production environment serving K-12 cybersecurity education and commercial cloud services. + +**Results:** +- 10x faster project delivery +- 80% reduction in "where's that file?" questions +- Zero Microsoft Office dependencies +- Engineers spend time engineering, not finding documents +- BD team self-serves on technical content + +--- + +**Built with ❤️ by Vector Zulu** +*Transforming companies through AI-native operations* + +Grimlock - AI-native company operating system. The Jarvis for modern businesses. \ No newline at end of file diff --git a/backend/core/ai_client.py b/backend/core/ai_client.py new file mode 100644 index 0000000..f5a1634 --- /dev/null +++ b/backend/core/ai_client.py @@ -0,0 +1,96 @@ +""" +AI Client - Anthropic Claude Integration +""" + +import anthropic +import os +import logging +from typing import List, Dict, Optional + +logger = logging.getLogger(__name__) + +class AIClient: + """Client for interacting with Anthropic Claude API""" + + def __init__(self, api_key: str): + self.client = anthropic.Anthropic(api_key=api_key) + self.model = os.getenv("AI_MODEL", "claude-sonnet-4-5-20250514") + self.max_tokens = int(os.getenv("AI_MAX_TOKENS", "4096")) + self.temperature = float(os.getenv("AI_TEMPERATURE", "0.7")) + logger.info(f"AIClient initialized with model: {self.model}") + + async def chat( + self, + messages: List[Dict[str, str]], + system_prompt: Optional[str] = None, + max_tokens: Optional[int] = None + ) -> str: + """ + Send chat messages to Claude and get response + + Args: + messages: List of message dicts with 'role' and 'content' + system_prompt: Optional system prompt + max_tokens: Optional max tokens override + + Returns: + Response text from Claude + """ + try: + kwargs = { + "model": self.model, + "max_tokens": max_tokens or self.max_tokens, + "temperature": self.temperature, + "messages": messages + } + + if system_prompt: + kwargs["system"] = system_prompt + + response = self.client.messages.create(**kwargs) + + # Extract text from response + if response.content and len(response.content) > 0: + return response.content[0].text + + return "No response generated" + + except Exception as e: + logger.error(f"Error calling Claude API: {e}") + raise + + async def chat_stream( + self, + messages: List[Dict[str, str]], + system_prompt: Optional[str] = None, + max_tokens: Optional[int] = None + ): + """ + Stream chat response from Claude + + Args: + messages: List of message dicts + system_prompt: Optional system prompt + max_tokens: Optional max tokens override + + Yields: + Text chunks from Claude + """ + try: + kwargs = { + "model": self.model, + "max_tokens": max_tokens or self.max_tokens, + "temperature": self.temperature, + "messages": messages + } + + if system_prompt: + kwargs["system"] = system_prompt + + with self.client.messages.stream(**kwargs) as stream: + for text in stream.text_stream: + yield text + + except Exception as e: + logger.error(f"Error streaming from Claude API: {e}") + raise diff --git a/backend/core/context_manager.py b/backend/core/context_manager.py new file mode 100644 index 0000000..b5149c4 --- /dev/null +++ b/backend/core/context_manager.py @@ -0,0 +1,171 @@ +""" +Context Manager - Loads and manages company context +""" + +import os +import logging +from pathlib import Path +from typing import Dict, List, Optional +import yaml + +logger = logging.getLogger(__name__) + +class ContextManager: + """Manages loading and accessing company context""" + + def __init__(self, context_path: str): + self.context_path = Path(context_path) + self.context = { + "projects": {}, + "patterns": {}, + "anti_patterns": {}, + "cost_models": {}, + "repos": {} + } + self._loaded = False + + def load_all_context(self): + """Load all context from disk""" + logger.info(f"Loading context from {self.context_path}") + + # Create context directories if they don't exist + self.context_path.mkdir(parents=True, exist_ok=True) + for subdir in ["projects", "patterns", "anti_patterns", "cost_models", "repos"]: + (self.context_path / subdir).mkdir(exist_ok=True) + + # Load each context type + self._load_directory("projects") + self._load_directory("patterns") + self._load_directory("anti_patterns") + self._load_directory("cost_models") + + self._loaded = True + logger.info(f"Context loaded: {self.get_summary()}") + + def _load_directory(self, directory: str): + """Load all markdown files from a directory""" + dir_path = self.context_path / directory + if not dir_path.exists(): + logger.warning(f"Context directory not found: {dir_path}") + return + + for file_path in dir_path.glob("*.md"): + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + self.context[directory][file_path.stem] = content + logger.debug(f"Loaded {directory}/{file_path.name}") + except Exception as e: + logger.error(f"Error loading {file_path}: {e}") + + def get_context_for_query(self, query: str, role: Optional[str] = None) -> str: + """ + Get relevant context for a query + + Args: + query: User query + role: User role (engineer, bd, admin, exec) + + Returns: + Formatted context string + """ + # Simple implementation: include all context + # TODO: Implement smarter context selection based on query relevance + + context_parts = [] + + # Add projects + if self.context["projects"]: + context_parts.append("# Vector Zulu Projects\n") + for name, content in self.context["projects"].items(): + context_parts.append(f"## {name}\n{content}\n") + + # Add patterns (most relevant for engineers) + if self.context["patterns"] and (role == "engineer" or role is None): + context_parts.append("\n# Reference Architectures & Patterns\n") + for name, content in self.context["patterns"].items(): + context_parts.append(f"## {name}\n{content}\n") + + # Add anti-patterns + if self.context["anti_patterns"]: + context_parts.append("\n# Anti-Patterns (Things to Avoid)\n") + for name, content in self.context["anti_patterns"].items(): + context_parts.append(f"## {name}\n{content}\n") + + # Add cost models (relevant for estimates) + if "cost" in query.lower() or "price" in query.lower(): + if self.context["cost_models"]: + context_parts.append("\n# Cost Models\n") + for name, content in self.context["cost_models"].items(): + context_parts.append(f"## {name}\n{content}\n") + + return "\n".join(context_parts) + + def get_system_prompt(self, role: Optional[str] = None) -> str: + """ + Generate system prompt based on role + + Args: + role: User role + + Returns: + System prompt for Claude + """ + base_prompt = """You are Grimlock, Vector Zulu's AI assistant. You help team members by: +- Answering questions about projects, patterns, and internal systems +- Generating documents, spreadsheets, code, and other artifacts +- Providing technical guidance based on Vector Zulu's proven patterns +- Keeping responses concise and actionable + +You have access to Vector Zulu's internal context including: +- Project summaries (UTILEN, Vector Zulu platform, blockchain) +- Reference architectures and patterns +- Anti-patterns to avoid +- Cost models and estimates +""" + + role_prompts = { + "engineer": "\nYou're speaking with an engineer. Provide technical depth, code examples, and architecture details.", + "bd": "\nYou're speaking with business development. Focus on capabilities, timelines, costs, and client-facing information.", + "admin": "\nYou're speaking with admin/operations. Focus on processes, reports, schedules, and organizational information.", + "exec": "\nYou're speaking with an executive. Provide high-level summaries, key metrics, and strategic insights." + } + + if role and role in role_prompts: + base_prompt += role_prompts[role] + + return base_prompt + + def is_loaded(self) -> bool: + """Check if context is loaded""" + return self._loaded + + def get_summary(self) -> Dict: + """Get summary of loaded context""" + return { + "projects": len(self.context["projects"]), + "patterns": len(self.context["patterns"]), + "anti_patterns": len(self.context["anti_patterns"]), + "cost_models": len(self.context["cost_models"]), + "repos": len(self.context["repos"]) + } + + def add_context(self, category: str, name: str, content: str): + """Add or update context""" + if category in self.context: + self.context[category][name] = content + logger.info(f"Added/updated {category}/{name}") + + def save_context(self, category: str, name: str): + """Save context to disk""" + if category not in self.context or name not in self.context[category]: + logger.error(f"Context not found: {category}/{name}") + return + + file_path = self.context_path / category / f"{name}.md" + try: + with open(file_path, 'w', encoding='utf-8') as f: + f.write(self.context[category][name]) + logger.info(f"Saved {category}/{name} to disk") + except Exception as e: + logger.error(f"Error saving {file_path}: {e}") diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..cc34609 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,106 @@ +""" +Grimlock - AI-Native Company Operating System +Main FastAPI Application +""" + +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from contextlib import asynccontextmanager +import logging +from dotenv import load_dotenv +import os + +from api.chat import router as chat_router +from core.context_manager import ContextManager +from core.ai_client import AIClient + +# Load environment variables +load_dotenv() + +# Configure logging +logging.basicConfig( + level=os.getenv("LOG_LEVEL", "INFO"), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# Global state +context_manager = None +ai_client = None + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Startup and shutdown events""" + global context_manager, ai_client + + logger.info("Starting Grimlock backend...") + + # Initialize context manager + context_path = os.getenv("CONTEXT_PATH", "./context") + context_manager = ContextManager(context_path) + context_manager.load_all_context() + logger.info(f"Loaded context from {context_path}") + + # Initialize AI client + api_key = os.getenv("ANTHROPIC_API_KEY") + if not api_key: + logger.error("ANTHROPIC_API_KEY not set!") + raise ValueError("ANTHROPIC_API_KEY environment variable is required") + + ai_client = AIClient(api_key=api_key) + logger.info("AI client initialized") + + yield + + # Cleanup + logger.info("Shutting down Grimlock backend...") + +# Create FastAPI app +app = FastAPI( + title="Grimlock", + description="AI-Native Company Operating System", + version="0.1.0", + lifespan=lifespan +) + +# CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Configure appropriately for production + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include routers +app.include_router(chat_router, prefix="/api/chat", tags=["chat"]) + +@app.get("/") +async def root(): + """Health check endpoint""" + return { + "status": "online", + "service": "Grimlock", + "version": "0.1.0" + } + +@app.get("/api/health") +async def health(): + """Detailed health check""" + return { + "status": "healthy", + "context_loaded": context_manager is not None and context_manager.is_loaded(), + "ai_client_ready": ai_client is not None + } + +def get_context_manager() -> ContextManager: + """Dependency to get context manager""" + if context_manager is None: + raise HTTPException(status_code=500, detail="Context manager not initialized") + return context_manager + +def get_ai_client() -> AIClient: + """Dependency to get AI client""" + if ai_client is None: + raise HTTPException(status_code=500, detail="AI client not initialized") + return ai_client diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..e7f25d8 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,13 @@ +fastapi==0.109.0 +uvicorn[standard]==0.27.0 +anthropic==0.18.1 +python-dotenv==1.0.0 +pydantic==2.5.3 +pydantic-settings==2.1.0 +python-multipart==0.0.6 +aiofiles==23.2.1 +GitPython==3.1.41 +PyYAML==6.0.1 +markdown==3.5.2 +weasyprint==60.2 +pandas==2.2.0