{"id":30785155,"url":"https://github.com/prassanna-ravishankar/torale","last_synced_at":"2025-12-14T11:02:30.608Z","repository":{"id":276388220,"uuid":"929136736","full_name":"prassanna-ravishankar/torale","owner":"prassanna-ravishankar","description":"AI powered conditional automation for monitoring the web","archived":false,"fork":false,"pushed_at":"2025-12-10T16:46:30.000Z","size":3880,"stargazers_count":3,"open_issues_count":23,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-11T02:16:14.308Z","etag":null,"topics":["durable","grounded-search","llm","monitoring"],"latest_commit_sha":null,"homepage":"https://torale.ai","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/prassanna-ravishankar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-07T21:51:28.000Z","updated_at":"2025-12-07T00:48:10.000Z","dependencies_parsed_at":"2025-06-16T01:14:37.914Z","dependency_job_id":"1f4a286a-319e-47f5-a549-a90e28076a4b","html_url":"https://github.com/prassanna-ravishankar/torale","commit_stats":null,"previous_names":["prassanna-ravishankar/ambi-alert","prassanna-ravishankar/torale"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/prassanna-ravishankar/torale","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prassanna-ravishankar%2Ftorale","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prassanna-ravishankar%2Ftorale/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prassanna-ravishankar%2Ftorale/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prassanna-ravishankar%2Ftorale/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prassanna-ravishankar","download_url":"https://codeload.github.com/prassanna-ravishankar/torale/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prassanna-ravishankar%2Ftorale/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27726944,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-14T02:00:11.348Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["durable","grounded-search","llm","monitoring"],"created_at":"2025-09-05T11:27:20.587Z","updated_at":"2025-12-14T11:02:30.603Z","avatar_url":"https://github.com/prassanna-ravishankar.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./frontend/public/logo.svg\" alt=\"τorale\" width=\"120\" height=\"120\"\u003e\n  \u003ch1\u003eτorale\u003c/h1\u003e\n  \u003cp\u003e\u003cstrong\u003eGrounded search monitoring platform for AI-powered conditional automation\u003c/strong\u003e\u003c/p\u003e\n\n  [![PyPI version](https://badge.fury.io/py/torale.svg)](https://badge.fury.io/py/torale)\n  [![Deploy](https://github.com/prassanna-ravishankar/torale/actions/workflows/production.yml/badge.svg)](https://github.com/prassanna-ravishankar/torale/actions/workflows/production.yml)\n  [![App](https://img.shields.io/badge/app-torale.ai-green)](https://torale.ai)\n  [![Documentation](https://img.shields.io/badge/docs-torale.ai-blue)](https://docs.torale.ai)\n  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\u003c/div\u003e\n\n---\n\nMonitor the web for specific conditions using Google Search + LLM analysis, then get notified when they're met.\n\n## Use Cases\n\n- **Product Launches**: \"Tell me when the next iPhone release date is announced\"\n- **Availability Monitoring**: \"Notify me when swimming pool memberships open for summer\"\n- **Stock Alerts**: \"Alert me when PS5 is back in stock at Best Buy\"\n- **Event Tracking**: \"Let me know when GPT-5 launch date is confirmed\"\n- **Price Monitoring**: \"Tell me when iPhone 15 price drops below $500\"\n\n## Installation\n\n```bash\npip install torale\n```\n\nGet started at **[torale.ai](https://torale.ai)** or see the [Quick Start](#quick-start) guide below.\n\n## How It Works\n\n1. **Create a monitoring task** with a search query and condition\n2. **Torale runs scheduled searches** via Google Search (grounded via Gemini)\n3. **LLM evaluates** if your condition is met based on search results\n4. **You get notified** when condition triggers (once, always, or on state change)\n\n## Quick Start\n\n### Option 1: Use the Hosted Service (Recommended)\n\nThe fastest way to get started is using the hosted service at **[torale.ai](https://torale.ai)**:\n\n1. **Sign up** at https://torale.ai (Google/GitHub OAuth or email)\n2. **Create monitoring tasks** via the web dashboard\n3. **Get notified** when conditions are met\n\n### Option 2: Install the CLI\n\nInstall the Torale CLI to manage tasks from your terminal:\n\n```bash\npip install torale\n```\n\n**Configure authentication:**\n\n```bash\n# Generate an API key at https://torale.ai (or your self-hosted instance)\ntorale auth set-api-key\n\n# Create your first monitoring task\ntorale task create \"iPhone Release Monitor\" \\\n  --schedule \"0 9 * * *\" \\\n  --prompt \"Search for iPhone release date announcements\"\n\n# List all tasks\ntorale task list\n\n# View task notifications\ntorale notifications TASK_ID\n```\n\n### Option 3: Use the Python SDK\n\nIntegrate Torale into your Python applications for programmatic task management.\n\n#### Installation\n\n```bash\npip install torale\n```\n\n#### Authentication\n\nThe SDK requires developer access. To get an API key:\n\n1. Sign up at https://torale.ai\n2. Contact support to request developer access (adds `role: \"developer\"` to your account)\n3. Go to Settings → API Access and generate an API key\n4. Configure the SDK with your API key\n\n#### Quick Start - Synchronous Client\n\n```python\nfrom torale import Torale\n\n# Option 1: Environment variable (recommended for development)\n# export TORALE_API_KEY=sk_...\nclient = Torale()  # Auto-discovers from environment\n\n# Option 2: Explicit API key (useful for testing, not recommended for production)\nclient = Torale(api_key=\"sk_your_api_key_here\")\n\n# Option 3: CLI config file (recommended for local CLI usage)\n# Run: torale auth set-api-key\n# Stores in: ~/.torale/config.json\nclient = Torale()  # Auto-discovers from config file\n\n# Create a monitoring task\ntask = client.tasks.create(\n    name=\"iPhone Release Monitor\",\n    search_query=\"When is the next iPhone being released?\",\n    condition_description=\"A specific release date has been announced\",\n    schedule=\"0 9 * * *\",  # Daily at 9am\n    notify_behavior=\"once\",  # Options: \"once\", \"always\", \"track_state\"\n    notifications=[\n        {\"type\": \"webhook\", \"url\": \"https://myapp.com/alert\"}\n    ]\n)\n\nprint(f\"Created task: {task.id}\")\n```\n\n#### Async Client\n\nFor better performance with concurrent operations:\n\n```python\nimport asyncio\nfrom torale import ToraleAsync\n\nasync def main():\n    async with ToraleAsync(api_key=\"sk_...\") as client:\n        # Create multiple tasks concurrently\n        task1 = client.tasks.create(\n            name=\"iPhone Monitor\",\n            search_query=\"When is iPhone 16 being released?\",\n            condition_description=\"A specific date is announced\"\n        )\n        task2 = client.tasks.create(\n            name=\"PS5 Stock Monitor\",\n            search_query=\"Is PS5 in stock at Best Buy?\",\n            condition_description=\"PS5 is available for purchase\"\n        )\n\n        # Wait for both to complete\n        tasks = await asyncio.gather(task1, task2)\n        print(f\"Created {len(tasks)} tasks\")\n\nasyncio.run(main())\n```\n\n#### API Reference\n\n**Task Management**\n\n```python\n# List all tasks\ntasks = client.tasks.list(active=True)\n\n# Get specific task\ntask = client.tasks.get(task_id=\"550e8400-...\")\n\n# Update task\ntask = client.tasks.update(\n    task_id=\"550e8400-...\",\n    name=\"New Name\",\n    state=\"paused\"  # \"active\", \"paused\", or \"completed\"\n)\n\n# Delete task\nclient.tasks.delete(task_id=\"550e8400-...\")\n\n# Manual execution (test run)\nexecution = client.tasks.execute(task_id=\"550e8400-...\")\nprint(execution.status)  # \"pending\", \"running\", \"success\", \"failed\"\n```\n\n**Preview Queries**\n\nTest search queries before creating tasks:\n\n```python\n# Preview with explicit condition\nresult = client.tasks.preview(\n    search_query=\"When is iPhone 16 being released?\",\n    condition_description=\"A specific release date is announced\"\n)\n\n# Preview without condition (LLM will infer)\nresult = client.tasks.preview(\n    search_query=\"What's the latest news on GPT-5?\"\n)\n\nprint(result[\"answer\"])\nprint(f\"Condition met: {result['condition_met']}\")\nif \"inferred_condition\" in result:\n    print(f\"Inferred: {result['inferred_condition']}\")\n\nfor source in result[\"grounding_sources\"]:\n    print(f\"- {source['title']}: {source['url']}\")\n```\n\n**Execution History \u0026 Notifications**\n\n```python\n# Get all executions\nexecutions = client.tasks.executions(task_id=\"550e8400-...\", limit=100)\nfor exec in executions:\n    print(f\"{exec.started_at}: {exec.status}\")\n\n# Get only notifications (condition met)\nnotifications = client.tasks.notifications(task_id=\"550e8400-...\", limit=10)\nfor notif in notifications:\n    print(f\"{notif.started_at}: {notif.change_summary}\")\n```\n\n**Fluent Builder API**\n\nFor a more expressive syntax:\n\n```python\nfrom torale import monitor\n\ntask = (monitor(\"When is iPhone 16 being released?\")\n    .when(\"A specific release date is announced\")\n    .check_every(\"6 hours\")  # Human-readable schedules\n    .notify(webhook=\"https://myapp.com/alert\")\n    .named(\"iPhone Release Monitor\")\n    .create())\n```\n\n**Notification Configuration**\n\n```python\n# Webhook notifications\ntask = client.tasks.create(\n    name=\"Bitcoin Alert\",\n    search_query=\"Bitcoin price USD\",\n    condition_description=\"Price exceeds $50,000\",\n    notifications=[\n        {\"type\": \"webhook\", \"url\": \"https://myapp.com/webhook\"}\n    ]\n)\n\n# Email notifications (requires verified email)\ntask = client.tasks.create(\n    name=\"Job Alert\",\n    search_query=\"Software Engineer jobs in NYC\",\n    condition_description=\"New positions posted\",\n    notifications=[\n        {\"type\": \"email\", \"address\": \"you@example.com\"}\n    ]\n)\n\n# Multiple notification channels\ntask = client.tasks.create(\n    name=\"Multi-channel Alert\",\n    search_query=\"Product launch announcement\",\n    condition_description=\"Official announcement is made\",\n    notifications=[\n        {\"type\": \"email\", \"address\": \"you@example.com\"},\n        {\"type\": \"webhook\", \"url\": \"https://myapp.com/webhook\"}\n    ]\n)\n```\n\n**Environment Configuration**\n\n```bash\n# Production (default)\nexport TORALE_API_KEY=sk_your_api_key_here\n\n# Local development with authentication\nexport TORALE_API_KEY=sk_local_key\nexport TORALE_DEV=1  # Uses http://localhost:8000\n\n# Local development without authentication\nexport TORALE_NOAUTH=1  # Skips auth, uses localhost\n\n# Custom API URL\nexport TORALE_API_URL=https://custom.domain.com\n```\n\n**Context Managers**\n\nBoth sync and async clients support context managers for automatic cleanup:\n\n```python\n# Synchronous\nwith Torale() as client:\n    tasks = client.tasks.list()\n\n# Asynchronous\nasync with ToraleAsync() as client:\n    tasks = await client.tasks.list()\n```\n\n**Error Handling**\n\n```python\nfrom torale.sdk.exceptions import (\n    AuthenticationError,\n    NotFoundError,\n    ValidationError,\n    APIError\n)\n\ntry:\n    task = client.tasks.create(...)\nexcept AuthenticationError:\n    print(\"Invalid API key or not authenticated\")\nexcept ValidationError as e:\n    print(f\"Invalid input: {e}\")\nexcept NotFoundError:\n    print(\"Resource not found\")\nexcept APIError as e:\n    print(f\"API error: {e.status_code} - {e.message}\")\n```\n\n### Option 4: Self-Hosted Setup\n\nRun Torale on your own infrastructure:\n\n#### 1. Install Dependencies\n```bash\npip install uv\nuv sync\n```\n\n#### 2. Set up Environment\n```bash\ncp .env.example .env\n```\nEdit `.env` with your API keys:\n- **Google AI**: Get key from https://aistudio.google.com/app/apikey (required)\n- **Database**: PostgreSQL connection string (local default works)\n- **Secret Key**: Generate with `openssl rand -hex 32`\n\n#### 3. Start Services\n```bash\n# Start all services (PostgreSQL + Temporal + API + Workers)\ndocker compose up -d\n\n# Check status\ndocker compose ps\n```\n\n#### 4. Access the Web Interface\n```bash\n# Start frontend\ncd frontend \u0026\u0026 npm run dev\n\n# Navigate to http://localhost:3000\n# Sign in with Clerk (Google/GitHub OAuth or email/password)\n# Create tasks via the dashboard UI\n```\n\n#### 5. Or use the API directly\n```bash\n# Use your API key from the web dashboard\ncurl -X POST http://localhost:8000/api/v1/tasks \\\n  -H \"Authorization: Bearer sk_your_api_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"iPhone Release Monitor\",\n    \"schedule\": \"0 9 * * *\",\n    \"executor_type\": \"llm_grounded_search\",\n    \"search_query\": \"When is the next iPhone being released?\",\n    \"condition_description\": \"A specific release date has been announced\",\n    \"notify_behavior\": \"once\",\n    \"config\": {\n      \"model\": \"gemini-2.0-flash-exp\"\n    }\n  }'\n```\n\n## Frontend\n\nThe Torale frontend is a React + TypeScript application built with Vite.\n\n### Setup\n```bash\n# Install frontend dependencies\ncd frontend \u0026\u0026 npm install\n\n# Create frontend environment file\ncat \u003e frontend/.env \u003c\u003c EOF\nVITE_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key\nVITE_API_BASE_URL=http://localhost:8000\nEOF\n\n# Start development server\nnpm run dev\n```\n\n### Features\n- **Authentication**: Clerk (Google/GitHub OAuth + email/password)\n- **Dashboard**: View and manage all monitoring tasks\n- **Task Creation**: Create new monitoring tasks with search queries and conditions\n- **Task Details**: View execution history, notifications, and state changes\n- **API Key Management**: Generate API keys for CLI access\n- **Real-time Updates**: Auto-refresh execution status\n- **Toast Notifications**: User feedback for all actions\n\n### Tech Stack\n- React 18 + TypeScript\n- Vite (build tool)\n- Clerk (authentication)\n- React Router (routing)\n- Tailwind CSS (styling)\n- shadcn/ui (component library)\n- Sonner (toast notifications)\n\nAccess the frontend at http://localhost:3000 after starting the dev server.\n\n## Architecture\n\n### Local Development\n- **API**: FastAPI with Clerk authentication + API keys\n- **Database**: PostgreSQL 16 via Docker Compose\n- **Workers**: Temporal workflows (self-hosted via Docker Compose)\n- **Executor**: Grounded search + LLM condition evaluation\n- **Scheduler**: Temporal cron schedules\n- **Search**: Google Search via Gemini grounding\n- **CLI**: Python typer with API key authentication\n\n### Production (GKE)\n- **Infrastructure**: GKE Autopilot (clusterkit) in us-central1\n- **Database**: Cloud SQL PostgreSQL 16 (managed, zonal)\n- **Orchestration**: Temporal Cloud + GitHub Actions CI/CD\n- **Cost**: Spot pods (60-91% savings), zonal Cloud SQL\n- **Domains**: api.torale.ai (API), torale.ai (Frontend)\n\n## Features\n\n### ✅ Implemented\n- Grounded search monitoring via Google Search\n- Intelligent condition evaluation (LLM-based)\n- Automatic scheduled execution (cron)\n- State tracking (no duplicate alerts)\n- User-configurable notify behavior:\n  - `once`: Notify once, then auto-disable\n  - `always`: Notify every time condition is met\n  - `track_state`: Notify only when state changes\n- In-app notifications endpoint\n- Task templates for common use cases\n- Clerk authentication (OAuth + email/password)\n- API key authentication for CLI\n- CLI for task management\n- Temporal Cloud integration (production)\n- Frontend dashboard with task management\n- GKE deployment with cost optimization\n- **Live Search Preview** - Test queries before creating tasks (#37)\n- **Immediate Task Execution** - Run monitoring tasks instantly after creation (#36)\n- **Fixed Grounding Source Display** - Clean domain names instead of Vertex AI redirect URLs (#38)\n- **AI-Powered Task Creation** - \"Magic Input\" uses LLM to generate task configuration from natural language\n- **Context-Aware Task Refinement** - \"Magic Refine\" updates existing tasks while preserving context\n- **Visual Schedule Builder** - Custom schedule dialog with hourly/daily/weekly presets and cron support\n- **Simplified Task Creation UX** - Single-page form with progressive disclosure for advanced options\n- **Modernized Task Editing** - Consistent UX with creation dialog, includes Magic Refine\n- **Temporal Context for Change Detection** - Better change detection with LLM awareness of execution history\n\n### 🚧 In Progress\n- Historical state comparison UI\n- External notifications (email/SMS)\n\n### 📋 Future Roadmap\n- **Shareable Tasks**: Share monitoring tasks with rich OpenGraph previews\n- External notifications (email/SMS/Slack via webhooks)\n- Browser automation for dynamic sites\n- Price tracking with charts\n- Multi-step conditional workflows\n- Template marketplace\n- Team/organization support\n- Natural language schedule input (\"every weekday at 9am\")\n- Timezone selection and display\n- Advanced scheduling (date ranges, skip holidays)\n\n## Known Issues\n\n### Frontend\n- **Alert Component Layout**: The info panel in the task creation dialog has alignment issues with the icon and text. The shadcn/ui Alert component's grid layout may need adjustment for proper spacing.\n\n## Research\n\nSystematic evaluation framework for comparing grounded search approaches. See [`backend/research/`](backend/research/) for details.\n\n**Results**: Perplexity achieves 80% accuracy at ~800 tokens (~9s), outperforming Gemini (60%/~750 tokens/~3.4s) and OpenAI (70%/~14,500 tokens/~28s).\n\n## Testing\n\nTorale has comprehensive unit, integration, and E2E tests covering Temporal workflows, grounded search, and scheduled execution.\n\n### Unit Tests\n\nRun pytest tests without requiring services:\n\n```bash\njust test               # Run backend unit tests\njust test-cov           # Run with coverage report\njust lint               # Run ruff linting\n```\n\n### E2E Integration Tests\n\nE2E tests require running services (PostgreSQL, Temporal, API, Workers) and support two authentication modes:\n\n**Option 1: No-Auth Mode (Recommended for Development)**\n\n```bash\n# Start services with no-auth mode\nTORALE_NOAUTH=1 just dev-bg\n\n# Run all E2E tests\nTORALE_NOAUTH=1 just test-e2e\n```\n\nThis automatically creates a test user and bypasses Clerk authentication for testing.\n\n**Option 2: Clerk Authentication (Production-like)**\n\n```bash\n# Start services normally\njust dev-bg\n\n# Get a Clerk session token:\n# 1. Login at http://localhost:3000\n# 2. Open browser dev tools (F12)\n# 3. Go to Application/Storage → Cookies\n# 4. Copy the __session cookie value\n\n# Run tests with Clerk token\nexport CLERK_TEST_TOKEN='your-clerk-session-token'\njust test-e2e\n```\n\n**Available E2E Tests:**\n- `test_temporal_e2e.sh` - Tests Temporal workflow execution\n- `test_schedule.sh` - Tests automatic scheduled task execution\n- `test_grounded_search.sh` - Tests grounded search monitoring functionality\n\nSee [docs-site/contributing/testing.md](https://docs.torale.ai/contributing/testing) for detailed testing guide, including debugging workflows and troubleshooting.\n\n## Deployment\n\n### Local Development\n```bash\njust dev        # Start all services via docker-compose\njust dev-all    # Include frontend dev server\n```\n\n### CI/CD (Recommended)\n\nTorale uses **GitHub Actions** for automated CI/CD with production and branch deployments.\n\n**Setup (one-time with keyless auth):**\n```bash\n./scripts/setup-github-wif.sh\n```\n\nThen add 3 GitHub secrets (outputted by script):\n- `GCP_PROJECT_ID`\n- `GCP_SERVICE_ACCOUNT`\n- `GCP_WORKLOAD_IDENTITY_PROVIDER`\n\nSee [CI/CD Setup](https://docs.torale.ai/deployment/ci-cd) for detailed setup.\n\n**Automatic deployments:**\n- **Push to `main`** → Production deployment (`torale` namespace)\n- **Push to `feat/**`, `fix/**`** → Branch deployment (`torale-{branch}` namespace)\n- **Pull Request** → Build and scan only (no deployment)\n\n**Branch management:**\n```bash\njust list-branches              # List all branch deployments\njust cleanup-branch feat-auth   # Delete specific branch\njust cleanup-old-branches       # Delete branches \u003e7 days old\n```\n\n**Workflows:**\n- `.github/workflows/production.yml` - Production deployment\n- `.github/workflows/branch.yml` - Branch deployments\n- `.github/workflows/pr.yml` - PR checks\n- `.github/workflows/build.yml` - Reusable build/scan job\n\n**Features:**\n- ✅ Parallel Docker builds (3x matrix jobs)\n- ✅ Security scanning with Trivy\n- ✅ Automated Helmfile deployment to GKE\n- ✅ Health checks and rollout verification\n- ✅ Isolated branch test environments\n\n### Production (GKE ClusterKit)\n\n**Prerequisites:** gcloud CLI, kubectl, helm, helmfile\n\n```bash\n# One-time setup\njust k8s-auth       # Get cluster credentials\njust k8s-setup      # Create Cloud SQL + IAM\njust k8s-secrets    # Create K8s secrets from .env\n\n# Manual deploy (if not using CI/CD)\njust k8s-deploy-all # Deploy Temporal + Torale\n\n# Manage\njust k8s-status     # Check deployment status\njust k8s-logs-api   # View API logs\njust k8s-logs-workers # View worker logs\n```\n\n**Access:**\n- Frontend: https://torale.ai\n- API: https://api.torale.ai\n- Temporal UI: `just k8s-port-forward-temporal` → http://localhost:8080\n\nSee [Kubernetes Deployment](https://docs.torale.ai/deployment/kubernetes) for detailed guide.\n\n## How Grounded Search Works\n\n1. **Task Created**: User defines search query + condition to monitor\n2. **Scheduled Execution**: Temporal triggers task based on cron schedule\n3. **Grounded Search**: Gemini performs Google Search with grounding\n4. **LLM Evaluation**: LLM analyzes search results and evaluates condition\n5. **State Comparison**: Compares with `last_known_state` to detect changes\n6. **Notification**: If condition met (and not already notified), creates in-app notification\n7. **Auto-disable** (optional): If `notify_behavior = \"once\"`, task deactivates after first alert\n\n## Configuration\n\n### Notify Behaviors\n\n- **`once`**: Alert once when condition is first met, then auto-disable task\n- **`always`**: Alert every time condition is met (use with caution)\n- **`track_state`**: Alert only when underlying state changes (smart deduplication)\n\n### Schedule Formats\n\nUse standard cron expressions:\n- `* * * * *`: Every minute (testing only)\n- `0 * * * *`: Every hour\n- `0 9 * * *`: Every day at 9 AM\n- `0 9 * * 1`: Every Monday at 9 AM\n- `0 9 1 * *`: First day of every month at 9 AM\n\n## API Endpoints\n\n### Authentication\n```\nPOST   /auth/sync-user                     # Sync Clerk user to database (auto-called)\nGET    /auth/me                            # Get current user info\nPOST   /auth/api-keys                      # Generate API key for CLI\nGET    /auth/api-keys                      # List user's API keys\nDELETE /auth/api-keys/{id}                 # Revoke API key\n```\n\n### Tasks\n```\nPOST   /api/v1/tasks/suggest               # AI-powered task suggestion from natural language (context-aware)\nPOST   /api/v1/tasks/preview               # Preview search query (test without creating task)\nPOST   /api/v1/tasks                       # Create monitoring task\nGET    /api/v1/tasks                       # List tasks\nGET    /api/v1/tasks/{id}                  # Get task details\nPUT    /api/v1/tasks/{id}                  # Update task\nDELETE /api/v1/tasks/{id}                  # Delete task + schedule\nPOST   /api/v1/tasks/{id}/execute          # Manual execution (testing)\nGET    /api/v1/tasks/{id}/executions       # Full execution history\nGET    /api/v1/tasks/{id}/notifications    # Filtered: condition_met = true\n```\n\n## CLI Commands\n\n```bash\n# Authentication\ntorale auth set-api-key                    # Configure API key\ntorale auth status                         # Check auth status\ntorale auth logout                         # Remove credentials\n\n# Tasks\ntorale task create NAME --schedule CRON --prompt PROMPT\ntorale task list [--active]\ntorale task get TASK_ID\ntorale task update TASK_ID [--name NAME] [--schedule CRON] [--active/--inactive]\ntorale task delete TASK_ID [--yes]\ntorale task execute TASK_ID               # Manual execution\ntorale task logs TASK_ID [--limit N]      # View execution logs\n\n# Development mode (no auth required)\nexport TORALE_NOAUTH=1\ntorale task list\n```\n\n## Environment Variables\n\n### Backend (.env)\n```bash\n# Database\nDATABASE_URL=postgresql://torale:torale@localhost:5432/torale\n\n# Clerk Authentication\nCLERK_SECRET_KEY=sk_test_...              # Backend: Verify Clerk tokens\nCLERK_PUBLISHABLE_KEY=pk_test_...         # Backend: Initialize Clerk client\n\n# Temporal\nTEMPORAL_HOST=localhost:7233\nTEMPORAL_NAMESPACE=default\n\n# AI (Gemini required for grounded search)\nGOOGLE_API_KEY=your-gemini-api-key\n\n# Development/Testing (optional)\nTORALE_NOAUTH=1                            # Disable auth for local testing (DO NOT USE IN PRODUCTION)\n```\n\n### Frontend (frontend/.env)\n```bash\n# Clerk\nVITE_CLERK_PUBLISHABLE_KEY=pk_test_...    # Frontend: Initialize ClerkProvider\nVITE_API_BASE_URL=http://localhost:8000   # Frontend: API endpoint\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests\n5. Submit a pull request\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprassanna-ravishankar%2Ftorale","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprassanna-ravishankar%2Ftorale","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprassanna-ravishankar%2Ftorale/lists"}