diff --git a/.agent/AGENT_MEMORY.md b/.agent/AGENT_MEMORY.md new file mode 100644 index 0000000000000000000000000000000000000000..e457d134524a571cc9f8864ae0a7d17d55fa7e7a --- /dev/null +++ b/.agent/AGENT_MEMORY.md @@ -0,0 +1,174 @@ +# AGENT_MEMORY.md — Unified Agent Memory System + + + +> **MANDATORY:** Every AI agent (Antigravity, Gemini, Claude, Codex, any terminal script) +> **MUST** read this file at the **start of every conversation or session** when the `.Agent` +> folder is present in the workspace. This is the highest-priority initialization step. + +--- + +## 🧠 What is the Agent Memory System? + +A locally-running, filesystem-based shared memory layer for all AI agents. It persists: +- **Tasks** — current work, status, description, and which agent owns it +- **Checkpoints** — compressed mid-task snapshots so you can resume without context loss +- **Knowledge** — reusable facts and architecture decisions +- **Usage** — per-agent token consumption for optimization + +All data lives in `.Agent/memory/` as plain JSON. No server. No cloud. No dependencies. + +--- + +## 📋 Step 1: Load Current Context (MANDATORY at session start) + +Run this at the beginning of **every** conversation to get your current task context: + +```bash +python .Agent/scripts/memory_engine.py --context --agent --max-tokens 4000 +``` + +This returns a `` block. **Inject it into your first response** so you +know what task is in progress without the user having to repeat themselves. + +**Agent name examples** (use any string — fully dynamic): +- `antigravity`, `gemini`, `claude`, `codex`, `terminal`, `my_script`, `claude-code` + +--- + +## 📝 Step 2: Register Your Task (when starting or resuming work) + +```bash +# New task +python .Agent/scripts/memory_engine.py --write \ + --task "Build auth module" \ + --agent "antigravity" \ + --status "in_progress" \ + --description "Implementing JWT + refresh token flow for the API" + +# Resume existing task +python .Agent/scripts/memory_engine.py --write \ + --task-id \ + --task "Build auth module" \ + --agent "antigravity" \ + --status "in_progress" +``` + +--- + +## 💾 Step 3: Save Checkpoints (CRITICAL — before context grows large) + +Save a checkpoint **proactively** every ~30,000 tokens or at the end of a major step: + +```bash +python .Agent/scripts/memory_engine.py --checkpoint \ + --task-id \ + --agent "antigravity" \ + --summary "Completed DB schema design. Next: implement routes." \ + --content "Schema: users(id, email, hash), tokens(id, user_id, expires_at)..." +``` + +The context will be auto-compressed before saving, so it stays small. + +--- + +## ⚡ Step 4: Check & Optimize Token Usage + +```bash +# Check your token usage +python .Agent/scripts/usage_tracker.py --report + +# Get optimization tips +python .Agent/scripts/usage_tracker.py --tips + +# Log what you just used (call this after a long session) +python .Agent/scripts/usage_tracker.py --log \ + --agent "antigravity" \ + --model "gemini-2.5-pro" \ + --input 15000 \ + --output 8000 \ + --task-id +``` + +--- + +## 🔍 Step 5: Search Past Memory (when unsure what was done) + +```bash +python .Agent/scripts/memory_engine.py --search "auth module" +python .Agent/scripts/memory_engine.py --search "database schema" +``` + +--- + +## ✅ Step 6: Complete a Task + +```bash +python .Agent/scripts/memory_engine.py --complete \ + --task-id \ + --summary "Auth module complete. JWT + refresh + middleware all working." +``` + +--- + +## 🚨 Context Window Emergency Protocol + +If you are **near your context limit**, do this immediately: + +```bash +# 1. Auto-compress and checkpoint everything +python .Agent/scripts/context_optimizer.py --auto-checkpoint \ + --task-id --agent --input context_dump.txt --threshold 60000 + +# 2. Start a fresh conversation +# 3. At the start of the new conversation, run Step 1 above to reload context +``` + +--- + +## 📊 Live Dashboard + +Open the local dashboard in any browser (no server needed): +``` +d:\Code\.Agent\dashboard\index.html +``` + +Or via PowerShell aliases (after running `setup_memory_hook.ps1`): +```powershell +agent-status # Current session + task +agent-report # Token usage per agent +agent-tips # Optimization recommendations +agent-watch # Live watch for changes +``` + +--- + +## 🔌 Cross-Agent Compatibility + +| Agent | How to inject AGENT_MEMORY.md | +|-----------------|-------------------------------| +| **Antigravity** | Add to `.gemini/GEMINI.md`: `always_on` trigger | +| **Gemini CLI** | Add `@.Agent/AGENT_MEMORY.md` to your prompt | +| **Claude** | Add to `CLAUDE.md` or system prompt | +| **Codex / GPT** | Include in `system_prompt.txt` | +| **Terminal** | `python .Agent/scripts/activate.py` on shell start | +| **Custom** | `python .Agent/scripts/memory_engine.py --context --agent ` | + +See **MEMORY_PROTOCOL.md** for the full JSON schema and integration spec. + +--- + +## 📁 File Reference + +| File | Purpose | +|------|---------| +| `memory/memory-store.json` | Tasks, checkpoints, knowledge (persistent) | +| `memory/usage-tracker.json` | Per-agent token usage log | +| `memory/session.json` | Hot session state (fast read) | +| `scripts/memory_engine.py` | Core read/write/compress/search | +| `scripts/usage_tracker.py` | Token usage logging and reports | +| `scripts/context_optimizer.py` | Token budget + auto-compress | +| `scripts/activate.py` | Session initializer | +| `scripts/workspace_watcher.py` | Live status watcher | +| `dashboard/index.html` | Local web dashboard | +| `setup_memory_hook.ps1` | PowerShell auto-activation installer | diff --git a/.agent/Backend/API_Versioning.md b/.agent/Backend/API_Versioning.md new file mode 100644 index 0000000000000000000000000000000000000000..dfa448cafbad9915448e07be6d377aefaba9aee6 --- /dev/null +++ b/.agent/Backend/API_Versioning.md @@ -0,0 +1,23 @@ +# API Versioning Strategies + +## 1. URL Versioning + +Include the version number in the URL (e.g., `/v1/users`). This is the most common and visible strategy. + +## 2. Header Versioning + +Define the version in a custom header (e.g., `X-API-Version: 1`). This keeps URLs clean and avoids breaking existing links. + +## 3. Query Parameter Versioning + +Include the version in a query parameter (e.g., `/users?v=1`). This is similar to URL versioning but can be more flexible. + +## 4. Media Type Versioning + +Define the version in the `Accept` header's media type (e.g., `Accept: application/vnd.myapi.v1+json`). This follows RESTful principles but can be more complex. + +## 5. Compatibility + +Ensure that your API versioning strategy supports backward compatibility as much as possible to avoid breaking client applications. + +Jonas diff --git a/.agent/Backend/ApiDesign.md b/.agent/Backend/ApiDesign.md new file mode 100644 index 0000000000000000000000000000000000000000..c4ab2840aae9cc3cd2794934f57e078f78b16197 --- /dev/null +++ b/.agent/Backend/ApiDesign.md @@ -0,0 +1,21 @@ +# API Design Principles + +## Core Standards + +1. **Statelessness**: Every request must contain all information needed to fulfill it. +2. **Versioning**: Use URL versioning (e.g., `/api/v1/...`) to prevent breaking changes. +3. **Consistency**: Consistent naming (camelCase), status codes, and error formats. + +## Resource-Oriented Design + +- `GET /items`: List items. +- `POST /items`: Create item. +- `GET /items/:id`: Get specific item. +- `PUT /items/:id`: Replace item. +- `PATCH /items/:id`: Update item partially. +- `DELETE /items/:id`: Remove item. + +## Documentation + +- Self-document using Swagger/OpenAPI or TSON (Type-Safe Object Notation). +- Include examples for request bodies and response schemas. diff --git a/.agent/Backend/Authentication_Strategies.md b/.agent/Backend/Authentication_Strategies.md new file mode 100644 index 0000000000000000000000000000000000000000..dabf551c0f64beeeac18fcabadda188c3ddc8752 --- /dev/null +++ b/.agent/Backend/Authentication_Strategies.md @@ -0,0 +1,21 @@ +# Authentication Strategies + +## 1. JWT (JSON Web Tokens) + +Stateless authentication using tokens. Good for scalability, but requires careful management of token expiration and revocation. + +## 2. Session-Based + +Traditional stateful authentication where the server stores session data and the client holds a session ID in a cookie. + +## 3. OAuth2 / OpenID Connect + +Standards for delegated authorization and authentication, often used for third-party logins (e.g., Google, GitHub). + +## 4. Multi-Factor Authentication (MFA) + +Enhance security by requiring more than one form of verification. + +## 5. Password Hashing + +Always use strong, salted hashing algorithms like Argon2 or BCrypt to store passwords. diff --git a/.agent/Backend/CachingStrategies.md b/.agent/Backend/CachingStrategies.md new file mode 100644 index 0000000000000000000000000000000000000000..fb98b1313c10c768d8969be5b6d6fd0e1cc148c5 --- /dev/null +++ b/.agent/Backend/CachingStrategies.md @@ -0,0 +1,23 @@ +# Caching Strategies for Backend Performance + +## 1. Identifying Cacheable Data + +Cache data that is frequently accessed and relatively static, such as configuration settings or product catalogs. + +## 2. Cache Levels + +Implement caching at multiple levels, including in-memory (e.g., Redis, Memcached) and at the application level. + +## 3. Cache Invalidation + +Establish clear strategies for invalidating the cache when the underlying data changes to ensure consistency. + +## 4. Cache Expiration (TTL) + +Set appropriate Time-To-Live (TTL) values for cached data to prevent it from becoming stale. + +## 5. Monitoring + +Monitor cache hit rates and performance to ensure your caching strategy is effective. + +Jonas diff --git a/.agent/Backend/DatabaseNormalization.md b/.agent/Backend/DatabaseNormalization.md new file mode 100644 index 0000000000000000000000000000000000000000..69595e7d3cad97a0d719f6bc990a8023ebbae9bc --- /dev/null +++ b/.agent/Backend/DatabaseNormalization.md @@ -0,0 +1,25 @@ +# Database Normalization Principles + +## 1. Goal + +The primary goal of database normalization is to reduce data redundancy and improve data integrity by organizing data into related tables. + +## 2. Normal Forms (1NF, 2NF, 3NF) + +Understand and apply the different normal forms to ensure your database design is efficient and robust. + +## 3. Benefits + +- Reduced data anomalies. +- Improved data consistency. +- More efficient data storage and retrieval. + +## 4. Trade-offs + +In some cases, over-normalization can lead to complex queries and performance issues. Be mindful of the trade-offs. + +## 5. Practical Application + +Apply normalization principles during the database design phase to create a solid foundation for your application. + +Jonas diff --git a/.agent/Backend/DatabaseOptimization.md b/.agent/Backend/DatabaseOptimization.md new file mode 100644 index 0000000000000000000000000000000000000000..3bff112c3dd4246d52881ea0942bd4c5aac33a84 --- /dev/null +++ b/.agent/Backend/DatabaseOptimization.md @@ -0,0 +1,19 @@ +# Database Optimization & Schema Design + +## Schema Rules + +1. **Normalization**: Aim for 3NF to reduce redundancy. +2. **Indexing**: Index frequently queried columns (IDs, Slugs, Timestamps). Avoid over-indexing. +3. **Foreign Keys**: Enforce data integrity with proper relational constraints. + +## Query Performance + +- **Selectivity**: Only SELECT the columns you need. Avoid `SELECT *`. +- **Joins**: Optimize JOIN operations; avoid N+1 query problems using eager loading. +- **Pagination**: Use cursor-based pagination for large datasets. + +## Migration Standards + +- Use a robust migration tool (e.g., Prisma, Knex). +- Never modify existing migrations; always create a new one. +- Migration scripts must be reversible (Up/Down). diff --git a/.agent/Backend/DatabaseScalability.md b/.agent/Backend/DatabaseScalability.md new file mode 100644 index 0000000000000000000000000000000000000000..1aff4c102752ed1545b7778c91aac34d59c0ee7b --- /dev/null +++ b/.agent/Backend/DatabaseScalability.md @@ -0,0 +1,22 @@ +# Database Scalability: Sharding & Replication + +## 1. Read Replication + +- Direct read queries to "read-only" replicas to scale out read heavy workloads. +- The master node handles all writes. + +## 2. Horizontal Sharding + +- Splitting data across multiple database instances based on a shard key (e.g., `user_id`, `region`). + +## 3. Data Consistency + +- Understand the trade-offs between strictly consistent systems and eventually consistent systems. + +## 4. Failover & High Availability + +- Configure automatic failover to a standby node if the master node goes down. + +## 5. Partitioning + +- Use database-level partitioning to split large tables into smaller, more manageable pieces within a single instance. diff --git a/.agent/Backend/DenoRuntime.md b/.agent/Backend/DenoRuntime.md new file mode 100644 index 0000000000000000000000000000000000000000..737efe740085c51f5a9554ca71e1188d7e43620d --- /dev/null +++ b/.agent/Backend/DenoRuntime.md @@ -0,0 +1,21 @@ +# Deno Runtime vs Node.js + +## 1. Security by Default + +- No file, network, or environment access unless explicitly enabled via flags (e.g., `--allow-net`). + +## 2. TypeScript Support + +- Deno has built-in TypeScript support; no separate compiler or config needed. + +## 3. Standard Library + +- Comprehensive, audited standard library following Go's model. + +## 4. ES Modules Only + +- No `require()`; all modules must be imported via URLs or file paths. + +## 5. Built-in Utilities + +- Includes a test runner, linter, and formatter out of the box. diff --git a/.agent/Backend/Microservices_Communication.md b/.agent/Backend/Microservices_Communication.md new file mode 100644 index 0000000000000000000000000000000000000000..8a47027dbf0a33a4030752cbea6e080981aba922 --- /dev/null +++ b/.agent/Backend/Microservices_Communication.md @@ -0,0 +1,21 @@ +# Microservices Communication + +## 1. Synchronous vs Asynchronous + +Understand when to use REST/gRPC (synchronous) versus Message Queues/Pub-Sub (asynchronous). + +## 2. API Gateways + +Use an API gateway to centralize authentication, rate limiting, and request routing. + +## 3. Service Discovery + +Implement a mechanism for services to find each other dynamically (e.g., Consul, Kubernetes DNS). + +## 4. Resiliency + +Use patterns like circuit breakers and retries to handle transient failures between services. + +## 5. Message Serialization + +Choose efficient serialization formats like Protocol Buffers (protobuf) for inter-service communication. diff --git a/.agent/Backend/Nodejs_Cluster.md b/.agent/Backend/Nodejs_Cluster.md new file mode 100644 index 0000000000000000000000000000000000000000..fd399efbeaa3eb38d6c669340abf40e47620c8a0 --- /dev/null +++ b/.agent/Backend/Nodejs_Cluster.md @@ -0,0 +1,21 @@ +# Node.js Cluster Module & Multi-threading + +## 1. Scaling to Multiple Cores + +- Since Node.js is single-threaded, use the `cluster` module to spawn worker processes that share the same server port. + +## 2. Master & Worker Processes + +- The Master process manages workers and distributes incoming connections. + +## 3. IPC (Inter-Process Communication) + +- Use `process.send()` and `on('message')` to communicate between master and worker processes. + +## 4. Zero-Downtime Reloads + +- Restart workers one by one when releasing new code to ensure the server stays active. + +## 5. PM2 Process Manager + +- In most production environments, prefer an external manager like **PM2** which handles clustering and automatic restarts out of the box. diff --git a/.agent/Backend/ORM_BestPractices.md b/.agent/Backend/ORM_BestPractices.md new file mode 100644 index 0000000000000000000000000000000000000000..197753404a449c163f98bbfc8b3544aa501fd136 --- /dev/null +++ b/.agent/Backend/ORM_BestPractices.md @@ -0,0 +1,21 @@ +# ORM Best Practices (Prisma/Drizzle) + +## 1. Type Safety + +- Leverage auto-generated types from your schema to ensure end-to-end type safety. + +## 2. Efficient Joins + +- Use `include` or `select` (Prisma) or explicit joins (Drizzle) to fetch exactly what you need. Avoid over-fetching. + +## 3. Batching & Transaction + +- Use `$transaction` to ensure atomic operations (either all succeed or all fail). + +## 4. Middleware/Hooks + +- Use ORM-level middleware for cross-cutting concerns like logging or soft deletes (marking as deleted instead of removing). + +## 5. Connection Pooling + +- Use tools like `accelerate` (Prisma) or standard pg-pool to manage database connections in serverless environments. diff --git a/.agent/Backend/PubSub_Architecture.md b/.agent/Backend/PubSub_Architecture.md new file mode 100644 index 0000000000000000000000000000000000000000..25129488e919135f032c032ae557d920e170986c --- /dev/null +++ b/.agent/Backend/PubSub_Architecture.md @@ -0,0 +1,21 @@ +# Pub-Sub Architecture & Message Queues + +## 1. Decoupling Services + +- Use Publisher-Subscriber patterns to allow services to communicate without knowing about each other. + +## 2. Message Brokers + +- Use Redis (BullMQ), RabbitMQ, or Amazon SQS for reliable message delivery. + +## 3. Idempotency + +- Ensure that message consumers can handle the same message multiple times without unintended side effects. + +## 4. Retries & Dead Letter Queues (DLQ) + +- Automatically retry failed tasks. Move persistently failing tasks to a DLQ for manual inspection. + +## 5. Event-Driven Workflows + +- Trigger long-running tasks (e.g., report generation, bulk email) asynchronously via the queue. diff --git a/.agent/Backend/ServerActions.md b/.agent/Backend/ServerActions.md new file mode 100644 index 0000000000000000000000000000000000000000..07817b269453f84590680394982ae065f0a468d9 --- /dev/null +++ b/.agent/Backend/ServerActions.md @@ -0,0 +1,14 @@ +# Backend & Server Actions + +## Server Actions (`lib/actions`) + +1. **Async Synchronization**: Use `Promise.all` for parallel tasks and proper `await` for sequential ones. +2. **Validation**: Validate all inputs using `Zod` before processing. +3. **Error Handling**: Use try-catch blocks and return structured error objects. +4. **Security**: Ensure user session/permission check at the start of every action. + +## API Integration + +- Keep server-side logic strictly in `lib/actions`. +- Avoid leaking database schemas to the client; use DTOs (Data Transfer Objects). +- Implement rate limiting and input sanitization. diff --git a/.agent/Backend/WebSockets.md b/.agent/Backend/WebSockets.md new file mode 100644 index 0000000000000000000000000000000000000000..adfaec41a555e69ac983a6ad16e60374d8915da6 --- /dev/null +++ b/.agent/Backend/WebSockets.md @@ -0,0 +1,13 @@ +# WebSockets & Real-time Communication + +## Logic Standards + +1. **Connection Management**: Implement robust auto-reconnect logic with exponential backoff. +2. **Event Naming**: Use a clear `namespace:action` format (e.g., `billing:invoice_updated`). +3. **Security**: Authenticate the socket connection using JWT or session tokens during the handshake. + +## Performance + +- **Throttling**: Limit the rate of outgoing messages for high-frequency data (e.g., mouse positions, typing). +- **Binary Format**: Use `Protocol Buffers` or `MsgPack` for large binary data to reduce payload size. +- **Heartbeats**: Send regular ping/pong messages to keep the connection alive and detect dead links. diff --git a/.agent/Design/Animations.md b/.agent/Design/Animations.md new file mode 100644 index 0000000000000000000000000000000000000000..9d361c1c723a051961c6c2a0b05f093b6b7653fc --- /dev/null +++ b/.agent/Design/Animations.md @@ -0,0 +1,14 @@ +# Animation Principles & Motion + +## Core Rules + +1. **Purpose-Driven**: Every animation must serve a purpose (e.g., feedback, hierarchy, entry). +2. **Smooth Transitions**: Standard durations: `200ms` for small transitions, `400-600ms` for larger layout shifts. +3. **Easing**: Use `cubic-bezier(0.4, 0, 0.2, 1)` for natural motion. Avoid linear timing. +4. **Staggering**: Stagger entry of list items to create a flow effect. + +## Library Standards + +- **Framer Motion**: Default for complex UI transitions. +- **GSAP**: Use for high-performance timeline-based animations. +- **CSS Transitions**: Preferred for simple hover and focus states. diff --git a/.agent/Design/ColorPalettes.md b/.agent/Design/ColorPalettes.md new file mode 100644 index 0000000000000000000000000000000000000000..4b42ee0d5b8b548eadade25b1ea3e0e44c3e9e49 --- /dev/null +++ b/.agent/Design/ColorPalettes.md @@ -0,0 +1,21 @@ +# Color Palettes for Modern UI + +## 1. Sourcing Inspiration + +Draw inspiration for your color palettes from nature, art, and successful designs. + +## 2. Creating a Palette + +Create a cohesive color palette with a primary color, secondary colors, and accent colors. + +## 3. Contrast and Accessibility + +Ensure your color palette provides sufficient contrast for readability and accessibility. + +## 4. Consistency + +Use your color palette consistently throughout your application to create a sense of unity. + +## 5. Testing + +Test your color palette with real users to ensure it's visually appealing and easy to use. diff --git a/.agent/Design/ColorTheory.md b/.agent/Design/ColorTheory.md new file mode 100644 index 0000000000000000000000000000000000000000..dbd4c217fec5fc5ecb5656cb505c781cb6f36356 --- /dev/null +++ b/.agent/Design/ColorTheory.md @@ -0,0 +1,25 @@ +# Color Theory for Premium UI + +## 1. Visual Hierarchy + +- Use bold, varied colors for primary actions and soft, neutral colors for background elements. +- **60-30-10 Rule**: 60% dominant (neutral), 30% secondary, 10% accent (bright/vibrant). + +## 2. HSL over HEX + +- Prefer HSL (Hue, Saturation, Lightness) for easier adjustments and programmatic color generation. +- **Consistent Tonal Variations**: Build a palette based on different lightness levels of a few core hues. + +## 3. Contrast & Accessibility (WCAG) + +- Ensure a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. +- Use tools like Lighthouse to verify accessibility. + +## 4. Semantic Colors + +- Define standardized names: `primary`, `secondary`, `success`, `warning`, `danger`, `info`. + +## 5. Dark Mode Strategies + +- Use deep grays/navy instead of pure black (`#000000`) for backgrounds to reduce eye strain. +- Invert lightness while maintaining hue and saturation for icons and accents. diff --git a/.agent/Design/DesignSystem.md b/.agent/Design/DesignSystem.md new file mode 100644 index 0000000000000000000000000000000000000000..58994fe5f9330764b11c04470036d9ab9f243ff5 --- /dev/null +++ b/.agent/Design/DesignSystem.md @@ -0,0 +1,18 @@ +# Design System & Aesthetics + +## Visual Identity + +- **Glassmorphism**: Use subtle backgrounds with `backdrop-filter: blur()`, thin borders, and low-opacity fills. +- **Color Palette**: Curated HSL colors. Avoid generic primary colors. Use deep bolds and soft pastels. +- **Typography**: Primary: `Inter` or `Outfit`. Secondary: `Roboto Mono` for data. + +## UI Components + +- **Buttons**: Smooth transitions, hover scaling (1.02x), and active state indentation. +- **Inputs**: Clear focus rings, animated labels, and inline validation. +- **Micro-animations**: Use GSAP or Framer Motion for subtle entry/exit effects. + +## Layout + +- Grid/Flexbox for responsiveness. +- Consistent spacing (8px base unit). diff --git a/.agent/Design/Glassmorphism.md b/.agent/Design/Glassmorphism.md new file mode 100644 index 0000000000000000000000000000000000000000..8d1550f17b37bad03746f7c0e6c45a3428047868 --- /dev/null +++ b/.agent/Design/Glassmorphism.md @@ -0,0 +1,21 @@ +# Glassmorphism & Modern UI Style + +## Visual Standards + +1. **Backgrounds**: Transparent, high-blur (10-20px) layers with a white or tinted overlay (10-20% opacity). +2. **Borders**: Thin (0.5-1px), semi-transparent borders to define edges. +3. **Lighting**: Subtle inner shadows or gradients to simulate 3D depth and light source. +4. **Saturation**: Boost the background saturation behind blurred elements for a premium feel. + +## Implementation (CSS) + +```css +.glass-container { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); +} +``` diff --git a/.agent/Design/Glassmorphism_Advanced.md b/.agent/Design/Glassmorphism_Advanced.md new file mode 100644 index 0000000000000000000000000000000000000000..5f6f2d59798b2426b4ccf150b84806acfd4ed6ac --- /dev/null +++ b/.agent/Design/Glassmorphism_Advanced.md @@ -0,0 +1,21 @@ +# Advanced Glassmorphism Techniques + +## 1. Layered Blurs + +- Use multiple stacked containers with varying `backdrop-filter: blur()` and boarder opacities to simulate depth. + +## 2. Noise & Texture + +- Add a subtle noise texture (`svg` or `png`) as a low-opacity background layer to give the glass a material feel. + +## 3. Dynamic Refraction + +- Use subtle gradients that move with the mouse cursor to simulate light refraction on a glass surface. + +## 4. Inner Glow (Inner Shadow) + +- Use `box-shadow: inset ...` to highlight edges and make the glass element "pop" from its background. + +## 5. Saturation Boost + +- Use `backdrop-filter: blur(12px) saturate(180%)` to make the colors behind the glass look more vibrant and expensive. diff --git a/.agent/Design/Layout_Grid.md b/.agent/Design/Layout_Grid.md new file mode 100644 index 0000000000000000000000000000000000000000..def1b55b11797659d14a2fbcfb2d579a89c9d0d7 --- /dev/null +++ b/.agent/Design/Layout_Grid.md @@ -0,0 +1,21 @@ +# Layout Grids in UI Design + +## 1. Importance + +Layout grids provide a framework for organizing content and creating a consistent visual hierarchy. + +## 2. Common Grid Systems + +Common grid systems include the 12-column grid and the modular grid. + +## 3. Responsive Design + +Use responsive grids that adapt to different screen sizes and orientations. + +## 4. Consistency + +Use a consistent grid system throughout your application to create a sense of order. + +## 5. Breaking the Grid + +Don't be afraid to occasionally break the grid for visual interest or to highlight important content. diff --git a/.agent/Design/Layout_Pillars.md b/.agent/Design/Layout_Pillars.md new file mode 100644 index 0000000000000000000000000000000000000000..a261f67ec013e0f2609786fcc9e9ee30561b7da9 --- /dev/null +++ b/.agent/Design/Layout_Pillars.md @@ -0,0 +1,27 @@ +# Holy Grail Layout & Modern Grids + +## 1. The Holy Grail Layout + +- Header, Main Content (with sidebars), and Footer. +- Use CSS Grid for the most robust implementation. + +## 2. Flexbox for Micro-Layouts + +- Perfect for navbars, card headers, and button groups. + +## 3. Subgrid & Nested Grids + +- Use `grid-template-columns: subgrid` to align nested elements with the parent grid. + +## 4. Aspect Ratio Control + +- Use `aspect-ratio` property to reserve space for images and videos, preventing Layout Shift (CLS). + +## 5. Container Queries + +- Favor `@container` over `@media` for components that need to respond to their parent container's size rather than the viewport. + +## 6. Sticky & Overlay Elements + +- Use `position: sticky` for persistent navs and sidebars. +- Implementation of glassmorphism on sticky headers for a premium floating feel. diff --git a/.agent/Design/MicroAnimations.md b/.agent/Design/MicroAnimations.md new file mode 100644 index 0000000000000000000000000000000000000000..e3f6bf067449ab3d189358bc75548df268a80d3d --- /dev/null +++ b/.agent/Design/MicroAnimations.md @@ -0,0 +1,21 @@ +# Micro-Animations in UI Design + +## 1. Purpose + +Micro-animations are small, subtle animations that provide feedback and enhance the user experience. + +## 2. Feedback + +Use micro-animations to provide feedback when a user interacts with an element, such as a button click or form submission. + +## 3. Guiding the User + +Use micro-animations to guide the user's attention to important parts of the interface. + +## 4. Transitions + +Use smooth transitions to make the interface feel more fluid and natural. + +## 5. Overusing + +Avoid overusing micro-animations, as they can become distracting and even frustrating for users. diff --git a/.agent/Design/Typography_Advanced.md b/.agent/Design/Typography_Advanced.md new file mode 100644 index 0000000000000000000000000000000000000000..6887387770e80353edd32051918ca36ef0e71914 --- /dev/null +++ b/.agent/Design/Typography_Advanced.md @@ -0,0 +1,25 @@ +# Advanced Typography Standards + +## 1. Typographic Scale + +- Use a consistent ratio (e.g., 1.25 Modular Scale) to define sizes for `h1` through `h6`. +- Root size: `16px` (1rem). + +## 2. Line Heights & Spacing + +- Body text: `1.5` to `1.6` for optimal readability. +- Headings: `1.1` to `1.3` for a compact, authoritative look. +- Letter spacing: `-0.01em` to `-0.02em` for headings to increase "tightness." + +## 3. Font Loading & Performance + +- Use `font-display: swap` to prevent FOUT (Flash of Unstyled Text). +- Prefer variable fonts to reduce bundle size if multiple weights are needed. + +## 4. Optical Sizing + +- Enable `font-optical-sizing: auto` when available for better legibility at small sizes. + +## 5. System Fonts Fallback + +- Always include a reliable system font stack as a fallback (e.g., `-apple-system, BlinkMacSystemFont, "Segoe UI", ...`). diff --git a/.agent/Design/Typography_Scales.md b/.agent/Design/Typography_Scales.md new file mode 100644 index 0000000000000000000000000000000000000000..7bb569e0a3a5df950bbae33173dbe5daf6b78578 --- /dev/null +++ b/.agent/Design/Typography_Scales.md @@ -0,0 +1,21 @@ +# Typography Scales in UI Design + +## 1. Definition + +A typography scale is a set of predefined font sizes used consistently throughout a design. + +## 2. Creating a Scale + +Create a typography scale with sizes for headings, body text, and captions. + +## 3. Consistency + +Use your typography scale consistently throughout your application to create a sense of hierarchy and rhythm. + +## 4. Legibility + +Ensure your typography scale provides sufficient legibility for all users. + +## 5. Testing + +Test your typography scale with real users to ensure it's easy to read and provides clear visual hierarchy. diff --git a/.agent/DevOps/CiCd.md b/.agent/DevOps/CiCd.md new file mode 100644 index 0000000000000000000000000000000000000000..b0eb1c69bcb55fb43ed3e7cb30da6db025ccd20d --- /dev/null +++ b/.agent/DevOps/CiCd.md @@ -0,0 +1,20 @@ +# DevOps & CI/CD Pipelines + +## Core Principles + +1. **Automate Everything**: From testing to deployments. +2. **Shift Left**: Catch bugs as early as possible (Git hooks). +3. **Immutable Infrastructure**: Changes should be applied through code, not manual tweaks. + +## GitHub Actions Standards + +- **Pull Request**: Trigger lint, typecheck, and unit tests. +- **Merge to Main**: Trigger build and staging deployment. +- **Tag/Release**: Trigger production deployment. +- **Fail Fast**: Cancel previous runs if a new commit is pushed. + +## Secret Management + +- Never commit secrets to the repository. +- Use GitHub Secrets or AWS Secrets Manager. +- Use `.env.template` for developer onboarding. diff --git a/.agent/DevOps/ContinuousDeployment.md b/.agent/DevOps/ContinuousDeployment.md new file mode 100644 index 0000000000000000000000000000000000000000..ed566344abe8e8b59fa8bef76b534f19ef70165d --- /dev/null +++ b/.agent/DevOps/ContinuousDeployment.md @@ -0,0 +1,25 @@ +# Continuous Deployment (CD) + +## 1. Definition + +Continuous Deployment is the practice of automatically deploying code changes to production after they have passed a suite of automated tests. + +## 2. Benefits + +- Faster delivery of new features and fixes. +- Reduced risk of human error during deployment. +- Improved collaboration and communication. + +## 3. Automation + +Automate your entire deployment process, from building and testing to deploying and monitoring. + +## 4. Rollback + +Establish a mechanism for automatically rolling back a deployment if it fails. + +## 5. Monitoring + +Continuously monitor your production environment for potential issues after a deployment. + +Jonas diff --git a/.agent/DevOps/DockerBestPractices.md b/.agent/DevOps/DockerBestPractices.md new file mode 100644 index 0000000000000000000000000000000000000000..f0583b334a52a2e62e767b7e52b14e3c664a3c9a --- /dev/null +++ b/.agent/DevOps/DockerBestPractices.md @@ -0,0 +1,21 @@ +# Docker Best Practices for Production + +## 1. Use Small Base Images + +- Prefer `alpine` or `distroless` images to reduce attack surface and build time. + +## 2. Multi-Stage Builds + +- Separate your build environment from your runtime environment to keep final images small. + +## 3. Don't Run as Root + +- Use the `USER` instruction to run your application as a non-privileged user. + +## 4. Use .dockerignore + +- Exclude `node_modules`, logs, and local configuration files from the build context. + +## 5. Layer Caching + +- Order your Dockerfile instructions from least-to-most frequently changed to optimize build caching. diff --git a/.agent/DevOps/IaC.md b/.agent/DevOps/IaC.md new file mode 100644 index 0000000000000000000000000000000000000000..e62ad6d6661ac0673e2b88d9a9dacda1e36aac8c --- /dev/null +++ b/.agent/DevOps/IaC.md @@ -0,0 +1,13 @@ +# Infrastructure as Code (IaC) + +## Standards + +1. **Declarative**: Use Terraform or AWS CloudFormation to describe your infrastructure. +2. **Version Control**: Infrastructure code must reside in the same repo as the application. +3. **Modularity**: Create reusable modules for VPCs, S3 buckets, and RDS instances. + +## Cloud Strategy + +- **AWS/Google Cloud**: Scale based on traffic. +- **Edge Computing**: Use Cloudflare Workers for caching and security at the edge. +- **Serverless**: Prefer serverless functions for cost efficiency on low-traffic modules. diff --git a/.agent/DevOps/InfrastructureAsCode.md b/.agent/DevOps/InfrastructureAsCode.md new file mode 100644 index 0000000000000000000000000000000000000000..ba71fe93f0825cc8c6519d690d1ff05e081c130e --- /dev/null +++ b/.agent/DevOps/InfrastructureAsCode.md @@ -0,0 +1,25 @@ +# Infrastructure as Code (IaC) + +## 1. Definition + +IaC is the process of managing and provisioning your infrastructure using machine-readable definition files, rather than manual configuration. + +## 2. Benefits + +- Increased efficiency and speed. +- Improved consistency and reliability. +- Better collaboration and version control. + +## 3. Tooling + +Use tools like Terraform, Ansible, or CloudFormation to define and manage your infrastructure as code. + +## 4. Version Control + +Store your IaC files in a version control system like Git to track changes and collaborate with others. + +## 5. Testing + +Test your IaC code to ensure it correctly provisions your infrastructure as expected. + +Jonas diff --git a/.agent/DevOps/Kubernetes.md b/.agent/DevOps/Kubernetes.md new file mode 100644 index 0000000000000000000000000000000000000000..f6219d3434b02df10dc93a689acc11d132ab6c9e --- /dev/null +++ b/.agent/DevOps/Kubernetes.md @@ -0,0 +1,23 @@ +# Kubernetes Orchestration Basics + +## 1. Pods & Services + +- Understand that Pods are logically grouped containers. +- Use Services to provide a stable IP/DNS name for a group of Pods. + +## 2. Deployments & Rollouts + +- Use Deployments to manage stateless applications. +- Implement Rolling Updates to replace old pods with new ones without downtime. + +## 3. ConfigMaps & Secrets + +- Store configuration and sensitive data externally from the pod image. + +## 4. Ingress Controllers + +- Use Ingress (like Nginx) to route external HTTP/S traffic to internal services. + +## 5. Autoscaling (HPA) + +- Automatically scale the number of pods based on CPU or memory usage. diff --git a/.agent/DevOps/Logging_Monitoring.md b/.agent/DevOps/Logging_Monitoring.md new file mode 100644 index 0000000000000000000000000000000000000000..99d01c62f232d13a24a5f220214dfb0d0abc905d --- /dev/null +++ b/.agent/DevOps/Logging_Monitoring.md @@ -0,0 +1,22 @@ +# Logging & Monitoring: Prometheus & Grafana + +## 1. Structured Logging + +- Log in JSON format to make it easy for log collectors (like Fluentd, ELK) to parse and index. + +## 2. Redaction of PII + +- Automatically strip sensitive info (email, phone, credit card) from logs. + +## 3. Correlation IDs + +- Append a unique Request-ID to every log message in a single request flow to trace it across services. + +## 4. Key Metrics + +- Track the "Four Golden Signals": Latency, Traffic, Errors, and Saturation. + +## 5. Dashboards and Alerts + +- Use Grafana to visualize metrics. +- Set up alerts for high error rates or unusual latency spikes. diff --git a/.agent/DevOps/MonitoringPerformance.md b/.agent/DevOps/MonitoringPerformance.md new file mode 100644 index 0000000000000000000000000000000000000000..27c7c73d015ca766402339b2009d65a79579cf6e --- /dev/null +++ b/.agent/DevOps/MonitoringPerformance.md @@ -0,0 +1,23 @@ +# Monitoring Performance in Production + +## 1. Key Performance Indicators (KPIs) + +Identify the key performance indicators for your application, such as response time, error rate, and throughput. + +## 2. Real-Time Monitoring + +Monitor your application in real-time to detect performance issues as they happen. + +## 3. Alerting and Notification + +Set up alerts for when KPIs exceed predefined thresholds. + +## 4. Performance Analysis + +Analyze your performance data to identify trends and potential optimizations. + +## 5. Capacity Planning + +Use your performance data to plan for future growth and ensure your infrastructure can handle it. + +Jonas diff --git a/.agent/DevOps/Monitoring_Alerting.md b/.agent/DevOps/Monitoring_Alerting.md new file mode 100644 index 0000000000000000000000000000000000000000..fce2f304f29df3d86d113f49e8903042221c9340 --- /dev/null +++ b/.agent/DevOps/Monitoring_Alerting.md @@ -0,0 +1,21 @@ +# Monitoring and Alerting + +## 1. Purpose + +Monitoring provides visibility into the health and performance of your application, while alerting notifies you of critical issues in real-time. + +## 2. Key Metrics to Monitor + +Monitor vital signs like CPU usage, memory consumption, request latency, and error rates. + +## 3. Setting Up Alerts + +Define threshold-based alerts for critical metrics and configure notifications through channels like email, Slack, or PagerDuty. + +## 4. Log Aggregation + +Centralize logs from all your services into a single searchable location (e.g., ELK stack, Datadog, or CloudWatch Logs). + +## 5. Automated Response + +Consider implementing automated responses to certain alerts, such as auto-scaling resources or restarting services. diff --git a/.agent/Engineering/API_First_Development.md b/.agent/Engineering/API_First_Development.md new file mode 100644 index 0000000000000000000000000000000000000000..a886edb51f96bf5352d208bd87766217ef1d6f47 --- /dev/null +++ b/.agent/Engineering/API_First_Development.md @@ -0,0 +1,23 @@ +# API-First Development + +## 1. Principle + +Design the API first, before any implementation. This ensures the API meets the needs of its consumers and provides a stable contract. + +## 2. Benefits + +- Improved developer experience for API consumers. +- Reduced development time through parallel frontend and backend development. +- Higher quality and better-designed APIs. + +## 3. Tooling + +Use tools like Swagger or Stoplight to design and document your APIs using the OpenAPI Specification. + +## 4. Collaborative Design + +Involve stakeholders and API consumers in the design process to ensure the API meets their needs. + +## 5. Implementation + +Once the API is designed, implement the backend and frontend in parallel, using the API contract as a guide. diff --git a/.agent/Engineering/BigO_Notation.md b/.agent/Engineering/BigO_Notation.md new file mode 100644 index 0000000000000000000000000000000000000000..593159628947b66a3dfc202e60a72bedbc750795 --- /dev/null +++ b/.agent/Engineering/BigO_Notation.md @@ -0,0 +1,25 @@ +# Big O Notation & Algorithmic Complexity + +## 1. O(1) - Constant Time + +- Execution time is independent of the input size (e.g., accessing an array element by index). + +## 2. O(log n) - Logarithmic Time + +- Execution time grows logarithmically with input size (e.g., Binary Search). + +## 3. O(n) - Linear Time + +- Execution time grows linearly with input size (e.g., iterating through a list). + +## 4. O(n log n) - Linearithmic Time + +- Common in efficient sorting algorithms like Merge Sort and Quick Sort. + +## 5. O(n²) - Quadratic Time + +- Execution time is proportional to the square of input size (e.g., nested loops). + +## 6. O(2ⁿ) - Exponential Time + +- Execution time doubles with each addition to the input (e.g., recursive Fibonacci). diff --git a/.agent/Engineering/CI_CD_Philosophy.md b/.agent/Engineering/CI_CD_Philosophy.md new file mode 100644 index 0000000000000000000000000000000000000000..948413af6a358acdd4f4121f60d0b0704d8c338d --- /dev/null +++ b/.agent/Engineering/CI_CD_Philosophy.md @@ -0,0 +1,21 @@ +# CI/CD Philosophy + +## 1. Automation + +Automate every aspect of the software delivery process, from testing to deployment. + +## 2. Small, Frequent Updates + +Ship smaller changes more frequently to reduce risk and get faster feedback. + +## 3. Continuous Feedback + +Incorporate automated tests and quality checks at every step to catch issues early. + +## 4. Reproducibility + +Ensure that every build and deployment is reproducible, with clear versioning and configuration management. + +## 5. Deployment Pipelines + +Define clear pipelines for moving code from development to production, with appropriate manual approvals if necessary. diff --git a/.agent/Engineering/CleanCode.md b/.agent/Engineering/CleanCode.md new file mode 100644 index 0000000000000000000000000000000000000000..4227550d65a0e8596e2bd0732a146f3fef23390e --- /dev/null +++ b/.agent/Engineering/CleanCode.md @@ -0,0 +1,20 @@ +# Clean Code Standards + +## Core Principles + +1. **Readable > Clever**: Code should be self-documenting. +2. **DRY (Don't Repeat Yourself)**: Abstract logic into reusable hooks or utilities. +3. **KISS (Keep It Simple, Stupid)**: Avoid over-engineering. +4. **Single Responsibility**: Each function/component should do ONE thing well. + +## Naming Conventions + +- **Files**: `PascalCase` for components, `camelCase` for hooks/utils. +- **Variables**: Descriptive `camelCase`. +- **Booleans**: Prefix with `is`, `has`, `should`. + +## Formatting + +- Use Prettier/ESLint configs. +- Limit line length to 80-100 characters. +- Consistent indentation (2 spaces). diff --git a/.agent/Engineering/CodeReviewGuidelines.md b/.agent/Engineering/CodeReviewGuidelines.md new file mode 100644 index 0000000000000000000000000000000000000000..fb77422c06f55ecdef935b08427a5ac10232ed0c --- /dev/null +++ b/.agent/Engineering/CodeReviewGuidelines.md @@ -0,0 +1,28 @@ +# Code Review Guidelines + +## 1. Purpose + +Code reviews are a collaborative process intended to improve code quality, share knowledge, and ensure consistency. + +## 2. Review Checklist + +- **Functionality**: Does the code do what it's supposed to do? +- **Readability**: Is the code easy to understand and maintain? +- **Performance**: Are there any potential performance issues? +- **Security**: Are there any security vulnerabilities? +- **Testing**: Is the code properly tested? + +## 3. Best Practices + +- Keep reviews small and focused. +- Provide constructive feedback. +- Be respectful and professional. +- Use tools to automate parts of the review process. + +## 4. Code Ownership + +Everyone on the team is responsible for the quality of the codebase. + +## 5. Continuous Improvement + +Use code reviews as an opportunity to learn and improve your coding skills. diff --git a/.agent/Engineering/Concurrency_Patterns.md b/.agent/Engineering/Concurrency_Patterns.md new file mode 100644 index 0000000000000000000000000000000000000000..cf5a7afa6855fe668b013b284cea63c6b21207f0 --- /dev/null +++ b/.agent/Engineering/Concurrency_Patterns.md @@ -0,0 +1,27 @@ +# Concurrency Patterns & Async Flow + +## 1. Single Threaded Event Loop + +- Understand how the Node.js/Browser event loop handles asynchronous operations using the task and microtask queues. + +## 2. Promises & Async/Await + +- Use for multi-step asynchronous processes. +- Handle multiple promises with `Promise.all` (parallel), `Promise.allSettled` (robust), and `Promise.race` (timeout). + +## 3. Web Workers + +- Use to run CPU-intensive tasks in a background thread to prevent blocking the UI/main thread. + +## 4. Generators & Iterators + +- Use for custom iteration behavior and lazy data streams. + +## 5. Streams (Node.js) + +- Use for processing large datasets chunk by chunk rather than loading everything into memory. +- `Readable`, `Writable`, and `Transform` streams. + +## 6. Sagas & Side Effect Management + +- In large applications, use structured side effect management (e.g., Redux-Saga or custom event-based handlers). diff --git a/.agent/Engineering/Constants.md b/.agent/Engineering/Constants.md new file mode 100644 index 0000000000000000000000000000000000000000..99ff0c914243b232637e8abe52996a2e65ae5501 --- /dev/null +++ b/.agent/Engineering/Constants.md @@ -0,0 +1,23 @@ +# Single Source of Truth: Constants & Configuration + +## Rules + +1. **Centralization**: All non-changeable text, magic numbers, and config must reside in `constants/` or `.env`. +2. **PascalCase for Constants**: Use `UPPER_SNAKE_CASE` for global constants. +3. **No Hardcoded Strings**: Even UI labels should be pulled from a constants file or i18n store. + +## Implementation + +```typescript +// constants/billing.ts +export const INVOICE_STATUS = { + PAID: 'paid', + PENDING: 'pending', + OVERDUE: 'overdue', +} as const; + +export const TAX_RATES = { + GST: 0.18, + VAT: 0.05, +} as const; +``` diff --git a/.agent/Engineering/DRY_KISS_YAGNI.md b/.agent/Engineering/DRY_KISS_YAGNI.md new file mode 100644 index 0000000000000000000000000000000000000000..a7537673223d69f24b46bbec55f88058e9c1a10d --- /dev/null +++ b/.agent/Engineering/DRY_KISS_YAGNI.md @@ -0,0 +1,16 @@ +# DRY, KISS, and YAGNI + +## DRY (Don't Repeat Yourself) + +- Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. +- Avoid copy-pasting code; use functions, hooks, or shared components. + +## KISS (Keep It Simple, Stupid) + +- Most systems work best if they are kept simple rather than made complicated. +- Avoid over-engineering and unnecessary abstractions. + +## YAGNI (You Ain't Gonna Need It) + +- Always implement things when you actually need them, never when you just foresee that you need them. +- Avoid "just-in-case" coding features that add complexity without immediate value. diff --git a/.agent/Engineering/DataStructures.md b/.agent/Engineering/DataStructures.md new file mode 100644 index 0000000000000000000000000000000000000000..486bf1fa4e46f48fe4256fcad20a9d58b78e1572 --- /dev/null +++ b/.agent/Engineering/DataStructures.md @@ -0,0 +1,27 @@ +# Essential Data Structures + +## 1. Arrays & Linked Lists + +- O(1) access for arrays, O(n) for linked lists. +- O(1) insertion at the head for linked lists. + +## 2. Stacks & Queues + +- **Stack**: LIFO (Last-In-First-Out). +- **Queue**: FIFO (First-In-First-Out). + +## 3. Hash Tables (Maps/Objects) + +- Provides average O(1) lookup, insertion, and deletion. + +## 4. Trees (Binary Search Trees) + +- Allow for O(log n) searching and sorted traversal. + +## 5. Graphs + +- Represent relationships between objects. Used for social networks, maps, and dependency tracking. + +## 6. Heaps + +- Specialized tree-based data structure used for priority queues. diff --git a/.agent/Engineering/Debugging.md b/.agent/Engineering/Debugging.md new file mode 100644 index 0000000000000000000000000000000000000000..a3ca79c920a3bb43a70db412602c77dfaab3fbb3 --- /dev/null +++ b/.agent/Engineering/Debugging.md @@ -0,0 +1,21 @@ +# Debugging Techniques for Complex Systems + +## 1. Rubber Duck Debugging + +- Explain your code and logic to a surrogate (a literal rubber duck or a colleague) to find logical flaws. + +## 2. Binary Search Debugging + +- Comment out halves of your code to isolate the specific section causing the error. + +## 3. Logpoints over Breakpoints + +- In production or complex async environments, use conditional logpoints to trace state without pausing execution. + +## 4. Time Travel Debugging + +- Use tools like Redux DevTools or specialized IDE features to step backwards through state changes. + +## 5. Network & Performance Profiling + +- Use browser DevTools to identify slow API calls, memory leaks, and unnecessary layout shifts. diff --git a/.agent/Engineering/DependencyInjection.md b/.agent/Engineering/DependencyInjection.md new file mode 100644 index 0000000000000000000000000000000000000000..4ac3bee000be3dee29d67216c56da7400db2c27a --- /dev/null +++ b/.agent/Engineering/DependencyInjection.md @@ -0,0 +1,17 @@ +# Dependency Injection & Inversion of Control + +## IoC (Inversion of Control) + +- Design principle in which a software component's dependencies are provided by an external framework or container, rather than the component managing them itself. + +## Dependency Injection (DI) + +- Pattern that implements IoC by injecting dependencies into a class's constructor, properties, or methods. +- **Constructor Injection**: Preferred method for mandatory dependencies. +- **Setter Injection**: Used for optional dependencies. + +## Advantages + +- **Decoupling**: Components are less tethered to specific implementations. +- **Testability**: Dependencies can be easily mocked or stubbed. +- **Maintainability**: Swapping implementations requires minimal code changes. diff --git a/.agent/Engineering/DesignPatterns_Behavioral.md b/.agent/Engineering/DesignPatterns_Behavioral.md new file mode 100644 index 0000000000000000000000000000000000000000..6ef54b24394c3c86e71f6384e8ff29720a1022b7 --- /dev/null +++ b/.agent/Engineering/DesignPatterns_Behavioral.md @@ -0,0 +1,41 @@ +# Behavioral Design Patterns + +## Chain of Responsibility + +- Lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain. + +## Command + +- Turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method arguments, delay or queue a request’s execution, and support undoable operations. + +## Iterator + +- Lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.). + +## Mediator + +- Lets you reduce chaotic dependencies between classes. The pattern restricts direct communications between the classes and forces them to collaborate only via a mediator object. + +## Memento + +- Lets you save and restore the previous state of an object without revealing the details of its implementation. + +## Observer + +- Lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing. + +## State + +- Lets an object alter its behavior when its internal state changes. It appears as if the object changed its class. + +## Strategy + +- Lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable. + +## Template Method + +- Defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure. + +## Visitor + +- Lets you separate algorithms from the objects on which they operate. diff --git a/.agent/Engineering/DesignPatterns_Creational.md b/.agent/Engineering/DesignPatterns_Creational.md new file mode 100644 index 0000000000000000000000000000000000000000..2c2b5cac2c9c460e85c6cea61afe5586e8d040c0 --- /dev/null +++ b/.agent/Engineering/DesignPatterns_Creational.md @@ -0,0 +1,24 @@ +# Creational Design Patterns + +## Singleton + +- Ensures a class has only one instance and provides a global point of access to it. +- Use for database connections or shared configuration managers. + +## Factory Method + +- Defines an interface for creating an object, but lets subclasses decide which class to instantiate. +- Useful for dynamic instantiation based on runtime conditions. + +## Abstract Factory + +- Provides an interface for creating families of related or dependent objects without specifying their concrete classes. + +## Builder + +- Separates the construction of a complex object from its representation. +- Excellent for objects with many optional parameters. + +## Prototype + +- Specifies the kinds of objects to create using a prototypical instance, and creates new objects by copying this prototype. diff --git a/.agent/Engineering/DesignPatterns_Structural.md b/.agent/Engineering/DesignPatterns_Structural.md new file mode 100644 index 0000000000000000000000000000000000000000..f0939089cc22d45aba8f5dfdeef4d4e2aa900b04 --- /dev/null +++ b/.agent/Engineering/DesignPatterns_Structural.md @@ -0,0 +1,30 @@ +# Structural Design Patterns + +## Adapter + +- Allows objects with incompatible interfaces to collaborate. +- Acts as a wrapper between two objects. + +## Bridge + +- Lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently. + +## Composite + +- Lets you compose objects into tree structures and then work with these structures as if they were individual objects. + +## Decorator + +- Lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors. + +## Facade + +- Provides a simplified interface to a library, a framework, or any other complex set of classes. + +## Flyweight + +- Lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object. + +## Proxy + +- Provides a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object. diff --git a/.agent/Engineering/E2ETesting.md b/.agent/Engineering/E2ETesting.md new file mode 100644 index 0000000000000000000000000000000000000000..c6d99d6eb7953e2fd8e5963f60eef62c74de5908 --- /dev/null +++ b/.agent/Engineering/E2ETesting.md @@ -0,0 +1,21 @@ +# End-to-End (E2E) Testing Guidelines + +## 1. Scope + +E2E tests simulate real user scenarios from start to finish, exercising the entire application stack. + +## 2. Tooling + +Use Playwright or Cypress for reliable, cross-browser E2E testing. + +## 3. Key Workflows + +Focus on critical paths such as login, user registration, and checkout processes. + +## 4. Environment + +Run E2E tests against a production-like environment (Staging) before deploying to production. + +## 5. Maintenance + +E2E tests can be fragile. Design them to be as robust as possible, but be prepared to update them as the UI evolves. diff --git a/.agent/Engineering/ErrorHandling.md b/.agent/Engineering/ErrorHandling.md new file mode 100644 index 0000000000000000000000000000000000000000..206473a2fa2b8112921a1bbf683d095160eba7c7 --- /dev/null +++ b/.agent/Engineering/ErrorHandling.md @@ -0,0 +1,25 @@ +# Error Handling Best Practices + +## 1. Centralized Error Handling + +- Use a central error handler to manage all errors in a consistent way. +- This allows for easier logging and more uniform error responses. + +## 2. Specific Error Types + +- Create custom error classes for different types of errors (e.g., `ValidationError`, `DatabaseError`, `AuthenticationError`). +- This makes it easier to catch and handle specific errors appropriately. + +## 3. Graceful Failure + +- Design your application to fail gracefully when an error occurs. +- Provide clear and helpful error messages to the user, without exposing sensitive information. + +## 4. Logging and Monitoring + +- Log all errors with sufficient context to aid in debugging. +- Use monitoring tools to track error rates and alert you to potential issues. + +## 5. Defensive Programming + +- Use techniques like input validation and assertions to prevent errors from occurring in the first place. diff --git a/.agent/Engineering/FunctionalProgramming.md b/.agent/Engineering/FunctionalProgramming.md new file mode 100644 index 0000000000000000000000000000000000000000..2c948262b038733afb55c4babc25903ffbf2ca4f --- /dev/null +++ b/.agent/Engineering/FunctionalProgramming.md @@ -0,0 +1,24 @@ +# Functional Programming in Modern JS/TS + +## Pure Functions + +- Given the same input, always return the same output. +- No side effects (no global state mutation, no I/O directly within the function). + +## Immutability + +- Avoid changing data; create new copies instead. +- Use `Readonly` and `const` for data structures. + +## Higher-Order Functions + +- Functions that take other functions as arguments or return them (e.g., `map`, `filter`, `reduce`). + +## Declarative vs Imperative + +- Focus on _what_ to do (Functional) rather than _how_ to do it (Imperative). + +## Currying and Composition + +- Currying: Transforming a function that takes multiple arguments into a sequence of functions that each take a single argument. +- Composition: Combining multiple functions to create a new function. diff --git a/.agent/Engineering/IntegrationTesting.md b/.agent/Engineering/IntegrationTesting.md new file mode 100644 index 0000000000000000000000000000000000000000..ced40deb2e0c009bdbee410ea698d04b38cb2d88 --- /dev/null +++ b/.agent/Engineering/IntegrationTesting.md @@ -0,0 +1,21 @@ +# Integration Testing Guidelines + +## 1. Scope + +Integration tests verify that different modules or services work together correctly. + +## 2. Interaction-focused + +Focus on the interaction between components, such as a service calling a database or an API. + +## 3. Real Dependencies + +Use real (or near-real) dependencies where possible, such as a test database. + +## 4. Environment Setup + +Ensure the test environment is correctly configured before running integration tests. + +## 5. Reliability + +Address flaky integration tests immediately, as they can undermine trust in the test suite. diff --git a/.agent/Engineering/LoggingBestPractices.md b/.agent/Engineering/LoggingBestPractices.md new file mode 100644 index 0000000000000000000000000000000000000000..627b14ef615cefc70dbbfba796793bd224bbf474 --- /dev/null +++ b/.agent/Engineering/LoggingBestPractices.md @@ -0,0 +1,26 @@ +# Logging Best Practices + +## 1. Structured Logging + +- Use a structured logging format (like JSON) to make it easier to search and analyze logs. +- Include relevant metadata like timestamps, log levels, and request IDs. + +## 2. Meaningful Log Levels + +- Use appropriate log levels (DEBUG, INFO, WARN, ERROR, FATAL) to indicate the severity of the log message. +- This helps in filtering and prioritizing logs. + +## 3. Contextual Information + +- Include sufficient context in your log messages to make them useful for debugging. +- For example, include the user ID, request path, and any relevant data. + +## 4. Centralized Logging + +- Aggregate logs from all your services into a single, searchable location. +- This provides a holistic view of your system's health and performance. + +## 5. Monitoring and Alerting + +- Use logging as a source for monitoring and alerting. +- Set up alerts based on log patterns or error rates to stay informed about potential issues. diff --git a/.agent/Engineering/MemoryManagement.md b/.agent/Engineering/MemoryManagement.md new file mode 100644 index 0000000000000000000000000000000000000000..4790c1426d852b3c77c13dad8c3ed2eeaff41dc1 --- /dev/null +++ b/.agent/Engineering/MemoryManagement.md @@ -0,0 +1,24 @@ +# Memory Management in JS/TS + +## 1. Stack vs Heap + +- **Stack**: Stores static data (primitives, pointers) where the size is known at compile time. +- **Heap**: Stores objects and functions where the size is dynamic and known at runtime. + +## 2. Garbage Collection (GC) + +- The process of freeing up memory that is no longer being used. +- Most modern JS engines use the **Mark-and-Sweep** algorithm. + +## 3. Memory Leaks + +- **Global Variables**: Unintended global variables that stay in memory forever. +- **Forgotten Timers**: `setInterval` calls that never get cleared. +- **Closures**: Functions that hold onto large objects in their parent scope unnecessarily. +- **Detached DOM Nodes**: Keeping references to DOM nodes that have been removed from the document. + +## 4. Best Practices + +- **Explicit Cleanup**: Use `useEffect` cleanup functions to remove event listeners and clear intervals. +- **WeakMap/WeakSet**: Use for objects that should be garbage collected if no other references exist. +- **Minimize Scope**: Keep variables as local as possible. diff --git a/.agent/Engineering/ObjectOrientedDesign.md b/.agent/Engineering/ObjectOrientedDesign.md new file mode 100644 index 0000000000000000000000000000000000000000..8245f1305840d2ccc3fcb8f70b03ad1efb68ab83 --- /dev/null +++ b/.agent/Engineering/ObjectOrientedDesign.md @@ -0,0 +1,21 @@ +# Object-Oriented Design (OOD) + +## Encapsulation + +- Bundling data and methods that operate on that data within a single unit (class). +- Use `private`, `protected`, and `public` modifiers to control access. + +## Abstraction + +- Hiding complex implementation details and showing only the essential features of the object. +- Focus on "what" an object does rather than "how". + +## Inheritance + +- Mechanisms where one object acquires the property of another object. +- Prefer **Composition over Inheritance** where possible to avoid rigid hierarchies. + +## Polymorphism + +- Ability of a variable, function, or object to take on multiple forms. +- Method overriding and overloading are primary forms. diff --git a/.agent/Engineering/Refactoring.md b/.agent/Engineering/Refactoring.md new file mode 100644 index 0000000000000000000000000000000000000000..53da04480697bfcd37610c11d02827deadef5936 --- /dev/null +++ b/.agent/Engineering/Refactoring.md @@ -0,0 +1,14 @@ +# Legacy Refactoring & Migration + +## Strategy + +1. **Strangler Pattern**: Gradually replace legacy functionality with new microservices/modules. +2. **Test Coverage**: Ensure 100% test coverage for the legacy code you are replacing. +3. **Incremental Changes**: Make small, verifiable commits rather than "big bang" rewrites. + +## Refactoring Steps + +- Identify the most critical/buggy parts of the legacy system. +- Create an abstraction layer (Adapter/Facade) to decouple the new and old systems. +- Redirect traffic to the new implementation while monitoring for issues. +- Decommission the old code once the new system is verified. diff --git a/.agent/Engineering/SOLID_Principles.md b/.agent/Engineering/SOLID_Principles.md new file mode 100644 index 0000000000000000000000000000000000000000..76739ef88a221c710dfea0817dd8fee883046e04 --- /dev/null +++ b/.agent/Engineering/SOLID_Principles.md @@ -0,0 +1,25 @@ +# SOLID Principles in Software Development + +## 1. Single Responsibility Principle (SRP) + +- Every module or class should have responsibility over a single part of the functionality. +- "A class should have one, and only one, reason to change." + +## 2. Open/Closed Principle (OCP) + +- Software entities should be open for extension, but closed for modification. +- Use interfaces and abstract classes to allow behavior to be extended without changing existing code. + +## 3. Liskov Substitution Principle (LSP) + +- Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. + +## 4. Interface Segregation Principle (ISP) + +- Many client-specific interfaces are better than one general-purpose interface. +- "Clients should not be forced to depend upon interfaces that they do not use." + +## 5. Dependency Inversion Principle (DIP) + +- Depend upon abstractions, not concretions. +- High-level modules should not depend on low-level modules. Both should depend on abstractions. diff --git a/.agent/Engineering/TypeScript.md b/.agent/Engineering/TypeScript.md new file mode 100644 index 0000000000000000000000000000000000000000..d216c224c37257fe09d3edf5bd48c36f280ab1c6 --- /dev/null +++ b/.agent/Engineering/TypeScript.md @@ -0,0 +1,14 @@ +# TypeScript Standards + +## Strict Typing Policy + +1. **No `any`**: The use of `any` is strictly prohibited. Use `unknown` if the type is truly unknown. +2. **Explicit Interfaces**: Define interfaces for all component props and API responses. +3. **Proper Generics**: Use generic types for reusable components and hooks. +4. **Discriminated Unions**: Use for complex state or action types. + +## Type Safety + +- Enable `strict: true` in `tsconfig`. +- Use `Readonly` for immutable arrays/objects. +- Avoid non-null assertions (`!`). Use optional chaining or nullish coalescing. diff --git a/.agent/Engineering/UnitTesting.md b/.agent/Engineering/UnitTesting.md new file mode 100644 index 0000000000000000000000000000000000000000..090235241e706d6e55f54655c2665adc0f843fbe --- /dev/null +++ b/.agent/Engineering/UnitTesting.md @@ -0,0 +1,21 @@ +# Unit Testing Guidelines + +## 1. Scope + +Unit tests should focus on a single unit of code, typically a function or a class method, in isolation. + +## 2. Fast & Isolated + +Tests should run quickly and not depend on external resources like databases or APIs. + +## 3. Mocking + +Use mocks for dependencies to ensure you are only testing the specific unit's logic. + +## 4. Clear Naming + +Name tests clearly to describe what is being tested and the expected outcome (e.g., `calculateTotal_returns_correct_sum`). + +## 5. Coverage + +Aim for high coverage of critical business logic and complex algorithms. diff --git a/.agent/Frontend/Accessibility.md b/.agent/Frontend/Accessibility.md new file mode 100644 index 0000000000000000000000000000000000000000..458b16000b09dbd633d9143bef15a7f034304fb6 --- /dev/null +++ b/.agent/Frontend/Accessibility.md @@ -0,0 +1,23 @@ +# Accessibility (a11y) in Web Development + +## 1. Semantic HTML + +Use the correct HTML elements for their intended purposes (e.g., ` + + + + + + +
+ + +
+ + +
+
+
Total Tasks
+
+
stored in memory
+
+
+
Checkpoints
+
+
context snapshots
+
+
+
Total Tokens
+
+
across all agents
+
+
+
Est. Cost
+
+
USD cumulative
+
+
+ + + + +
+ +
+
Recent Tasks
+
+
Loading...
+
+
+ + +
+
Agent Usage Summary
+
+
Loading...
+
+
+
+ +
+ + +
+
+
All Tasks
+
+
+
+
+
+ + +
+
+
Checkpoint Timeline
+
+
+
+
+
+ + +
+ +
+ +
+
Token Usage per Agent
+
+
+ +
+
Recent Sessions
+
+
+
+ + +
+
+
Knowledge Base
+
+
+
+ + + + + +
+
+
⚡ Token Optimization Tips
+
+
+ +
+
📋 Quick Commands
+
+
+
Load context at session start
+
python .Agent/scripts/memory_engine.py --context --agent <name> --max-tokens 4000
+
+
+
Save a checkpoint
+
python .Agent/scripts/memory_engine.py --checkpoint --task-id <id> --agent <name> --summary "..."
+
+
+
Log token usage
+
python .Agent/scripts/usage_tracker.py --log --agent <name> --model gemini-2.5-pro --input 1200 --output 800
+
+
+
Auto-compress + checkpoint when context is large
+
python .Agent/scripts/context_optimizer.py --auto-checkpoint --task-id <id> --agent <name> --input ctx.txt
+
+
+
+
+ +
+ + + + + diff --git a/.agent/hooks/bash_completions.sh b/.agent/hooks/bash_completions.sh new file mode 100644 index 0000000000000000000000000000000000000000..af10b2b9835035921af2a5d2b5ba43a00d9c8265 --- /dev/null +++ b/.agent/hooks/bash_completions.sh @@ -0,0 +1,33 @@ + +## BEGIN: MemCompletion ################################################### +_mem_completions() { + local cur prev words + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + words=(${COMP_WORDS[@]}) + + if [[ ${#words[@]} -le 2 ]]; then + COMPREPLY=( $(compgen -W "status init ctx context write cp checkpoint log search done complete report tips watch know open analyze compress agent read help" -- "$cur") ) + elif [[ "$prev" == "--agent" ]]; then + COMPREPLY=( $(compgen -W "antigravity gemini claude codex cursor copilot jetbrains-ai zed-ai sublime terminal my_script warp" -- "$cur") ) + elif [[ "$prev" == "--model" ]]; then + COMPREPLY=( $(compgen -W "gemini-2.5-pro gemini-2.0-flash claude-3-7-sonnet claude-3-5-haiku gpt-4o gpt-4o-mini o3 codex-davinci default" -- "$cur") ) + elif [[ "$cur" == --* ]]; then + local sub="${words[1]}" + local flags="" + case "$sub" in + ctx) flags="--agent --max-tokens" ;; + write) flags="--agent --desc --status --tags --id" ;; + cp) flags="--agent --context" ;; + log) flags="--agent --model --task-id --note" ;; + report) flags="--agent" ;; + analyze) flags="--model" ;; + compress) flags="--output" ;; + watch) flags="--interval" ;; + esac + COMPREPLY=( $(compgen -W "$flags" -- "$cur") ) + fi +} +complete -F _mem_completions mem +## END: MemCompletion ################################################### + diff --git a/.agent/hooks/post-commit.py b/.agent/hooks/post-commit.py new file mode 100644 index 0000000000000000000000000000000000000000..61c997d2b65810cb1792f2b50735c5ec1525dcb7 --- /dev/null +++ b/.agent/hooks/post-commit.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +post-commit — Git Hook: Auto-checkpoint after each commit +========================================================= +Saves a memory checkpoint with the commit message as the summary. +Place this file in .git/hooks/post-commit and make executable. + +On Windows, this is called by the Git hook wrapper post-commit.ps1. +""" +from __future__ import annotations +import subprocess +import sys +import os +from pathlib import Path + +def main() -> None: + # Find workspace root + workspace = Path.cwd() + agent_dir = None + for d in [workspace, *workspace.parents]: + if (d / ".Agent").is_dir(): + agent_dir = d / ".Agent" + break + + if not agent_dir: + return # Not in an Agent workspace — skip + + # Get last commit message + try: + msg = subprocess.check_output( + ["git", "log", "-1", "--pretty=%B"], + text=True, stderr=subprocess.DEVNULL + ).strip()[:120] + except Exception: + msg = "Git commit" + + # Get active task from session + sys.path.insert(0, str(agent_dir / "scripts")) + try: + from memory_engine import _load_json, SESSION_PATH, save_checkpoint + session = _load_json(SESSION_PATH) + task_id = session.get("active_task_id") + agent = session.get("active_agent") or os.environ.get("MEM_AGENT", "git-hook") + + if task_id: + cid = save_checkpoint( + task_id=task_id, + agent=agent, + summary=f"[git-commit] {msg}", + context_snapshot=f"Commit: {msg}", + ) + print(f"🧠 Memory checkpoint saved: {cid} — {msg[:60]}") + else: + # Still save as a knowledge entry + from memory_engine import save_knowledge + save_knowledge( + title=f"Commit: {msg[:50]}", + content=f"Git commit at {subprocess.check_output(['git','log','-1','--pretty=%H'], text=True).strip()[:8]}: {msg}", + tags=["git", "commit"], + ) + except Exception as e: + print(f"🧠 Memory hook warning: {e}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/.agent/hooks/zsh_completions.zsh b/.agent/hooks/zsh_completions.zsh new file mode 100644 index 0000000000000000000000000000000000000000..35ef71affa7cea8f073e9ca8c164094f1687dfa1 --- /dev/null +++ b/.agent/hooks/zsh_completions.zsh @@ -0,0 +1,31 @@ + +## BEGIN: MemCompletion ################################################### +_mem_complete() { + local -a cmds agents models flags + cmds=(status init ctx context write cp checkpoint log search done complete report tips watch know open analyze compress agent read help) + agents=(antigravity gemini claude codex cursor copilot jetbrains-ai zed-ai sublime terminal my_script warp) + models=(gemini-2.5-pro gemini-2.0-flash claude-3-7-sonnet claude-3-5-haiku gpt-4o gpt-4o-mini o3 codex-davinci default) + + if (( CURRENT == 2 )); then + _describe 'mem command' cmds + elif [[ ${words[${CURRENT-1}]} == '--agent' ]]; then + _describe 'agent' agents + elif [[ ${words[${CURRENT-1}]} == '--model' ]]; then + _describe 'model' models + else + case ${words[2]} in + ctx) flags=(--agent --max-tokens) ;; + write) flags=(--agent --desc --status --tags --id) ;; + cp) flags=(--agent --context) ;; + log) flags=(--agent --model --task-id --note) ;; + report) flags=(--agent) ;; + analyze) flags=(--model) ;; + compress) flags=(--output) ;; + watch) flags=(--interval) ;; + esac + _describe 'option' flags + fi +} +compdef _mem_complete mem +## END: MemCompletion ################################################### + diff --git a/.agent/memory/memory-store.json b/.agent/memory/memory-store.json new file mode 100644 index 0000000000000000000000000000000000000000..3b35d6217bb715abaff6481121ebde739e7bfac3 --- /dev/null +++ b/.agent/memory/memory-store.json @@ -0,0 +1,85 @@ +{ + "version": "1.0.0", + "created_at": "2026-03-02T14:13:49+05:30", + "tasks": [ + { + "id": "6dd703be", + "title": "Redesign Architecture to Cloud IDE Orchestrator Spec", + "agent": "antigravity", + "description": "Implementing the user's specific JSON spec for Turso workspaces table, Dockerode proxy mapping, and NextAuth.", + "status": "completed", + "tags": [], + "created_at": "2026-03-03T10:00:08.034211+05:30", + "updated_at": "2026-03-03T10:21:55.603432+05:30", + "checkpoints": [], + "completion_summary": "Completed Phase 18 Cloud IDE Orchestrator redo. Implemented Turso DB orchestration, Bento-box dashboard, Booting terminal UI, container networking idle sleep checks, and passed strict tsc type checks." + }, + { + "id": "a3881578", + "title": "Testing mem CLI aliases", + "agent": "antigravity", + "description": "Final integration test", + "status": "in_progress", + "tags": [], + "created_at": "2026-03-02T14:56:42.716422+05:30", + "updated_at": "2026-03-02T14:56:42.716422+05:30", + "checkpoints": [] + }, + { + "id": "c5239484", + "title": "Optimizer Test Task", + "agent": "test_runner", + "description": "", + "status": "in_progress", + "tags": [], + "created_at": "2026-03-02T14:38:25.370944+05:30", + "updated_at": "2026-03-02T14:38:25.389279+05:30", + "checkpoints": [ + "42d87da5" + ] + }, + { + "id": "1cd7d64d", + "title": "Test Task: Memory Engine Validation", + "agent": "test_runner", + "description": "Verifying read/write/compress/search cycle", + "status": "completed", + "tags": [], + "created_at": "2026-03-02T14:37:44.434421+05:30", + "updated_at": "2026-03-02T14:37:44.491603+05:30", + "checkpoints": [ + "1e9737b3" + ], + "completion_summary": "All engine tests passed" + } + ], + "checkpoints": [ + { + "id": "42d87da5", + "task_id": "c5239484", + "agent": "test_runner", + "summary": "Auto-checkpoint at 2026-03-02T14:38:25.378203+05:30 — original 12250 tokens, compressed to 309", + "compressed_context": "The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally. The agent memory system stores tasks and checkpoints locally.", + "token_estimate": 154, + "created_at": "2026-03-02T14:38:25.389279+05:30" + }, + { + "id": "1e9737b3", + "task_id": "1cd7d64d", + "agent": "test_runner", + "summary": "Step 1 complete", + "compressed_context": "The memory engine stores tasks and checkpoints. It supports any agent. Compression reduces large context to key sentences. Search finds relevant tasks by keyword matching.", + "token_estimate": 42, + "created_at": "2026-03-02T14:37:44.455486+05:30" + } + ], + "knowledge": [ + { + "id": "74cd4891", + "title": "Dynamic agents", + "content": "Any agent name is valid — no hardcoded list", + "tags": [], + "created_at": "2026-03-02T14:37:44.481652+05:30" + } + ] +} \ No newline at end of file diff --git a/.agent/memory/model_costs.json b/.agent/memory/model_costs.json new file mode 100644 index 0000000000000000000000000000000000000000..d1ce688ab981d1c1abb2bc902f25929e0df3019a --- /dev/null +++ b/.agent/memory/model_costs.json @@ -0,0 +1,31 @@ +{ + "_comment": "Model pricing (USD per 1M tokens). Edit freely or run: mem log --add-model --cost-input X --cost-output Y", + "default": { + "input": 1.0, + "output": 4.0 + }, + "gemini-2.5-pro": { + "input": 1.25, + "output": 10.0 + }, + "claude-3-7-sonnet": { + "input": 3.0, + "output": 15.0 + }, + "gpt-4o": { + "input": 2.5, + "output": 10.0 + }, + "gemini-2.0-flash": { + "input": 0.1, + "output": 0.4 + }, + "gpt-4o-mini": { + "input": 0.15, + "output": 0.6 + }, + "my-brand-new-llm": { + "input": 1.0, + "output": 4.0 + } +} \ No newline at end of file diff --git a/.agent/memory/session.json b/.agent/memory/session.json new file mode 100644 index 0000000000000000000000000000000000000000..b98f9fdc791b847a94491f8a4f0ff989b2d11c18 --- /dev/null +++ b/.agent/memory/session.json @@ -0,0 +1,13 @@ +{ + "version": "1.0.0", + "session_id": "2e489ee7", + "started_at": "2026-03-15T20:46:31.162981+05:30", + "workspace": "D:\\Code\\codeverse", + "active_task_id": null, + "active_agent": null, + "status": "idle", + "last_checkpoint_at": null, + "context_tokens_used": 0, + "context_budget": 100000, + "notes": [] +} \ No newline at end of file diff --git a/.agent/memory/usage-tracker.json b/.agent/memory/usage-tracker.json new file mode 100644 index 0000000000000000000000000000000000000000..62dd2075b3c7f4728887e5c0336fa95d6d3572ec --- /dev/null +++ b/.agent/memory/usage-tracker.json @@ -0,0 +1,284 @@ +{ + "version": "1.0.0", + "created_at": "2026-03-02T14:13:49+05:30", + "sessions": [ + { + "id": "1190d59b", + "agent": "my_custom_agent", + "model": "my-brand-new-llm", + "input_tokens": 6000, + "output_tokens": 3000, + "total_tokens": 9000, + "cost_usd": 0.018, + "task_id": "test-5", + "note": "Self-test", + "logged_at": "2026-03-02T15:11:32.644975+05:30" + }, + { + "id": "6a957f79", + "agent": "terminal", + "model": "gpt-4o-mini", + "input_tokens": 5000, + "output_tokens": 2500, + "total_tokens": 7500, + "cost_usd": 0.00225, + "task_id": "test-4", + "note": "Self-test", + "logged_at": "2026-03-02T15:11:32.632833+05:30" + }, + { + "id": "12c2d13f", + "agent": "codex", + "model": "gemini-2.0-flash", + "input_tokens": 4000, + "output_tokens": 2000, + "total_tokens": 6000, + "cost_usd": 0.0012, + "task_id": "test-3", + "note": "Self-test", + "logged_at": "2026-03-02T15:11:32.620793+05:30" + }, + { + "id": "b0906ef3", + "agent": "claude", + "model": "gpt-4o", + "input_tokens": 3000, + "output_tokens": 1500, + "total_tokens": 4500, + "cost_usd": 0.0225, + "task_id": "test-2", + "note": "Self-test", + "logged_at": "2026-03-02T15:11:32.607507+05:30" + }, + { + "id": "75327ef9", + "agent": "gemini", + "model": "claude-3-7-sonnet", + "input_tokens": 2000, + "output_tokens": 1000, + "total_tokens": 3000, + "cost_usd": 0.021, + "task_id": "test-1", + "note": "Self-test", + "logged_at": "2026-03-02T15:11:32.594838+05:30" + }, + { + "id": "4f83faa1", + "agent": "antigravity", + "model": "gemini-2.5-pro", + "input_tokens": 1000, + "output_tokens": 500, + "total_tokens": 1500, + "cost_usd": 0.00625, + "task_id": "test-0", + "note": "Self-test", + "logged_at": "2026-03-02T15:11:32.544406+05:30" + }, + { + "id": "1dd1ce33", + "agent": "my_custom_agent", + "model": "my-brand-new-llm", + "input_tokens": 6000, + "output_tokens": 3000, + "total_tokens": 9000, + "cost_usd": 0.018, + "task_id": "test-5", + "note": "Self-test", + "logged_at": "2026-03-02T15:05:06.377939+05:30" + }, + { + "id": "3c6a08e6", + "agent": "terminal", + "model": "gpt-4o-mini", + "input_tokens": 5000, + "output_tokens": 2500, + "total_tokens": 7500, + "cost_usd": 0.00225, + "task_id": "test-4", + "note": "Self-test", + "logged_at": "2026-03-02T15:05:06.344667+05:30" + }, + { + "id": "cc0e088f", + "agent": "codex", + "model": "gemini-2.0-flash", + "input_tokens": 4000, + "output_tokens": 2000, + "total_tokens": 6000, + "cost_usd": 0.0012, + "task_id": "test-3", + "note": "Self-test", + "logged_at": "2026-03-02T15:05:06.320661+05:30" + }, + { + "id": "3c0daba2", + "agent": "claude", + "model": "gpt-4o", + "input_tokens": 3000, + "output_tokens": 1500, + "total_tokens": 4500, + "cost_usd": 0.0225, + "task_id": "test-2", + "note": "Self-test", + "logged_at": "2026-03-02T15:05:06.286481+05:30" + }, + { + "id": "6db6246e", + "agent": "gemini", + "model": "claude-3-7-sonnet", + "input_tokens": 2000, + "output_tokens": 1000, + "total_tokens": 3000, + "cost_usd": 0.021, + "task_id": "test-1", + "note": "Self-test", + "logged_at": "2026-03-02T15:05:06.265481+05:30" + }, + { + "id": "d7fa7e3a", + "agent": "antigravity", + "model": "gemini-2.5-pro", + "input_tokens": 1000, + "output_tokens": 500, + "total_tokens": 1500, + "cost_usd": 0.00625, + "task_id": "test-0", + "note": "Self-test", + "logged_at": "2026-03-02T15:05:06.167770+05:30" + }, + { + "id": "a3726392", + "agent": "my_custom_agent", + "model": "codex-davinci", + "input_tokens": 6000, + "output_tokens": 3000, + "total_tokens": 9000, + "cost_usd": 0.018, + "task_id": "test-task-5", + "note": "Self-test entry", + "logged_at": "2026-03-02T14:38:20.246573+05:30" + }, + { + "id": "2109bf1f", + "agent": "terminal", + "model": "gpt-4o-mini", + "input_tokens": 5000, + "output_tokens": 2500, + "total_tokens": 7500, + "cost_usd": 0.00225, + "task_id": "test-task-4", + "note": "Self-test entry", + "logged_at": "2026-03-02T14:38:20.237117+05:30" + }, + { + "id": "1861b746", + "agent": "codex", + "model": "gemini-2.0-flash", + "input_tokens": 4000, + "output_tokens": 2000, + "total_tokens": 6000, + "cost_usd": 0.0012, + "task_id": "test-task-3", + "note": "Self-test entry", + "logged_at": "2026-03-02T14:38:20.222504+05:30" + }, + { + "id": "310f975b", + "agent": "claude", + "model": "gpt-4o", + "input_tokens": 3000, + "output_tokens": 1500, + "total_tokens": 4500, + "cost_usd": 0.0225, + "task_id": "test-task-2", + "note": "Self-test entry", + "logged_at": "2026-03-02T14:38:20.211912+05:30" + }, + { + "id": "f5d61c9d", + "agent": "gemini", + "model": "claude-3-7-sonnet", + "input_tokens": 2000, + "output_tokens": 1000, + "total_tokens": 3000, + "cost_usd": 0.021, + "task_id": "test-task-1", + "note": "Self-test entry", + "logged_at": "2026-03-02T14:38:20.201218+05:30" + }, + { + "id": "a7b034ba", + "agent": "antigravity", + "model": "gemini-2.5-pro", + "input_tokens": 1000, + "output_tokens": 500, + "total_tokens": 1500, + "cost_usd": 0.00625, + "task_id": "test-task-0", + "note": "Self-test entry", + "logged_at": "2026-03-02T14:38:20.188250+05:30" + } + ], + "totals": { + "antigravity": { + "input_tokens": 3000, + "output_tokens": 1500, + "total_tokens": 4500, + "cost_usd": 0.01875, + "session_count": 3, + "models_used": [ + "gemini-2.5-pro" + ] + }, + "gemini": { + "input_tokens": 6000, + "output_tokens": 3000, + "total_tokens": 9000, + "cost_usd": 0.063, + "session_count": 3, + "models_used": [ + "claude-3-7-sonnet" + ] + }, + "claude": { + "input_tokens": 9000, + "output_tokens": 4500, + "total_tokens": 13500, + "cost_usd": 0.0675, + "session_count": 3, + "models_used": [ + "gpt-4o" + ] + }, + "codex": { + "input_tokens": 12000, + "output_tokens": 6000, + "total_tokens": 18000, + "cost_usd": 0.0036, + "session_count": 3, + "models_used": [ + "gemini-2.0-flash" + ] + }, + "terminal": { + "input_tokens": 15000, + "output_tokens": 7500, + "total_tokens": 22500, + "cost_usd": 0.00675, + "session_count": 3, + "models_used": [ + "gpt-4o-mini" + ] + }, + "my_custom_agent": { + "input_tokens": 18000, + "output_tokens": 9000, + "total_tokens": 27000, + "cost_usd": 0.054, + "session_count": 3, + "models_used": [ + "my-brand-new-llm" + ] + } + } +} \ No newline at end of file diff --git a/.agent/scripts/__pycache__/activate.cpython-314.pyc b/.agent/scripts/__pycache__/activate.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9163fe39a0d44a1ab9bc619bc9ef6473eba3351 Binary files /dev/null and b/.agent/scripts/__pycache__/activate.cpython-314.pyc differ diff --git a/.agent/scripts/__pycache__/context_optimizer.cpython-314.pyc b/.agent/scripts/__pycache__/context_optimizer.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b91655778dbb099f6aa9595b97b02b62c017fc6c Binary files /dev/null and b/.agent/scripts/__pycache__/context_optimizer.cpython-314.pyc differ diff --git a/.agent/scripts/__pycache__/memory_engine.cpython-314.pyc b/.agent/scripts/__pycache__/memory_engine.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40bc9f8f66bb728764dc34efb4ddccfe30ae642d Binary files /dev/null and b/.agent/scripts/__pycache__/memory_engine.cpython-314.pyc differ diff --git a/.agent/scripts/__pycache__/usage_tracker.cpython-314.pyc b/.agent/scripts/__pycache__/usage_tracker.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45194c5809f8836a01cfc801f39a4c5d749bec64 Binary files /dev/null and b/.agent/scripts/__pycache__/usage_tracker.cpython-314.pyc differ diff --git a/.agent/scripts/activate.py b/.agent/scripts/activate.py new file mode 100644 index 0000000000000000000000000000000000000000..44f24ad01c15c46d6bdb826ca4053db107c3165e --- /dev/null +++ b/.agent/scripts/activate.py @@ -0,0 +1,170 @@ +""" +activate.py — Unified Agent Memory Session Activator +===================================================== +Detects the .Agent folder in the workspace (CWD or parent tree), +initialises a new memory session, and prints a boot banner. + +Run automatically from PowerShell hook or manually: + python .Agent/scripts/activate.py + python .Agent/scripts/activate.py --status # just show current session + python .Agent/scripts/activate.py --workspace d:\\Code +""" + +from __future__ import annotations + +import argparse +import json +import sys +import uuid +from datetime import datetime, timezone +from pathlib import Path + +# --------------------------------------------------------------------------- +# Locate .Agent folder +# --------------------------------------------------------------------------- +_THIS_DIR = Path(__file__).parent +AGENT_DIR = _THIS_DIR.parent + + +def find_agent_dir(start: Path | None = None) -> Path | None: + """Walk up from `start` (default: CWD) looking for a .Agent folder.""" + search = start or Path.cwd() + for directory in [search, *search.parents]: + candidate = directory / ".Agent" + if candidate.is_dir(): + return candidate + return None + + +# --------------------------------------------------------------------------- +# Lazy imports after path validation +# --------------------------------------------------------------------------- + +def _import_engine(): + sys.path.insert(0, str(AGENT_DIR / "scripts")) + from memory_engine import _load_json, _save_json, SESSION_PATH, STORE_PATH + from usage_tracker import get_report, get_optimization_tips + return _load_json, _save_json, SESSION_PATH, STORE_PATH, get_report, get_optimization_tips + + +# --------------------------------------------------------------------------- +# Session management +# --------------------------------------------------------------------------- + +def _now_iso() -> str: + return datetime.now(timezone.utc).astimezone().isoformat() + + +def init_session(workspace: Path) -> dict: + _load_json, _save_json, SESSION_PATH, STORE_PATH, _, _ = _import_engine() + + session_id = str(uuid.uuid4())[:8] + session = { + "version": "1.0.0", + "session_id": session_id, + "started_at": _now_iso(), + "workspace": str(workspace), + "active_task_id": None, + "active_agent": None, + "status": "idle", + "last_checkpoint_at": None, + "context_tokens_used": 0, + "context_budget": 100_000, + "notes": [], + } + _save_json(SESSION_PATH, session) + return session + + +def read_session() -> dict: + _load_json, _, SESSION_PATH, STORE_PATH, _, _ = _import_engine() + return _load_json(SESSION_PATH) + + +def read_store() -> dict: + _load_json, _, SESSION_PATH, STORE_PATH, _, _ = _import_engine() + return _load_json(STORE_PATH) + + +# --------------------------------------------------------------------------- +# Banner +# --------------------------------------------------------------------------- + +BANNER = """ +╔══════════════════════════════════════════════════════════╗ +║ 🧠 Agent Memory System — Session Active ║ +╚══════════════════════════════════════════════════════════╝ +""" + + +def print_status(session: dict, store: dict) -> None: + print(BANNER) + print(f" Session ID : {session.get('session_id', 'N/A')}") + print(f" Workspace : {session.get('workspace', 'N/A')}") + print(f" Started : {session.get('started_at', 'N/A')}") + print(f" Status : {session.get('status', 'idle')}") + + active_id = session.get("active_task_id") + if active_id: + task = next((t for t in store.get("tasks", []) if t["id"] == active_id), None) + if task: + print(f"\n 📌 Active Task : [{task['status']}] {task['title']}") + print(f" Agent : {task['agent']}") + print(f" Updated : {task['updated_at']}") + else: + tasks = store.get("tasks", []) + in_progress = [t for t in tasks if t.get("status") == "in_progress"] + if in_progress: + t = in_progress[0] + print(f"\n 📌 Last In-Progress Task : [{t['id']}] {t['title']}") + elif tasks: + t = tasks[0] + print(f"\n 📋 Last Task : [{t['status']}] {t['title']}") + + print() + total_tasks = len(store.get("tasks", [])) + total_checkpoints = len(store.get("checkpoints", [])) + total_knowledge = len(store.get("knowledge", [])) + print(f" Memory: {total_tasks} task(s) | {total_checkpoints} checkpoint(s) | {total_knowledge} knowledge item(s)") + print() + print(" Quick Commands:") + print(" python .Agent/scripts/memory_engine.py --read") + print(" python .Agent/scripts/memory_engine.py --context --agent ") + print(" python .Agent/scripts/usage_tracker.py --report") + print(" python .Agent/scripts/usage_tracker.py --tips") + print() + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser(description="Agent Memory Session Activator") + parser.add_argument("--status", action="store_true", help="Show current session status only") + parser.add_argument("--workspace", default=None, help="Workspace path (default: auto-detect)") + args = parser.parse_args() + + # Resolve workspace + if args.workspace: + workspace = Path(args.workspace) + else: + agent_dir = find_agent_dir() + workspace = agent_dir.parent if agent_dir else Path.cwd() + + if args.status: + session = read_session() + store = read_store() + print_status(session, store) + return + + # Initialize new session + session = init_session(workspace) + store = read_store() + print_status(session, store) + print(f" ✅ New session started: {session['session_id']}") + print() + + +if __name__ == "__main__": + main() diff --git a/.agent/scripts/context_optimizer.py b/.agent/scripts/context_optimizer.py new file mode 100644 index 0000000000000000000000000000000000000000..9a5c8a04ae62e6babfad7f8ec9354f35bd940398 --- /dev/null +++ b/.agent/scripts/context_optimizer.py @@ -0,0 +1,335 @@ +""" +context_optimizer.py — Token Budget Advisor & Context Pruner +============================================================= +Analyses context text, estimates token count, detects redundancy, +and prunes/compresses to fit within a token budget. + +Usage: + python context_optimizer.py --analyze --input context.txt + python context_optimizer.py --compress --input context.txt --budget 4000 --output compressed.txt + python context_optimizer.py --suggest --input context.txt --budget 4000 + python context_optimizer.py --auto-checkpoint --task-id --agent --threshold 80000 + python context_optimizer.py --test +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any + +# Internal imports from same package +_SCRIPTS_DIR = Path(__file__).parent +sys.path.insert(0, str(_SCRIPTS_DIR)) +from memory_engine import ( + STORE_PATH, + compress_context, + save_checkpoint, + _load_json, + _estimate_tokens, + _now_iso, +) + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- +REDUNDANCY_THRESHOLD = 0.3 # >30% repeated phrases = high redundancy +HIGH_TOKEN_WARNING = 50_000 +CRITICAL_TOKEN_LIMIT = 80_000 + +# Model context windows (tokens) +MODEL_CONTEXT_LIMITS: dict[str, int] = { + "gemini-2.5-pro": 1_000_000, + "gemini-2.0-flash": 1_000_000, + "claude-3-7-sonnet": 200_000, + "claude-3-5-haiku": 200_000, + "gpt-4o": 128_000, + "gpt-4o-mini": 128_000, + "o3": 200_000, + "codex-davinci": 8_000, + "default": 100_000, +} + +# --------------------------------------------------------------------------- +# Analysis +# --------------------------------------------------------------------------- + +def analyze_context(text: str, model: str = "default") -> dict[str, Any]: + """ + Analyse a context string and return a detailed report. + """ + if not text.strip(): + return {"error": "Empty context"} + + token_count = _estimate_tokens(text) + char_count = len(text) + limit = MODEL_CONTEXT_LIMITS.get(model, MODEL_CONTEXT_LIMITS["default"]) + usage_pct = (token_count / limit) * 100 + + sentences = re.split(r"(?<=[.!?\n])\s+", text.strip()) + sentence_count = len(sentences) + + # Redundancy: measure repeated n-grams + words = re.findall(r"\w+", text.lower()) + bigrams: dict[str, int] = {} + for i in range(len(words) - 1): + bg = f"{words[i]} {words[i+1]}" + bigrams[bg] = bigrams.get(bg, 0) + 1 + repeated_bigrams = sum(1 for c in bigrams.values() if c > 2) + redundancy_score = min(1.0, repeated_bigrams / max(len(bigrams), 1)) + + # Compression estimate (based on redundancy) + compression_ratio = 1.0 - (redundancy_score * 0.5) # 0.5x–1.0x + compressed_estimate = int(token_count * compression_ratio) + tokens_saved = token_count - compressed_estimate + + status = "✅ OK" + if usage_pct > 90: + status = "🔴 CRITICAL — near context limit" + elif usage_pct > 70: + status = "🟠 HIGH — consider compressing" + elif usage_pct > 40: + status = "🟡 MODERATE" + + return { + "token_count": token_count, + "char_count": char_count, + "sentence_count": sentence_count, + "model": model, + "context_limit": limit, + "usage_pct": round(usage_pct, 2), + "status": status, + "redundancy_score": round(redundancy_score, 3), + "compressed_token_estimate": compressed_estimate, + "tokens_saved_estimate": tokens_saved, + "compression_ratio": round(compression_ratio, 3), + } + + +def suggest_pruning(text: str, budget: int) -> list[str]: + """ + Return actionable suggestions to reduce context to the given token budget. + """ + current = _estimate_tokens(text) + suggestions: list[str] = [] + + if current <= budget: + suggestions.append(f"✅ Context ({current} tokens) is within budget ({budget} tokens). No action needed.") + return suggestions + + deficit = current - budget + suggestions.append(f"⚠️ Context is {current} tokens — {deficit} over the {budget}-token budget.") + suggestions.append("") + + # Suggestion 1: Compress + analysis = analyze_context(text) + est_after_compress = analysis["compressed_token_estimate"] + if est_after_compress <= budget: + suggestions.append(f" 1️⃣ Run extractive compression → estimated {est_after_compress} tokens (saves ~{current - est_after_compress}).") + else: + suggestions.append(f" 1️⃣ Compression yields ~{est_after_compress} tokens — still {est_after_compress - budget} over budget.") + + # Suggestion 2: Drop old history + suggestions.append(f" 2️⃣ Remove conversation turns older than the last 3 exchanges.") + + # Suggestion 3: Checkpoint first + suggestions.append(f" 3️⃣ Save a checkpoint NOW, then start a new conversation with only the compressed context.") + + # Suggestion 4: Split task + if current > CRITICAL_TOKEN_LIMIT: + suggestions.append(f" 4️⃣ Consider splitting this task into two sub-tasks (context is very large: {current} tokens).") + + # Suggestion 5: Use smaller excerpts from docs + suggestions.append(f" 5️⃣ Replace full file contents with concise summaries or targeted excerpts.") + + return suggestions + + +def compress_to_budget(text: str, budget: int) -> str: + """ + Iteratively compress text to fit within the token budget. + Returns compressed string. + """ + result = text + max_sentences = 20 + + while _estimate_tokens(result) > budget and max_sentences > 2: + result = compress_context(result, max_sentences=max_sentences) + max_sentences = max(2, max_sentences - 3) + + return result + + +def auto_checkpoint( + task_id: str, + agent: str, + context_text: str, + threshold: int = CRITICAL_TOKEN_LIMIT, +) -> dict[str, Any]: + """ + Automatically saves a compressed checkpoint if context exceeds threshold. + Returns a dict with action taken and checkpoint_id if saved. + """ + token_count = _estimate_tokens(context_text) + + if token_count < threshold: + return { + "action": "noop", + "token_count": token_count, + "threshold": threshold, + "message": f"Context ({token_count} tokens) is below threshold ({threshold}). No checkpoint needed.", + } + + compressed = compress_to_budget(context_text, budget=4000) + summary = f"Auto-checkpoint at {_now_iso()} — original {token_count} tokens, compressed to {_estimate_tokens(compressed)}" + + cid = save_checkpoint( + task_id=task_id, + agent=agent, + summary=summary, + context_snapshot=compressed, + ) + + return { + "action": "checkpoint_saved", + "checkpoint_id": cid, + "original_tokens": token_count, + "compressed_tokens": _estimate_tokens(compressed), + "reduction_pct": round((1 - _estimate_tokens(compressed) / token_count) * 100, 1), + "message": f"Checkpoint {cid} saved. Reduced {token_count} → {_estimate_tokens(compressed)} tokens.", + } + + +# --------------------------------------------------------------------------- +# Self-test +# --------------------------------------------------------------------------- + +def _run_tests() -> None: + print("🧪 context_optimizer.py — Self Test") + print("-" * 40) + + sample = ( + "The agent memory system stores tasks and checkpoints locally. " + "Each agent reads the shared memory store at session start. " + "The agent memory system stores tasks and checkpoints locally. " + "Token usage is tracked per agent (any string identifier). " + "The context optimizer compresses large contexts to save tokens. " + "Agents should save checkpoints before hitting context limits. " + "The agent memory system stores tasks and checkpoints locally. " + "The dashboard shows live usage stats and optimization hints. " + ) * 5 + + # Analyze + report = analyze_context(sample, model="gpt-4o") + assert report["token_count"] > 0 + print(f"✅ analyze_context → {report['token_count']} tokens, redundancy={report['redundancy_score']}") + + # Suggest + suggestions = suggest_pruning(sample, budget=50) + assert len(suggestions) > 0 + print(f"✅ suggest_pruning → {len(suggestions)} suggestions") + + # Compress to budget + compressed = compress_to_budget(sample, budget=100) + assert _estimate_tokens(compressed) <= 150 # allow 50 token tolerance + print(f"✅ compress_to_budget → {_estimate_tokens(compressed)} tokens (budget=100)") + + # Auto-checkpoint (threshold low so it fires) + from memory_engine import write_memory + tid = write_memory(title="Optimizer Test Task", agent="test_runner") + result = auto_checkpoint( + task_id=tid, + agent="test_runner", + context_text=sample * 20, # large enough to trigger + threshold=100, + ) + assert result["action"] == "checkpoint_saved" + print(f"✅ auto_checkpoint → {result['checkpoint_id']} ({result['reduction_pct']}% reduction)") + + print("-" * 40) + print("🎉 All tests passed!") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser(description="Context Optimizer & Token Budget Advisor") + parser.add_argument("--analyze", action="store_true", help="Analyze context file or stdin") + parser.add_argument("--compress", action="store_true", help="Compress context to budget") + parser.add_argument("--suggest", action="store_true", help="Get pruning suggestions") + parser.add_argument("--auto-checkpoint", action="store_true", help="Auto-save checkpoint if over threshold") + parser.add_argument("--test", action="store_true", help="Run self-tests") + parser.add_argument("--input", default=None, metavar="FILE", help="Input context file (uses stdin if not provided)") + parser.add_argument("--output", default=None, metavar="FILE", help="Output file for compressed context") + parser.add_argument("--budget", type=int, default=4000, help="Token budget") + parser.add_argument("--model", default="default", help="Target model for limit check") + parser.add_argument("--task-id", default=None) + parser.add_argument("--agent", default="unknown") + parser.add_argument("--threshold", type=int, default=CRITICAL_TOKEN_LIMIT) + + args = parser.parse_args() + + if args.test: + _run_tests() + return + + # Load text + text = "" + if args.input: + p = Path(args.input) + if not p.exists(): + print(f"ERROR: File not found: {args.input}", file=sys.stderr) + sys.exit(1) + text = p.read_text(encoding="utf-8") + elif not sys.stdin.isatty(): + text = sys.stdin.read() + + if args.analyze: + report = analyze_context(text, model=args.model) + for k, v in report.items(): + print(f" {k}: {v}") + + elif args.compress: + if not text: + print("ERROR: Provide --input file or pipe text via stdin", file=sys.stderr) + sys.exit(1) + result = compress_to_budget(text, budget=args.budget) + if args.output: + Path(args.output).write_text(result, encoding="utf-8") + print(f"Compressed context written to {args.output}") + else: + print(result) + + elif args.suggest: + if not text: + print("ERROR: Provide --input file or pipe text via stdin", file=sys.stderr) + sys.exit(1) + for line in suggest_pruning(text, budget=args.budget): + print(line) + + elif args.auto_checkpoint: + if not args.task_id: + print("ERROR: --task-id required for auto-checkpoint", file=sys.stderr) + sys.exit(1) + if not text: + print("ERROR: Provide --input file or pipe text via stdin", file=sys.stderr) + sys.exit(1) + result = auto_checkpoint( + task_id=args.task_id, + agent=args.agent, + context_text=text, + threshold=args.threshold, + ) + print(json.dumps(result, indent=2)) + + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/.agent/scripts/mem.py b/.agent/scripts/mem.py new file mode 100644 index 0000000000000000000000000000000000000000..a27ab381c56fc4ecb3801a675f12164319a357a2 --- /dev/null +++ b/.agent/scripts/mem.py @@ -0,0 +1,485 @@ +""" +mem.py — Unified Short CLI Dispatcher for the Agent Memory System +================================================================= +Single-entry dispatcher. All long commands become short subcommands. +Detects current agent/IDE automatically. + +Aliases installed by setup_memory_hook.ps1 / .bashrc: + mem → python .Agent/scripts/mem.py + mem ctx → get context for auto-detected agent + mem status → session status + mem write → start/update a task + mem cp → save checkpoint + mem log → log token usage + mem search → search memory + mem done → complete task + mem report → usage report + mem tips → optimization tips + mem watch → live watcher + mem know → save knowledge + mem open → open dashboard in browser + mem analyze → analyze context file for token cost + mem compress → compress context file to budget + mem init → init new session + +Usage examples: + mem status + mem ctx # auto-detects agent from env/IDE + mem ctx --agent claude # explicit agent + mem write "Build auth module" + mem cp abc123 "Step 1 done. Next: routes." + mem log 1200 800 # input output tokens (auto-detects agent/model) + mem log 1200 800 --agent zed --model gpt-4o + mem search "auth module" + mem done abc123 "Feature complete" + mem report + mem tips + mem watch + mem open + mem know "Key decision" "Use JWT not sessions" + mem analyze context.txt + mem compress context.txt 4000 + mem init +""" + +from __future__ import annotations + +import json +import os +import platform +import re +import subprocess +import sys +import webbrowser +from pathlib import Path + +# --------------------------------------------------------------------------- +# Path bootstrap — works from ANY directory +# --------------------------------------------------------------------------- +_SCRIPTS = Path(__file__).resolve().parent +_AGENT = _SCRIPTS.parent +sys.path.insert(0, str(_SCRIPTS)) + +import memory_engine as _me +import usage_tracker as _ut +import context_optimizer as _co +from activate import print_status, init_session, read_session, read_store + + +def _find_agent_dir() -> Path: + """Find the .Agent dir: script-relative first, then walk up from CWD.""" + if (_AGENT / "memory").is_dir(): + return _AGENT + for d in [Path.cwd(), *Path.cwd().parents]: + c = d / ".Agent" + if c.is_dir() and (c / "memory").is_dir(): + return c + return _AGENT # last resort + +# --------------------------------------------------------------------------- +# Auto-detect current agent & model +# --------------------------------------------------------------------------- + +_IDE_MAP: dict[str, str] = { + # Environment variable → agent name + "CURSOR_SESSIONID": "cursor", + "VSCODE_PID": "vscode-copilot", + "VSCODE_INJECTION_VERSION": "vscode-copilot", + "JB_IDE": "jetbrains-ai", + "IDEA_INITIAL_DIRECTORY": "jetbrains-ai", + "ZED_PID": "zed-ai", + "SUBLIME_SESSION": "sublime", + "TERM_PROGRAM": None, # handled below + "GEMINI_API_KEY": "gemini", + "ANTHROPIC_API_KEY": "claude", + "OPENAI_API_KEY": "openai", +} + +_TERMINAL_PROGRAM_MAP: dict[str, str] = { + "hyper": "terminal", + "warp": "warp", + "alacritty": "terminal", + "iTerm.app": "terminal", + "WindowsTerminal": "terminal", +} + +def detect_agent() -> str: + """Auto-detect which AI agent / IDE is running this command.""" + env = os.environ + + # 1. Explicit override wins + if "MEM_AGENT" in env: + return env["MEM_AGENT"] + + # 2. Check known IDE env vars + for var, name in _IDE_MAP.items(): + if var in env: + if name: + return name + if var == "TERM_PROGRAM": + tp = env.get("TERM_PROGRAM", "").lower() + return _TERMINAL_PROGRAM_MAP.get(tp, "terminal") + + # 3. Check parent process name on Windows + if platform.system() == "Windows": + try: + ppid = os.getppid() + out = subprocess.check_output( + ["wmic", "process", "where", f"processid={ppid}", "get", "Name"], + text=True, stderr=subprocess.DEVNULL + ).strip().split("\n") + name = out[-1].strip().lower() + if "code" in name: return "vscode-copilot" + if "idea" in name: return "jetbrains-ai" + if "pycharm" in name: return "jetbrains-ai" + if "zed" in name: return "zed-ai" + if "sublime" in name: return "sublime" + if "cursor" in name: return "cursor" + if "warp" in name: return "warp" + except Exception: + pass + + # 4. Fallback: active session agent + session = _me._load_json(_me.SESSION_PATH) + if session.get("active_agent"): + return session["active_agent"] + + return "terminal" + + +def detect_model(agent: str) -> str: + """ + Return the last model used by this agent in this workspace. + Reads from usage-tracker.json — fully dynamic, no hardcoded map. + Falls back to 'default' if no history exists. + """ + try: + usage_path = _find_agent_dir() / "memory" / "usage-tracker.json" + if not usage_path.exists(): + return "default" + data = json.loads(usage_path.read_text(encoding="utf-8")) + # Walk sessions newest-first, find the last model this agent used + for session in data.get("sessions", []): + if session.get("agent", "").lower() == agent.lower(): + return session["model"] + # Fallback: check totals.models_used + totals = data.get("totals", {}) + for key, val in totals.items(): + if key.lower() == agent.lower(): + used = val.get("models_used", []) + if used: + return used[-1] + except Exception: + pass + return "default" + + +# --------------------------------------------------------------------------- +# Subcommand handlers +# --------------------------------------------------------------------------- + +def cmd_status(_args: list[str]) -> None: + session = read_session() + store = read_store() + print_status(session, store) + + +def cmd_init(_args: list[str]) -> None: + """Initialize a new session.""" + agent_dir = _AGENT + workspace = agent_dir.parent + session = init_session(workspace) + store = read_store() + print_status(session, store) + print(f" ✅ Session started: {session['session_id']}") + + +def cmd_ctx(args: list[str]) -> None: + """Get context blob for agent (auto-detect or --agent ).""" + agent = _parse_flag(args, "--agent") or detect_agent() + try: + max_tokens = int(_parse_flag(args, "--max-tokens") or "4000") + except ValueError: + max_tokens = 4000 + print(_me.get_context_for_agent(agent, max_tokens=max_tokens)) + + +def cmd_write(args: list[str]) -> None: + """ + mem write "Task title" [--desc "..."] [--status in_progress] [--tags a,b] [--id existing_id] + """ + title = _positional(args, 0, "Untitled Task") + agent = _parse_flag(args, "--agent") or detect_agent() + description = _parse_flag(args, "--desc") or _parse_flag(args, "--description") or "" + status = _parse_flag(args, "--status") or "in_progress" + task_id = _parse_flag(args, "--id") or _parse_flag(args, "--task-id") + tags_raw = _parse_flag(args, "--tags") or "" + tags = [t.strip() for t in tags_raw.split(",") if t.strip()] + + tid = _me.write_memory( + title=title, agent=agent, description=description, + status=status, tags=tags, task_id=task_id, + ) + print(f"✅ Task saved id={tid} agent={agent} status={status}") + print(f" Title: {title}") + + +def cmd_cp(args: list[str]) -> None: + """ + mem cp "Summary of what was done" + """ + task_id = _positional(args, 0) + summary = _positional(args, 1, "Checkpoint") + agent = _parse_flag(args, "--agent") or detect_agent() + context = _parse_flag(args, "--context") or "" + + if not task_id: + # Try active task from session + session = _me._load_json(_me.SESSION_PATH) + task_id = session.get("active_task_id") + if not task_id: + print("ERROR: no active task. Provide task-id: mem cp 'summary'", file=sys.stderr) + sys.exit(1) + + cid = _me.save_checkpoint(task_id=task_id, agent=agent, summary=summary, context_snapshot=context) + print(f"✅ Checkpoint id={cid} task={task_id} agent={agent}") + print(f" Summary: {summary}") + + +def cmd_log(args: list[str]) -> None: + """ + mem log [--agent name] [--model name] [--task-id id] + """ + try: + inp = int(_positional(args, 0, "0")) + out = int(_positional(args, 1, "0")) + except ValueError: + print("ERROR: mem log ", file=sys.stderr); sys.exit(1) + + agent = _parse_flag(args, "--agent") or detect_agent() + model = _parse_flag(args, "--model") or detect_model(agent) + task_id = _parse_flag(args, "--task-id") or _active_task_id() + note = _parse_flag(args, "--note") or "" + + eid = _ut.log_usage(agent=agent, model=model, input_tokens=inp, output_tokens=out, + task_id=task_id, note=note) + cost = _ut._cost_usd(model, inp, out) + print(f"✅ Usage logged id={eid} agent={agent} model={model}") + print(f" Tokens: {inp+out:,} (in={inp:,} out={out:,}) Cost: ${cost:.4f}") + + +def cmd_search(args: list[str]) -> None: + query = " ".join(a for a in args if not a.startswith("--")) or "" + if not query: + print("ERROR: mem search ", file=sys.stderr); sys.exit(1) + results = _me.search_memory(query) + if not results: + print("No results found.") + return + for r in results: + label = r.get("title") or r.get("summary") or "—" + print(f" [{r['type']:10}] {r.get('agent','?'):15} {label[:60]}") + + +def cmd_done(args: list[str]) -> None: + """ + mem done [task-id] ["Summary of completion"] + """ + task_id = _positional(args, 0) or _active_task_id() + summary = _positional(args, 1, "") + if not task_id: + print("ERROR: no active task. Provide: mem done ", file=sys.stderr); sys.exit(1) + _me.complete_task(task_id, summary=summary) + print(f"✅ Task {task_id} marked as completed.") + if summary: + print(f" Summary: {summary}") + + +def cmd_report(args: list[str]) -> None: + agent_filter = _parse_flag(args, "--agent") or _positional(args, 0) + print(_ut.get_report(agent_filter=agent_filter or None)) + + +def cmd_tips(_args: list[str]) -> None: + print(_ut.get_optimization_tips()) + + +def cmd_watch(args: list[str]) -> None: + try: + interval = int(_parse_flag(args, "--interval") or "3") + except ValueError: + interval = 3 + sys.path.insert(0, str(_SCRIPTS)) + from workspace_watcher import watch + watch(interval=interval) + + +def cmd_know(args: list[str]) -> None: + """ + mem know "Title" "Content" [--tags a,b] + """ + title = _positional(args, 0, "Knowledge") + content = _positional(args, 1, "") + tags_raw = _parse_flag(args, "--tags") or "" + tags = [t.strip() for t in tags_raw.split(",") if t.strip()] + kid = _me.save_knowledge(title=title, content=content, tags=tags) + print(f"✅ Knowledge saved id={kid}") + print(f" {title}: {content[:60]}") + + +def cmd_open(_args: list[str]) -> None: + dashboard = _AGENT / "dashboard" / "index.html" + if not dashboard.exists(): + print(f"Dashboard not found: {dashboard}", file=sys.stderr); sys.exit(1) + url = dashboard.as_uri() + print(f"Opening dashboard: {url}") + webbrowser.open(url) + + +def cmd_analyze(args: list[str]) -> None: + """mem analyze [--model name]""" + path_arg = _positional(args, 0) + model = _parse_flag(args, "--model") or "default" + text = _read_file_or_stdin(path_arg) + report = _co.analyze_context(text, model=model) + _print_table(report) + + +def cmd_compress(args: list[str]) -> None: + """mem compress [--output file]""" + path_arg = _positional(args, 0) + try: + budget = int(_positional(args, 1, "4000")) + except ValueError: + budget = 4000 + out_path = _parse_flag(args, "--output") + text = _read_file_or_stdin(path_arg) + compressed = _co.compress_to_budget(text, budget=budget) + original = _me._estimate_tokens(text) + final = _me._estimate_tokens(compressed) + print(f"✅ Compressed: {original:,} → {final:,} tokens ({100*(1-final/original):.0f}% reduction)") + if out_path: + Path(out_path).write_text(compressed, encoding="utf-8") + print(f" Written to: {out_path}") + else: + print("\n" + compressed) + + +def cmd_agent(args: list[str]) -> None: + """mem agent — show detected agent and its last-used model from workspace history.""" + a = detect_agent() + m = detect_model(a) + active = _ut.get_active_models() + print(f" Detected agent : {a}") + print(f" Last model used : {m}") + print(f" Active models (ws) : {', '.join(active) if active else 'none yet'}") + print(f" Override agent via : MEM_AGENT= mem ctx") + + +def cmd_read(args: list[str]) -> None: + scope = _positional(args, 0) or "all" + print(json.dumps(_me.read_memory(scope), indent=2)) + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _positional(args: list[str], idx: int, default: str = "") -> str: + positional = [a for a in args if not a.startswith("--")] + return positional[idx] if idx < len(positional) else default + + +def _parse_flag(args: list[str], flag: str) -> str | None: + for i, a in enumerate(args): + if a == flag and i + 1 < len(args): + return args[i + 1] + if a.startswith(flag + "="): + return a.split("=", 1)[1] + return None + + +def _active_task_id() -> str | None: + session = _me._load_json(_me.SESSION_PATH) + return session.get("active_task_id") + + +def _read_file_or_stdin(path_arg: str | None) -> str: + if path_arg: + p = Path(path_arg) + if not p.exists(): + print(f"ERROR: File not found: {path_arg}", file=sys.stderr); sys.exit(1) + return p.read_text(encoding="utf-8") + if not sys.stdin.isatty(): + return sys.stdin.read() + print("ERROR: Provide a file path or pipe text via stdin", file=sys.stderr); sys.exit(1) + + +def _print_table(data: dict) -> None: + for k, v in data.items(): + print(f" {k:<30} {v}") + + +# --------------------------------------------------------------------------- +# Command registry +# --------------------------------------------------------------------------- + +COMMANDS: dict[str, tuple] = { + "status": (cmd_status, "Show current session status"), + "init": (cmd_init, "Initialize a new session"), + "ctx": (cmd_ctx, "Get context blob for agent (auto-detected)"), + "context": (cmd_ctx, "Alias for ctx"), + "write": (cmd_write, 'Write/update task: mem write "Title"'), + "cp": (cmd_cp, "Save checkpoint: mem cp [task-id] 'Summary'"), + "checkpoint":(cmd_cp, "Alias for cp"), + "log": (cmd_log, "Log token usage: mem log "), + "search": (cmd_search, "Search memory: mem search 'query'"), + "done": (cmd_done, "Complete task: mem done [task-id] 'Summary'"), + "complete": (cmd_done, "Alias for done"), + "report": (cmd_report, "Print usage report"), + "tips": (cmd_tips, "Print optimization tips"), + "watch": (cmd_watch, "Live memory watcher"), + "know": (cmd_know, "Save knowledge: mem know 'Title' 'Content'"), + "open": (cmd_open, "Open dashboard in browser"), + "analyze": (cmd_analyze, "Analyze context file: mem analyze file.txt"), + "compress": (cmd_compress, "Compress context: mem compress file.txt 4000"), + "agent": (cmd_agent, "Show auto-detected agent/model"), + "read": (cmd_read, "Read raw JSON store: mem read [all|tasks|checkpoints|knowledge|session]"), +} + + +def print_help() -> None: + print("\n🧠 mem — Unified Agent Memory CLI\n") + print(" Usage: mem [args]\n") + print(" Commands:") + for name, (_, desc) in COMMANDS.items(): + if name not in ("context", "checkpoint", "complete"): # skip aliases + print(f" {name:<12} {desc}") + print() + print(" Auto-detects agent from IDE/env. Override: MEM_AGENT=claude mem ctx") + print(" All data stored locally in .Agent/memory/ — no server required.\n") + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + +def main() -> None: + args = sys.argv[1:] + if not args or args[0] in ("-h", "--help", "help"): + print_help() + return + + sub = args[0].lower() + rest = args[1:] + + if sub in COMMANDS: + COMMANDS[sub][0](rest) + else: + print(f"Unknown command: {sub}\n", file=sys.stderr) + print_help() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.agent/scripts/memory_engine.py b/.agent/scripts/memory_engine.py new file mode 100644 index 0000000000000000000000000000000000000000..d73adf233d73a84f5b1173e1881463211722a8c1 --- /dev/null +++ b/.agent/scripts/memory_engine.py @@ -0,0 +1,521 @@ +""" +memory_engine.py — Unified Agent Memory Engine +================================================ +Core library for reading, writing, compressing, and searching the shared +agent memory store. Used by all agents: Antigravity, Gemini, Claude, +Codex, terminal scripts, or any custom agent. + +Usage: + python memory_engine.py --read + python memory_engine.py --write --task "Build auth module" --agent "antigravity" --status "in_progress" + python memory_engine.py --checkpoint --task-id --summary "Completed DB schema" + python memory_engine.py --search "auth module" + python memory_engine.py --context --agent "gemini" --max-tokens 4000 + python memory_engine.py --test +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +import uuid +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +# --------------------------------------------------------------------------- +# Paths +# --------------------------------------------------------------------------- +AGENT_DIR = Path(__file__).parent.parent +MEMORY_DIR = AGENT_DIR / "memory" +STORE_PATH = MEMORY_DIR / "memory-store.json" +SESSION_PATH = MEMORY_DIR / "session.json" +USAGE_PATH = MEMORY_DIR / "usage-tracker.json" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _now_iso() -> str: + return datetime.now(timezone.utc).astimezone().isoformat() + + +def _load_json(path: Path) -> dict[str, Any]: + if not path.exists(): + return {} + with open(path, encoding="utf-8") as f: + return json.load(f) + + +def _save_json(path: Path, data: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with open(path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + +def _estimate_tokens(text: str) -> int: + """Rough estimate: ~4 chars per token (GPT-style tokenisation).""" + return max(1, len(text) // 4) + + +# --------------------------------------------------------------------------- +# Core API +# --------------------------------------------------------------------------- + +def read_memory(scope: str = "all") -> dict[str, Any]: + """ + Load the current memory store. + scope: "all" | "tasks" | "checkpoints" | "knowledge" | "session" + """ + if scope == "session": + return _load_json(SESSION_PATH) + + store = _load_json(STORE_PATH) + if scope == "all": + return store + return {scope: store.get(scope, [])} + + +def write_memory( + title: str, + agent: str, + description: str = "", + status: str = "in_progress", + tags: list[str] | None = None, + task_id: str | None = None, +) -> str: + """ + Create or update a task entry. Returns the task_id. + `agent` can be any string: "antigravity", "gemini", "claude", "terminal", etc. + """ + store = _load_json(STORE_PATH) + store.setdefault("tasks", []) + store.setdefault("checkpoints", []) + store.setdefault("knowledge", []) + + now = _now_iso() + + # Check if updating an existing task + existing = next((t for t in store["tasks"] if t.get("id") == task_id), None) + + if existing and task_id: + existing.update( + { + "title": title, + "agent": agent, + "description": description, + "status": status, + "tags": tags or existing.get("tags", []), + "updated_at": now, + } + ) + _save_json(STORE_PATH, store) + _update_session(task_id=task_id, agent=agent, status=status) + return task_id + + # New task + tid = task_id or str(uuid.uuid4())[:8] + task: dict[str, Any] = { + "id": tid, + "title": title, + "agent": agent, + "description": description, + "status": status, + "tags": tags or [], + "created_at": now, + "updated_at": now, + "checkpoints": [], + } + store["tasks"].insert(0, task) + _save_json(STORE_PATH, store) + _update_session(task_id=tid, agent=agent, status=status) + return tid + + +def save_checkpoint( + task_id: str, + summary: str, + agent: str, + context_snapshot: str = "", +) -> str: + """ + Save a compressed mid-task snapshot so agents can resume after context reset. + Returns checkpoint_id. + """ + store = _load_json(STORE_PATH) + store.setdefault("checkpoints", []) + store.setdefault("tasks", []) + + now = _now_iso() + cid = str(uuid.uuid4())[:8] + + # Compress the context snapshot + compressed = compress_context(context_snapshot) if context_snapshot else "" + + checkpoint: dict[str, Any] = { + "id": cid, + "task_id": task_id, + "agent": agent, + "summary": summary, + "compressed_context": compressed, + "token_estimate": _estimate_tokens(compressed), + "created_at": now, + } + store["checkpoints"].insert(0, checkpoint) + + # Link to task + task = next((t for t in store["tasks"] if t.get("id") == task_id), None) + if task: + task.setdefault("checkpoints", []).append(cid) + task["updated_at"] = now + + _save_json(STORE_PATH, store) + + # Update session last_checkpoint + session = _load_json(SESSION_PATH) + session["last_checkpoint_at"] = now + _save_json(SESSION_PATH, session) + + return cid + + +def search_memory(query: str, limit: int = 10) -> list[dict[str, Any]]: + """ + Fuzzy keyword search across tasks, checkpoints, and knowledge entries. + Returns ranked list of matches. + """ + store = _load_json(STORE_PATH) + results: list[dict[str, Any]] = [] + terms = [t.lower() for t in query.split()] + + def _score(text: str) -> int: + text_lower = text.lower() + return sum(1 for term in terms if term in text_lower) + + for task in store.get("tasks", []): + combined = f"{task.get('title','')} {task.get('description','')} {' '.join(task.get('tags', []))}" + score = _score(combined) + if score > 0: + results.append({"type": "task", "score": score, **task}) + + for cp in store.get("checkpoints", []): + combined = f"{cp.get('summary','')} {cp.get('compressed_context','')}" + score = _score(combined) + if score > 0: + results.append({"type": "checkpoint", "score": score, **cp}) + + for kn in store.get("knowledge", []): + combined = f"{kn.get('title','')} {kn.get('content','')}" + score = _score(combined) + if score > 0: + results.append({"type": "knowledge", "score": score, **kn}) + + results.sort(key=lambda x: x["score"], reverse=True) + return results[:limit] + + +def get_context_for_agent(agent_name: str, max_tokens: int = 4000) -> str: + """ + Build a budget-aware context blob for an agent to inject at conversation start. + Prioritises: active task → recent checkpoints → relevant knowledge. + """ + session = _load_json(SESSION_PATH) + store = _load_json(STORE_PATH) + + lines: list[str] = [ + "", + f"# Agent Memory System — Context for: {agent_name}", + f"# Loaded: {_now_iso()}", + "", + ] + tokens_used = 0 + budget = max_tokens + + # Active task + active_id = session.get("active_task_id") + if active_id: + task = next((t for t in store.get("tasks", []) if t["id"] == active_id), None) + if task: + block = ( + f"## Active Task\n" + f"- **ID**: {task['id']}\n" + f"- **Title**: {task['title']}\n" + f"- **Agent**: {task['agent']}\n" + f"- **Status**: {task['status']}\n" + f"- **Description**: {task.get('description', 'N/A')}\n" + f"- **Updated**: {task['updated_at']}\n" + ) + tokens_used += _estimate_tokens(block) + lines.append(block) + budget -= tokens_used + + # Recent checkpoints for the active task + if active_id and budget > 200: + relevant_cps = [ + cp for cp in store.get("checkpoints", []) if cp.get("task_id") == active_id + ][:3] + if relevant_cps: + lines.append("## Recent Checkpoints") + for cp in relevant_cps: + entry = f"- [{cp['created_at']}] {cp['summary']}" + if cp.get("compressed_context"): + entry += f"\n Context: {cp['compressed_context'][:200]}..." + t = _estimate_tokens(entry) + if budget - t <= 0: + break + lines.append(entry) + budget -= t + + # Knowledge snippets (all, trimmed to budget) + if budget > 100: + knowledge = store.get("knowledge", [])[:5] + if knowledge: + lines.append("\n## Knowledge Base") + for kn in knowledge: + entry = f"- **{kn.get('title','')}**: {kn.get('content','')[:150]}" + t = _estimate_tokens(entry) + if budget - t <= 0: + break + lines.append(entry) + budget -= t + + # Recent tasks (excluding active) + recent_tasks = [ + t for t in store.get("tasks", []) if t.get("id") != active_id + ][:5] + if recent_tasks and budget > 100: + lines.append("\n## Recent Tasks") + for t in recent_tasks: + entry = f"- [{t['status']}] {t['title']} (by {t['agent']}, {t['updated_at'][:10]})" + lines.append(entry) + + lines.append("\n") + return "\n".join(lines) + + +def compress_context(text: str, max_sentences: int = 10) -> str: + """ + Extractive compression: scores sentences by keyword frequency and keeps top N. + No external AI required — pure Python. + """ + if not text.strip(): + return "" + + # Sentence tokenization (simple split on . ! ?) + sentences = re.split(r"(?<=[.!?])\s+", text.strip()) + if len(sentences) <= max_sentences: + return text + + # Score by word frequency (TF-style) + words = re.findall(r"\w+", text.lower()) + freq: dict[str, int] = {} + for word in words: + if len(word) > 3: # ignore short stop words + freq[word] = freq.get(word, 0) + 1 + + def _score_sentence(sentence: str) -> float: + s_words = re.findall(r"\w+", sentence.lower()) + if not s_words: + return 0.0 + return sum(freq.get(w, 0) for w in s_words) / len(s_words) + + scored = [(s, _score_sentence(s)) for s in sentences] + scored.sort(key=lambda x: x[1], reverse=True) + top = [s for s, _ in scored[:max_sentences]] + + # Preserve original order + order = {s: i for i, s in enumerate(sentences)} + top.sort(key=lambda s: order.get(s, 0)) + return " ".join(top) + + +def save_knowledge(title: str, content: str, tags: list[str] | None = None) -> str: + """Save a reusable fact/decision to the knowledge base.""" + store = _load_json(STORE_PATH) + store.setdefault("knowledge", []) + kid = str(uuid.uuid4())[:8] + store["knowledge"].insert(0, { + "id": kid, + "title": title, + "content": content, + "tags": tags or [], + "created_at": _now_iso(), + }) + _save_json(STORE_PATH, store) + return kid + + +def complete_task(task_id: str, summary: str = "") -> None: + """Mark a task as completed and save a final checkpoint.""" + store = _load_json(STORE_PATH) + task = next((t for t in store.get("tasks", []) if t["id"] == task_id), None) + if task: + task["status"] = "completed" + task["updated_at"] = _now_iso() + if summary: + task["completion_summary"] = summary + _save_json(STORE_PATH, store) + _update_session(task_id=None, agent=None, status="idle") + + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- + +def _update_session( + task_id: str | None, + agent: str | None, + status: str = "in_progress", +) -> None: + session = _load_json(SESSION_PATH) + session["active_task_id"] = task_id + session["active_agent"] = agent + session["status"] = status + if task_id and not session.get("session_id"): + session["session_id"] = str(uuid.uuid4())[:8] + session["started_at"] = _now_iso() + _save_json(SESSION_PATH, session) + + +# --------------------------------------------------------------------------- +# Self-test +# --------------------------------------------------------------------------- + +def _run_tests() -> None: + print("🧪 memory_engine.py — Self Test") + print("-" * 40) + + # Write task + tid = write_memory( + title="Test Task: Memory Engine Validation", + agent="test_runner", + description="Verifying read/write/compress/search cycle", + status="in_progress", + ) + print(f"✅ write_memory → task_id={tid}") + + # Checkpoint + cid = save_checkpoint( + task_id=tid, + agent="test_runner", + summary="Step 1 complete", + context_snapshot="The memory engine stores tasks and checkpoints. It supports any agent. Compression reduces large context to key sentences. Search finds relevant tasks by keyword matching.", + ) + print(f"✅ save_checkpoint → {cid}") + + # Read + mem = read_memory("tasks") + assert any(t["id"] == tid for t in mem["tasks"]), "Task not found in store" + print("✅ read_memory → task found") + + # Compress + long_text = " . ".join(["The agent must read memory at session start"] * 20) + compressed = compress_context(long_text, max_sentences=3) + assert len(compressed) < len(long_text), "Compression did not reduce size" + print(f"✅ compress_context → {len(long_text)} → {len(compressed)} chars") + + # Search + results = search_memory("memory engine validation") + assert len(results) > 0, "Search returned no results" + print(f"✅ search_memory → {len(results)} result(s)") + + # Context blob + blob = get_context_for_agent("gemini", max_tokens=2000) + assert "" in blob + print(f"✅ get_context_for_agent → {_estimate_tokens(blob)} tokens") + + # Knowledge + kid = save_knowledge("Dynamic agents", "Any agent name is valid — no hardcoded list") + print(f"✅ save_knowledge → {kid}") + + # Complete + complete_task(tid, summary="All engine tests passed") + store = _load_json(STORE_PATH) + task = next(t for t in store["tasks"] if t["id"] == tid) + assert task["status"] == "completed" + print("✅ complete_task → status=completed") + + print("-" * 40) + print("🎉 All tests passed!") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser(description="Agent Memory Engine") + parser.add_argument("--read", action="store_true", help="Read current memory (pretty print)") + parser.add_argument("--write", action="store_true", help="Write/update a task") + parser.add_argument("--checkpoint", action="store_true", help="Save a checkpoint") + parser.add_argument("--search", metavar="QUERY", help="Search memory") + parser.add_argument("--context", action="store_true", help="Get context blob for agent") + parser.add_argument("--complete", action="store_true", help="Mark task as completed") + parser.add_argument("--know", action="store_true", help="Save a knowledge entry") + parser.add_argument("--test", action="store_true", help="Run self-tests") + parser.add_argument("--task-id", default=None) + parser.add_argument("--task", "--title", default="Untitled Task", dest="title") + parser.add_argument("--agent", default="unknown") + parser.add_argument("--status", default="in_progress") + parser.add_argument("--description", "--desc", default="", dest="description") + parser.add_argument("--summary", default="") + parser.add_argument("--content", default="") + parser.add_argument("--tags", default="", help="Comma-separated tags") + parser.add_argument("--max-tokens", type=int, default=4000) + parser.add_argument("--scope", default="all") + + args = parser.parse_args() + tags = [t.strip() for t in args.tags.split(",") if t.strip()] + + if args.test: + _run_tests() + elif args.read: + print(json.dumps(read_memory(args.scope), indent=2)) + elif args.write: + tid = write_memory( + title=args.title, + agent=args.agent, + description=args.description, + status=args.status, + tags=tags, + task_id=args.task_id, + ) + print(f"Task saved: {tid}") + elif args.checkpoint: + if not args.task_id: + print("ERROR: --task-id is required for checkpoints", file=sys.stderr) + sys.exit(1) + cid = save_checkpoint( + task_id=args.task_id, + agent=args.agent, + summary=args.summary, + context_snapshot=args.content, + ) + print(f"Checkpoint saved: {cid}") + elif args.search: + results = search_memory(args.search) + if not results: + print("No results found.") + else: + for r in results: + print(f"[{r['type']}] (score={r['score']}) {r.get('title', r.get('summary',''))}") + elif args.context: + print(get_context_for_agent(args.agent, max_tokens=args.max_tokens)) + elif args.complete: + if not args.task_id: + print("ERROR: --task-id required", file=sys.stderr) + sys.exit(1) + complete_task(args.task_id, summary=args.summary) + print(f"Task {args.task_id} marked as completed.") + elif args.know: + kid = save_knowledge(title=args.title, content=args.content, tags=tags) + print(f"Knowledge saved: {kid}") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/.agent/scripts/shell_completions.py b/.agent/scripts/shell_completions.py new file mode 100644 index 0000000000000000000000000000000000000000..cb94c08f6f31bdc0ec784fc8b0b191436857fc64 --- /dev/null +++ b/.agent/scripts/shell_completions.py @@ -0,0 +1,260 @@ +""" +shell_completions.py — Generate Shell Completions for `mem` command +==================================================================== +Completions are FULLY DYNAMIC — models and agents are read from +the actual usage-tracker.json so completions reflect only what is +in use in this workspace. Falls back to an empty list for agents +and models until usage data exists. + +Usage: + python .Agent/scripts/shell_completions.py powershell >> $PROFILE + python .Agent/scripts/shell_completions.py bash >> ~/.bashrc + python .Agent/scripts/shell_completions.py zsh >> ~/.zshrc + python .Agent/scripts/shell_completions.py --list # print current live data +""" + +from __future__ import annotations +import json +import sys +from pathlib import Path + +# --------------------------------------------------------------------------- +# Dynamic data — reads from actual workspace usage files +# --------------------------------------------------------------------------- + +def _find_agent_dir() -> Path: + """Find .Agent directory from script location or CWD walk.""" + script_based = Path(__file__).resolve().parent.parent + if (script_based / "memory").is_dir(): + return script_based + for d in [Path.cwd(), *Path.cwd().parents]: + c = d / ".Agent" + if c.is_dir() and (c / "memory").is_dir(): + return c + return script_based + + +def get_live_agents() -> list[str]: + """Return agent names actually used in this workspace.""" + agent_dir = _find_agent_dir() + path = agent_dir / "memory" / "usage-tracker.json" + if not path.exists(): + return [] + try: + data = json.loads(path.read_text(encoding="utf-8")) + agents = sorted({s.get("agent", "") for s in data.get("sessions", [])} | + set(data.get("totals", {}).keys())) + return [a for a in agents if a] + except Exception: + return [] + + +def get_live_models() -> list[str]: + """Return model names actually logged in this workspace's usage-tracker.""" + agent_dir = _find_agent_dir() + path = agent_dir / "memory" / "usage-tracker.json" + if not path.exists(): + return [] + try: + data = json.loads(path.read_text(encoding="utf-8")) + models = sorted({s.get("model", "") for s in data.get("sessions", [])}) + return [m for m in models if m] + except Exception: + return [] + + +def get_known_models() -> list[str]: + """Return all models in model_costs.json (broader than just used ones).""" + agent_dir = _find_agent_dir() + costs_path = agent_dir / "memory" / "model_costs.json" + if not costs_path.exists(): + return get_live_models() + try: + data = json.loads(costs_path.read_text(encoding="utf-8")) + return sorted(k for k in data.keys() if not k.startswith("_")) + except Exception: + return get_live_models() + + +# Static commands — these don't change +MEM_COMMANDS = [ + "status", "init", "ctx", "write", "cp", "log", + "search", "done", "report", "tips", "watch", "know", + "open", "analyze", "compress", "agent", "read", "help", +] + +MEM_FLAGS_BY_CMD: dict[str, list[str]] = { + "ctx": ["--agent", "--max-tokens"], + "write": ["--agent", "--desc", "--status", "--tags", "--id"], + "cp": ["--agent", "--context"], + "log": ["--agent", "--model", "--task-id", "--note"], + "report": ["--agent"], + "analyze": ["--model"], + "compress": ["--output"], + "watch": ["--interval"], +} + +# --------------------------------------------------------------------------- +# PowerShell +# --------------------------------------------------------------------------- + +def powershell_completion() -> str: + agents = get_live_agents() + models = get_known_models() # broader: known pricing config + cmds = " ".join(f'"{c}"' for c in MEM_COMMANDS) + ag_str = " ".join(f'"{a}"' for a in agents) + mo_str = " ".join(f'"{m}"' for m in models) + + # Build switch arms for per-subcommand flag completion + switch_arms = "\n".join( + f" '{cmd}' {{ {', '.join(repr(f) for f in flags)} }}" + for cmd, flags in MEM_FLAGS_BY_CMD.items() + ) + + return f""" +## BEGIN: MemCompletion ################################################## +# Tab completion for `mem` — agents and models from workspace usage data + +$global:_mem_cmds = @({cmds}) +$global:_mem_agents = @({ag_str or '"(no agents yet — use: mem log ... --agent )"'}) +$global:_mem_models = @({mo_str or '"(no models yet — use: mem log ... --model )"'}) + +Register-ArgumentCompleter -Native -CommandName @('mem','mem-ctx','mem-write','mem-cp','mem-log','mem-search','mem-done','mem-report') -ScriptBlock {{ + param($word, $cmd, $cursor) + $tokens = $cmd.CommandElements + $prev = if ($tokens.Count -gt 1) {{ $tokens[-1].Value }} else {{ '' }} + + if ($tokens.Count -le 2 -and $cmd.CommandElements[0].Value -eq 'mem') {{ + return $global:_mem_cmds | Where-Object {{ $_ -like "$word*" }} | + ForEach-Object {{ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }} + }} + if ($prev -eq '--agent') {{ + return $global:_mem_agents | Where-Object {{ $_ -like "$word*" }} | + ForEach-Object {{ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }} + }} + if ($prev -eq '--model') {{ + return $global:_mem_models | Where-Object {{ $_ -like "$word*" }} | + ForEach-Object {{ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }} + }} + if ($word -like '--*') {{ + $sub = if ($tokens.Count -gt 1) {{ $tokens[1].Value }} else {{ '' }} + $flags = switch ($sub) {{ +{switch_arms} + default {{ @() }} + }} + return $flags | Where-Object {{ $_ -like "$word*" }} | + ForEach-Object {{ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }} + }} +}} +## END: MemCompletion ################################################### +""" + + +# --------------------------------------------------------------------------- +# Bash +# --------------------------------------------------------------------------- + +def bash_completion() -> str: + agents = " ".join(get_live_agents()) + models = " ".join(get_known_models()) + cmds = " ".join(MEM_COMMANDS) + + flag_cases = "\n".join( + f" {cmd}) flags=\"{' '.join(flags)}\" ;;" + for cmd, flags in MEM_FLAGS_BY_CMD.items() + ) + + return f""" +## BEGIN: MemCompletion ################################################## +_mem_completions() {{ + local cur prev words + cur="${{COMP_WORDS[COMP_CWORD]}}" + prev="${{COMP_WORDS[COMP_CWORD-1]}}" + words=(${{COMP_WORDS[@]}}) + + if [[ ${{#words[@]}} -le 2 ]]; then + COMPREPLY=($(compgen -W "{cmds}" -- "$cur")) + elif [[ "$prev" == "--agent" ]]; then + COMPREPLY=($(compgen -W "{agents}" -- "$cur")) + elif [[ "$prev" == "--model" ]]; then + COMPREPLY=($(compgen -W "{models}" -- "$cur")) + elif [[ "$cur" == --* ]]; then + local sub="${{words[1]}}" flags="" + case "$sub" in +{flag_cases} + esac + COMPREPLY=($(compgen -W "$flags" -- "$cur")) + fi +}} +complete -F _mem_completions mem +## END: MemCompletion ################################################### +""" + + +# --------------------------------------------------------------------------- +# Zsh +# --------------------------------------------------------------------------- + +def zsh_completion() -> str: + agents = " ".join(get_live_agents()) + models = " ".join(get_known_models()) + cmds = " ".join(MEM_COMMANDS) + + flag_cases = "\n".join( + f" {cmd}) flags=({' '.join(flags)}) ;;" + for cmd, flags in MEM_FLAGS_BY_CMD.items() + ) + + return f""" +## BEGIN: MemCompletion ################################################## +_mem_complete() {{ + local -a cmds agents models flags + cmds=({cmds}) + agents=({agents}) + models=({models}) + + if (( CURRENT == 2 )); then + _describe 'command' cmds + elif [[ ${{words[${{CURRENT-1}}]}} == '--agent' ]]; then + _describe 'agent' agents + elif [[ ${{words[${{CURRENT-1}}]}} == '--model' ]]; then + _describe 'model' models + else + case ${{words[2]}} in +{flag_cases} + esac + _describe 'option' flags + fi +}} +compdef _mem_complete mem +## END: MemCompletion ################################################### +""" + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main() -> None: + if "--list" in sys.argv: + agents = get_live_agents() + models = get_known_models() + print(f" Workspace agents ({len(agents)}): {', '.join(agents) or 'none yet'}") + print(f" Known models ({len(models)}): {', '.join(models) or 'none yet'}") + print(f" Active models : {', '.join(get_live_models()) or 'none yet'}") + return + + shell = sys.argv[1].lower() if len(sys.argv) > 1 and not sys.argv[1].startswith("--") else "powershell" + if shell == "powershell": + print(powershell_completion()) + elif shell == "bash": + print(bash_completion()) + elif shell == "zsh": + print(zsh_completion()) + else: + print(f"Unknown shell: {shell}. Options: powershell, bash, zsh", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.agent/scripts/usage_tracker.py b/.agent/scripts/usage_tracker.py new file mode 100644 index 0000000000000000000000000000000000000000..3d01fe0da3efe35635fcda1ff4707a2af965134b --- /dev/null +++ b/.agent/scripts/usage_tracker.py @@ -0,0 +1,485 @@ +""" +usage_tracker.py — Dynamic LLM Token Usage Tracker +==================================================== +Logs token usage per agent (any string identifier) and per model. +Model cost config is loaded from memory/model_costs.json — fully editable. +New models auto-discover pricing from a built-in seed table and persist it. +Only models actually used in this workspace appear in reports. + +Usage: + python usage_tracker.py --log --agent "antigravity" --model "gemini-2.5-pro" --input 1200 --output 800 + python usage_tracker.py --log --agent "terminal" --model "my-custom-llm" --input 500 --output 300 + python usage_tracker.py --report + python usage_tracker.py --tips + python usage_tracker.py --models # list all known models + cost + python usage_tracker.py --add-model "my-llm" --cost-input 1.5 --cost-output 6.0 + python usage_tracker.py --test +""" + +from __future__ import annotations + +import argparse +import json +import sys +import uuid +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +# --------------------------------------------------------------------------- +# Path resolution — works from ANY directory +# --------------------------------------------------------------------------- +def _find_agent_dir() -> Path: + """Walk from CWD up to find .Agent directory. Falls back to script's parent.""" + # Primary: script-relative (always correct when called via `mem` alias) + script_based = Path(__file__).resolve().parent.parent + if (script_based / "memory").is_dir(): + return script_based + # Fallback: walk CWD upward + for d in [Path.cwd(), *Path.cwd().parents]: + candidate = d / ".Agent" + if candidate.is_dir() and (candidate / "memory").is_dir(): + return candidate + # Last resort: script-relative anyway + return script_based + +AGENT_DIR = _find_agent_dir() +MEMORY_DIR = AGENT_DIR / "memory" +USAGE_PATH = MEMORY_DIR / "usage-tracker.json" +COSTS_PATH = MEMORY_DIR / "model_costs.json" + +# --------------------------------------------------------------------------- +# Built-in seed pricing (USD per 1M tokens) — only used to bootstrap config +# This is NOT the source of truth; model_costs.json is. Update via: +# mem log ... --model (auto-seeds) +# python usage_tracker.py --add-model "name" --cost-input 1.5 --cost-output 6.0 +# --------------------------------------------------------------------------- +_SEED_COSTS: dict[str, dict[str, float]] = { + "gemini-2.5-pro": {"input": 1.25, "output": 10.0}, + "gemini-2.0-flash": {"input": 0.10, "output": 0.40}, + "gemini-1.5-pro": {"input": 1.25, "output": 5.00}, + "claude-3-7-sonnet": {"input": 3.00, "output": 15.0}, + "claude-3-5-sonnet": {"input": 3.00, "output": 15.0}, + "claude-3-5-haiku": {"input": 0.80, "output": 4.00}, + "claude-3-opus": {"input": 15.0, "output": 75.0}, + "gpt-4o": {"input": 2.50, "output": 10.0}, + "gpt-4o-mini": {"input": 0.15, "output": 0.60}, + "gpt-4-turbo": {"input": 10.0, "output": 30.0}, + "o1": {"input": 15.0, "output": 60.0}, + "o3": {"input": 10.0, "output": 40.0}, + "o3-mini": {"input": 1.10, "output": 4.40}, + "codex-davinci": {"input": 2.00, "output": 2.00}, + "deepseek-v3": {"input": 0.27, "output": 1.10}, + "deepseek-r1": {"input": 0.55, "output": 2.19}, + "mistral-large": {"input": 2.00, "output": 6.00}, + "llama-3.3-70b": {"input": 0.59, "output": 0.79}, + "jetbrains-llm": {"input": 0.00, "output": 0.00}, + "default": {"input": 1.00, "output": 4.00}, +} + +# --------------------------------------------------------------------------- +# Model cost store (dynamic) — backed by model_costs.json +# --------------------------------------------------------------------------- + +def _load_costs() -> dict[str, dict[str, float]]: + """Load model costs from config file. Seeds file if missing or incomplete.""" + if not COSTS_PATH.exists(): + _save_costs({"default": _SEED_COSTS["default"]}) + with open(COSTS_PATH, encoding="utf-8") as f: + raw = json.load(f) + raw.pop("_comment", None) + # Always ensure 'default' exists + if "default" not in raw: + raw["default"] = _SEED_COSTS["default"] + return raw + + +def _save_costs(costs: dict[str, dict[str, float]]) -> None: + COSTS_PATH.parent.mkdir(parents=True, exist_ok=True) + out: dict[str, Any] = { + "_comment": ( + "Model pricing (USD per 1M tokens). " + "Edit freely or run: mem log --add-model --cost-input X --cost-output Y" + ), + **costs, + } + with open(COSTS_PATH, "w", encoding="utf-8") as f: + json.dump(out, f, indent=2, ensure_ascii=False) + + +def _ensure_model_cost(model: str) -> None: + """Auto-seed a new model into model_costs.json from the seed table if not present.""" + costs = _load_costs() + if model not in costs: + seed = _SEED_COSTS.get(model, _SEED_COSTS["default"]) + costs[model] = seed + _save_costs(costs) + + +def add_model_cost(model: str, cost_input: float, cost_output: float) -> None: + """Manually register or update pricing for a model.""" + costs = _load_costs() + costs[model] = {"input": cost_input, "output": cost_output} + _save_costs(costs) + + +def get_known_models() -> list[str]: + """Return all models with known pricing in this workspace.""" + return [k for k in _load_costs().keys() if not k.startswith("_")] + + +def get_active_models() -> list[str]: + """Return only models actually logged in this workspace's usage-tracker.""" + data = _load() + seen = {s.get("model", "") for s in data.get("sessions", [])} + return sorted(m for m in seen if m) + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _now_iso() -> str: + return datetime.now(timezone.utc).astimezone().isoformat() + + +def _load() -> dict[str, Any]: + if not USAGE_PATH.exists(): + return {"version": "1.0.0", "sessions": [], "totals": {}} + with open(USAGE_PATH, encoding="utf-8") as f: + return json.load(f) + + +def _save(data: dict[str, Any]) -> None: + USAGE_PATH.parent.mkdir(parents=True, exist_ok=True) + with open(USAGE_PATH, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + +def _cost_usd(model: str, input_tokens: int, output_tokens: int) -> float: + costs = _load_costs() + c = costs.get(model) or costs.get("default", {"input": 1.0, "output": 4.0}) + return (input_tokens * c["input"] + output_tokens * c["output"]) / 1_000_000 + + +# --------------------------------------------------------------------------- +# Core API +# --------------------------------------------------------------------------- + +def log_usage( + agent: str, + model: str, + input_tokens: int, + output_tokens: int, + task_id: str | None = None, + note: str = "", +) -> str: + """ + Log a usage event. `agent` and `model` are any strings — fully dynamic. + Auto-seeds model cost if not yet in config. + Returns the session_entry_id. + """ + _ensure_model_cost(model) # auto-register unknown models + + data = _load() + data.setdefault("sessions", []) + data.setdefault("totals", {}) + + entry_id = str(uuid.uuid4())[:8] + now = _now_iso() + cost = _cost_usd(model, input_tokens, output_tokens) + total_tokens = input_tokens + output_tokens + + entry: dict[str, Any] = { + "id": entry_id, + "agent": agent, + "model": model, + "input_tokens": input_tokens, + "output_tokens": output_tokens, + "total_tokens": total_tokens, + "cost_usd": round(cost, 6), + "task_id": task_id, + "note": note, + "logged_at": now, + } + data["sessions"].insert(0, entry) + + # Totals — fully dynamic; agent and model keys created on-demand + totals = data["totals"] + totals.setdefault(agent, {}) + ag: dict[str, Any] = totals[agent] + ag.setdefault("input_tokens", 0) + ag.setdefault("output_tokens", 0) + ag.setdefault("total_tokens", 0) + ag.setdefault("cost_usd", 0.0) + ag.setdefault("session_count", 0) + ag.setdefault("models_used", []) + + ag["input_tokens"] += input_tokens + ag["output_tokens"] += output_tokens + ag["total_tokens"] += total_tokens + ag["cost_usd"] = round(ag["cost_usd"] + cost, 6) + ag["session_count"] += 1 + if model not in ag["models_used"]: + ag["models_used"].append(model) + + _save(data) + return entry_id + + +def get_report(agent_filter: str | None = None) -> str: + data = _load() + totals = data.get("totals", {}) + sessions = data.get("sessions", []) + + if agent_filter: + totals = {k: v for k, v in totals.items() if agent_filter.lower() in k.lower()} + sessions = [s for s in sessions if agent_filter.lower() in s.get("agent", "").lower()] + + lines = [ + "╔══════════════════════════════════════════════════════╗", + "║ 🧠 Unified Agent Token Usage Report ║", + "╚══════════════════════════════════════════════════════╝", + "", + ] + + if not totals: + lines.append(" No usage data recorded yet.") + return "\n".join(lines) + + grand_tokens = 0 + grand_cost = 0.0 + + for agent_name, stats in sorted(totals.items()): + bar_len = min(40, stats["total_tokens"] // 1000) + bar = "█" * bar_len + "░" * (40 - bar_len) + models = ", ".join(stats.get("models_used", [])) + lines += [ + f" Agent: {agent_name}", + f" ├─ Input : {stats['input_tokens']:>10,} tokens", + f" ├─ Output : {stats['output_tokens']:>10,} tokens", + f" ├─ Total : {stats['total_tokens']:>10,} tokens [{bar}]", + f" ├─ Cost : ${stats['cost_usd']:>10.4f} USD", + f" ├─ Sessions: {stats['session_count']}", + f" └─ Models : {models or 'N/A'}", + "", + ] + grand_tokens += stats["total_tokens"] + grand_cost += stats["cost_usd"] + + lines += [ + "─" * 54, + f" GRAND TOTAL : {grand_tokens:,} tokens", + f" GRAND COST : ${grand_cost:.4f} USD", + "─" * 54, + "", + ] + + # Active models in workspace + active = get_active_models() + if active: + lines.append(f" Active models in workspace: {', '.join(active)}") + + recent = sessions[:5] + if recent: + lines.append("\n Recent Sessions:") + for s in recent: + lines.append( + f" [{s['logged_at'][:10]}] {s['agent']} / {s['model']}" + f" — {s['total_tokens']:,} tokens (${s['cost_usd']:.4f})" + + (f" — {s['note']}" if s.get("note") else "") + ) + + return "\n".join(lines) + + +def get_optimization_tips() -> str: + data = _load() + sessions = data.get("sessions", []) + totals = data.get("totals", {}) + + lines = [ + "╔══════════════════════════════════════════════════════╗", + "║ ⚡ Token Optimization Recommendations ║", + "╚══════════════════════════════════════════════════════╝", + "", + ] + + if not sessions: + lines.append(" No usage data to analyse yet.") + return "\n".join(lines) + + # Tip 1: High output/input ratio + for agent_name, stats in totals.items(): + if stats.get("input_tokens", 0) > 0: + ratio = stats["output_tokens"] / stats["input_tokens"] + if ratio > 2: + lines.append( + f" WARNING [{agent_name}] Output/Input ratio is {ratio:.1f}x — " + f"use --max-tokens or tighter prompts." + ) + + # Tip 2: Large sessions + large = [s for s in sessions if s["total_tokens"] > 50_000] + if large: + lines.append( + f" WARNING {len(large)} session(s) used >50K tokens. " + f"Use `mem compress` to pre-compress context." + ) + + # Tip 3: Expensive models — inferred from cost config + costs = _load_costs() + active_models = get_active_models() + expensive = [m for m in active_models if costs.get(m, {}).get("output", 0) >= 10.0] + cheaper = [m for m in get_known_models() if costs.get(m, {}).get("output", 99) < 1.0 and m != "default"] + if expensive and cheaper: + lines.append( + f" TIP Expensive models in use: {', '.join(expensive)}. " + f"Cheaper alternatives: {', '.join(cheaper[:3])}." + ) + + # Tip 4: Daily average + dates = [s["logged_at"][:10] for s in sessions] + days = len(set(dates)) or 1 + daily_avg = sum(s["total_tokens"] for s in sessions) / days + lines.append(f" INFO Daily average: {daily_avg:,.0f} tokens/day") + + # Tip 5: Checkpoint advice + lines.append( + " TIP Save checkpoints every ~30K tokens:\n" + " mem cp \"Step done. Next: ...\"" + ) + lines.append( + " TIP Compress context before large tasks:\n" + " mem compress context.txt 4000" + ) + + return "\n".join(lines) + + +def get_summary_for_dashboard() -> dict[str, Any]: + """Return structured data for the HTML dashboard — only active workspace data.""" + data = _load() + active_models = get_active_models() + costs = _load_costs() + active_costs = {m: costs[m] for m in active_models if m in costs} + + return { + "totals": data.get("totals", {}), + "recent_sessions": data.get("sessions", [])[:20], + "active_models": active_models, + "model_costs": active_costs, # only models used in this workspace + } + + +# --------------------------------------------------------------------------- +# Self-test +# --------------------------------------------------------------------------- + +def _run_tests() -> None: + print("🧪 usage_tracker.py — Self Test") + print("-" * 40) + + agents = ["antigravity", "gemini", "claude", "codex", "terminal", "my_custom_agent"] + models = ["gemini-2.5-pro", "claude-3-7-sonnet", "gpt-4o", "gemini-2.0-flash", "gpt-4o-mini", "my-brand-new-llm"] + + for i, (ag, mo) in enumerate(zip(agents, models)): + eid = log_usage( + agent=ag, model=mo, + input_tokens=(i + 1) * 1000, output_tokens=(i + 1) * 500, + task_id=f"test-{i}", note="Self-test", + ) + print(f"✅ log_usage [{ag} / {mo}] → {eid}") + + # Verify unknown model was auto-seeded into model_costs.json + costs = _load_costs() + assert "my-brand-new-llm" in costs, "Unknown model not auto-seeded" + print("✅ Unknown model 'my-brand-new-llm' auto-seeded into model_costs.json") + + report = get_report() + assert "GRAND TOTAL" in report + print("✅ get_report → generated") + + filtered = get_report(agent_filter="gemini") + assert "gemini" in filtered + print("✅ get_report (filtered) → generated") + + tips = get_optimization_tips() + assert "Token Optimization" in tips + print("✅ get_optimization_tips → generated") + + summary = get_summary_for_dashboard() + assert "active_models" in summary + assert "my-brand-new-llm" in summary["active_models"] + print(f"✅ get_summary_for_dashboard → {len(summary['totals'])} agents, {len(summary['active_models'])} active models") + + active = get_active_models() + assert "my-brand-new-llm" in active + print(f"✅ get_active_models → {active}") + + print("-" * 40) + print("🎉 All tests passed!") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser(description="Agent Token Usage Tracker") + sub = parser.add_subparsers(dest="cmd") + + # Legacy flags (backward compat) + parser.add_argument("--log", action="store_true") + parser.add_argument("--report", action="store_true") + parser.add_argument("--tips", action="store_true") + parser.add_argument("--dashboard-data",action="store_true") + parser.add_argument("--models", action="store_true", help="List known models + cost") + parser.add_argument("--add-model", metavar="MODEL", help="Register model pricing") + parser.add_argument("--cost-input", type=float, default=1.0) + parser.add_argument("--cost-output", type=float, default=4.0) + parser.add_argument("--test", action="store_true") + parser.add_argument("--agent", default="unknown") + parser.add_argument("--model", default="default") + parser.add_argument("--input", type=int, default=0, dest="input_tokens") + parser.add_argument("--output", type=int, default=0, dest="output_tokens") + parser.add_argument("--task-id", default=None) + parser.add_argument("--note", default="") + + args = parser.parse_args() + + if args.test: + _run_tests() + elif args.add_model: + add_model_cost(args.add_model, args.cost_input, args.cost_output) + print(f"Model '{args.add_model}' registered: input=${args.cost_input}/M output=${args.cost_output}/M") + elif args.models: + costs = _load_costs() + active = set(get_active_models()) + print(f"\n {'Model':<30} {'Input/1M':>10} {'Output/1M':>10} {'In Use'}") + print(f" {'-'*30} {'-'*10} {'-'*10} {'-'*6}") + for m, c in sorted(costs.items()): + if m.startswith("_"): + continue + used = "YES" if m in active else "---" + print(f" {m:<30} ${c['input']:>9.2f} ${c['output']:>9.2f} {used}") + print() + elif args.log: + eid = log_usage( + agent=args.agent, model=args.model, + input_tokens=args.input_tokens, output_tokens=args.output_tokens, + task_id=args.task_id, note=args.note, + ) + print(f"Usage logged: {eid}") + elif args.report: + print(get_report(agent_filter=args.agent if args.agent != "unknown" else None)) + elif args.tips: + print(get_optimization_tips()) + elif args.dashboard_data: + print(json.dumps(get_summary_for_dashboard(), indent=2)) + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/.agent/scripts/workspace_watcher.py b/.agent/scripts/workspace_watcher.py new file mode 100644 index 0000000000000000000000000000000000000000..9118d34f51fc5a87eb8ff0924297607381cc082b --- /dev/null +++ b/.agent/scripts/workspace_watcher.py @@ -0,0 +1,163 @@ +""" +workspace_watcher.py — File-system Watcher for Agent Memory +============================================================ +Watches the .Agent/memory/ directory for changes and: + - Re-prints status when session.json or memory-store.json changes + - Provides --status flag for a one-shot status print + - Provides --watch for continuous monitoring (uses polling; no deps) + +Usage: + python workspace_watcher.py --status + python workspace_watcher.py --watch + python workspace_watcher.py --watch --interval 5 +""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +import time +from datetime import datetime, timezone +from pathlib import Path + +# --------------------------------------------------------------------------- +# Paths +# --------------------------------------------------------------------------- +_THIS_DIR = Path(__file__).parent +AGENT_DIR = _THIS_DIR.parent +MEMORY_DIR = AGENT_DIR / "memory" +SESSION_PATH = MEMORY_DIR / "session.json" +STORE_PATH = MEMORY_DIR / "memory-store.json" +USAGE_PATH = MEMORY_DIR / "usage-tracker.json" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _load(path: Path) -> dict: + if not path.exists(): + return {} + try: + with open(path, encoding="utf-8") as f: + return json.load(f) + except json.JSONDecodeError: + return {} + + +def _now() -> str: + return datetime.now(timezone.utc).astimezone().strftime("%H:%M:%S") + + +def _mtime(path: Path) -> float: + return path.stat().st_mtime if path.exists() else 0.0 + + +def _token_bar(total: int, max_tokens: int = 100_000) -> str: + pct = min(1.0, total / max_tokens) + filled = int(pct * 30) + color = "🟢" if pct < 0.5 else ("🟡" if pct < 0.8 else "🔴") + return f"{color} [{'█' * filled}{'░' * (30 - filled)}] {pct*100:.0f}%" + + +# --------------------------------------------------------------------------- +# Status printer +# --------------------------------------------------------------------------- + +def print_status() -> None: + session = _load(SESSION_PATH) + store = _load(STORE_PATH) + usage = _load(USAGE_PATH) + + print(f"\n╔══ 🧠 Agent Memory Status ─── {_now()} ══╗") + print(f" Session : {session.get('session_id', 'none')} | Status: {session.get('status', 'idle')}") + print(f" Workspace: {session.get('workspace', 'N/A')}") + + # Active task + active_id = session.get("active_task_id") + if active_id: + task = next((t for t in store.get("tasks", []) if t["id"] == active_id), None) + if task: + print(f"\n 📌 Active Task: [{task['status'].upper()}] {task['title']}") + print(f" Agent: {task['agent']} | Updated: {task['updated_at'][:19]}") + else: + recent = store.get("tasks", [])[:1] + if recent: + t = recent[0] + print(f"\n 📋 Last Task: [{t['status']}] {t['title']}") + + # Checkpoints + cps = store.get("checkpoints", []) + if cps: + cp = cps[0] + print(f" 💾 Last Checkpoint: {cp.get('summary','')[:60]} ({cp['created_at'][:10]})") + + # Context usage + ctx_tokens = session.get("context_tokens_used", 0) + ctx_budget = session.get("context_budget", 100_000) + if ctx_tokens > 0: + print(f"\n 🔢 Context Usage: {_token_bar(ctx_tokens, ctx_budget)} {ctx_tokens:,}/{ctx_budget:,} tokens") + + # Usage totals (dynamic agents) + totals = usage.get("totals", {}) + if totals: + print(f"\n 📊 Token Usage (all agents):") + for agent_name, stats in sorted(totals.items()): + tot = stats.get("total_tokens", 0) + cost = stats.get("cost_usd", 0.0) + print(f" {agent_name:<20} {tot:>10,} tokens ${cost:.4f}") + + print(f"\n Tasks: {len(store.get('tasks', []))} | Checkpoints: {len(store.get('checkpoints', []))} | Knowledge: {len(store.get('knowledge', []))}") + print("╚══════════════════════════════════════════════════════╝\n") + + +# --------------------------------------------------------------------------- +# Watcher (polling — no external dependencies) +# --------------------------------------------------------------------------- + +def watch(interval: int = 3) -> None: + print(f"👁️ Watching .Agent/memory/ for changes (interval={interval}s) — Ctrl+C to stop\n") + mtimes = { + SESSION_PATH: _mtime(SESSION_PATH), + STORE_PATH: _mtime(STORE_PATH), + USAGE_PATH: _mtime(USAGE_PATH), + } + print_status() + + try: + while True: + time.sleep(interval) + changed = False + for path in list(mtimes.keys()): + new_mt = _mtime(path) + if new_mt != mtimes[path]: + mtimes[path] = new_mt + changed = True + if changed: + print_status() + except KeyboardInterrupt: + print("\n Watcher stopped.") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser(description="Agent Memory Workspace Watcher") + parser.add_argument("--status", action="store_true", help="Print current status and exit") + parser.add_argument("--watch", action="store_true", help="Watch memory folder for changes") + parser.add_argument("--interval", type=int, default=3, help="Polling interval in seconds (default: 3)") + args = parser.parse_args() + + if args.status: + print_status() + elif args.watch: + watch(interval=args.interval) + else: + print_status() # default: show status once + + +if __name__ == "__main__": + main() diff --git a/.agent/setup_memory_hook.ps1 b/.agent/setup_memory_hook.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..5b2ae13a436273677869f4535eacf496e00f1deb --- /dev/null +++ b/.agent/setup_memory_hook.ps1 @@ -0,0 +1,216 @@ +# setup_memory_hook.ps1 v2 - Universal Agent Memory Auto-Activation +# ================================================================== +# Installs short `mem` aliases, tab-completion, CD-hook, git hook. +# +# Usage (run once): +# powershell -ExecutionPolicy Bypass -File .Agent\setup_memory_hook.ps1 +# +# Options: +# -Uninstall Remove all hooks from profile +# -GitHookOnly Only install git post-commit hook +# ================================================================== + +param( + [switch]$Uninstall, + [switch]$GitHookOnly +) + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$WorkDir = Split-Path -Parent $ScriptDir +$MemPy = Join-Path $ScriptDir "scripts\mem.py" +$CompPy = Join-Path $ScriptDir "scripts\shell_completions.py" +$PostCommitPy = Join-Path $ScriptDir "hooks\post-commit.py" + +function Install-GitHook { + $gitDir = Join-Path $WorkDir ".git" + if (-not (Test-Path $gitDir)) { + Write-Host " SKIP: No .git directory found - skipping git hook" -ForegroundColor Yellow + return + } + $hooksDir = Join-Path $gitDir "hooks" + $hookFile = Join-Path $hooksDir "post-commit" + New-Item -ItemType Directory -Path $hooksDir -Force | Out-Null + $content = "#!/bin/sh`npython `"$($PostCommitPy.Replace('\','/'))`"" + Set-Content -Path $hookFile -Value $content -Encoding utf8 -NoNewline + Write-Host " OK: Git post-commit hook installed" -ForegroundColor Green +} + +$profileBlock = @' + +## BEGIN: AgentMemorySystem ################################################ +# Unified Agent Memory System - Auto-activated on workspace detection +# Uninstall: remove the BEGIN/END block from $PROFILE + +# -- Core dispatcher -------------------------------------------------------- +function global:mem { + $agentDir = Get-AgentDir + if (-not $agentDir) { + Write-Host "WARN: No .Agent folder found in this workspace" -ForegroundColor Yellow + return + } + python "$agentDir\scripts\mem.py" @args +} + +# -- Short aliases ---------------------------------------------------------- +function global:mem-status { mem status } +function global:mem-ctx { mem ctx @args } +function global:mem-write { mem write @args } +function global:mem-cp { mem cp @args } +function global:mem-log { mem log @args } +function global:mem-search { mem search @args } +function global:mem-done { mem done @args } +function global:mem-report { mem report @args } +function global:mem-tips { mem tips } +function global:mem-watch { mem watch @args } +function global:mem-know { mem know @args } +function global:mem-open { mem open } +function global:mem-compress { mem compress @args } +function global:mem-analyze { mem analyze @args } +function global:mem-agent { mem agent } +function global:mem-init { mem init } + +# -- Workspace resolver ----------------------------------------------------- +function global:Get-AgentDir { + $current = (Get-Location).Path + while ($current) { + $candidate = Join-Path $current ".Agent" + if (Test-Path $candidate -PathType Container) { return $candidate } + $parent = Split-Path $current -Parent + if ($parent -eq $current) { return $null } + $current = $parent + } + return $null +} + +# -- Tab completion --------------------------------------------------------- +$global:_mem_cmds = @("status","init","ctx","write","cp","log","search","done","report","tips","watch","know","open","analyze","compress","agent","read","help") +$global:_mem_agents = @("antigravity","gemini","claude","codex","cursor","copilot","jetbrains-ai","zed-ai","sublime","terminal","warp","my_script") +$global:_mem_models = @("gemini-2.5-pro","gemini-2.0-flash","claude-3-7-sonnet","claude-3-5-haiku","gpt-4o","gpt-4o-mini","o3","codex-davinci","default") + +Register-ArgumentCompleter -Native -CommandName @('mem','mem-ctx','mem-write','mem-cp','mem-log','mem-search','mem-done','mem-report') -ScriptBlock { + param($word, $cmd, $cursor) + $tokens = $cmd.CommandElements + $prev = if ($tokens.Count -gt 1) { $tokens[-1].Value } else { '' } + + if ($tokens.Count -le 2 -and $cmd.CommandElements[0].Value -eq 'mem') { + return $global:_mem_cmds | Where-Object { $_ -like "$word*" } | + ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } + } + if ($prev -eq '--agent') { + return $global:_mem_agents | Where-Object { $_ -like "$word*" } | + ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } + } + if ($prev -eq '--model') { + return $global:_mem_models | Where-Object { $_ -like "$word*" } | + ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } + } + if ($word -like '--*') { + $sub = if ($tokens.Count -gt 1) { $tokens[1].Value } else { '' } + $flags = switch ($sub) { + 'ctx' { '--agent','--max-tokens' } + 'write' { '--agent','--desc','--status','--tags','--id' } + 'cp' { '--agent','--context' } + 'log' { '--agent','--model','--task-id','--note' } + 'report' { '--agent' } + 'analyze' { '--model' } + 'compress' { '--output' } + 'watch' { '--interval' } + default { @() } + } + return $flags | Where-Object { $_ -like "$word*" } | + ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } + } +} + +# -- Auto-activate on cd ---------------------------------------------------- +function global:Invoke-AgentMemoryHook { + $agentDir = Get-AgentDir + if ($agentDir) { + $activate = Join-Path $agentDir "scripts\activate.py" + if (Test-Path $activate) { + python "$activate" 2>$null + } + } +} + +function global:Set-Location { + param([string]$Path) + Microsoft.PowerShell.Management\Set-Location @args + Invoke-AgentMemoryHook +} + +Invoke-AgentMemoryHook + +## END: AgentMemorySystem ################################################## +'@ + +# -- Uninstall -------------------------------------------------------------- +if ($Uninstall) { + if (Test-Path $PROFILE) { + $content = Get-Content $PROFILE -Raw + $cleaned = $content -replace '(?ms)## BEGIN: AgentMemorySystem.*?## END: AgentMemorySystem\s*', '' + Set-Content $PROFILE -Value $cleaned + Write-Host "OK: AgentMemorySystem removed from profile" -ForegroundColor Green + } + return +} + +# -- Git hook only ---------------------------------------------------------- +if ($GitHookOnly) { + Install-GitHook + return +} + +# -- Full install ----------------------------------------------------------- +if (-not (Test-Path $PROFILE)) { + New-Item -ItemType File -Path $PROFILE -Force | Out-Null +} + +$existing = Get-Content $PROFILE -Raw -ErrorAction SilentlyContinue + +if ($existing -and $existing.Contains("## BEGIN: AgentMemorySystem")) { + $updated = $existing -replace '(?ms)## BEGIN: AgentMemorySystem.*?## END: AgentMemorySystem', $profileBlock.Trim() + Set-Content $PROFILE -Value $updated + Write-Host "OK: AgentMemorySystem updated in profile" -ForegroundColor Cyan +} else { + Add-Content -Path $PROFILE -Value $profileBlock + Write-Host "" + Write-Host "Agent Memory System - Installed!" -ForegroundColor Cyan +} + +Install-GitHook + +# Generate Bash/Zsh completions +$hooksDir = Join-Path $ScriptDir "hooks" +New-Item -ItemType Directory -Path $hooksDir -Force | Out-Null +python $CompPy bash 2>$null | Set-Content (Join-Path $hooksDir "bash_completions.sh") -Encoding utf8 -ErrorAction SilentlyContinue +python $CompPy zsh 2>$null | Set-Content (Join-Path $hooksDir "zsh_completions.zsh") -Encoding utf8 -ErrorAction SilentlyContinue + +Write-Host "" +Write-Host " Profile : $PROFILE" +Write-Host "" +Write-Host " Short Aliases (available after reopening terminal):" -ForegroundColor Green +Write-Host " mem Show status" +Write-Host " mem ctx Load context (auto-detects agent)" +Write-Host " mem ctx --agent claude" +Write-Host " mem write 'Task' Start a task" +Write-Host " mem cp 'Summary' Checkpoint (uses active task)" +Write-Host " mem log 5000 2000 Log token usage" +Write-Host " mem log 5000 2000 --model gemini-2.5-pro --agent antigravity" +Write-Host " mem search 'query' Search all memory" +Write-Host " mem done 'Summary' Complete active task" +Write-Host " mem report Usage report" +Write-Host " mem tips Optimization advice" +Write-Host " mem open Open dashboard in browser" +Write-Host " mem watch Live watcher" +Write-Host " mem agent Show auto-detected agent" +Write-Host "" +Write-Host " Tab Completion:" -ForegroundColor Green +Write-Host " mem [TAB] All subcommands" +Write-Host " mem ctx --agent [TAB] All agent names" +Write-Host " mem log 0 0 --model [TAB] All model names" +Write-Host "" +Write-Host " Bash/Zsh completions saved to .Agent\hooks\" -ForegroundColor Green +Write-Host " Add to ~/.bashrc: source .Agent/hooks/bash_completions.sh" +Write-Host "" +Write-Host " Open a NEW terminal to activate." -ForegroundColor Yellow diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000000000000000000000000000000000000..18f355a69589dc6269385d87bce12d7d3327d19e --- /dev/null +++ b/.cursorrules @@ -0,0 +1,14 @@ + +## Cursor Memory Bootstrap + +When this workspace is loaded, initialize the shared memory context before +continuing: + +1. Run `python ../.Agent/scripts/mem.py ctx --agent cursor --max-tokens 4000`. +2. Load the returned `` into working context before proceeding. +3. If there is an active in-progress task, continue it unless the user redirects. +4. Save checkpoints with `python ../.Agent/scripts/mem.py cp "what was done. next step" --agent cursor`. +5. Finish with `python ../.Agent/scripts/mem.py done "summary"` and `python ../.Agent/scripts/mem.py log --agent cursor --model ` when token counts are available. + +Reference: `../.Agent/AGENT_MEMORY.md` + diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md new file mode 100644 index 0000000000000000000000000000000000000000..c8bed51434ba63c76d05182543bb9247ec41a609 --- /dev/null +++ b/.gemini/GEMINI.md @@ -0,0 +1,14 @@ + +## Antigravity Memory Bootstrap + +When this workspace is loaded, initialize the shared memory context before +continuing: + +1. Run `python ../.Agent/scripts/mem.py ctx --agent antigravity --max-tokens 4000`. +2. Load the returned `` into working context before proceeding. +3. If there is an active in-progress task, continue it unless the user redirects. +4. Save checkpoints with `python ../.Agent/scripts/mem.py cp "what was done. next step" --agent antigravity`. +5. Finish with `python ../.Agent/scripts/mem.py done "summary"` and `python ../.Agent/scripts/mem.py log --agent antigravity --model ` when token counts are available. + +Reference: `../.Agent/AGENT_MEMORY.md` + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000000000000000000000000000000000..b25dd61918a281ec93a7fd170c185d77d0b9532f --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,14 @@ + +## Copilot Memory Bootstrap + +When this workspace is loaded, initialize the shared memory context before +continuing: + +1. Run `python ../.Agent/scripts/mem.py ctx --agent copilot --max-tokens 4000`. +2. Load the returned `` into working context before proceeding. +3. If there is an active in-progress task, continue it unless the user redirects. +4. Save checkpoints with `python ../.Agent/scripts/mem.py cp "what was done. next step" --agent copilot`. +5. Finish with `python ../.Agent/scripts/mem.py done "summary"` and `python ../.Agent/scripts/mem.py log --agent copilot --model ` when token counts are available. + +Reference: `../.Agent/AGENT_MEMORY.md` + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5ef6a520780202a1d6addd833d800ccb1ecac0bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..45cb1650e58cd9057316af9062c755b9834484eb --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,74 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Mem: Init Session", + "type": "shell", + "command": "python", + "args": [ + "../.Agent/scripts/mem.py", + "init" + ], + "presentation": { + "reveal": "silent", + "panel": "shared", + "clear": true + }, + "runOptions": { + "runOn": "folderOpen", + "instanceLimit": 1 + } + }, + { + "label": "Mem: Load Context", + "type": "shell", + "command": "python", + "args": [ + "../.Agent/scripts/mem.py", + "ctx", + "--agent", + "${input:memAgent}" + ], + "presentation": { + "reveal": "always", + "panel": "shared", + "clear": true + } + }, + { + "label": "Mem: Status", + "type": "shell", + "command": "python", + "args": [ + "../.Agent/scripts/mem.py", + "status" + ], + "presentation": { + "reveal": "always", + "panel": "shared", + "clear": true + } + } + ], + "inputs": [ + { + "id": "memAgent", + "type": "pickString", + "description": "Which agent context should be loaded?", + "options": [ + "codex", + "copilot", + "cursor", + "claude", + "antigravity", + "claw", + "tiny-claw", + "pico-claw", + "micro-claw", + "nano-claw", + "rtiny-claw" + ], + "default": "codex" + } + ] +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..f2274b6e9cca03cd0c559635aafa4e955e5bac1f --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,16 @@ +{ + "tasks": [ + { + "label": "Mem: Status", + "command": "python ../.Agent/scripts/mem.py status" + }, + { + "label": "Mem: Load Context", + "command": "python ../.Agent/scripts/mem.py ctx --agent codex" + }, + { + "label": "Mem: Init Session", + "command": "python ../.Agent/scripts/mem.py init" + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000000000000000000000000000000..7a8a25989be0b4c21c65068f67f5768305fded07 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,14 @@ + +## Codex Memory Bootstrap + +When this workspace is loaded, initialize the shared memory context before +continuing: + +1. Run `python ../.Agent/scripts/mem.py ctx --agent codex --max-tokens 4000`. +2. Load the returned `` into working context before proceeding. +3. If there is an active in-progress task, continue it unless the user redirects. +4. Save checkpoints with `python ../.Agent/scripts/mem.py cp "what was done. next step" --agent codex`. +5. Finish with `python ../.Agent/scripts/mem.py done "summary"` and `python ../.Agent/scripts/mem.py log --agent codex --model ` when token counts are available. + +Reference: `../.Agent/AGENT_MEMORY.md` + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000000000000000000000000000000000..ee389589bcceb425f08c34ca75f3917462fe9f95 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,14 @@ + +## Claude Memory Bootstrap + +When this workspace is loaded, initialize the shared memory context before +continuing: + +1. Run `python ../.Agent/scripts/mem.py ctx --agent claude --max-tokens 4000`. +2. Load the returned `` into working context before proceeding. +3. If there is an active in-progress task, continue it unless the user redirects. +4. Save checkpoints with `python ../.Agent/scripts/mem.py cp "what was done. next step" --agent claude`. +5. Finish with `python ../.Agent/scripts/mem.py done "summary"` and `python ../.Agent/scripts/mem.py log --agent claude --model ` when token counts are available. + +Reference: `../.Agent/AGENT_MEMORY.md` + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..17ef37167875f7ff76944fb8c84c51fbb77b03f5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,57 @@ +# Use Node.js LTS (Alpine for smaller image size) +FROM node:20-alpine AS base + +# Step 1. Rebuild the source code only when needed +FROM base AS builder +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat python3 make g++ git +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./ +RUN \ + if [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ + elif [ -f yarn.lock ]; then yarn install --frozen-lockfile; \ + else echo "Lockfile not found." && npm i; \ + fi + +# Copy source code and build +COPY . . + +# Prevent Prisma/Next.js telemetry +ENV NEXT_TELEMETRY_DISABLED 1 + +# Generate the standalone Next.js build +RUN npm run build + +# Step 2. Production image, copy all the files and start next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/public ./public + +# Switch to the non-root user +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +# Note: server.js is created by next build from the standalone output +CMD ["node", "server.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..077a44cf1aff56d3602c93a6c47a7da6c3b810d1 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ + +# CodeVerse: Cloud IDE Framework + +CodeVerse is a modern, web-based Cloud IDE Orchestrator inspired by Google Project IDX. It leverages Docker to spin up containerized development environments on the fly. + +## Key Features + +- **Dynamic Workspaces**: Instantly provision `code-server` Docker containers per project. +- **Project IDX Emulators**: Built-in side-by-side split pane emulators. + - **Android**: KVM-accelerated `docker-android-x86` sidecar containers streaming over noVNC. + - **iOS**: Web-simulated fallback or true macOS/iOS simulation when integrated with Appetize.io. +- **Workspace Configuration (`codeverse.json`)**: + - Similar to `.idx/dev.nix`, place a `codeverse.json` in your project root to configure the environment. + - **`env`**: Inject custom Linux environment variables. + - **`packages.apt`**: Define Debian packages to be installed globally on container boot. + - **`packages.npm`**: Define NPM globals to be installed on boot. + - **Live Rebuild API**: Modify your config and click "Rebuild Environment" in the IDE to immediately destroy and re-provision your workspace container with the new settings. +- **Next.js & Turbopack Frontend**: Fast, responsive dashboard and IDE client interface. +- **Model Context Protocol (MCP)**: Implements specialized AI orchestration connecting deep file system and terminal tools directly to LLMs. + +## `codeverse.json` Schema Reference + +```json +{ + "env": { + "PORT": "3000", + "CUSTOM_VAR": "value" + }, + "packages": { + "apt": ["htop", "wget"], + "npm": ["firebase-tools", "yarn"] + }, + "ios": { + "appetizeUrl": "https://appetize.io/embed/your_token?device=iphone15pro" + } +} +``` diff --git a/app/api/agent/route.ts b/app/api/agent/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..84d0952ab432df5a4ac5a17657d716c596e1008c --- /dev/null +++ b/app/api/agent/route.ts @@ -0,0 +1,47 @@ +import { streamText, generateObject, LanguageModel } from "ai"; +import { z } from "zod"; +import { getModel } from "@/lib/agents/registry"; +import { coreTools } from "@/lib/mcp/tools"; +import { NextRequest } from "next/server"; + +export async function POST(req: NextRequest) { + try { + const { messages, modelId, mode = "execute", systemPrompt } = await req.json(); + const model: LanguageModel = getModel(modelId, req); + + if (mode === "plan") { + // In plan mode, we don't stream immediately. We generate a structured JSON plan first. + const result = await generateObject({ + model, + system: systemPrompt, + messages, + schema: z.object({ + goal: z.string().describe("A 1-sentence summary of the requested goal"), + steps: z.array( + z.object({ + id: z.string(), + description: z.string(), + filesData: z.array(z.string()).optional(), + }) + ).describe("The sequence of steps to execute to fulfill the user's request"), + }), + }); + + return new Response(JSON.stringify(result.object), { + headers: { "Content-Type": "application/json" } + }); + } + + // Execute mode: Standard stream-text with workspace tool calls enabled + const result = streamText({ + model, + system: systemPrompt, + messages, + tools: coreTools as any, + }); + + return result.toTextStreamResponse(); + } catch (error: any) { + return new Response(JSON.stringify({ error: error.message }), { status: 500 }); + } +} diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..86c9f3daa6ea3d485edaeb2fd15d97455e1cd3e2 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/auth"; + +export const { GET, POST } = handlers; diff --git a/app/api/billing/route.ts b/app/api/billing/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd5580fce4d05f1ef07cb85b93ebd3079845e2e4 --- /dev/null +++ b/app/api/billing/route.ts @@ -0,0 +1,26 @@ +import { NextRequest, NextResponse } from "next/server"; +import { logUsage, getUsageReport } from "@/lib/billing"; + +export async function GET() { + try { + const report = await getUsageReport(); + return NextResponse.json(report); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function POST(req: NextRequest) { + try { + const { agent = "codeverse_ui", modelId, inputTokens, outputTokens, taskTitle } = await req.json(); + + if (!modelId || typeof inputTokens !== 'number' || typeof outputTokens !== 'number') { + return NextResponse.json({ error: "Missing required tracking data" }, { status: 400 }); + } + + const entry = await logUsage(agent, modelId, inputTokens, outputTokens, taskTitle); + return NextResponse.json({ success: true, entry }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/app/api/browser/route.ts b/app/api/browser/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e65aab28fd5db6c83add5fdb3dddc82d830616a --- /dev/null +++ b/app/api/browser/route.ts @@ -0,0 +1,39 @@ +import { NextRequest, NextResponse } from "next/server"; +import { browserManager } from "@/lib/browser/playwright"; + +export async function POST(req: NextRequest) { + try { + const { action, payload } = await req.json(); + + switch (action) { + case "navigate": + if (!payload?.url) return NextResponse.json({ error: "URL required" }, { status: 400 }); + const finalUrl = await browserManager.navigate(payload.url); + return NextResponse.json({ url: finalUrl }); + + case "click": + if (!payload?.selector) return NextResponse.json({ error: "Selector required" }, { status: 400 }); + await browserManager.click(payload.selector); + return NextResponse.json({ success: true }); + + case "type": + if (!payload?.selector || !payload?.text) return NextResponse.json({ error: "Selector and text required" }, { status: 400 }); + await browserManager.type(payload.selector, payload.text); + return NextResponse.json({ success: true }); + + case "snapshot": + const data = await browserManager.getSnapshot(); + return NextResponse.json(data); + + case "close": + await browserManager.close(); + return NextResponse.json({ success: true }); + + default: + return NextResponse.json({ error: "Unknown action" }, { status: 400 }); + } + } catch (error: any) { + console.error("Browser API Error:", error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/app/api/collab/route.ts b/app/api/collab/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd8bd28e5270064a061fb4400738c5f36f891cff --- /dev/null +++ b/app/api/collab/route.ts @@ -0,0 +1,103 @@ +import { NextRequest } from "next/server"; +import { WebSocketServer } from "ws"; +import * as Y from "yjs"; +import * as awarenessProtocol from "y-protocols/awareness"; +import * as syncProtocol from "y-protocols/sync"; +import * as encoding from "lib0/encoding"; +import * as decoding from "lib0/decoding"; +import * as map from "lib0/map"; + +// Yjs shared documents keyed by document path +const docs = new Map(); +let wss: WebSocketServer | null = null; + +const getOrCreateDoc = (docName: string) => { + return map.setIfUndefined(docs, docName, () => { + const doc = new Y.Doc(); + const awareness = new awarenessProtocol.Awareness(doc); + return { doc, awareness }; + }); +}; + +export async function GET(req: NextRequest, res: any) { + if (!res.socket.server.yjs_ws) { + wss = new WebSocketServer({ noServer: true }); + + res.socket.server.on("upgrade", (request: any, socket: any, head: any) => { + const url = new URL(request.url, `ws://localhost`); + if (url.pathname.startsWith("/api/collab")) { + wss?.handleUpgrade(request, socket, head, (ws) => { + wss?.emit("connection", ws, request); + }); + } + }); + + wss.on("connection", (conn, req) => { + const url = new URL(req.url ?? "", "ws://localhost"); + const docName = url.searchParams.get("doc") ?? "default"; + const { doc, awareness } = getOrCreateDoc(docName); + + conn.binaryType = "arraybuffer"; + + const sendSyncStep1 = () => { + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, 0); // messageSync + syncProtocol.writeSyncStep1(encoder, doc); + conn.send(encoding.toUint8Array(encoder)); + }; + + sendSyncStep1(); + + // Send awareness state + const awarenessEncoder = encoding.createEncoder(); + encoding.writeVarUint(awarenessEncoder, 1); // messageAwareness + encoding.writeVarUint8Array( + awarenessEncoder, + awarenessProtocol.encodeAwarenessUpdate(awareness, Array.from(awareness.getStates().keys())) + ); + conn.send(encoding.toUint8Array(awarenessEncoder)); + + conn.on("message", (message: ArrayBuffer) => { + const encoder = encoding.createEncoder(); + const decoder = decoding.createDecoder(new Uint8Array(message)); + const messageType = decoding.readVarUint(decoder); + + if (messageType === 0) { + // Sync + encoding.writeVarUint(encoder, 0); + syncProtocol.readSyncMessage(decoder, encoder, doc, null); + if (encoding.length(encoder) > 1) { + conn.send(encoding.toUint8Array(encoder)); + } + } else if (messageType === 1) { + // Awareness + awarenessProtocol.applyAwarenessUpdate( + awareness, + decoding.readVarUint8Array(decoder), + conn + ); + } + }); + + const updateHandler = (update: Uint8Array, origin: any) => { + if (origin !== conn) { + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, 0); + syncProtocol.writeUpdate(encoder, update); + conn.send(encoding.toUint8Array(encoder)); + } + }; + + doc.on("update", updateHandler); + + conn.on("close", () => { + doc.off("update", updateHandler); + awarenessProtocol.removeAwarenessStates(awareness, [doc.clientID], null); + }); + }); + + res.socket.server.yjs_ws = wss; + } + + res.end(); +} diff --git a/app/api/fs/route.ts b/app/api/fs/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ebca97524c97441c80c78381da2a86c5bb6097f --- /dev/null +++ b/app/api/fs/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from "next/server"; +import { readDir, readFile, writeFile, deletePath } from "@/lib/fs"; + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const path = searchParams.get("path") || ""; + const action = searchParams.get("action") || "list"; + + try { + if (action === "list") { + const nodes = await readDir(path); + return NextResponse.json(nodes); + } + if (action === "read") { + const content = await readFile(path); + return NextResponse.json({ content }); + } + return NextResponse.json({ error: "Invalid action" }, { status: 400 }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function POST(req: NextRequest) { + try { + const { path, content } = await req.json(); + if (!path) return NextResponse.json({ error: "Path required" }, { status: 400 }); + + await writeFile(path, content); + return NextResponse.json({ success: true }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function DELETE(req: NextRequest) { + const { searchParams } = new URL(req.url); + const path = searchParams.get("path"); + + try { + if (!path) return NextResponse.json({ error: "Path required" }, { status: 400 }); + await deletePath(path); + return NextResponse.json({ success: true }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/app/api/git/route.ts b/app/api/git/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..55b3b73417463062bc390603494d5fc4f61bb095 --- /dev/null +++ b/app/api/git/route.ts @@ -0,0 +1,82 @@ +import { NextRequest, NextResponse } from "next/server"; +import { + getGitStatus, + getBranchList, + commitFiles, + getFileDiff, + pushBranch, + pullBranch, + checkoutBranch, + git +} from "@/lib/git"; + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const action = searchParams.get("action"); + + try { + switch (action) { + case "status": + return NextResponse.json(await getGitStatus()); + case "branches": + return NextResponse.json(await getBranchList()); + case "diff": + const file = searchParams.get("file"); + if (!file) return NextResponse.json({ error: "File required" }, { status: 400 }); + return NextResponse.json({ diff: await getFileDiff(file) }); + case "log": + const log = await git.log({ maxCount: 50 }); + return NextResponse.json(log.all); + case "checkConfig": + const name = await git.getConfig("user.name", "local"); + const email = await git.getConfig("user.email", "local"); + return NextResponse.json({ + configured: !!(name.value && email.value), + name: name.value, + email: email.value + }); + default: + return NextResponse.json({ error: "Invalid action" }, { status: 400 }); + } + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { action } = body; + + switch (action) { + case "commit": + if (!body.message) return NextResponse.json({ error: "Message required" }, { status: 400 }); + const res = await commitFiles(body.message, body.files); + return NextResponse.json({ success: true, commit: res.commit }); + + case "push": + await pushBranch(); + return NextResponse.json({ success: true }); + + case "pull": + await pullBranch(); + return NextResponse.json({ success: true }); + + case "checkout": + if (!body.branch) return NextResponse.json({ error: "Branch required" }, { status: 400 }); + await checkoutBranch(body.branch, body.create); + return NextResponse.json({ success: true }); + + case "setConfig": + if (!body.name || !body.email) return NextResponse.json({ error: "Name and email required" }, { status: 400 }); + await git.addConfig("user.name", body.name, false, "local"); + await git.addConfig("user.email", body.email, false, "local"); + return NextResponse.json({ success: true }); + + default: + return NextResponse.json({ error: "Invalid action" }, { status: 400 }); + } + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/app/api/health/auth/route.ts b/app/api/health/auth/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f441fe46122e750ff303388aa3b4346909774a8 --- /dev/null +++ b/app/api/health/auth/route.ts @@ -0,0 +1,6 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + const hasGithubVars = !!process.env.GITHUB_ID && !!process.env.GITHUB_SECRET; + return NextResponse.json({ hasGithubVars }); +} diff --git a/app/api/lsp/route.ts b/app/api/lsp/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..b12551335ebd41660a4baf82764e4c11e468ff95 --- /dev/null +++ b/app/api/lsp/route.ts @@ -0,0 +1,95 @@ +import { NextRequest, NextResponse } from "next/server"; +import { spawn } from "child_process"; +import path from "path"; + +// Language Server mapping: extension → command to start the language server +const LSP_SERVERS: Record = { + py: { cmd: "pyright-langserver", args: ["--stdio"] }, + go: { cmd: "gopls", args: [] }, + rs: { cmd: "rust-analyzer", args: [] }, + rb: { cmd: "solargraph", args: ["stdio"] }, + java: { cmd: "jdtls", args: [] }, + cs: { cmd: "omnisharp", args: ["-lsp"] }, +}; + +// Store active LSP processes +const activeServers = new Map>(); + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const lang = searchParams.get("lang"); + + if (!lang) { + return NextResponse.json({ + available: Object.keys(LSP_SERVERS), + status: Object.fromEntries( + Object.keys(LSP_SERVERS).map(l => [l, activeServers.has(l) ? "running" : "idle"]) + ) + }); + } + + const config = LSP_SERVERS[lang]; + if (!config) { + return NextResponse.json({ error: `No LSP configured for .${lang}` }, { status: 404 }); + } + + return NextResponse.json({ + lang, + status: activeServers.has(lang) ? "running" : "idle", + cmd: config.cmd + }); +} + +export async function POST(req: NextRequest) { + try { + const { action, lang } = await req.json(); + + if (action === "start") { + if (!lang) return NextResponse.json({ error: "lang required" }, { status: 400 }); + + const config = LSP_SERVERS[lang]; + if (!config) return NextResponse.json({ error: `No LSP for .${lang}` }, { status: 404 }); + + if (activeServers.has(lang)) { + return NextResponse.json({ status: "already_running" }); + } + + try { + const proc = spawn(config.cmd, config.args, { + cwd: process.cwd(), + stdio: ["pipe", "pipe", "pipe"], + shell: process.platform === "win32" + }); + + activeServers.set(lang, proc); + + proc.on("exit", () => activeServers.delete(lang)); + proc.on("error", (err) => { + console.warn(`LSP ${lang}: ${err.message} — install ${config.cmd} to enable`); + activeServers.delete(lang); + }); + + return NextResponse.json({ status: "started", pid: proc.pid }); + } catch { + return NextResponse.json({ + status: "unavailable", + message: `Install '${config.cmd}' to enable LSP for .${lang} files.` + }); + } + } + + if (action === "stop") { + if (!lang) return NextResponse.json({ error: "lang required" }, { status: 400 }); + const proc = activeServers.get(lang); + if (proc) { + proc.kill(); + activeServers.delete(lang); + } + return NextResponse.json({ status: "stopped" }); + } + + return NextResponse.json({ error: "Unknown action" }, { status: 400 }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/app/api/projects/route.ts b/app/api/projects/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..081b872a0250113d445ba1823742bf6aed82eb23 --- /dev/null +++ b/app/api/projects/route.ts @@ -0,0 +1,310 @@ +import { NextRequest, NextResponse } from "next/server"; +import path from "path"; +import fs from "fs/promises"; +import simpleGit from "simple-git"; +import { spawn } from "child_process"; +import type { PackageManager } from "@/lib/package-managers"; +import { auth } from "@/auth"; +import { db } from "@/lib/db"; +import { randomUUID } from "crypto"; + +const WORKSPACE_ROOT = path.join(process.cwd(), "workspaces"); + +async function ensureWorkspaceRoot() { + await fs.mkdir(WORKSPACE_ROOT, { recursive: true }); +} + +// POST /api/projects +export async function POST(req: NextRequest) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const userId = session.user.id; + + const { searchParams } = new URL(req.url); + const action = searchParams.get("action"); + + if (action === "clone") { + return handleClone(req, userId); + } else if (action === "scaffold") { + return handleScaffold(req, userId); + } else if (action === "install") { + return handleInstall(req); // install doesn't insert to db + } + + return NextResponse.json({ error: "Unknown action" }, { status: 400 }); +} + +export async function GET() { + return handleList(); +} + +export async function DELETE(req: NextRequest) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(req.url); + const workspaceId = searchParams.get("workspaceId"); + + if (!workspaceId) { + return NextResponse.json({ error: "Missing workspaceId" }, { status: 400 }); + } + + try { + // Fetch it from DB to ensure it belongs to the user + const res = await db.execute({ + sql: "SELECT project_name FROM workspaces WHERE id = ? AND user_id = ?", + args: [workspaceId, session.user.id] + }); + + if (res.rows.length === 0) { + return NextResponse.json({ error: "Workspace not found or unauthorized" }, { status: 404 }); + } + + const projectName = res.rows[0].project_name as string; + const safeName = projectName.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 60); + + // Remove from filesystem + const targetPath = path.join(WORKSPACE_ROOT, safeName); + if (targetPath.startsWith(WORKSPACE_ROOT)) { + await fs.rm(targetPath, { recursive: true, force: true }); + } + + // Remove from database + await db.execute({ + sql: "DELETE FROM workspaces WHERE id = ?", + args: [workspaceId] + }); + + return NextResponse.json({ success: true }); + } catch (e: unknown) { + if (e instanceof Error) { + return NextResponse.json({ error: e.message }, { status: 500 }); + } + return NextResponse.json({ error: e as string }, { status: 500 }); + } +} + +async function handleList() { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ projects: [] }); + } + + try { + const res = await db.execute({ + sql: "SELECT * FROM workspaces WHERE user_id = ?", + args: [session.user.id] + }); + + const projects = res.rows.map(row => ({ + id: row.id, + name: row.project_name, + path: path.join(WORKSPACE_ROOT, (row.project_name as string).replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 60)), + containerStatus: row.status, + gitRemote: "", // We could fetch this on demand or ignore it for the DB view + hasPackageJson: true, + starred: false + })); + + return NextResponse.json({ projects }); + } catch { + return NextResponse.json({ projects: [] }); + } +} + +async function handleClone(req: NextRequest, userId: string) { + const { repoUrl, projectName } = await req.json() as { repoUrl: string; projectName: string }; + if (!repoUrl || !projectName) { + return NextResponse.json({ error: "repoUrl and projectName are required" }, { status: 400 }); + } + + await ensureWorkspaceRoot(); + const safeName = projectName.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 60); + const dest = path.join(WORKSPACE_ROOT, safeName); + + // Stream progress via SSE + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + async start(controller) { + const send = (data: object) => + controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); + + try { + send({ type: "progress", message: `Cloning ${repoUrl}…` }); + await simpleGit().clone(repoUrl, dest, ["--progress"]); + + // Insert into Database + const workspaceId = randomUUID(); + await db.execute({ + sql: "INSERT INTO workspaces (id, user_id, project_name, status) VALUES (?, ?, ?, ?)", + args: [workspaceId, userId, projectName, "stopped"] + }); + + send({ type: "done", projectPath: dest, projectName: safeName, id: workspaceId }); + } catch (e) { + send({ type: "error", message: String(e) }); + } finally { + controller.close(); + } + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); +} + +async function handleScaffold(req: NextRequest, userId: string) { + const { templateId, projectName, packageManager } = await req.json() as { + templateId: string; + projectName: string; + packageManager: PackageManager; + }; + + if (!templateId || !projectName) { + return NextResponse.json({ error: "templateId and projectName are required" }, { status: 400 }); + } + + await ensureWorkspaceRoot(); + const safeName = projectName.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 60); + const dest = path.join(WORKSPACE_ROOT, safeName); + await fs.mkdir(dest, { recursive: true }); + + // Template scaffold commands (IDs must exactly match TEMPLATE_REGISTRY ids in constants/extensions.ts) + const SCAFFOLD_CMDS: Record = { + "nextjs-app": ["npx", "create-next-app@latest", ".", "--typescript", "--tailwind", "--app", "--no-git", "--yes"], + "react-vite": ["npx", "create-vite@latest", ".", "--template", "react-ts"], + "sveltekit": ["npx", "sv", "create", ".", "--template", "minimal", "--types", "ts", "--no-add-ons"], + "astro": ["npx", "create-astro@latest", ".", "--template", "minimal", "--typescript", "strict", "--no-git", "--no-install"], + "express-ts": ["npx", "express-generator-typescript", "."], + "turborepo": ["npx", "create-turbo@latest", ".", "--package-manager", packageManager ?? "npm"], + "python-fastapi": ["python3", "-m", "venv", "venv"], + "django": ["python3", "-m", "venv", "venv"], + "go-gin": ["go", "mod", "init", safeName], + "rust-axum": ["cargo", "init", "."], + }; + + const cmd = SCAFFOLD_CMDS[templateId]; + if (!cmd) { + return NextResponse.json({ error: "Unknown template" }, { status: 400 }); + } + + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + start(controller) { + const send = (data: object) => + controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); + + send({ type: "progress", message: `Scaffolding ${templateId}…` }); + + const proc = spawn(cmd[0], cmd.slice(1), { + cwd: dest, + shell: true, + env: { ...process.env, CI: "1" }, + }); + + proc.stdout.on("data", (d: Buffer) => + send({ type: "stdout", message: d.toString() }) + ); + proc.stderr.on("data", (d: Buffer) => + send({ type: "stderr", message: d.toString() }) + ); + proc.on("close", async (code) => { + if (code === 0) { + // Insert into DB + const workspaceId = randomUUID(); + try { + await db.execute({ + sql: "INSERT INTO workspaces (id, user_id, project_name, status) VALUES (?, ?, ?, ?)", + args: [workspaceId, userId, projectName, "stopped"] + }); + send({ type: "done", projectPath: dest, projectName: safeName, id: workspaceId }); + } catch (e: unknown) { + if (e instanceof Error) { + send({ type: "error", message: `DB Error: ${e.message}` }); + } + send({ type: "error", message: `DB Error: ${JSON.stringify(e)}` }) + } + } else { + send({ type: "error", message: `Process exited with code ${code}` }); + } + controller.close(); + }); + proc.on("error", (e) => { + send({ type: "error", message: e.message }); + controller.close(); + }); + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); +} + +async function handleInstall(req: NextRequest) { + const { projectPath, packageManager } = await req.json() as { + projectPath: string; + packageManager: PackageManager; + }; + + if (!projectPath) { + return NextResponse.json({ error: "projectPath is required" }, { status: 400 }); + } + + const PM_CMDS: Record = { + npm: ["npm", "install"], + pnpm: ["pnpm", "install"], + bun: ["bun", "install"], + yarn: ["yarn", "install"], + }; + + const [bin, ...args] = PM_CMDS[packageManager ?? "npm"]; + const encoder = new TextEncoder(); + + const stream = new ReadableStream({ + start(controller) { + const send = (data: object) => + controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); + + send({ type: "progress", message: `Running ${bin} install…` }); + + const proc = spawn(bin, args, { + cwd: projectPath, + shell: true, + env: process.env, + }); + + proc.stdout.on("data", (d: Buffer) => + send({ type: "stdout", message: d.toString() }) + ); + proc.stderr.on("data", (d: Buffer) => + send({ type: "stderr", message: d.toString() }) + ); + proc.on("close", (code) => { + send({ type: code === 0 ? "done" : "error", message: code === 0 ? "Installation complete!" : `Exited with ${code}` }); + controller.close(); + }); + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); +} diff --git a/app/api/workspace/route.ts b/app/api/workspace/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..962eabbc4916e077c615161e94c92088d6997ef0 --- /dev/null +++ b/app/api/workspace/route.ts @@ -0,0 +1,102 @@ +import { NextResponse } from "next/server"; +import { startWorkspaceContainer, stopWorkspaceContainer } from "@/lib/docker/manager"; +import { auth } from "@/auth"; +import { db } from "@/lib/db"; + +export async function GET(req: Request) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(req.url); + const action = searchParams.get("action"); + + if (action === "statusAll") { + try { + // Fetch all workspaces for user and return their statuses + const res = await db.execute({ + sql: "SELECT id, status FROM workspaces WHERE user_id = ?", + args: [session.user.id] + }); + + const statuses: Record = {}; + res.rows.forEach(row => { + statuses[row.id as string] = row.status as string; + }); + + return NextResponse.json({ statuses }); + } catch (e: unknown) { + console.error("[WORKSPACE_API_ERROR]", e); + return NextResponse.json({ error: (e as Error).message }, { status: 500 }); + } + } + + return NextResponse.json({ error: "Invalid action" }, { status: 400 }); +} + +export async function POST(req: Request) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + const body = await req.json(); + const { action, id, image } = body; + + if (!id) { + return NextResponse.json({ error: "Missing workspace id" }, { status: 400 }); + } + + // Verify ownership + const verifyObj = await db.execute({ + sql: "SELECT id FROM workspaces WHERE id = ? AND user_id = ?", + args: [id, session.user.id] + }); + + if (verifyObj.rows.length === 0) { + return NextResponse.json({ error: "Workspace not found or unauthorized" }, { status: 404 }); + } + + if (action === "start") { + const { withAndroidEmulator } = body; + const result = await startWorkspaceContainer({ id, image, withAndroidEmulator }); + if (result.success) { + await db.execute({ + sql: "UPDATE workspaces SET status = 'running', container_id = ?, android_container_id = ?, android_port = ? WHERE id = ?", + args: [result.containerId || null, result.androidContainerId || null, result.androidPort || null, id] + }); + } + return NextResponse.json(result); + } else if (action === "stop") { + const result = await stopWorkspaceContainer(id); + if (result.success) { + await db.execute({ + sql: "UPDATE workspaces SET status = 'stopped' WHERE id = ?", + args: [id] + }); + } + return NextResponse.json(result); + } else if (action === "rebuild") { + const { withAndroidEmulator } = body; + // 1. Fully destroy existing containers + await stopWorkspaceContainer(id, true); + + // 2. Recreate them (this will pick up codeverse.json changes) + const result = await startWorkspaceContainer({ id, image, withAndroidEmulator }); + + if (result.success) { + await db.execute({ + sql: "UPDATE workspaces SET status = 'running', container_id = ?, android_container_id = ?, android_port = ? WHERE id = ?", + args: [result.containerId || null, result.androidContainerId || null, result.androidPort || null, id] + }); + } + return NextResponse.json(result); + } + + return NextResponse.json({ error: "Invalid action" }, { status: 400 }); + } catch (e: unknown) { + return NextResponse.json({ error: (e as Error).message }, { status: 500 }); + } +} diff --git a/app/dashboard/booting/BootSequenceClient.tsx b/app/dashboard/booting/BootSequenceClient.tsx new file mode 100644 index 0000000000000000000000000000000000000000..32901db3733cedb728758816331eb01d40db24f6 --- /dev/null +++ b/app/dashboard/booting/BootSequenceClient.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { useEffect, useState, useRef, useCallback } from "react"; +import { useSearchParams, useRouter } from "next/navigation"; +import { Loader2, ArrowLeft, CheckCircle2, XCircle, RefreshCw } from "lucide-react"; + +export default function BootSequenceClient() { + const searchParams = useSearchParams(); + const router = useRouter(); + const id = searchParams.get("id"); + const [logs, setLogs] = useState([]); + const [status, setStatus] = useState<"booting" | "ready" | "error">("booting"); + const endRef = useRef(null); + + const boot = useCallback(async () => { + if (!id) { + setLogs(["ERROR: No workspace ID provided."]); + setStatus("error"); + return; + } + + setStatus("booting"); + setLogs([]); + + let isMounted = true; + const addLog = (msg: string) => { + if (isMounted) setLogs(prev => [...prev, msg]); + }; + + try { + addLog(`[SYSTEM] Initializing boot sequence for workspace: ${id.slice(0, 8)}...`); + await new Promise(r => setTimeout(r, 600)); + + addLog(`[NETWORK] Resolving internal DNS routing...`); + await new Promise(r => setTimeout(r, 400)); + + addLog(`[DOCKER] Sending boot command to daemon via Dockerode...`); + + const res = await fetch("/api/workspace", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "start", id }) + }); + + if (!res.ok) { + const err = await res.json(); + throw new Error(err.error || "Failed to boot container"); + } + + addLog(`[DOCKER] Container successfully provisioned.`); + addLog(`[SYSTEM] Database state updated to 'running'.`); + await new Promise(r => setTimeout(r, 500)); + + addLog(`[PROXY] Establishing reverse proxy tunnel on port 8080...`); + await new Promise(r => setTimeout(r, 800)); + + addLog(`[SYSTEM] Environment ready. Redirecting to workspace...`); + if (isMounted) setStatus("ready"); + + setTimeout(() => { + if (isMounted) { + router.push(`/?workspace=${encodeURIComponent(id)}`); + } + }, 1000); + + } catch (err: unknown) { + addLog(`[FATAL] ${(err as Error).message}`); + if (isMounted) setStatus("error"); + } + + return () => { isMounted = false; }; + }, [id, router]); + + useEffect(() => { + boot(); + }, [boot]); + + useEffect(() => { + if (endRef.current) { + endRef.current.scrollIntoView({ behavior: "smooth" }); + } + }, [logs]); + + return ( +
+ {/* Terminal Log */} +
+ {logs.map((log, i) => ( +
+ {log.startsWith("[ERROR]") || log.startsWith("[FATAL]") ? ( + {log} + ) : log.includes("ready") || log.includes("success") || log.includes("complete") ? ( + {log} + ) : log.startsWith("[SYSTEM]") ? ( + {log} + ) : ( + {log} + )} +
+ ))} + + {status === "booting" && ( +
+ + Running boot procedures... +
+ )} + {status === "ready" && ( +
+ + Boot successful! Redirecting... +
+ )} + {status === "error" && ( +
+ + Boot sequence failed. +
+ )} +
+
+ + {/* Action Buttons on Failure */} + {status === "error" && ( +
+ + +
+ )} +
+ ); +} diff --git a/app/dashboard/booting/page.tsx b/app/dashboard/booting/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e7c14df246a8a41606b7b197ef8de56c4082abea --- /dev/null +++ b/app/dashboard/booting/page.tsx @@ -0,0 +1,25 @@ +import { Suspense } from "react"; +import BootSequenceClient from "./BootSequenceClient"; + +export default function BootingPage() { + return ( +
+
+ +
+
+
+
+
+
+
+ Cloud IDE Orchestrator +
+ + Initializing connection...
}> + + +
+
+ ); +} diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000000000000000000000000000000000000..fbd7bf917022e2598d37c25cbffd5511fdd56d00 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,557 @@ +@import "tailwindcss"; + +/* ─── Theme Tokens ─────────────────────────────────────────────────────────── */ +:root, [data-theme="dark"] { + --bg: #0d1117; + --bg-2: #161b22; + --bg-3: #21262d; + --surface: #1c2128; + --surface-hover: #292e36; + --border: #30363d; + --border-subtle: #21262d; + --accent: #39d353; + --accent-dim: #26a641; + --accent-text: #39d353; + --text: #e6edf3; + --text-2: #8b949e; + --text-muted: #6e7681; + --text-on-accent: #0d1117; + --error: #f85149; + --warning: #d29922; + --info: #58a6ff; + --success: #39d353; + --status-bar: #161b22; + --activity-bar: #0d1117; + --sidebar: #161b22; + --tab-active: #0d1117; + --tab-inactive: #161b22; + --scrollbar: #30363d; + --btn-primary: #39d353; + --btn-text: #0d1117; + --editor-bg: #0d1117; + --editor-line: #161b22; + --editor-selection: #264f78; + --terminal-bg: #0d1117; + --terminal-fg: #e6edf3; + --terminal-cursor: #39d353; +} + +[data-theme="light"] { + --bg: #faf8f5; + --bg-2: #f0ece4; + --bg-3: #e8e0d4; + --surface: #ffffff; + --surface-hover: #f0ece4; + --border: #d4c9b8; + --border-subtle: #e8e0d4; + --accent: #6f4e37; + --accent-dim: #8b6347; + --accent-text: #6f4e37; + --text: #1c1412; + --text-2: #5c3d2e; + --text-muted: #8b7355; + --text-on-accent: #faf8f5; + --error: #c0392b; + --warning: #d68910; + --info: #2471a3; + --success: #27ae60; + --status-bar: #f0ece4; + --activity-bar: #e8e0d4; + --sidebar: #f0ece4; + --tab-active: #faf8f5; + --tab-inactive: #f0ece4; + --scrollbar: #d4c9b8; + --btn-primary: #6f4e37; + --btn-text: #faf8f5; + --editor-bg: #faf8f5; + --editor-line: #f0ece4; + --editor-selection: #d4c9b8; + --terminal-bg: #1c1412; + --terminal-fg: #faf8f5; + --terminal-cursor: #6f4e37; +} + +/* ─── Base ─────────────────────────────────────────────────────────────────── */ +@layer base { + * { box-sizing: border-box; margin: 0; padding: 0; } + + html, body { height: 100%; overflow: hidden; } + + body { + font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif; + background: var(--bg); + color: var(--text); + font-size: 13px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + } +} + +/* ─── Scrollbars ───────────────────────────────────────────────────────────── */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: var(--text-muted); } + +/* ─── Monaco / Editor Overrides ───────────────────────────────────────────── */ +.monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input { + background: var(--editor-bg) !important; +} +.monaco-editor .margin { background: var(--editor-bg) !important; } +.monaco-editor .minimap { background: var(--bg-2) !important; } + +/* ─── Terminal ─────────────────────────────────────────────────────────────── */ +.xterm-viewport { background: var(--terminal-bg) !important; } +.xterm { background: var(--terminal-bg) !important; } + +/* ─── Animations ───────────────────────────────────────────────────────────── */ +@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } +@keyframes slideIn { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: translateX(0); } } +@keyframes pulse-accent { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } +@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } +@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } + +.animate-fade-in { animation: fadeIn 0.2s ease forwards; } +.animate-slide-in { animation: slideIn 0.15s ease forwards; } +.animate-pulse-accent { animation: pulse-accent 2s ease-in-out infinite; } +.animate-spin { animation: spin 1s linear infinite; } + +/* ─── Skeleton ─────────────────────────────────────────────────────────────── */ +.skeleton { + background: linear-gradient(90deg, var(--bg-2) 25%, var(--bg-3) 50%, var(--bg-2) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; + border-radius: 4px; +} + +/* ─── IDE Shell ─────────────────────────────────────────────────────────────── */ +.ide-shell { + display: grid; + grid-template-rows: 1fr 22px; /* editor area + status bar */ + grid-template-columns: 48px 1fr; /* activity bar + content */ + height: 100dvh; + width: 100vw; + overflow: hidden; +} + +/* ─── Activity Bar ─────────────────────────────────────────────────────────── */ +.activity-bar { + grid-row: 1; + grid-column: 1; + background: var(--activity-bar); + border-right: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + align-items: center; + padding: 8px 0; + gap: 2px; + z-index: 10; +} + +.activity-btn { + position: relative; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + color: var(--text-2); + cursor: pointer; + transition: background 0.15s, color 0.15s; + border: none; + background: transparent; +} + +.activity-btn:hover { background: var(--surface-hover); color: var(--text); } +.activity-btn.active { color: var(--accent); } +.activity-btn.active::before { + content: ""; + position: absolute; + left: 0; + top: 8px; + bottom: 8px; + width: 2px; + background: var(--accent); + border-radius: 0 2px 2px 0; +} + +/* ─── Status Bar ─────────────────────────────────────────────────────────────── */ +.status-bar { + grid-row: 2; + grid-column: 1 / -1; + background: var(--status-bar); + border-top: 1px solid var(--border-subtle); + display: flex; + align-items: center; + gap: 0; + padding: 0 8px; + font-size: 11px; + color: var(--text-2); + user-select: none; + z-index: 50; +} + +.status-item { + display: flex; + align-items: center; + gap: 4px; + padding: 0 8px; + height: 22px; + cursor: pointer; + transition: background 0.1s; + white-space: nowrap; +} +.status-item:hover { background: var(--surface-hover); } +.status-item.accent { color: var(--accent); } +.status-item.error { color: var(--error); } +.status-item.warning { color: var(--warning); } +.status-divider { width: 1px; height: 12px; background: var(--border); margin: 0 2px; } + +/* ─── Tabs ─────────────────────────────────────────────────────────────────── */ +.tab-bar { + display: flex; + align-items: center; + background: var(--bg-2); + border-bottom: 1px solid var(--border-subtle); + overflow-x: auto; + min-height: 35px; + flex-shrink: 0; +} + +.tab-bar::-webkit-scrollbar { height: 0; } + +.tab { + display: flex; + align-items: center; + gap: 6px; + padding: 0 12px; + height: 35px; + border-right: 1px solid var(--border-subtle); + cursor: pointer; + white-space: nowrap; + font-size: 12px; + color: var(--text-2); + background: var(--tab-inactive); + transition: background 0.1s, color 0.1s; + position: relative; + flex-shrink: 0; +} + +.tab.active { + background: var(--tab-active); + color: var(--text); + border-bottom: 1px solid var(--accent); +} + +.tab:hover:not(.active) { background: var(--surface-hover); color: var(--text); } + +.tab-close { + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + opacity: 0; + cursor: pointer; + color: var(--text-2); + transition: opacity 0.1s, background 0.1s; + flex-shrink: 0; +} + +.tab:hover .tab-close, .tab.active .tab-close { opacity: 1; } +.tab-close:hover { background: var(--surface-hover); color: var(--error); } + +/* ─── Sidebar ─────────────────────────────────────────────────────────────── */ +.sidebar { + background: var(--sidebar); + border-right: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 0; +} + +.sidebar-header { + padding: 8px 12px 6px; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.08em; + color: var(--text-muted); + text-transform: uppercase; + display: flex; + align-items: center; + justify-content: space-between; + flex-shrink: 0; +} + +/* ─── File Tree ─────────────────────────────────────────────────────────────── */ +.file-tree-item { + display: flex; + align-items: center; + gap: 4px; + padding: 2px 0 2px 8px; + cursor: pointer; + border-radius: 4px; + font-size: 13px; + color: var(--text); + transition: background 0.1s; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.file-tree-item:hover { background: var(--surface-hover); } +.file-tree-item.active { background: var(--bg-3); color: var(--accent); } + +/* ─── Context Menu ─────────────────────────────────────────────────────────── */ +.context-menu { + position: fixed; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 4px; + min-width: 180px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + z-index: 9999; + animation: fadeIn 0.1s ease; +} + +.context-menu-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-radius: 5px; + cursor: pointer; + font-size: 12px; + color: var(--text); + transition: background 0.1s; +} + +.context-menu-item:hover { background: var(--surface-hover); } +.context-menu-item.destructive:hover { background: rgba(248, 81, 73, 0.15); color: var(--error); } +.context-menu-separator { height: 1px; background: var(--border-subtle); margin: 4px 0; } + +/* ─── Command Palette ─────────────────────────────────────────────────────── */ +.command-palette-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.6); + backdrop-filter: blur(4px); + z-index: 10000; + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 10vh; +} + +.command-palette { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + width: min(640px, 90vw); + max-height: 60vh; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.6); + display: flex; + flex-direction: column; +} + +.command-input { + padding: 14px 16px; + font-size: 14px; + background: transparent; + border: none; + border-bottom: 1px solid var(--border-subtle); + color: var(--text); + width: 100%; + outline: none; + font-family: "Inter", sans-serif; +} + +.command-input::placeholder { color: var(--text-muted); } +.command-results { overflow-y: auto; max-height: 50vh; padding: 4px; } + +.command-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + color: var(--text); + font-size: 13px; + transition: background 0.1s; +} + +.command-item:hover, .command-item.focused { background: var(--surface-hover); } +.command-item kbd { + font-size: 10px; + background: var(--bg-3); + border: 1px solid var(--border); + border-radius: 4px; + padding: 1px 5px; + color: var(--text-2); + font-family: "JetBrains Mono", monospace; +} + +/* ─── Buttons ─────────────────────────────────────────────────────────────── */ +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s; + border: 1px solid transparent; + font-family: inherit; +} + +.btn-primary { + background: var(--btn-primary); + color: var(--btn-text); + border-color: var(--btn-primary); +} +.btn-primary:hover { filter: brightness(1.1); } + +.btn-secondary { + background: transparent; + color: var(--text); + border-color: var(--border); +} +.btn-secondary:hover { background: var(--surface-hover); } + +.btn-ghost { + background: transparent; + color: var(--text-2); + border-color: transparent; +} +.btn-ghost:hover { background: var(--surface-hover); color: var(--text); } + +.btn:disabled { opacity: 0.4; cursor: not-allowed; } + +/* ─── Input ─────────────────────────────────────────────────────────────────── */ +.input { + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 12px; + font-size: 13px; + color: var(--text); + font-family: inherit; + outline: none; + transition: border-color 0.15s; + width: 100%; +} +.input:focus { border-color: var(--accent); } +.input::placeholder { color: var(--text-muted); } + +/* ─── Cards ─────────────────────────────────────────────────────────────────── */ +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + overflow: hidden; +} + +.card:hover { border-color: var(--accent); } + +/* ─── Modal ─────────────────────────────────────────────────────────────────── */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.7); + backdrop-filter: blur(6px); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.modal { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: 0 24px 80px rgba(0,0,0,0.6); + width: min(560px, 95vw); + max-height: 90vh; + overflow-y: auto; +} + +.modal-header { + padding: 20px 24px 0; + display: flex; + align-items: center; + justify-content: space-between; +} + +.modal-body { padding: 20px 24px; } +.modal-footer { + padding: 0 24px 20px; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; +} + +/* ─── Dashboard ─────────────────────────────────────────────────────────────── */ +.dashboard-grid { + display: grid; + grid-template-columns: 340px 1fr; + gap: 0; + height: 100%; + overflow: hidden; +} + +@media (max-width: 768px) { + .dashboard-grid { grid-template-columns: 1fr; } + .ide-shell { grid-template-columns: 1fr; } + .activity-bar { display: none; } + .sidebar { display: none; } +} + +/* ─── Badge ─────────────────────────────────────────────────────────────────── */ +.badge { + display: inline-flex; + align-items: center; + padding: 2px 7px; + border-radius: 20px; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.04em; +} + +.badge-accent { background: rgba(57, 211, 83, 0.15); color: var(--accent); } +.badge-error { background: rgba(248, 81, 73, 0.15); color: var(--error); } +.badge-warning { background: rgba(210, 153, 34, 0.15); color: var(--warning); } +.badge-info { background: rgba(88, 166, 255, 0.15); color: var(--info); } + +/* ─── Agent Plan View ─────────────────────────────────────────────────────── */ +.plan-step { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 10px 12px; + border-radius: 8px; + border: 1px solid var(--border-subtle); + background: var(--bg-2); + margin-bottom: 6px; +} + +.plan-step.running { border-color: var(--accent); background: rgba(57, 211, 83, 0.05); } +.plan-step.done { opacity: 0.7; } +.plan-step.error { border-color: var(--error); background: rgba(248, 81, 73, 0.05); } + +/* ─── Responsive ─────────────────────────────────────────────────────────── */ +@media (max-width: 768px) { + .modal { width: 95vw; } + .command-palette { width: 95vw; } + body { overflow: auto; } + html, body { height: auto; } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d8bbba2dd18dc327a8427e80cd9260ed1c221dbc --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from "next"; +import { Inter, JetBrains_Mono } from "next/font/google"; +import "./globals.css"; +import { AuthProvider } from "@/components/auth/AuthProvider"; + +const inter = Inter({ subsets: ["latin"], display: "swap" }); +const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"], display: "swap", variable: "--font-mono" }); + +export const metadata: Metadata = { + title: "CodeVerse | The 2026 Agentic IDE", + description: "Advanced AI-powered coding environment with local memory integration.", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + ); +} diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ffc387cd6e93d62b46e0e328e0437cfda349dce2 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { signIn } from "next-auth/react"; +import { Github, Code2, ArrowRight } from "lucide-react"; +import { motion } from "framer-motion"; + +export default function LoginPage() { + return ( +
+ + {/* Ambient Background Glow */} +
+
+ + +
+ + + + + +

CodeVerse Studio

+

+ The fully managed, agentic VS Code environment running securely in the browser. +

+ + + +
+

Powered by highly scalable containerized VS Code engines.

+ +
+
+
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f311a8bdc45423f1385bc32ab4989d220679ee93 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,18 @@ +import { Suspense } from "react"; +import { auth } from "@/auth"; +import { redirect } from "next/navigation"; +import IDEClient from "@/components/workspace/IDEClient"; + +export default async function IDEPage() { + const session = await auth(); + + if (!session) { + redirect("/login"); + } + + return ( + Loading Platform...
}> + + + ); +} diff --git a/auth.ts b/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1e4390939718d3dc1fe988417b17c8b182292a1 --- /dev/null +++ b/auth.ts @@ -0,0 +1,227 @@ +import NextAuth, { Session } from "next-auth"; +import GitHubProvider from "next-auth/providers/github"; +import { db } from "@/lib/db"; +import { randomUUID } from "crypto"; +import type { Adapter } from "@auth/core/adapters"; + +// Define explicit strict types so we avoid using "any" +export interface TursoAdapterUser { + id: string; + email: string; + emailVerified: Date | null; + name?: string | null; + image?: string | null; + github_username?: string | null; +} + +export interface TursoAdapterAccount { + userId: string; + type: string; + provider: string; + providerAccountId: string; + refresh_token?: string; + access_token?: string; + expires_at?: number; + token_type?: string; + scope?: string; + id_token?: string; + session_state?: string; +} + +export interface TursoAdapterSession { + sessionToken: string; + userId: string; + expires: Date; +} + +export interface TursoVerificationToken { + identifier: string; + token: string; + expires: Date; +} + +// Basic custom Turso adapter implementation for NextAuth v5 with Strict Types +const TursoAdapter = { + async createUser(user: Omit): Promise { + const id = randomUUID(); + await db.execute({ + sql: "INSERT INTO users (id, name, email, image, github_username) VALUES (?, ?, ?, ?, ?)", + args: [id, user.name || null, user.email, user.image || null, user.github_username || null], + }); + return { ...(user as TursoAdapterUser), id, emailVerified: null }; + }, + async getUser(id: string): Promise { + const res = await db.execute({ + sql: "SELECT * FROM users WHERE id = ?", + args: [id], + }); + if (res.rows.length === 0) return null; + const row = res.rows[0]; + return { + id: row.id as string, + email: row.email as string, + emailVerified: null, + name: row.name as string | null, + image: row.image as string | null, + github_username: row.github_username as string | null + }; + }, + async getUserByEmail(email: string): Promise { + const res = await db.execute({ + sql: "SELECT * FROM users WHERE email = ?", + args: [email], + }); + if (res.rows.length === 0) return null; + const row = res.rows[0]; + return { + id: row.id as string, + email: row.email as string, + emailVerified: null, + name: row.name as string | null, + image: row.image as string | null, + github_username: row.github_username as string | null + }; + }, + async getUserByAccount({ providerAccountId, provider }: Pick): Promise { + const res = await db.execute({ + sql: `SELECT u.* FROM users u + JOIN accounts a ON u.id = a.userId + WHERE a.providerAccountId = ? AND a.provider = ?`, + args: [providerAccountId, provider], + }); + if (res.rows.length === 0) return null; + const row = res.rows[0]; + return { + id: row.id as string, + email: row.email as string, + emailVerified: null, + name: row.name as string | null, + image: row.image as string | null, + github_username: row.github_username as string | null + }; + }, + async updateUser(user: Partial & Pick): Promise { + await db.execute({ + sql: "UPDATE users SET name = ?, email = ?, image = ?, github_username = ? WHERE id = ?", + args: [user.name || null, user.email || null, user.image || null, user.github_username || null, user.id], + }); + return user as TursoAdapterUser; + }, + async deleteUser(userId: string): Promise { + await db.execute({ sql: "DELETE FROM users WHERE id = ?", args: [userId] }); + }, + async linkAccount(account: TursoAdapterAccount): Promise { + await db.execute({ + sql: `INSERT INTO accounts (id, userId, provider, providerAccountId, refresh_token, access_token, expires_at, token_type, scope, id_token, session_state) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + args: [ + randomUUID(), account.userId, account.provider, account.providerAccountId, + account.refresh_token || null, account.access_token || null, account.expires_at || null, + account.token_type || null, account.scope || null, account.id_token || null, account.session_state || null + ], + }); + return account; + }, + async unlinkAccount({ providerAccountId, provider }: Pick): Promise { + await db.execute({ + sql: "DELETE FROM accounts WHERE providerAccountId = ? AND provider = ?", + args: [providerAccountId, provider], + }); + }, + async createSession(session: TursoAdapterSession): Promise { + await db.execute({ + sql: "INSERT INTO sessions (id, sessionToken, userId, expires) VALUES (?, ?, ?, ?)", + args: [randomUUID(), session.sessionToken, session.userId, session.expires.toISOString()], + }); + return session; + }, + async getSessionAndUser(sessionToken: string): Promise<{ session: TursoAdapterSession; user: TursoAdapterUser } | null> { + const res = await db.execute({ + sql: `SELECT s.sessionToken, s.userId, s.expires, u.id as u_id, u.name, u.email, u.image + FROM sessions s JOIN users u ON s.userId = u.id + WHERE s.sessionToken = ?`, + args: [sessionToken], + }); + if (res.rows.length === 0) return null; + const row = res.rows[0]; + + let expiresDate: Date; + if (typeof row.expires === 'number') { + expiresDate = new Date(row.expires); + } else if (typeof row.expires === 'string') { + expiresDate = new Date(row.expires); + } else { + expiresDate = new Date(); + } + + return { + session: { sessionToken: row.sessionToken as string, userId: row.userId as string, expires: expiresDate }, + user: { id: row.u_id as string, name: row.name as string | null, email: row.email as string, image: row.image as string | null, emailVerified: null }, + }; + }, + async updateSession(session: Partial & Pick): Promise { + if (!session.expires) return null; + await db.execute({ + sql: "UPDATE sessions SET expires = ? WHERE sessionToken = ?", + args: [session.expires.toISOString(), session.sessionToken], + }); + return session as TursoAdapterSession; + }, + async deleteSession(sessionToken: string): Promise { + await db.execute({ sql: "DELETE FROM sessions WHERE sessionToken = ?", args: [sessionToken] }); + }, + async createVerificationToken(token: TursoVerificationToken): Promise { + await db.execute({ + sql: "INSERT INTO verification_tokens (identifier, token, expires) VALUES (?, ?, ?)", + args: [token.identifier, token.token, token.expires.toISOString()], + }); + return token; + }, + async useVerificationToken({ identifier, token }: Pick): Promise { + const res = await db.execute({ + sql: "SELECT * FROM verification_tokens WHERE identifier = ? AND token = ?", + args: [identifier, token], + }); + if (res.rows.length === 0) return null; + await db.execute({ + sql: "DELETE FROM verification_tokens WHERE identifier = ? AND token = ?", + args: [identifier, token], + }); + const row = res.rows[0]; + + let expiresDate: Date; + if (typeof row.expires === 'number') { + expiresDate = new Date(row.expires); + } else if (typeof row.expires === 'string') { + expiresDate = new Date(row.expires); + } else { + expiresDate = new Date(); + } + + return { identifier: row.identifier as string, token: row.token as string, expires: expiresDate }; + } +}; + +const authOptions = { + adapter: TursoAdapter as unknown as Adapter, + providers: [ + GitHubProvider({ + clientId: process.env.GITHUB_ID ?? "", + clientSecret: process.env.GITHUB_SECRET ?? "", + }), + ], + session: { strategy: "database" as const }, + callbacks: { + async session({ session, user }: { session: Session, user: TursoAdapterUser }) { + if (session.user) { + session.user.id = user.id; + } + return session; + }, + }, + pages: { + signIn: "/login", + }, +}; + +export const { handlers, auth, signIn, signOut } = NextAuth(authOptions); diff --git a/codeverse.db b/codeverse.db new file mode 100644 index 0000000000000000000000000000000000000000..c8bf3a2dbc32d632c9c46bf2d0be5d1203a8372b Binary files /dev/null and b/codeverse.db differ diff --git a/components/agents/AgentChat.tsx b/components/agents/AgentChat.tsx new file mode 100644 index 0000000000000000000000000000000000000000..79cd892945a17cfaf5b5f6d2756037b0a9a6484a --- /dev/null +++ b/components/agents/AgentChat.tsx @@ -0,0 +1,241 @@ +// @ts-nocheck +"use client"; + +import { useMemo, useState } from "react"; +import { useChat } from "@ai-sdk/react"; +import { useAgentStore } from "@/store/agent"; +import { MODELS } from "@/constants/pricing"; +import { PlanView, PlanStep } from "./PlanView"; +import { motion, AnimatePresence } from "framer-motion"; +import { + Bot, Send, Square, Play, Code2, Cpu, Settings, FileCode2, Command +} from "lucide-react"; +import ReactMarkdown from "react-markdown"; +import { toast } from "sonner"; + +export default function AgentChat() { + const { mode, setMode, modelId, setModelId, planSteps, setPlanSteps, systemPrompt } = useAgentStore(); + const [input, setInput] = useState(""); + const [isPlanning, setIsPlanning] = useState(false); + const [activeTab, setActiveTab] = useState<"chat" | "plan">("chat"); + + // Execute mode: Standard Vercel AI useChat with tool calls capabilities mapped to route.ts + // @ts-ignore - Ignore type mismatches across AI SDK versions + const { messages, append, stop, isLoading } = useChat({ + api: "/api/agent", + body: { + modelId, + mode: "execute", + systemPrompt, + byokKey: localStorage.getItem("codeverse_byok") || undefined, + }, + onError: (err) => { + toast.error(err.message || "Agent execution failed"); + } + }); + + const generatePlan = async (query: string) => { + setIsPlanning(true); + setMode("plan"); + setActiveTab("plan"); + setInput(""); + + try { + const res = await fetch("/api/agent", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + messages: [{ role: "user", content: query }], + modelId, + mode: "plan", + systemPrompt, + byokKey: localStorage.getItem("codeverse_byok") || undefined, + }) + }); + + if (!res.ok) throw new Error("Failed to generate plan"); + const data = await res.json(); + + setPlanSteps(data.steps.map((s: any) => ({ ...s, status: "pending" }))); + // Auto-switch to plan view + } catch (e: any) { + toast.error(e.message || "Plan generation failed"); + } finally { + setIsPlanning(false); + } + }; + + const handleSend = (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + + if (mode === "plan" && activeTab === "plan") { + generatePlan(input); + } else { + append({ role: "user", content: input }); + setInput(""); + setActiveTab("chat"); + } + }; + + const executePlanStep = async (id: string, description: string) => { + // Dispatch a hidden message to the assistant to execute the specific step + append({ + role: "user", + content: `Execute the following plan step immediately and autonomoulsy using tools: ${description}` + }); + }; + + return ( +
+ {/* Header */} +
+
+ + AI Agent +
+ +
+ + {/* Tabs */} +
+ + +
+ + {/* Messages Area */} +
+ {activeTab === "chat" ? ( + messages.length === 0 ? ( +
+ +

Execute Mode

+

I will execute your requests directly using MCP tools.

+
+ ) : ( + messages.map(m => ( +
+
+ {m.role === "assistant" ? : null} + {m.role} +
+
+ { + return inline ? + {children} : +
+                                                        {children}
+                                                    
+ } + }} + > + {m.content || "*Thinking...*"} +
+ + {/* Tool Invocations */} + {m.toolInvocations && m.toolInvocations.map((toolInvoc) => ( +
+
+ {toolInvoc.toolName} +
+
+ {JSON.stringify(toolInvoc.args, null, 2)} +
+
+ ))} +
+
+ )) + ) + ) : ( + planSteps.length === 0 ? ( +
+ +

Plan Mode

+

Ask me to build a large feature. I will outline a structured step-by-step checklist before executing code.

+
+ ) : ( + { + const step = planSteps.find(s => s.id === id); + if (step) executePlanStep(id, step.description); + }} + onExecute={() => { + const pending = planSteps.filter(s => s.status !== "done"); + if (pending.length) executePlanStep(pending[0].id, pending[0].description); + }} + /> + ) + )} + + {(isLoading || isPlanning) && activeTab === "chat" && ( +
+ + Agent is thinking... +
+ )} +
+
+ + {/* Input Area */} +
+
+