diff --git a/README.md b/README.md
index 317aa02b9d..aa25eda538 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,36 @@ Wait for the model to download, then visit [http://localhost:3000](http://localh
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.1:8b
```
+#### Using an External Ollama Instance
+
+If you already have Ollama running on your host machine (outside Docker), you need to configure the `OLLAMA_URL` to use `host.docker.internal` instead of `localhost`:
+
+```bash
+# Docker Desktop (macOS/Windows)
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+
+# Linux (add extra_hosts or use host IP)
+docker compose -f docker-compose.prod.yml up -d # Then set OLLAMA_URL to your host's IP
+```
+
+**Why?** When running inside Docker, `localhost` refers to the container itself, not your host machine. `host.docker.internal` is a special DNS name that resolves to the host.
+
+For Linux users, you can either:
+- Use your host machine's actual IP address (e.g., `http://192.168.1.100:11434`)
+- Add `extra_hosts: ["host.docker.internal:host-gateway"]` to the simstudio service in your compose file
+
+#### Using vLLM
+
+Sim also supports [vLLM](https://docs.vllm.ai/) for self-hosted models with OpenAI-compatible API:
+
+```bash
+# Set these environment variables
+VLLM_BASE_URL=http://your-vllm-server:8000
+VLLM_API_KEY=your_optional_api_key # Only if your vLLM instance requires auth
+```
+
+When running with Docker, use `host.docker.internal` if vLLM is on your host machine (same as Ollama above).
+
### Self-hosted: Dev Containers
1. Open VS Code with the [Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
@@ -190,6 +220,46 @@ Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
- Go to https://sim.ai → Settings → Copilot and generate a Copilot API key
- Set `COPILOT_API_KEY` environment variable in your self-hosted apps/sim/.env file to that value
+## Environment Variables
+
+Key environment variables for self-hosted deployments (see `apps/sim/.env.example` for full list):
+
+| Variable | Required | Description |
+|----------|----------|-------------|
+| `DATABASE_URL` | Yes | PostgreSQL connection string with pgvector |
+| `BETTER_AUTH_SECRET` | Yes | Auth secret (`openssl rand -hex 32`) |
+| `BETTER_AUTH_URL` | Yes | Your app URL (e.g., `http://localhost:3000`) |
+| `NEXT_PUBLIC_APP_URL` | Yes | Public app URL (same as above) |
+| `ENCRYPTION_KEY` | Yes | Encryption key (`openssl rand -hex 32`) |
+| `OLLAMA_URL` | No | Ollama server URL (default: `http://localhost:11434`) |
+| `VLLM_BASE_URL` | No | vLLM server URL for self-hosted models |
+| `COPILOT_API_KEY` | No | API key from sim.ai for Copilot features |
+
+## Troubleshooting
+
+### Ollama models not showing in dropdown (Docker)
+
+If you're running Ollama on your host machine and Sim in Docker, change `OLLAMA_URL` from `localhost` to `host.docker.internal`:
+
+```bash
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+```
+
+See [Using an External Ollama Instance](#using-an-external-ollama-instance) for details.
+
+### Database connection issues
+
+Ensure PostgreSQL has the pgvector extension installed. When using Docker, wait for the database to be healthy before running migrations.
+
+### Port conflicts
+
+If ports 3000, 3002, or 5432 are in use, configure alternatives:
+
+```bash
+# Custom ports
+NEXT_PUBLIC_APP_URL=http://localhost:3100 POSTGRES_PORT=5433 docker compose up -d
+```
+
## Tech Stack
- **Framework**: [Next.js](https://nextjs.org/) (App Router)
diff --git a/apps/docs/app/[lang]/[[...slug]]/page.tsx b/apps/docs/app/[lang]/[[...slug]]/page.tsx
index b75a7e5bab..f20d2604c3 100644
--- a/apps/docs/app/[lang]/[[...slug]]/page.tsx
+++ b/apps/docs/app/[lang]/[[...slug]]/page.tsx
@@ -182,7 +182,11 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
tableOfContent={{
style: 'clerk',
enabled: true,
- header:
diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts
index a5267e9eb8..fb56fb1dbf 100644
--- a/apps/docs/components/ui/icon-mapping.ts
+++ b/apps/docs/components/ui/icon-mapping.ts
@@ -15,6 +15,7 @@ import {
CalendlyIcon,
ClayIcon,
ConfluenceIcon,
+ CursorIcon,
DatadogIcon,
DiscordIcon,
DocumentIcon,
@@ -32,6 +33,7 @@ import {
GoogleDocsIcon,
GoogleDriveIcon,
GoogleFormsIcon,
+ GoogleGroupsIcon,
GoogleIcon,
GoogleSheetsIcon,
GoogleSlidesIcon,
@@ -195,6 +197,7 @@ export const blockTypeToIconMap: Record = {
google_vault: GoogleVaultIcon,
google_slides: GoogleSlidesIcon,
google_sheets: GoogleSheetsIcon,
+ google_groups: GoogleGroupsIcon,
google_forms: GoogleFormsIcon,
google_drive: GoogleDriveIcon,
google_docs: GoogleDocsIcon,
@@ -212,6 +215,7 @@ export const blockTypeToIconMap: Record = {
dropbox: DropboxIcon,
discord: DiscordIcon,
datadog: DatadogIcon,
+ cursor: CursorIcon,
confluence: ConfluenceIcon,
clay: ClayIcon,
calendly: CalendlyIcon,
diff --git a/apps/docs/content/docs/de/blocks/condition.mdx b/apps/docs/content/docs/de/blocks/condition.mdx
index 176c026ffe..5d8687e81d 100644
--- a/apps/docs/content/docs/de/blocks/condition.mdx
+++ b/apps/docs/content/docs/de/blocks/condition.mdx
@@ -143,7 +143,7 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Bewährte Praktiken
- **Bedingungen korrekt anordnen**: Platzieren Sie spezifischere Bedingungen vor allgemeinen, um sicherzustellen, dass spezifische Logik Vorrang vor Fallbacks hat
-- **Eine Standardbedingung einfügen**: Fügen Sie eine Auffangbedingung (`true`) als letzte Bedingung hinzu, um nicht übereinstimmende Fälle zu behandeln und zu verhindern, dass die Workflow-Ausführung stecken bleibt
-- **Ausdrücke einfach halten**: Verwenden Sie klare, unkomplizierte boolesche Ausdrücke für bessere Lesbarkeit und einfachere Fehlersuche
+- **Verwenden Sie den Else-Zweig bei Bedarf**: Wenn keine Bedingungen übereinstimmen und der Else-Zweig nicht verbunden ist, endet der Workflow-Zweig ordnungsgemäß. Verbinden Sie den Else-Zweig, wenn Sie einen Fallback-Pfad für nicht übereinstimmende Fälle benötigen
+- **Halten Sie Ausdrücke einfach**: Verwenden Sie klare, unkomplizierte boolesche Ausdrücke für bessere Lesbarkeit und einfachere Fehlersuche
- **Dokumentieren Sie Ihre Bedingungen**: Fügen Sie Beschreibungen hinzu, um den Zweck jeder Bedingung für bessere Teamzusammenarbeit und Wartung zu erklären
-- **Grenzfälle testen**: Überprüfen Sie, ob Bedingungen Grenzwerte korrekt behandeln, indem Sie mit Werten an den Grenzen Ihrer Bedingungsbereiche testen
+- **Testen Sie Grenzfälle**: Überprüfen Sie, ob Bedingungen Grenzwerte korrekt behandeln, indem Sie mit Werten an den Grenzen Ihrer Bedingungsbereiche testen
diff --git a/apps/docs/content/docs/de/introduction/index.mdx b/apps/docs/content/docs/de/introduction/index.mdx
index 5cc6003211..277562dd39 100644
--- a/apps/docs/content/docs/de/introduction/index.mdx
+++ b/apps/docs/content/docs/de/introduction/index.mdx
@@ -72,7 +72,7 @@ Für benutzerdefinierte Integrationen nutzen Sie unsere [MCP (Model Context Prot
-## KI-gesteuerter Copilot
+## Copilot
**Fragen stellen & Anleitung erhalten**
Der Copilot beantwortet Fragen zu Sim, erklärt Ihre Workflows und gibt Verbesserungsvorschläge. Verwenden Sie das `@` Symbol, um auf Workflows, Blöcke, Dokumentation, Wissen und Protokolle für kontextbezogene Unterstützung zu verweisen.
diff --git a/apps/docs/content/docs/de/knowledgebase/index.mdx b/apps/docs/content/docs/de/knowledgebase/index.mdx
index ad1f61d802..b6ce546fbd 100644
--- a/apps/docs/content/docs/de/knowledgebase/index.mdx
+++ b/apps/docs/content/docs/de/knowledgebase/index.mdx
@@ -1,5 +1,7 @@
---
-title: Wissensdatenbank
+title: Übersicht
+description: Laden Sie Ihre Dokumente hoch, verarbeiten und durchsuchen Sie sie
+ mit intelligenter Vektorsuche und Chunking
---
import { Video } from '@/components/ui/video'
diff --git a/apps/docs/content/docs/de/self-hosting/docker.mdx b/apps/docs/content/docs/de/self-hosting/docker.mdx
new file mode 100644
index 0000000000..6623e7aabd
--- /dev/null
+++ b/apps/docs/content/docs/de/self-hosting/docker.mdx
@@ -0,0 +1,155 @@
+---
+title: Docker
+description: Sim Studio mit Docker Compose bereitstellen
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Schnellstart
+
+```bash
+# Clone and start
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Öffnen Sie [http://localhost:3000](http://localhost:3000)
+
+## Produktionseinrichtung
+
+### 1. Umgebung konfigurieren
+
+```bash
+# Generate secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+```
+
+### 2. Dienste starten
+
+```bash
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### 3. SSL einrichten
+
+
+
+Caddy verwaltet SSL-Zertifikate automatisch.
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+```
+
+Erstellen Sie `/etc/caddy/Caddyfile`:
+
+```
+sim.yourdomain.com {
+ reverse_proxy localhost:3000
+
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}
+```
+
+```bash
+sudo systemctl restart caddy
+```
+
+
+
+
+```bash
+# Install
+sudo apt install nginx certbot python3-certbot-nginx -y
+
+# Create /etc/nginx/sites-available/sim
+server {
+ listen 80;
+ server_name sim.yourdomain.com;
+
+ location / {
+ proxy_pass http://127.0.0.1:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /socket.io/ {
+ proxy_pass http://127.0.0.1:3002;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
+
+# Enable and get certificate
+sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
+sudo certbot --nginx -d sim.yourdomain.com
+```
+
+
+
+
+## Ollama
+
+```bash
+# With GPU
+docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
+
+# CPU only
+docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
+```
+
+Zusätzliche Modelle herunterladen:
+
+```bash
+docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
+```
+
+### Externes Ollama
+
+Wenn Ollama auf Ihrem Host-Rechner läuft (nicht in Docker):
+
+```bash
+# macOS/Windows
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+
+# Linux - use your host IP
+OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
+```
+
+
+ Innerhalb von Docker bezieht sich `localhost` auf den Container, nicht auf Ihren Host. Verwenden Sie `host.docker.internal` oder die IP-Adresse Ihres Hosts.
+
+
+## Befehle
+
+```bash
+# View logs
+docker compose -f docker-compose.prod.yml logs -f simstudio
+
+# Stop
+docker compose -f docker-compose.prod.yml down
+
+# Update
+docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
+
+# Backup database
+docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
+```
diff --git a/apps/docs/content/docs/de/self-hosting/environment-variables.mdx b/apps/docs/content/docs/de/self-hosting/environment-variables.mdx
new file mode 100644
index 0000000000..4a1f4d34df
--- /dev/null
+++ b/apps/docs/content/docs/de/self-hosting/environment-variables.mdx
@@ -0,0 +1,87 @@
+---
+title: Umgebungsvariablen
+description: Konfigurationsreferenz für Sim Studio
+---
+
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Erforderlich
+
+| Variable | Beschreibung |
+|----------|-------------|
+| `DATABASE_URL` | PostgreSQL-Verbindungszeichenfolge |
+| `BETTER_AUTH_SECRET` | Auth-Secret (32 Hex-Zeichen): `openssl rand -hex 32` |
+| `BETTER_AUTH_URL` | Ihre App-URL |
+| `ENCRYPTION_KEY` | Verschlüsselungsschlüssel (32 Hex-Zeichen): `openssl rand -hex 32` |
+| `INTERNAL_API_SECRET` | Internes API-Secret (32 Hex-Zeichen): `openssl rand -hex 32` |
+| `NEXT_PUBLIC_APP_URL` | Öffentliche App-URL |
+| `NEXT_PUBLIC_SOCKET_URL` | WebSocket-URL (Standard: `http://localhost:3002`) |
+
+## KI-Anbieter
+
+| Variable | Anbieter |
+|----------|----------|
+| `OPENAI_API_KEY` | OpenAI |
+| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
+| `GEMINI_API_KEY_1` | Google Gemini |
+| `MISTRAL_API_KEY` | Mistral |
+| `OLLAMA_URL` | Ollama (Standard: `http://localhost:11434`) |
+
+
+ Für Lastausgleich fügen Sie mehrere Schlüssel mit den Suffixen `_1`, `_2`, `_3` hinzu (z.B. `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Funktioniert mit OpenAI, Anthropic und Gemini.
+
+
+
+ In Docker verwenden Sie `OLLAMA_URL=http://host.docker.internal:11434` für Ollama auf dem Host-System.
+
+
+### Azure OpenAI
+
+| Variable | Beschreibung |
+|----------|-------------|
+| `AZURE_OPENAI_API_KEY` | Azure OpenAI API-Schlüssel |
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI Endpoint-URL |
+| `AZURE_OPENAI_API_VERSION` | API-Version (z.B. `2024-02-15-preview`) |
+
+### vLLM (Selbst-gehostet)
+
+| Variable | Beschreibung |
+|----------|-------------|
+| `VLLM_BASE_URL` | vLLM-Server-URL (z.B. `http://localhost:8000/v1`) |
+| `VLLM_API_KEY` | Optionaler Bearer-Token für vLLM |
+
+## OAuth-Anbieter
+
+| Variable | Beschreibung |
+|----------|-------------|
+| `GOOGLE_CLIENT_ID` | Google OAuth Client-ID |
+| `GOOGLE_CLIENT_SECRET` | Google OAuth Client-Secret |
+| `GITHUB_CLIENT_ID` | GitHub OAuth Client-ID |
+| `GITHUB_CLIENT_SECRET` | GitHub OAuth Client-Secret |
+
+## Optional
+
+| Variable | Beschreibung |
+|----------|-------------|
+| `API_ENCRYPTION_KEY` | Verschlüsselt gespeicherte API-Schlüssel (32 Hex-Zeichen): `openssl rand -hex 32` |
+| `COPILOT_API_KEY` | API-Schlüssel für Copilot-Funktionen |
+| `ADMIN_API_KEY` | Admin-API-Schlüssel für GitOps-Operationen |
+| `RESEND_API_KEY` | E-Mail-Dienst für Benachrichtigungen |
+| `ALLOWED_LOGIN_DOMAINS` | Registrierungen auf Domains beschränken (durch Kommas getrennt) |
+| `ALLOWED_LOGIN_EMAILS` | Registrierungen auf bestimmte E-Mails beschränken (durch Kommas getrennt) |
+| `DISABLE_REGISTRATION` | Auf `true` setzen, um neue Benutzerregistrierungen zu deaktivieren |
+
+## Beispiel .env
+
+```bash
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=
+BETTER_AUTH_URL=https://sim.yourdomain.com
+ENCRYPTION_KEY=
+INTERNAL_API_SECRET=
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+OPENAI_API_KEY=sk-...
+```
+
+Siehe `apps/sim/.env.example` für alle Optionen.
diff --git a/apps/docs/content/docs/de/self-hosting/index.mdx b/apps/docs/content/docs/de/self-hosting/index.mdx
new file mode 100644
index 0000000000..e615ea09b7
--- /dev/null
+++ b/apps/docs/content/docs/de/self-hosting/index.mdx
@@ -0,0 +1,50 @@
+---
+title: Self-Hosting
+description: Stellen Sie Sim Studio auf Ihrer eigenen Infrastruktur bereit
+---
+
+import { Card, Cards } from 'fumadocs-ui/components/card'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+Stellen Sie Sim Studio auf Ihrer eigenen Infrastruktur mit Docker oder Kubernetes bereit.
+
+## Anforderungen
+
+| Ressource | Minimum | Empfohlen |
+|----------|---------|-------------|
+| CPU | 2 Kerne | 4+ Kerne |
+| RAM | 12 GB | 16+ GB |
+| Speicher | 20 GB SSD | 50+ GB SSD |
+| Docker | 20.10+ | Neueste Version |
+
+## Schnellstart
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Öffnen Sie [http://localhost:3000](http://localhost:3000)
+
+## Bereitstellungsoptionen
+
+
+
+ Bereitstellung mit Docker Compose auf jedem Server
+
+
+ Bereitstellung mit Helm auf Kubernetes-Clustern
+
+
+ Anleitungen für Railway, DigitalOcean, AWS, Azure, GCP
+
+
+
+## Architektur
+
+| Komponente | Port | Beschreibung |
+|-----------|------|-------------|
+| simstudio | 3000 | Hauptanwendung |
+| realtime | 3002 | WebSocket-Server |
+| db | 5432 | PostgreSQL mit pgvector |
+| migrations | - | Datenbank-Migrationen (werden einmal ausgeführt) |
diff --git a/apps/docs/content/docs/de/self-hosting/kubernetes.mdx b/apps/docs/content/docs/de/self-hosting/kubernetes.mdx
new file mode 100644
index 0000000000..3cdfd712f1
--- /dev/null
+++ b/apps/docs/content/docs/de/self-hosting/kubernetes.mdx
@@ -0,0 +1,133 @@
+---
+title: Kubernetes
+description: Sim Studio mit Helm bereitstellen
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Voraussetzungen
+
+- Kubernetes 1.19+
+- Helm 3.0+
+- PV-Provisioner-Unterstützung
+
+## Installation
+
+```bash
+# Clone repo
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Generate secrets
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+
+# Install
+helm install sim ./helm/sim \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --namespace simstudio --create-namespace
+```
+
+## Cloud-spezifische Werte
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-aws.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-azure.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-gcp.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+## Schlüsselkonfiguration
+
+```yaml
+# Custom values.yaml
+app:
+ replicaCount: 2
+ env:
+ NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
+ OPENAI_API_KEY: "sk-..."
+
+postgresql:
+ persistence:
+ size: 50Gi
+
+ingress:
+ enabled: true
+ className: nginx
+ tls:
+ enabled: true
+ app:
+ host: sim.yourdomain.com
+```
+
+Siehe `helm/sim/values.yaml` für alle Optionen.
+
+## Externe Datenbank
+
+```yaml
+postgresql:
+ enabled: false
+
+externalDatabase:
+ enabled: true
+ host: "your-db-host"
+ port: 5432
+ username: "postgres"
+ password: "your-password"
+ database: "simstudio"
+ sslMode: "require"
+```
+
+## Befehle
+
+```bash
+# Port forward for local access
+kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
+
+# View logs
+kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
+
+# Upgrade
+helm upgrade sim ./helm/sim --namespace simstudio
+
+# Uninstall
+helm uninstall sim --namespace simstudio
+```
diff --git a/apps/docs/content/docs/de/self-hosting/platforms.mdx b/apps/docs/content/docs/de/self-hosting/platforms.mdx
new file mode 100644
index 0000000000..ef34af4def
--- /dev/null
+++ b/apps/docs/content/docs/de/self-hosting/platforms.mdx
@@ -0,0 +1,124 @@
+---
+title: Cloud-Plattformen
+description: Sim Studio auf Cloud-Plattformen bereitstellen
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Railway
+
+Bereitstellung mit einem Klick und automatischer PostgreSQL-Bereitstellung.
+
+[
+
+
+
+](https://railway.com/new/template/sim-studio)
+
+Nach der Bereitstellung fügen Sie Umgebungsvariablen im Railway-Dashboard hinzu:
+- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (automatisch generiert)
+- `OPENAI_API_KEY` oder andere KI-Anbieter-Schlüssel
+- Benutzerdefinierte Domain in Einstellungen → Netzwerk
+
+## VPS-Bereitstellung
+
+Für DigitalOcean, AWS EC2, Azure VMs oder jeden Linux-Server:
+
+
+
+**Empfohlen:** 16 GB RAM Droplet, Ubuntu 24.04
+
+```bash
+# Create Droplet via console, then SSH in
+ssh root@your-droplet-ip
+```
+
+
+
+**Empfohlen:** t3.xlarge (16 GB RAM), Ubuntu 24.04
+
+```bash
+ssh -i your-key.pem ubuntu@your-ec2-ip
+```
+
+
+
+**Empfohlen:** Standard_D4s_v3 (16 GB RAM), Ubuntu 24.04
+
+```bash
+ssh azureuser@your-vm-ip
+```
+
+
+
+
+### Docker installieren
+
+```bash
+# Install Docker (official method)
+curl -fsSL https://get.docker.com | sudo sh
+sudo usermod -aG docker $USER
+
+# Logout and reconnect, then verify
+docker --version
+```
+
+### Sim Studio bereitstellen
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Create .env with secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+
+# Start
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### SSL mit Caddy
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+
+# Configure (replace domain)
+echo 'sim.yourdomain.com {
+ reverse_proxy localhost:3000
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}' | sudo tee /etc/caddy/Caddyfile
+
+sudo systemctl restart caddy
+```
+
+Richten Sie den DNS A-Eintrag Ihrer Domain auf die IP-Adresse Ihres Servers.
+
+## Kubernetes (EKS, AKS, GKE)
+
+Siehe den [Kubernetes-Leitfaden](/self-hosting/kubernetes) für Helm-Deployment auf verwaltetem Kubernetes.
+
+## Verwaltete Datenbank (Optional)
+
+Für den Produktivbetrieb sollten Sie einen verwalteten PostgreSQL-Dienst verwenden:
+
+- **AWS RDS** / **Azure Database** / **Cloud SQL** - Aktivieren Sie die pgvector-Erweiterung
+- **Supabase** / **Neon** - pgvector enthalten
+
+Setzen Sie `DATABASE_URL` in Ihrer Umgebung:
+
+```bash
+DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
+```
diff --git a/apps/docs/content/docs/de/self-hosting/troubleshooting.mdx b/apps/docs/content/docs/de/self-hosting/troubleshooting.mdx
new file mode 100644
index 0000000000..97b4839984
--- /dev/null
+++ b/apps/docs/content/docs/de/self-hosting/troubleshooting.mdx
@@ -0,0 +1,113 @@
+---
+title: Fehlerbehebung
+description: Häufige Probleme und Lösungen
+---
+
+## Datenbankverbindung fehlgeschlagen
+
+```bash
+# Check database is running
+docker compose ps db
+
+# Test connection
+docker compose exec db psql -U postgres -c "SELECT 1"
+```
+
+Überprüfen Sie das `DATABASE_URL` Format: `postgresql://user:pass@host:5432/database`
+
+## Ollama-Modelle werden nicht angezeigt
+
+In Docker ist `localhost` = der Container, nicht Ihr Host-Rechner.
+
+```bash
+# For host-machine Ollama, use:
+OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
+OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
+```
+
+## WebSocket/Echtzeit funktioniert nicht
+
+1. Prüfen Sie, ob `NEXT_PUBLIC_SOCKET_URL` mit Ihrer Domain übereinstimmt
+2. Überprüfen Sie, ob der Echtzeit-Dienst läuft: `docker compose ps realtime`
+3. Stellen Sie sicher, dass der Reverse-Proxy WebSocket-Upgrades weiterleitet (siehe [Docker-Anleitung](/self-hosting/docker))
+
+## 502 Bad Gateway
+
+```bash
+# Check app is running
+docker compose ps simstudio
+docker compose logs simstudio
+
+# Common causes: out of memory, database not ready
+```
+
+## Migrationsfehler
+
+```bash
+# View migration logs
+docker compose logs migrations
+
+# Run manually
+docker compose exec simstudio bun run db:migrate
+```
+
+## pgvector nicht gefunden
+
+Verwenden Sie das richtige PostgreSQL-Image:
+
+```yaml
+image: pgvector/pgvector:pg17 # NOT postgres:17
+```
+
+## Zertifikatsfehler (CERT_HAS_EXPIRED)
+
+Wenn Sie SSL-Zertifikatsfehler beim Aufrufen externer APIs sehen:
+
+```bash
+# Update CA certificates in container
+docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
+
+# Or set in environment (not recommended for production)
+NODE_TLS_REJECT_UNAUTHORIZED=0
+```
+
+## Leere Seite nach dem Login
+
+1. Überprüfen Sie die Browser-Konsole auf Fehler
+2. Stellen Sie sicher, dass `NEXT_PUBLIC_APP_URL` mit Ihrer tatsächlichen Domain übereinstimmt
+3. Löschen Sie Browser-Cookies und lokalen Speicher
+4. Prüfen Sie, ob alle Dienste laufen: `docker compose ps`
+
+## Windows-spezifische Probleme
+
+**Turbopack-Fehler unter Windows:**
+
+```bash
+# Use WSL2 for better compatibility
+wsl --install
+
+# Or disable Turbopack in package.json
+# Change "next dev --turbopack" to "next dev"
+```
+
+**Zeilenende-Probleme:**
+
+```bash
+# Configure git to use LF
+git config --global core.autocrlf input
+```
+
+## Logs anzeigen
+
+```bash
+# All services
+docker compose logs -f
+
+# Specific service
+docker compose logs -f simstudio
+```
+
+## Hilfe erhalten
+
+- [GitHub Issues](https://github.com/simstudioai/sim/issues)
+- [Discord](https://discord.gg/Hr4UWYEcTT)
diff --git a/apps/docs/content/docs/de/tools/cursor.mdx b/apps/docs/content/docs/de/tools/cursor.mdx
new file mode 100644
index 0000000000..29e50be6af
--- /dev/null
+++ b/apps/docs/content/docs/de/tools/cursor.mdx
@@ -0,0 +1,182 @@
+---
+title: Cursor
+description: Starten und verwalten Sie Cursor Cloud-Agenten zur Arbeit an
+ GitHub-Repositories
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+{/* MANUAL-CONTENT-START:intro */}
+[Cursor](https://www.cursor.so/) ist eine KI-IDE und cloudbasierte Plattform, mit der Sie leistungsstarke KI-Agenten starten und verwalten können, die direkt mit Ihren GitHub-Repositories arbeiten können. Cursor-Agenten können Entwicklungsaufgaben automatisieren, die Produktivität Ihres Teams steigern und mit Ihnen zusammenarbeiten, indem sie Codeänderungen vornehmen, auf natürlichsprachliche Anweisungen reagieren und einen Gesprächsverlauf über ihre Aktivitäten führen.
+
+Mit Cursor können Sie:
+
+- **Cloud-Agenten für Codebasen starten**: Erstellen Sie sofort neue KI-Agenten, die in der Cloud an Ihren Repositories arbeiten
+- **Codierungsaufgaben mit natürlicher Sprache delegieren**: Leiten Sie Agenten mit schriftlichen Anweisungen, Änderungen und Klarstellungen an
+- **Fortschritt und Ergebnisse überwachen**: Rufen Sie den Agentenstatus ab, sehen Sie detaillierte Ergebnisse und prüfen Sie aktuelle oder abgeschlossene Aufgaben
+- **Zugriff auf den vollständigen Gesprächsverlauf**: Überprüfen Sie alle Eingabeaufforderungen und KI-Antworten für Transparenz und Nachvollziehbarkeit
+- **Steuerung und Verwaltung des Agenten-Lebenszyklus**: Listen Sie aktive Agenten auf, beenden Sie Agenten und verwalten Sie API-basierte Agentenstarts und Nachverfolgungen
+
+In Sim ermöglicht die Cursor-Integration Ihren Agenten und Workflows, programmatisch mit Cursor-Cloud-Agenten zu interagieren. Das bedeutet, Sie können Sim verwenden, um:
+
+- Alle Cloud-Agenten auflisten und ihren aktuellen Status durchsuchen (`cursor_list_agents`)
+- Aktuellen Status und Ausgaben für jeden Agenten abrufen (`cursor_get_agent`)
+- Den vollständigen Gesprächsverlauf für jeden Codierungsagenten anzeigen (`cursor_get_conversation`)
+- Nachfolgende Anweisungen oder neue Eingabeaufforderungen zu einem laufenden Agenten hinzufügen
+- Agenten nach Bedarf verwalten und beenden
+
+Diese Integration hilft Ihnen, die flexible Intelligenz von Sim-Agenten mit den leistungsstarken Automatisierungsfunktionen von Cursor zu kombinieren, wodurch es möglich wird, KI-gesteuerte Entwicklung über Ihre Projekte hinweg zu skalieren.
+{/* MANUAL-CONTENT-END */}
+
+## Gebrauchsanweisung
+
+Interagieren Sie mit der Cursor Cloud Agents API, um KI-Agenten zu starten, die an Ihren GitHub-Repositories arbeiten können. Unterstützt das Starten von Agenten, das Hinzufügen von Folgeanweisungen, die Statusprüfung, die Anzeige von Konversationen und die Verwaltung des Agenten-Lebenszyklus.
+
+## Tools
+
+### `cursor_list_agents`
+
+Listet alle Cloud-Agenten für den authentifizierten Benutzer mit optionaler Paginierung auf.
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Ja | Cursor API-Schlüssel |
+| `limit` | number | Nein | Anzahl der zurückzugebenden Agenten \(Standard: 20, max: 100\) |
+| `cursor` | string | Nein | Paginierungscursor aus der vorherigen Antwort |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `content` | string | Menschenlesbare Liste der Agenten |
+| `metadata` | object | Metadaten der Agentenliste |
+
+### `cursor_get_agent`
+
+Ruft den aktuellen Status und die Ergebnisse eines Cloud-Agenten ab.
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Ja | Cursor API-Schlüssel |
+| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `content` | string | Menschenlesbare Agentendetails |
+| `metadata` | object | Agenten-Metadaten |
+
+### `cursor_get_conversation`
+
+Ruft den Konversationsverlauf eines Cloud-Agenten ab, einschließlich aller Benutzeraufforderungen und Assistentenantworten.
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Ja | Cursor API-Schlüssel |
+| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
+
+#### Output
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `content` | string | Menschenlesbarer Konversationsverlauf |
+| `metadata` | object | Konversations-Metadaten |
+
+### `cursor_launch_agent`
+
+Starten Sie einen neuen Cloud-Agenten, um an einem GitHub-Repository mit den angegebenen Anweisungen zu arbeiten.
+
+#### Input
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Ja | Cursor API-Schlüssel |
+| `repository` | string | Ja | GitHub-Repository-URL \(z.B. https://github.com/your-org/your-repo\) |
+| `ref` | string | Nein | Branch, Tag oder Commit, von dem aus gearbeitet werden soll \(standardmäßig der Hauptbranch\) |
+| `promptText` | string | Ja | Der Anweisungstext für den Agenten |
+| `promptImages` | string | Nein | JSON-Array von Bildobjekten mit Base64-Daten und Abmessungen |
+| `model` | string | Nein | Zu verwendendes Modell \(leer lassen für automatische Auswahl\) |
+| `branchName` | string | Nein | Benutzerdefinierter Branch-Name für den Agenten |
+| `autoCreatePr` | boolean | Nein | Automatisches Erstellen eines PR, wenn der Agent fertig ist |
+| `openAsCursorGithubApp` | boolean | Nein | Öffnen des PR als Cursor GitHub App |
+| `skipReviewerRequest` | boolean | Nein | Überspringen der Anfrage nach Prüfern für den PR |
+
+#### Output
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `content` | string | Erfolgsmeldung mit Agenten-Details |
+| `metadata` | object | Metadaten zum Startergebnis |
+
+### `cursor_add_followup`
+
+Fügen Sie einem bestehenden Cloud-Agenten eine Folgeanweisung hinzu.
+
+#### Input
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Ja | Cursor API-Schlüssel |
+| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
+| `followupPromptText` | string | Ja | Der Folgeanweisungstext für den Agenten |
+| `promptImages` | string | Nein | JSON-Array von Bildobjekten mit Base64-Daten und Abmessungen \(max. 5\) |
+
+#### Output
+
+| Parameter | Type | Beschreibung |
+| --------- | ---- | ----------- |
+| `content` | string | Erfolgsmeldung |
+| `metadata` | object | Ergebnis-Metadaten |
+
+### `cursor_stop_agent`
+
+Stoppt einen laufenden Cloud-Agenten. Dies pausiert den Agenten, ohne ihn zu löschen.
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Ja | Cursor API-Schlüssel |
+| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `content` | string | Erfolgsmeldung |
+| `metadata` | object | Ergebnis-Metadaten |
+
+### `cursor_delete_agent`
+
+Löscht einen Cloud-Agenten dauerhaft. Diese Aktion kann nicht rückgängig gemacht werden.
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Ja | Cursor API-Schlüssel |
+| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `content` | string | Erfolgsmeldung |
+| `metadata` | object | Ergebnis-Metadaten |
+
+## Hinweise
+
+- Kategorie: `tools`
+- Typ: `cursor`
diff --git a/apps/docs/content/docs/de/tools/google_groups.mdx b/apps/docs/content/docs/de/tools/google_groups.mdx
new file mode 100644
index 0000000000..f9bdcb67ca
--- /dev/null
+++ b/apps/docs/content/docs/de/tools/google_groups.mdx
@@ -0,0 +1,217 @@
+---
+title: Google Groups
+description: Google Workspace-Gruppen und deren Mitglieder verwalten
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+## Gebrauchsanweisung
+
+Verbinden Sie sich mit Google Workspace, um Gruppen und deren Mitglieder mit der Admin SDK Directory API zu erstellen, zu aktualisieren und zu verwalten.
+
+## Tools
+
+### `google_groups_list_groups`
+
+Alle Gruppen in einer Google Workspace-Domain auflisten
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `customer` | string | Nein | Kunden-ID oder "my_customer" für die Domain des authentifizierten Benutzers |
+| `domain` | string | Nein | Domainname zum Filtern von Gruppen |
+| `maxResults` | number | Nein | Maximale Anzahl der zurückzugebenden Ergebnisse \(1-200\) |
+| `pageToken` | string | Nein | Token für Paginierung |
+| `query` | string | Nein | Suchabfrage zum Filtern von Gruppen \(z.B. "email:admin*"\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_get_group`
+
+Details einer bestimmten Google-Gruppe nach E-Mail oder Gruppen-ID abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_create_group`
+
+Eine neue Google-Gruppe in der Domain erstellen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `email` | string | Ja | E-Mail-Adresse für die neue Gruppe (z.B. team@yourdomain.com) |
+| `name` | string | Ja | Anzeigename für die Gruppe |
+| `description` | string | Nein | Beschreibung der Gruppe |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_update_group`
+
+Eine bestehende Google-Gruppe aktualisieren
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+| `name` | string | Nein | Neuer Anzeigename für die Gruppe |
+| `description` | string | Nein | Neue Beschreibung für die Gruppe |
+| `email` | string | Nein | Neue E-Mail-Adresse für die Gruppe |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_delete_group`
+
+Eine Google-Gruppe löschen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID zum Löschen |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_list_members`
+
+Alle Mitglieder einer Google-Gruppe auflisten
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+| `maxResults` | number | Nein | Maximale Anzahl der zurückzugebenden Ergebnisse \(1-200\) |
+| `pageToken` | string | Nein | Token für Seitenumbruch |
+| `roles` | string | Nein | Nach Rollen filtern \(durch Komma getrennt: OWNER, MANAGER, MEMBER\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_get_member`
+
+Details eines bestimmten Mitglieds in einer Google-Gruppe abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+| `memberKey` | string | Ja | E-Mail-Adresse des Mitglieds oder eindeutige Mitglieds-ID |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_add_member`
+
+Ein neues Mitglied zu einer Google-Gruppe hinzufügen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+| `email` | string | Ja | E-Mail-Adresse des hinzuzufügenden Mitglieds |
+| `role` | string | Nein | Rolle für das Mitglied \(MEMBER, MANAGER oder OWNER\). Standardmäßig MEMBER. |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_remove_member`
+
+Ein Mitglied aus einer Google-Gruppe entfernen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+| `memberKey` | string | Ja | E-Mail-Adresse oder eindeutige ID des zu entfernenden Mitglieds |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_update_member`
+
+Ein Mitglied aktualisieren
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+| `memberKey` | string | Ja | E-Mail-Adresse des Mitglieds oder eindeutige Mitglieds-ID |
+| `role` | string | Ja | Neue Rolle für das Mitglied \(MEMBER, MANAGER oder OWNER\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+### `google_groups_has_member`
+
+Prüfen, ob ein Benutzer Mitglied einer Google-Gruppe ist
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
+| `memberKey` | string | Ja | Zu prüfende E-Mail-Adresse des Mitglieds oder eindeutige Mitglieds-ID |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API-Antwortdaten |
+
+## Hinweise
+
+- Kategorie: `tools`
+- Typ: `google_groups`
diff --git a/apps/docs/content/docs/de/tools/salesforce.mdx b/apps/docs/content/docs/de/tools/salesforce.mdx
index 64ec7bbce6..f2a5f09eee 100644
--- a/apps/docs/content/docs/de/tools/salesforce.mdx
+++ b/apps/docs/content/docs/de/tools/salesforce.mdx
@@ -135,283 +135,684 @@ Löschen eines Kontos aus Salesforce CRM
### `salesforce_get_contacts`
+Kontakt(e) aus Salesforce abrufen - einzelner Kontakt, wenn ID angegeben, oder Liste, wenn nicht
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `contactId` | string | Nein | Kontakt-ID \(wenn angegeben, wird ein einzelner Kontakt zurückgegeben\) |
+| `limit` | string | Nein | Anzahl der Ergebnisse \(Standard: 100, max: 2000\). Nur für Listenabfrage. |
+| `fields` | string | Nein | Kommagetrennte Felder \(z.B. "Id,FirstName,LastName,Email,Phone"\) |
+| `orderBy` | string | Nein | Sortierfeld \(z.B. "LastName ASC"\). Nur für Listenabfrage. |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `output` | object | Kontaktdaten |
### `salesforce_create_contact`
+Einen neuen Kontakt im Salesforce CRM erstellen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `lastName` | string | Ja | Nachname \(erforderlich\) |
+| `firstName` | string | Nein | Vorname |
+| `email` | string | Nein | E-Mail-Adresse |
+| `phone` | string | Nein | Telefonnummer |
+| `accountId` | string | Nein | Konto-ID, mit der der Kontakt verknüpft werden soll |
+| `title` | string | Nein | Keine Beschreibung |
+| `department` | string | Nein | Abteilung |
+| `mailingStreet` | string | Nein | Postanschrift \(Straße\) |
+| `mailingCity` | string | Nein | Postanschrift \(Stadt\) |
+| `mailingState` | string | Nein | Postanschrift \(Bundesland\) |
+| `mailingPostalCode` | string | Nein | Postanschrift \(Postleitzahl\) |
+| `mailingCountry` | string | Nein | Postanschrift \(Land\) |
+| `description` | string | Nein | Kontaktbeschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `output` | object | Daten des erstellten Kontakts |
### `salesforce_update_contact`
+Aktualisieren eines bestehenden Kontakts in Salesforce CRM
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `contactId` | string | Ja | Zu aktualisierende Kontakt-ID \(erforderlich\) |
+| `lastName` | string | Nein | Nachname |
+| `firstName` | string | Nein | Vorname |
+| `email` | string | Nein | E-Mail-Adresse |
+| `phone` | string | Nein | Telefonnummer |
+| `accountId` | string | Nein | Zu verknüpfende Konto-ID |
+| `title` | string | Nein | Keine Beschreibung |
+| `department` | string | Nein | Abteilung |
+| `mailingStreet` | string | Nein | Postanschrift (Straße) |
+| `mailingCity` | string | Nein | Postanschrift (Stadt) |
+| `mailingState` | string | Nein | Postanschrift (Bundesland) |
+| `mailingPostalCode` | string | Nein | Postanschrift (Postleitzahl) |
+| `mailingCountry` | string | Nein | Postanschrift (Land) |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `output` | object | Daten des aktualisierten Kontakts |
### `salesforce_delete_contact`
+Löschen eines Kontakts aus Salesforce CRM
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `contactId` | string | Ja | Zu löschende Kontakt-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `output` | object | Daten des gelöschten Kontakts |
### `salesforce_get_leads`
+Lead(s) aus Salesforce abrufen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `leadId` | string | Nein | Lead-ID \(optional\) |
+| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
+| `fields` | string | Nein | Kommagetrennte Felder |
+| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Lead-Daten |
### `salesforce_create_lead`
+Einen neuen Lead erstellen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `lastName` | string | Ja | Nachname \(erforderlich\) |
+| `company` | string | Ja | Unternehmen \(erforderlich\) |
+| `firstName` | string | Nein | Vorname |
+| `email` | string | Nein | Keine Beschreibung |
+| `phone` | string | Nein | Keine Beschreibung |
+| `status` | string | Nein | Lead-Status |
+| `leadSource` | string | Nein | Lead-Quelle |
+| `title` | string | Nein | Keine Beschreibung |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Erstellter Lead |
### `salesforce_update_lead`
+Aktualisieren eines vorhandenen Leads
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `leadId` | string | Ja | Lead-ID (erforderlich) |
+| `lastName` | string | Nein | Nachname |
+| `company` | string | Nein | Keine Beschreibung |
+| `firstName` | string | Nein | Vorname |
+| `email` | string | Nein | Keine Beschreibung |
+| `phone` | string | Nein | Keine Beschreibung |
+| `status` | string | Nein | Lead-Status |
+| `leadSource` | string | Nein | Lead-Quelle |
+| `title` | string | Nein | Keine Beschreibung |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Aktualisierter Lead |
### `salesforce_delete_lead`
+Löschen eines Leads
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `leadId` | string | Ja | Lead-ID (erforderlich) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Gelöschter Lead |
### `salesforce_get_opportunities`
+Verkaufschance(n) aus Salesforce abrufen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `opportunityId` | string | Nein | Verkaufschancen-ID \(optional\) |
+| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
+| `fields` | string | Nein | Kommagetrennte Felder |
+| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Verkaufschancendaten |
### `salesforce_create_opportunity`
+Eine neue Verkaufschance erstellen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `name` | string | Ja | Name der Verkaufschance \(erforderlich\) |
+| `stageName` | string | Ja | Phasenname \(erforderlich\) |
+| `closeDate` | string | Ja | Abschlussdatum JJJJ-MM-TT \(erforderlich\) |
+| `accountId` | string | Nein | Konto-ID |
+| `amount` | string | Nein | Betrag \(Zahl\) |
+| `probability` | string | Nein | Wahrscheinlichkeit \(0-100\) |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Erstellte Verkaufschance |
### `salesforce_update_opportunity`
+Aktualisieren einer bestehenden Verkaufschance
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `opportunityId` | string | Ja | Verkaufschancen-ID (erforderlich) |
+| `name` | string | Nein | Name der Verkaufschance |
+| `stageName` | string | Nein | Phasenname |
+| `closeDate` | string | Nein | Abschlussdatum JJJJ-MM-TT |
+| `accountId` | string | Nein | Konto-ID |
+| `amount` | string | Nein | Keine Beschreibung |
+| `probability` | string | Nein | Wahrscheinlichkeit (0-100) |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Aktualisierte Verkaufschance |
### `salesforce_delete_opportunity`
+Löschen einer Verkaufschance
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `opportunityId` | string | Ja | Verkaufschancen-ID (erforderlich) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Gelöschte Verkaufschance |
### `salesforce_get_cases`
+Fall/Fälle aus Salesforce abrufen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `caseId` | string | Nein | Fall-ID \(optional\) |
+| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
+| `fields` | string | Nein | Kommagetrennte Felder |
+| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Falldaten |
### `salesforce_create_case`
+Einen neuen Fall erstellen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `subject` | string | Ja | Fallbetreff \(erforderlich\) |
+| `status` | string | Nein | Status \(z.B. Neu, In Bearbeitung, Eskaliert\) |
+| `priority` | string | Nein | Priorität \(z.B. Niedrig, Mittel, Hoch\) |
+| `origin` | string | Nein | Ursprung \(z.B. Telefon, E-Mail, Web\) |
+| `contactId` | string | Nein | Kontakt-ID |
+| `accountId` | string | Nein | Konto-ID |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Erstellter Fall |
### `salesforce_update_case`
+Aktualisieren eines vorhandenen Falls
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `caseId` | string | Ja | Fall-ID \(erforderlich\) |
+| `subject` | string | Nein | Fallbetreff |
+| `status` | string | Nein | Status |
+| `priority` | string | Nein | Priorität |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Aktualisierter Fall |
### `salesforce_delete_case`
+Löschen eines Falls
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `caseId` | string | Ja | Fall-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Gelöschter Fall |
### `salesforce_get_tasks`
+Aufgabe(n) von Salesforce abrufen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `taskId` | string | Nein | Aufgaben-ID \(optional\) |
+| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
+| `fields` | string | Nein | Kommagetrennte Felder |
+| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Aufgabendaten |
### `salesforce_create_task`
+Neue Aufgabe erstellen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `subject` | string | Ja | Aufgabenbetreff \(erforderlich\) |
+| `status` | string | Nein | Status \(z.B. Nicht begonnen, In Bearbeitung, Abgeschlossen\) |
+| `priority` | string | Nein | Priorität \(z.B. Niedrig, Normal, Hoch\) |
+| `activityDate` | string | Nein | Fälligkeitsdatum JJJJ-MM-TT |
+| `whoId` | string | Nein | Zugehörige Kontakt-/Lead-ID |
+| `whatId` | string | Nein | Zugehörige Konto-/Opportunity-ID |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Erstellte Aufgabe |
### `salesforce_update_task`
+Bestehende Aufgabe aktualisieren
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `taskId` | string | Ja | Aufgaben-ID \(erforderlich\) |
+| `subject` | string | Nein | Aufgabenbetreff |
+| `status` | string | Nein | Status |
+| `priority` | string | Nein | Priorität |
+| `activityDate` | string | Nein | Fälligkeitsdatum JJJJ-MM-TT |
+| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Aktualisierte Aufgabe |
### `salesforce_delete_task`
+Aufgabe löschen
+
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `taskId` | string | Ja | Aufgaben-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
-| `success` | boolean | Status des Operationserfolgs |
-| `output` | json | Ergebnisdaten der Operation |
+| `success` | boolean | Erfolg |
+| `output` | object | Gelöschte Aufgabe |
+
+### `salesforce_list_reports`
+
+Liste der für den aktuellen Benutzer zugänglichen Berichte abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `folderName` | string | Nein | Nach Ordnernamen filtern |
+| `searchTerm` | string | Nein | Suchbegriff zum Filtern von Berichten nach Namen |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Berichtsdaten |
+
+### `salesforce_get_report`
+
+Metadaten und Beschreibungsinformationen für einen bestimmten Bericht abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `reportId` | string | Ja | Berichts-ID \(erforderlich\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Berichtsmetadaten |
+
+### `salesforce_run_report`
+
+Einen Bericht ausführen und die Ergebnisse abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `reportId` | string | Ja | Berichts-ID \(erforderlich\) |
+| `includeDetails` | string | Nein | Detailzeilen einschließen \(true/false, Standard: true\) |
+| `filters` | string | Nein | JSON-String der anzuwendenden Berichtsfilter |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Berichtsergebnisse |
+
+### `salesforce_list_report_types`
+
+Eine Liste der verfügbaren Berichtstypen abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Daten zu Berichtstypen |
+
+### `salesforce_list_dashboards`
+
+Eine Liste der für den aktuellen Benutzer zugänglichen Dashboards abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `folderName` | string | Nein | Nach Ordnernamen filtern |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Dashboard-Daten |
+
+### `salesforce_get_dashboard`
+
+Details und Ergebnisse für ein bestimmtes Dashboard abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `dashboardId` | string | Ja | Dashboard-ID \(erforderlich\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Dashboard-Daten |
+
+### `salesforce_refresh_dashboard`
+
+Ein Dashboard aktualisieren, um die neuesten Daten zu erhalten
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `dashboardId` | string | Ja | Dashboard-ID \(erforderlich\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Aktualisierte Dashboard-Daten |
+
+### `salesforce_query`
+
+Eine benutzerdefinierte SOQL-Abfrage ausführen, um Daten aus Salesforce abzurufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `query` | string | Ja | SOQL-Abfrage zur Ausführung \(z.B. SELECT Id, Name FROM Account LIMIT 10\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Abfrageergebnisse |
+
+### `salesforce_query_more`
+
+Abrufen zusätzlicher Abfrageergebnisse mit der nextRecordsUrl aus einer vorherigen Abfrage
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `nextRecordsUrl` | string | Ja | Die nextRecordsUrl aus einer vorherigen Abfrageantwort |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Abfrageergebnisse |
+
+### `salesforce_describe_object`
+
+Metadaten und Feldinformationen für ein Salesforce-Objekt abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+| `objectName` | string | Ja | API-Name des Objekts \(z.B. Account, Contact, Lead, Custom_Object__c\) |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Objekt-Metadaten |
+
+### `salesforce_list_objects`
+
+Liste aller verfügbaren Salesforce-Objekte abrufen
+
+#### Eingabe
+
+| Parameter | Typ | Erforderlich | Beschreibung |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Nein | Keine Beschreibung |
+| `instanceUrl` | string | Nein | Keine Beschreibung |
+
+#### Ausgabe
+
+| Parameter | Typ | Beschreibung |
+| --------- | ---- | ----------- |
+| `success` | boolean | Erfolgsstatus |
+| `output` | object | Objektliste |
## Hinweise
diff --git a/apps/docs/content/docs/en/blocks/condition.mdx b/apps/docs/content/docs/en/blocks/condition.mdx
index 607fd64919..50c0488ad1 100644
--- a/apps/docs/content/docs/en/blocks/condition.mdx
+++ b/apps/docs/content/docs/en/blocks/condition.mdx
@@ -135,7 +135,7 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Best Practices
- **Order conditions correctly**: Place more specific conditions before general ones to ensure specific logic takes precedence over fallbacks
-- **Include a default condition**: Add a catch-all condition (`true`) as the last condition to handle unmatched cases and prevent workflow execution from getting stuck
+- **Use the else branch when needed**: If no conditions match and the else branch is not connected, the workflow branch will end gracefully. Connect the else branch if you need a fallback path for unmatched cases
- **Keep expressions simple**: Use clear, straightforward boolean expressions for better readability and easier debugging
- **Document your conditions**: Add descriptions to explain the purpose of each condition for better team collaboration and maintenance
- **Test edge cases**: Verify conditions handle boundary values correctly by testing with values at the edges of your condition ranges
diff --git a/apps/docs/content/docs/en/introduction/index.mdx b/apps/docs/content/docs/en/introduction/index.mdx
index 22a9617010..0843c61f12 100644
--- a/apps/docs/content/docs/en/introduction/index.mdx
+++ b/apps/docs/content/docs/en/introduction/index.mdx
@@ -72,7 +72,7 @@ For custom integrations, leverage our [MCP (Model Context Protocol) support](/mc
-## AI-Powered Copilot
+## Copilot
**Ask Questions & Get Guidance**
Copilot answers questions about Sim, explains your workflows, and provides suggestions for improvements. Use the `@` symbol to reference workflows, blocks, documentation, knowledge, and logs for contextual assistance.
diff --git a/apps/docs/content/docs/en/knowledgebase/index.mdx b/apps/docs/content/docs/en/knowledgebase/index.mdx
index 281d0efa3b..1a3874510f 100644
--- a/apps/docs/content/docs/en/knowledgebase/index.mdx
+++ b/apps/docs/content/docs/en/knowledgebase/index.mdx
@@ -1,5 +1,6 @@
---
-title: Knowledgebase
+title: Overview
+description: Upload, process, and search through your documents with intelligent vector search and chunking
---
import { Video } from '@/components/ui/video'
diff --git a/apps/docs/content/docs/en/knowledgebase/meta.json b/apps/docs/content/docs/en/knowledgebase/meta.json
new file mode 100644
index 0000000000..7b6b62f779
--- /dev/null
+++ b/apps/docs/content/docs/en/knowledgebase/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Knowledgebase",
+ "pages": ["index", "tags"]
+}
diff --git a/apps/docs/content/docs/en/meta.json b/apps/docs/content/docs/en/meta.json
index 6b5bf74d44..235c7c506a 100644
--- a/apps/docs/content/docs/en/meta.json
+++ b/apps/docs/content/docs/en/meta.json
@@ -13,7 +13,8 @@
"variables",
"execution",
"permissions",
- "sdks"
+ "sdks",
+ "self-hosting"
],
"defaultOpen": false
}
diff --git a/apps/docs/content/docs/en/self-hosting/docker.mdx b/apps/docs/content/docs/en/self-hosting/docker.mdx
new file mode 100644
index 0000000000..4ee0450bf0
--- /dev/null
+++ b/apps/docs/content/docs/en/self-hosting/docker.mdx
@@ -0,0 +1,150 @@
+---
+title: Docker
+description: Deploy Sim Studio with Docker Compose
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Quick Start
+
+```bash
+# Clone and start
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Open [http://localhost:3000](http://localhost:3000)
+
+## Production Setup
+
+### 1. Configure Environment
+
+```bash
+# Generate secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+```
+
+### 2. Start Services
+
+```bash
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### 3. Set Up SSL
+
+
+
+Caddy automatically handles SSL certificates.
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+```
+
+Create `/etc/caddy/Caddyfile`:
+```
+sim.yourdomain.com {
+ reverse_proxy localhost:3000
+
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}
+```
+
+```bash
+sudo systemctl restart caddy
+```
+
+
+```bash
+# Install
+sudo apt install nginx certbot python3-certbot-nginx -y
+
+# Create /etc/nginx/sites-available/sim
+server {
+ listen 80;
+ server_name sim.yourdomain.com;
+
+ location / {
+ proxy_pass http://127.0.0.1:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /socket.io/ {
+ proxy_pass http://127.0.0.1:3002;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
+
+# Enable and get certificate
+sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
+sudo certbot --nginx -d sim.yourdomain.com
+```
+
+
+
+## Ollama
+
+```bash
+# With GPU
+docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
+
+# CPU only
+docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
+```
+
+Pull additional models:
+```bash
+docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
+```
+
+### External Ollama
+
+If Ollama runs on your host machine (not in Docker):
+
+```bash
+# macOS/Windows
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+
+# Linux - use your host IP
+OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
+```
+
+
+ Inside Docker, `localhost` refers to the container, not your host. Use `host.docker.internal` or your host's IP.
+
+
+## Commands
+
+```bash
+# View logs
+docker compose -f docker-compose.prod.yml logs -f simstudio
+
+# Stop
+docker compose -f docker-compose.prod.yml down
+
+# Update
+docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
+
+# Backup database
+docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
+```
diff --git a/apps/docs/content/docs/en/self-hosting/environment-variables.mdx b/apps/docs/content/docs/en/self-hosting/environment-variables.mdx
new file mode 100644
index 0000000000..6b105211ad
--- /dev/null
+++ b/apps/docs/content/docs/en/self-hosting/environment-variables.mdx
@@ -0,0 +1,87 @@
+---
+title: Environment Variables
+description: Configuration reference for Sim Studio
+---
+
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Required
+
+| Variable | Description |
+|----------|-------------|
+| `DATABASE_URL` | PostgreSQL connection string |
+| `BETTER_AUTH_SECRET` | Auth secret (32 hex chars): `openssl rand -hex 32` |
+| `BETTER_AUTH_URL` | Your app URL |
+| `ENCRYPTION_KEY` | Encryption key (32 hex chars): `openssl rand -hex 32` |
+| `INTERNAL_API_SECRET` | Internal API secret (32 hex chars): `openssl rand -hex 32` |
+| `NEXT_PUBLIC_APP_URL` | Public app URL |
+| `NEXT_PUBLIC_SOCKET_URL` | WebSocket URL (default: `http://localhost:3002`) |
+
+## AI Providers
+
+| Variable | Provider |
+|----------|----------|
+| `OPENAI_API_KEY` | OpenAI |
+| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
+| `GEMINI_API_KEY_1` | Google Gemini |
+| `MISTRAL_API_KEY` | Mistral |
+| `OLLAMA_URL` | Ollama (default: `http://localhost:11434`) |
+
+
+ For load balancing, add multiple keys with `_1`, `_2`, `_3` suffixes (e.g., `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Works with OpenAI, Anthropic, and Gemini.
+
+
+
+ In Docker, use `OLLAMA_URL=http://host.docker.internal:11434` for host-machine Ollama.
+
+
+### Azure OpenAI
+
+| Variable | Description |
+|----------|-------------|
+| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key |
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint URL |
+| `AZURE_OPENAI_API_VERSION` | API version (e.g., `2024-02-15-preview`) |
+
+### vLLM (Self-Hosted)
+
+| Variable | Description |
+|----------|-------------|
+| `VLLM_BASE_URL` | vLLM server URL (e.g., `http://localhost:8000/v1`) |
+| `VLLM_API_KEY` | Optional bearer token for vLLM |
+
+## OAuth Providers
+
+| Variable | Description |
+|----------|-------------|
+| `GOOGLE_CLIENT_ID` | Google OAuth client ID |
+| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |
+| `GITHUB_CLIENT_ID` | GitHub OAuth client ID |
+| `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret |
+
+## Optional
+
+| Variable | Description |
+|----------|-------------|
+| `API_ENCRYPTION_KEY` | Encrypts stored API keys (32 hex chars): `openssl rand -hex 32` |
+| `COPILOT_API_KEY` | API key for copilot features |
+| `ADMIN_API_KEY` | Admin API key for GitOps operations |
+| `RESEND_API_KEY` | Email service for notifications |
+| `ALLOWED_LOGIN_DOMAINS` | Restrict signups to domains (comma-separated) |
+| `ALLOWED_LOGIN_EMAILS` | Restrict signups to specific emails (comma-separated) |
+| `DISABLE_REGISTRATION` | Set to `true` to disable new user signups |
+
+## Example .env
+
+```bash
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=
+BETTER_AUTH_URL=https://sim.yourdomain.com
+ENCRYPTION_KEY=
+INTERNAL_API_SECRET=
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+OPENAI_API_KEY=sk-...
+```
+
+See `apps/sim/.env.example` for all options.
diff --git a/apps/docs/content/docs/en/self-hosting/index.mdx b/apps/docs/content/docs/en/self-hosting/index.mdx
new file mode 100644
index 0000000000..63ef741914
--- /dev/null
+++ b/apps/docs/content/docs/en/self-hosting/index.mdx
@@ -0,0 +1,50 @@
+---
+title: Self-Hosting
+description: Deploy Sim Studio on your own infrastructure
+---
+
+import { Card, Cards } from 'fumadocs-ui/components/card'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+Deploy Sim Studio on your own infrastructure with Docker or Kubernetes.
+
+## Requirements
+
+| Resource | Minimum | Recommended |
+|----------|---------|-------------|
+| CPU | 2 cores | 4+ cores |
+| RAM | 12 GB | 16+ GB |
+| Storage | 20 GB SSD | 50+ GB SSD |
+| Docker | 20.10+ | Latest |
+
+## Quick Start
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Open [http://localhost:3000](http://localhost:3000)
+
+## Deployment Options
+
+
+
+ Deploy with Docker Compose on any server
+
+
+ Deploy with Helm on Kubernetes clusters
+
+
+ Railway, DigitalOcean, AWS, Azure, GCP guides
+
+
+
+## Architecture
+
+| Component | Port | Description |
+|-----------|------|-------------|
+| simstudio | 3000 | Main application |
+| realtime | 3002 | WebSocket server |
+| db | 5432 | PostgreSQL with pgvector |
+| migrations | - | Database migrations (runs once) |
diff --git a/apps/docs/content/docs/en/self-hosting/kubernetes.mdx b/apps/docs/content/docs/en/self-hosting/kubernetes.mdx
new file mode 100644
index 0000000000..da7a78970a
--- /dev/null
+++ b/apps/docs/content/docs/en/self-hosting/kubernetes.mdx
@@ -0,0 +1,127 @@
+---
+title: Kubernetes
+description: Deploy Sim Studio with Helm
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Prerequisites
+
+- Kubernetes 1.19+
+- Helm 3.0+
+- PV provisioner support
+
+## Installation
+
+```bash
+# Clone repo
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Generate secrets
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+
+# Install
+helm install sim ./helm/sim \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --namespace simstudio --create-namespace
+```
+
+## Cloud-Specific Values
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-aws.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-azure.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-gcp.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+## Key Configuration
+
+```yaml
+# Custom values.yaml
+app:
+ replicaCount: 2
+ env:
+ NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
+ OPENAI_API_KEY: "sk-..."
+
+postgresql:
+ persistence:
+ size: 50Gi
+
+ingress:
+ enabled: true
+ className: nginx
+ tls:
+ enabled: true
+ app:
+ host: sim.yourdomain.com
+```
+
+See `helm/sim/values.yaml` for all options.
+
+## External Database
+
+```yaml
+postgresql:
+ enabled: false
+
+externalDatabase:
+ enabled: true
+ host: "your-db-host"
+ port: 5432
+ username: "postgres"
+ password: "your-password"
+ database: "simstudio"
+ sslMode: "require"
+```
+
+## Commands
+
+```bash
+# Port forward for local access
+kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
+
+# View logs
+kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
+
+# Upgrade
+helm upgrade sim ./helm/sim --namespace simstudio
+
+# Uninstall
+helm uninstall sim --namespace simstudio
+```
diff --git a/apps/docs/content/docs/en/self-hosting/meta.json b/apps/docs/content/docs/en/self-hosting/meta.json
new file mode 100644
index 0000000000..805cfb659a
--- /dev/null
+++ b/apps/docs/content/docs/en/self-hosting/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "Self-Hosting",
+ "pages": [
+ "index",
+ "docker",
+ "kubernetes",
+ "platforms",
+ "environment-variables",
+ "troubleshooting"
+ ],
+ "defaultOpen": false
+}
diff --git a/apps/docs/content/docs/en/self-hosting/platforms.mdx b/apps/docs/content/docs/en/self-hosting/platforms.mdx
new file mode 100644
index 0000000000..bcc3e7a092
--- /dev/null
+++ b/apps/docs/content/docs/en/self-hosting/platforms.mdx
@@ -0,0 +1,116 @@
+---
+title: Cloud Platforms
+description: Deploy Sim Studio on cloud platforms
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Railway
+
+One-click deployment with automatic PostgreSQL provisioning.
+
+[](https://railway.com/new/template/sim-studio)
+
+After deployment, add environment variables in Railway dashboard:
+- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (auto-generated)
+- `OPENAI_API_KEY` or other AI provider keys
+- Custom domain in Settings → Networking
+
+## VPS Deployment
+
+For DigitalOcean, AWS EC2, Azure VMs, or any Linux server:
+
+
+
+**Recommended:** 16 GB RAM Droplet, Ubuntu 24.04
+
+```bash
+# Create Droplet via console, then SSH in
+ssh root@your-droplet-ip
+```
+
+
+**Recommended:** t3.xlarge (16 GB RAM), Ubuntu 24.04
+
+```bash
+ssh -i your-key.pem ubuntu@your-ec2-ip
+```
+
+
+**Recommended:** Standard_D4s_v3 (16 GB RAM), Ubuntu 24.04
+
+```bash
+ssh azureuser@your-vm-ip
+```
+
+
+
+### Install Docker
+
+```bash
+# Install Docker (official method)
+curl -fsSL https://get.docker.com | sudo sh
+sudo usermod -aG docker $USER
+
+# Logout and reconnect, then verify
+docker --version
+```
+
+### Deploy Sim Studio
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Create .env with secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+
+# Start
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### SSL with Caddy
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+
+# Configure (replace domain)
+echo 'sim.yourdomain.com {
+ reverse_proxy localhost:3000
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}' | sudo tee /etc/caddy/Caddyfile
+
+sudo systemctl restart caddy
+```
+
+Point your domain's DNS A record to your server IP.
+
+## Kubernetes (EKS, AKS, GKE)
+
+See the [Kubernetes guide](/self-hosting/kubernetes) for Helm deployment on managed Kubernetes.
+
+## Managed Database (Optional)
+
+For production, use a managed PostgreSQL service:
+
+- **AWS RDS** / **Azure Database** / **Cloud SQL** - Enable pgvector extension
+- **Supabase** / **Neon** - pgvector included
+
+Set `DATABASE_URL` in your environment:
+```bash
+DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
+```
diff --git a/apps/docs/content/docs/en/self-hosting/troubleshooting.mdx b/apps/docs/content/docs/en/self-hosting/troubleshooting.mdx
new file mode 100644
index 0000000000..54e5224ce1
--- /dev/null
+++ b/apps/docs/content/docs/en/self-hosting/troubleshooting.mdx
@@ -0,0 +1,110 @@
+---
+title: Troubleshooting
+description: Common issues and solutions
+---
+
+## Database Connection Failed
+
+```bash
+# Check database is running
+docker compose ps db
+
+# Test connection
+docker compose exec db psql -U postgres -c "SELECT 1"
+```
+
+Verify `DATABASE_URL` format: `postgresql://user:pass@host:5432/database`
+
+## Ollama Models Not Showing
+
+Inside Docker, `localhost` = the container, not your host machine.
+
+```bash
+# For host-machine Ollama, use:
+OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
+OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
+```
+
+## WebSocket/Realtime Not Working
+
+1. Check `NEXT_PUBLIC_SOCKET_URL` matches your domain
+2. Verify realtime service is running: `docker compose ps realtime`
+3. Ensure reverse proxy passes WebSocket upgrades (see [Docker guide](/self-hosting/docker))
+
+## 502 Bad Gateway
+
+```bash
+# Check app is running
+docker compose ps simstudio
+docker compose logs simstudio
+
+# Common causes: out of memory, database not ready
+```
+
+## Migration Errors
+
+```bash
+# View migration logs
+docker compose logs migrations
+
+# Run manually
+docker compose exec simstudio bun run db:migrate
+```
+
+## pgvector Not Found
+
+Use the correct PostgreSQL image:
+```yaml
+image: pgvector/pgvector:pg17 # NOT postgres:17
+```
+
+## Certificate Errors (CERT_HAS_EXPIRED)
+
+If you see SSL certificate errors when calling external APIs:
+
+```bash
+# Update CA certificates in container
+docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
+
+# Or set in environment (not recommended for production)
+NODE_TLS_REJECT_UNAUTHORIZED=0
+```
+
+## Blank Page After Login
+
+1. Check browser console for errors
+2. Verify `NEXT_PUBLIC_APP_URL` matches your actual domain
+3. Clear browser cookies and local storage
+4. Check that all services are running: `docker compose ps`
+
+## Windows-Specific Issues
+
+**Turbopack errors on Windows:**
+```bash
+# Use WSL2 for better compatibility
+wsl --install
+
+# Or disable Turbopack in package.json
+# Change "next dev --turbopack" to "next dev"
+```
+
+**Line ending issues:**
+```bash
+# Configure git to use LF
+git config --global core.autocrlf input
+```
+
+## View Logs
+
+```bash
+# All services
+docker compose logs -f
+
+# Specific service
+docker compose logs -f simstudio
+```
+
+## Getting Help
+
+- [GitHub Issues](https://github.com/simstudioai/sim/issues)
+- [Discord](https://discord.gg/Hr4UWYEcTT)
diff --git a/apps/docs/content/docs/en/tools/cursor.mdx b/apps/docs/content/docs/en/tools/cursor.mdx
new file mode 100644
index 0000000000..77bab828b0
--- /dev/null
+++ b/apps/docs/content/docs/en/tools/cursor.mdx
@@ -0,0 +1,186 @@
+---
+title: Cursor
+description: Launch and manage Cursor cloud agents to work on GitHub repositories
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+{/* MANUAL-CONTENT-START:intro */}
+[Cursor](https://www.cursor.so/) is an AI IDE and cloud-based platform that lets you launch and manage powerful AI agents able to work directly on your GitHub repositories. Cursor agents can automate development tasks, enhance your team's productivity, and collaborate with you by making code changes, responding to natural language instructions, and maintaining conversation history about their activities.
+
+With Cursor, you can:
+
+- **Launch cloud agents for codebases**: Instantly create new AI agents that work on your repositories in the cloud
+- **Delegate coding tasks using natural language**: Guide agents with written instructions, amendments, and clarifications
+- **Monitor progress and outputs**: Retrieve agent status, view detailed results, and inspect current or completed tasks
+- **Access full conversation history**: Review all prompts and AI responses for transparency and auditability
+- **Control and manage agent lifecycle**: List active agents, terminate agents, and manage API-based agent launches and follow-ups
+
+In Sim, the Cursor integration enables your agents and workflows to interact programmatically with Cursor cloud agents. This means you can use Sim to:
+
+- List all cloud agents and browse their current state (`cursor_list_agents`)
+- Retrieve up-to-date status and outputs for any agent (`cursor_get_agent`)
+- View the full conversation history for any coding agent (`cursor_get_conversation`)
+- Add follow-up instructions or new prompts to a running agent
+- Manage and terminate agents as needed
+
+This integration helps you combine the flexible intelligence of Sim agents with the powerful development automation capabilities of Cursor, making it possible to scale AI-driven development across your projects.
+{/* MANUAL-CONTENT-END */}
+
+
+## Usage Instructions
+
+Interact with Cursor Cloud Agents API to launch AI agents that can work on your GitHub repositories. Supports launching agents, adding follow-up instructions, checking status, viewing conversations, and managing agent lifecycle.
+
+
+
+## Tools
+
+### `cursor_list_agents`
+
+List all cloud agents for the authenticated user with optional pagination.
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Yes | Cursor API key |
+| `limit` | number | No | Number of agents to return \(default: 20, max: 100\) |
+| `cursor` | string | No | Pagination cursor from previous response |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Human-readable list of agents |
+| `metadata` | object | Agent list metadata |
+
+### `cursor_get_agent`
+
+Retrieve the current status and results of a cloud agent.
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Yes | Cursor API key |
+| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Human-readable agent details |
+| `metadata` | object | Agent metadata |
+
+### `cursor_get_conversation`
+
+Retrieve the conversation history of a cloud agent, including all user prompts and assistant responses.
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Yes | Cursor API key |
+| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Human-readable conversation history |
+| `metadata` | object | Conversation metadata |
+
+### `cursor_launch_agent`
+
+Start a new cloud agent to work on a GitHub repository with the given instructions.
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Yes | Cursor API key |
+| `repository` | string | Yes | GitHub repository URL \(e.g., https://github.com/your-org/your-repo\) |
+| `ref` | string | No | Branch, tag, or commit to work from \(defaults to default branch\) |
+| `promptText` | string | Yes | The instruction text for the agent |
+| `promptImages` | string | No | JSON array of image objects with base64 data and dimensions |
+| `model` | string | No | Model to use \(leave empty for auto-selection\) |
+| `branchName` | string | No | Custom branch name for the agent to use |
+| `autoCreatePr` | boolean | No | Automatically create a PR when the agent finishes |
+| `openAsCursorGithubApp` | boolean | No | Open the PR as the Cursor GitHub App |
+| `skipReviewerRequest` | boolean | No | Skip requesting reviewers on the PR |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Success message with agent details |
+| `metadata` | object | Launch result metadata |
+
+### `cursor_add_followup`
+
+Add a follow-up instruction to an existing cloud agent.
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Yes | Cursor API key |
+| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
+| `followupPromptText` | string | Yes | The follow-up instruction text for the agent |
+| `promptImages` | string | No | JSON array of image objects with base64 data and dimensions \(max 5\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Success message |
+| `metadata` | object | Result metadata |
+
+### `cursor_stop_agent`
+
+Stop a running cloud agent. This pauses the agent without deleting it.
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Yes | Cursor API key |
+| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Success message |
+| `metadata` | object | Result metadata |
+
+### `cursor_delete_agent`
+
+Permanently delete a cloud agent. This action cannot be undone.
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Yes | Cursor API key |
+| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Success message |
+| `metadata` | object | Result metadata |
+
+
+
+## Notes
+
+- Category: `tools`
+- Type: `cursor`
diff --git a/apps/docs/content/docs/en/tools/google_groups.mdx b/apps/docs/content/docs/en/tools/google_groups.mdx
new file mode 100644
index 0000000000..727ab72711
--- /dev/null
+++ b/apps/docs/content/docs/en/tools/google_groups.mdx
@@ -0,0 +1,221 @@
+---
+title: Google Groups
+description: Manage Google Workspace Groups and their members
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+## Usage Instructions
+
+Connect to Google Workspace to create, update, and manage groups and their members using the Admin SDK Directory API.
+
+
+
+## Tools
+
+### `google_groups_list_groups`
+
+List all groups in a Google Workspace domain
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `customer` | string | No | Customer ID or "my_customer" for the authenticated user\'s domain |
+| `domain` | string | No | Domain name to filter groups by |
+| `maxResults` | number | No | Maximum number of results to return \(1-200\) |
+| `pageToken` | string | No | Token for pagination |
+| `query` | string | No | Search query to filter groups \(e.g., "email:admin*"\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_get_group`
+
+Get details of a specific Google Group by email or group ID
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_create_group`
+
+Create a new Google Group in the domain
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `email` | string | Yes | Email address for the new group \(e.g., team@yourdomain.com\) |
+| `name` | string | Yes | Display name for the group |
+| `description` | string | No | Description of the group |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_update_group`
+
+Update an existing Google Group
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+| `name` | string | No | New display name for the group |
+| `description` | string | No | New description for the group |
+| `email` | string | No | New email address for the group |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_delete_group`
+
+Delete a Google Group
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID to delete |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_list_members`
+
+List all members of a Google Group
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+| `maxResults` | number | No | Maximum number of results to return \(1-200\) |
+| `pageToken` | string | No | Token for pagination |
+| `roles` | string | No | Filter by roles \(comma-separated: OWNER, MANAGER, MEMBER\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_get_member`
+
+Get details of a specific member in a Google Group
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+| `memberKey` | string | Yes | Member email address or unique member ID |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_add_member`
+
+Add a new member to a Google Group
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+| `email` | string | Yes | Email address of the member to add |
+| `role` | string | No | Role for the member \(MEMBER, MANAGER, or OWNER\). Defaults to MEMBER. |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_remove_member`
+
+Remove a member from a Google Group
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+| `memberKey` | string | Yes | Email address or unique ID of the member to remove |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_update_member`
+
+Update a member
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+| `memberKey` | string | Yes | Member email address or unique member ID |
+| `role` | string | Yes | New role for the member \(MEMBER, MANAGER, or OWNER\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+### `google_groups_has_member`
+
+Check if a user is a member of a Google Group
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Yes | Group email address or unique group ID |
+| `memberKey` | string | Yes | Member email address or unique member ID to check |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API response data |
+
+
+
+## Notes
+
+- Category: `tools`
+- Type: `google_groups`
diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json
index d50ec3bb67..73c1125a1c 100644
--- a/apps/docs/content/docs/en/tools/meta.json
+++ b/apps/docs/content/docs/en/tools/meta.json
@@ -11,6 +11,7 @@
"calendly",
"clay",
"confluence",
+ "cursor",
"datadog",
"discord",
"dropbox",
@@ -27,6 +28,7 @@
"google_docs",
"google_drive",
"google_forms",
+ "google_groups",
"google_search",
"google_sheets",
"google_slides",
diff --git a/apps/docs/content/docs/en/tools/salesforce.mdx b/apps/docs/content/docs/en/tools/salesforce.mdx
index ff898f20ad..870c6161d2 100644
--- a/apps/docs/content/docs/en/tools/salesforce.mdx
+++ b/apps/docs/content/docs/en/tools/salesforce.mdx
@@ -138,283 +138,684 @@ Delete an account from Salesforce CRM
### `salesforce_get_contacts`
+Get contact(s) from Salesforce - single contact if ID provided, or list if not
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `contactId` | string | No | Contact ID \(if provided, returns single contact\) |
+| `limit` | string | No | Number of results \(default: 100, max: 2000\). Only for list query. |
+| `fields` | string | No | Comma-separated fields \(e.g., "Id,FirstName,LastName,Email,Phone"\) |
+| `orderBy` | string | No | Order by field \(e.g., "LastName ASC"\). Only for list query. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Contact\(s\) data |
### `salesforce_create_contact`
+Create a new contact in Salesforce CRM
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `lastName` | string | Yes | Last name \(required\) |
+| `firstName` | string | No | First name |
+| `email` | string | No | Email address |
+| `phone` | string | No | Phone number |
+| `accountId` | string | No | Account ID to associate contact with |
+| `title` | string | No | No description |
+| `department` | string | No | Department |
+| `mailingStreet` | string | No | Mailing street |
+| `mailingCity` | string | No | Mailing city |
+| `mailingState` | string | No | Mailing state |
+| `mailingPostalCode` | string | No | Mailing postal code |
+| `mailingCountry` | string | No | Mailing country |
+| `description` | string | No | Contact description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Created contact data |
### `salesforce_update_contact`
+Update an existing contact in Salesforce CRM
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `contactId` | string | Yes | Contact ID to update \(required\) |
+| `lastName` | string | No | Last name |
+| `firstName` | string | No | First name |
+| `email` | string | No | Email address |
+| `phone` | string | No | Phone number |
+| `accountId` | string | No | Account ID to associate with |
+| `title` | string | No | No description |
+| `department` | string | No | Department |
+| `mailingStreet` | string | No | Mailing street |
+| `mailingCity` | string | No | Mailing city |
+| `mailingState` | string | No | Mailing state |
+| `mailingPostalCode` | string | No | Mailing postal code |
+| `mailingCountry` | string | No | Mailing country |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Updated contact data |
### `salesforce_delete_contact`
+Delete a contact from Salesforce CRM
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `contactId` | string | Yes | Contact ID to delete \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Deleted contact data |
### `salesforce_get_leads`
+Get lead(s) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `leadId` | string | No | Lead ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Lead data |
### `salesforce_create_lead`
+Create a new lead
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `lastName` | string | Yes | Last name \(required\) |
+| `company` | string | Yes | Company \(required\) |
+| `firstName` | string | No | First name |
+| `email` | string | No | No description |
+| `phone` | string | No | No description |
+| `status` | string | No | Lead status |
+| `leadSource` | string | No | Lead source |
+| `title` | string | No | No description |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created lead |
### `salesforce_update_lead`
+Update an existing lead
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `leadId` | string | Yes | Lead ID \(required\) |
+| `lastName` | string | No | Last name |
+| `company` | string | No | No description |
+| `firstName` | string | No | First name |
+| `email` | string | No | No description |
+| `phone` | string | No | No description |
+| `status` | string | No | Lead status |
+| `leadSource` | string | No | Lead source |
+| `title` | string | No | No description |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated lead |
### `salesforce_delete_lead`
+Delete a lead
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `leadId` | string | Yes | Lead ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted lead |
### `salesforce_get_opportunities`
+Get opportunity(ies) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `opportunityId` | string | No | Opportunity ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Opportunity data |
### `salesforce_create_opportunity`
+Create a new opportunity
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `name` | string | Yes | Opportunity name \(required\) |
+| `stageName` | string | Yes | Stage name \(required\) |
+| `closeDate` | string | Yes | Close date YYYY-MM-DD \(required\) |
+| `accountId` | string | No | Account ID |
+| `amount` | string | No | Amount \(number\) |
+| `probability` | string | No | Probability \(0-100\) |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created opportunity |
### `salesforce_update_opportunity`
+Update an existing opportunity
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `opportunityId` | string | Yes | Opportunity ID \(required\) |
+| `name` | string | No | Opportunity name |
+| `stageName` | string | No | Stage name |
+| `closeDate` | string | No | Close date YYYY-MM-DD |
+| `accountId` | string | No | Account ID |
+| `amount` | string | No | No description |
+| `probability` | string | No | Probability \(0-100\) |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated opportunity |
### `salesforce_delete_opportunity`
+Delete an opportunity
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `opportunityId` | string | Yes | Opportunity ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted opportunity |
### `salesforce_get_cases`
+Get case(s) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `caseId` | string | No | Case ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Case data |
### `salesforce_create_case`
+Create a new case
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `subject` | string | Yes | Case subject \(required\) |
+| `status` | string | No | Status \(e.g., New, Working, Escalated\) |
+| `priority` | string | No | Priority \(e.g., Low, Medium, High\) |
+| `origin` | string | No | Origin \(e.g., Phone, Email, Web\) |
+| `contactId` | string | No | Contact ID |
+| `accountId` | string | No | Account ID |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created case |
### `salesforce_update_case`
+Update an existing case
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `caseId` | string | Yes | Case ID \(required\) |
+| `subject` | string | No | Case subject |
+| `status` | string | No | Status |
+| `priority` | string | No | Priority |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated case |
### `salesforce_delete_case`
+Delete a case
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `caseId` | string | Yes | Case ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted case |
### `salesforce_get_tasks`
+Get task(s) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `taskId` | string | No | Task ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Task data |
### `salesforce_create_task`
+Create a new task
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `subject` | string | Yes | Task subject \(required\) |
+| `status` | string | No | Status \(e.g., Not Started, In Progress, Completed\) |
+| `priority` | string | No | Priority \(e.g., Low, Normal, High\) |
+| `activityDate` | string | No | Due date YYYY-MM-DD |
+| `whoId` | string | No | Related Contact/Lead ID |
+| `whatId` | string | No | Related Account/Opportunity ID |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created task |
### `salesforce_update_task`
+Update an existing task
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `taskId` | string | Yes | Task ID \(required\) |
+| `subject` | string | No | Task subject |
+| `status` | string | No | Status |
+| `priority` | string | No | Priority |
+| `activityDate` | string | No | Due date YYYY-MM-DD |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated task |
### `salesforce_delete_task`
+Delete a task
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `taskId` | string | Yes | Task ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted task |
+
+### `salesforce_list_reports`
+
+Get a list of reports accessible by the current user
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `folderName` | string | No | Filter by folder name |
+| `searchTerm` | string | No | Search term to filter reports by name |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Reports data |
+
+### `salesforce_get_report`
+
+Get metadata and describe information for a specific report
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `reportId` | string | Yes | Report ID \(required\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Report metadata |
+
+### `salesforce_run_report`
+
+Execute a report and retrieve the results
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `reportId` | string | Yes | Report ID \(required\) |
+| `includeDetails` | string | No | Include detail rows \(true/false, default: true\) |
+| `filters` | string | No | JSON string of report filters to apply |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Report results |
+
+### `salesforce_list_report_types`
+
+Get a list of available report types
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Report types data |
+
+### `salesforce_list_dashboards`
+
+Get a list of dashboards accessible by the current user
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `folderName` | string | No | Filter by folder name |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Dashboards data |
+
+### `salesforce_get_dashboard`
+
+Get details and results for a specific dashboard
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `dashboardId` | string | Yes | Dashboard ID \(required\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Dashboard data |
+
+### `salesforce_refresh_dashboard`
+
+Refresh a dashboard to get the latest data
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `dashboardId` | string | Yes | Dashboard ID \(required\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Refreshed dashboard data |
+
+### `salesforce_query`
+
+Execute a custom SOQL query to retrieve data from Salesforce
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `query` | string | Yes | SOQL query to execute \(e.g., SELECT Id, Name FROM Account LIMIT 10\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Query results |
+
+### `salesforce_query_more`
+
+Retrieve additional query results using the nextRecordsUrl from a previous query
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `nextRecordsUrl` | string | Yes | The nextRecordsUrl from a previous query response |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Query results |
+
+### `salesforce_describe_object`
+
+Get metadata and field information for a Salesforce object
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `objectName` | string | Yes | API name of the object \(e.g., Account, Contact, Lead, Custom_Object__c\) |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Object metadata |
+
+### `salesforce_list_objects`
+
+Get a list of all available Salesforce objects
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Success status |
+| `output` | object | Objects list |
diff --git a/apps/docs/content/docs/es/blocks/condition.mdx b/apps/docs/content/docs/es/blocks/condition.mdx
index a6d2e0080c..48e36b4cd2 100644
--- a/apps/docs/content/docs/es/blocks/condition.mdx
+++ b/apps/docs/content/docs/es/blocks/condition.mdx
@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Mejores prácticas
-- **Ordenar las condiciones correctamente**: Coloca las condiciones más específicas antes que las generales para asegurar que la lógica específica tenga prioridad sobre las alternativas
-- **Incluir una condición predeterminada**: Añade una condición general (`true`) como última condición para manejar casos no coincidentes y evitar que la ejecución del flujo de trabajo se detenga
-- **Mantener las expresiones simples**: Usa expresiones booleanas claras y directas para mejorar la legibilidad y facilitar la depuración
-- **Documentar tus condiciones**: Añade descripciones para explicar el propósito de cada condición para una mejor colaboración en equipo y mantenimiento
-- **Probar casos límite**: Verifica que las condiciones manejen correctamente los valores límite probando con valores en los extremos de tus rangos de condición
+- **Ordena las condiciones correctamente**: Coloca las condiciones más específicas antes que las generales para asegurar que la lógica específica tenga prioridad sobre las alternativas
+- **Usa la rama else cuando sea necesario**: Si ninguna condición coincide y la rama else no está conectada, la rama del flujo de trabajo terminará correctamente. Conecta la rama else si necesitas una ruta alternativa para casos no coincidentes
+- **Mantén las expresiones simples**: Usa expresiones booleanas claras y directas para mejorar la legibilidad y facilitar la depuración
+- **Documenta tus condiciones**: Añade descripciones para explicar el propósito de cada condición para una mejor colaboración en equipo y mantenimiento
+- **Prueba casos límite**: Verifica que las condiciones manejen correctamente los valores límite probando con valores en los extremos de los rangos de tus condiciones
diff --git a/apps/docs/content/docs/es/introduction/index.mdx b/apps/docs/content/docs/es/introduction/index.mdx
index d44c51520e..e908e0b1a6 100644
--- a/apps/docs/content/docs/es/introduction/index.mdx
+++ b/apps/docs/content/docs/es/introduction/index.mdx
@@ -72,7 +72,7 @@ Para integraciones personalizadas, aprovecha nuestro [soporte MCP (Protocolo de
-## Copiloto potenciado por IA
+## Copilot
**Haz preguntas y recibe orientación**
Copiloto responde preguntas sobre Sim, explica tus flujos de trabajo y proporciona sugerencias para mejorarlos. Usa el símbolo `@` para hacer referencia a flujos de trabajo, bloques, documentación, conocimiento y registros para obtener asistencia contextual.
diff --git a/apps/docs/content/docs/es/knowledgebase/index.mdx b/apps/docs/content/docs/es/knowledgebase/index.mdx
index 3a04aa5984..81456cc846 100644
--- a/apps/docs/content/docs/es/knowledgebase/index.mdx
+++ b/apps/docs/content/docs/es/knowledgebase/index.mdx
@@ -1,5 +1,7 @@
---
-title: Base de conocimientos
+title: Descripción general
+description: Sube, procesa y busca a través de tus documentos con búsqueda
+ vectorial inteligente y fragmentación
---
import { Video } from '@/components/ui/video'
diff --git a/apps/docs/content/docs/es/self-hosting/docker.mdx b/apps/docs/content/docs/es/self-hosting/docker.mdx
new file mode 100644
index 0000000000..a177da8bc1
--- /dev/null
+++ b/apps/docs/content/docs/es/self-hosting/docker.mdx
@@ -0,0 +1,155 @@
+---
+title: Docker
+description: Despliega Sim Studio con Docker Compose
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Inicio rápido
+
+```bash
+# Clone and start
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Abre [http://localhost:3000](http://localhost:3000)
+
+## Configuración de producción
+
+### 1. Configurar entorno
+
+```bash
+# Generate secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+```
+
+### 2. Iniciar servicios
+
+```bash
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### 3. Configurar SSL
+
+
+
+Caddy gestiona automáticamente los certificados SSL.
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+```
+
+Crea `/etc/caddy/Caddyfile`:
+
+```
+sim.yourdomain.com {
+ reverse_proxy localhost:3000
+
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}
+```
+
+```bash
+sudo systemctl restart caddy
+```
+
+
+
+
+```bash
+# Install
+sudo apt install nginx certbot python3-certbot-nginx -y
+
+# Create /etc/nginx/sites-available/sim
+server {
+ listen 80;
+ server_name sim.yourdomain.com;
+
+ location / {
+ proxy_pass http://127.0.0.1:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /socket.io/ {
+ proxy_pass http://127.0.0.1:3002;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
+
+# Enable and get certificate
+sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
+sudo certbot --nginx -d sim.yourdomain.com
+```
+
+
+
+
+## Ollama
+
+```bash
+# With GPU
+docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
+
+# CPU only
+docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
+```
+
+Descarga modelos adicionales:
+
+```bash
+docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
+```
+
+### Ollama externo
+
+Si Ollama se ejecuta en tu máquina host (no en Docker):
+
+```bash
+# macOS/Windows
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+
+# Linux - use your host IP
+OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
+```
+
+
+ Dentro de Docker, `localhost` se refiere al contenedor, no a tu host. Usa `host.docker.internal` o la IP de tu host.
+
+
+## Comandos
+
+```bash
+# View logs
+docker compose -f docker-compose.prod.yml logs -f simstudio
+
+# Stop
+docker compose -f docker-compose.prod.yml down
+
+# Update
+docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
+
+# Backup database
+docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
+```
diff --git a/apps/docs/content/docs/es/self-hosting/environment-variables.mdx b/apps/docs/content/docs/es/self-hosting/environment-variables.mdx
new file mode 100644
index 0000000000..cce23f2cdf
--- /dev/null
+++ b/apps/docs/content/docs/es/self-hosting/environment-variables.mdx
@@ -0,0 +1,87 @@
+---
+title: Variables de entorno
+description: Referencia de configuración para Sim Studio
+---
+
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Requeridas
+
+| Variable | Descripción |
+|----------|-------------|
+| `DATABASE_URL` | Cadena de conexión PostgreSQL |
+| `BETTER_AUTH_SECRET` | Secreto de autenticación (32 caracteres hex): `openssl rand -hex 32` |
+| `BETTER_AUTH_URL` | URL de tu aplicación |
+| `ENCRYPTION_KEY` | Clave de cifrado (32 caracteres hex): `openssl rand -hex 32` |
+| `INTERNAL_API_SECRET` | Secreto de API interna (32 caracteres hex): `openssl rand -hex 32` |
+| `NEXT_PUBLIC_APP_URL` | URL pública de la aplicación |
+| `NEXT_PUBLIC_SOCKET_URL` | URL de WebSocket (predeterminado: `http://localhost:3002`) |
+
+## Proveedores de IA
+
+| Variable | Proveedor |
+|----------|----------|
+| `OPENAI_API_KEY` | OpenAI |
+| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
+| `GEMINI_API_KEY_1` | Google Gemini |
+| `MISTRAL_API_KEY` | Mistral |
+| `OLLAMA_URL` | Ollama (predeterminado: `http://localhost:11434`) |
+
+
+ Para balanceo de carga, añade múltiples claves con sufijos `_1`, `_2`, `_3` (p. ej., `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Funciona con OpenAI, Anthropic y Gemini.
+
+
+
+ En Docker, usa `OLLAMA_URL=http://host.docker.internal:11434` para Ollama en la máquina host.
+
+
+### Azure OpenAI
+
+| Variable | Descripción |
+|----------|-------------|
+| `AZURE_OPENAI_API_KEY` | Clave de API de Azure OpenAI |
+| `AZURE_OPENAI_ENDPOINT` | URL del endpoint de Azure OpenAI |
+| `AZURE_OPENAI_API_VERSION` | Versión de API (p. ej., `2024-02-15-preview`) |
+
+### vLLM (autoalojado)
+
+| Variable | Descripción |
+|----------|-------------|
+| `VLLM_BASE_URL` | URL del servidor vLLM (p. ej., `http://localhost:8000/v1`) |
+| `VLLM_API_KEY` | Token bearer opcional para vLLM |
+
+## Proveedores OAuth
+
+| Variable | Descripción |
+|----------|-------------|
+| `GOOGLE_CLIENT_ID` | ID de cliente OAuth de Google |
+| `GOOGLE_CLIENT_SECRET` | Secreto de cliente OAuth de Google |
+| `GITHUB_CLIENT_ID` | ID de cliente OAuth de GitHub |
+| `GITHUB_CLIENT_SECRET` | Secreto de cliente OAuth de GitHub |
+
+## Opcional
+
+| Variable | Descripción |
+|----------|-------------|
+| `API_ENCRYPTION_KEY` | Encripta las claves API almacenadas (32 caracteres hexadecimales): `openssl rand -hex 32` |
+| `COPILOT_API_KEY` | Clave API para funciones de copilot |
+| `ADMIN_API_KEY` | Clave API de administrador para operaciones GitOps |
+| `RESEND_API_KEY` | Servicio de correo electrónico para notificaciones |
+| `ALLOWED_LOGIN_DOMAINS` | Restringir registros a dominios (separados por comas) |
+| `ALLOWED_LOGIN_EMAILS` | Restringir registros a correos electrónicos específicos (separados por comas) |
+| `DISABLE_REGISTRATION` | Establecer como `true` para deshabilitar nuevos registros de usuarios |
+
+## Ejemplo de archivo .env
+
+```bash
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=
+BETTER_AUTH_URL=https://sim.yourdomain.com
+ENCRYPTION_KEY=
+INTERNAL_API_SECRET=
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+OPENAI_API_KEY=sk-...
+```
+
+Consulta `apps/sim/.env.example` para todas las opciones.
diff --git a/apps/docs/content/docs/es/self-hosting/index.mdx b/apps/docs/content/docs/es/self-hosting/index.mdx
new file mode 100644
index 0000000000..b1016aca46
--- /dev/null
+++ b/apps/docs/content/docs/es/self-hosting/index.mdx
@@ -0,0 +1,50 @@
+---
+title: Autoalojamiento
+description: Despliega Sim Studio en tu propia infraestructura
+---
+
+import { Card, Cards } from 'fumadocs-ui/components/card'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+Despliega Sim Studio en tu propia infraestructura con Docker o Kubernetes.
+
+## Requisitos
+
+| Recurso | Mínimo | Recomendado |
+|----------|---------|-------------|
+| CPU | 2 núcleos | 4+ núcleos |
+| RAM | 12 GB | 16+ GB |
+| Almacenamiento | 20 GB SSD | 50+ GB SSD |
+| Docker | 20.10+ | Última versión |
+
+## Inicio rápido
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Abre [http://localhost:3000](http://localhost:3000)
+
+## Opciones de despliegue
+
+
+
+ Despliega con Docker Compose en cualquier servidor
+
+
+ Despliega con Helm en clústeres de Kubernetes
+
+
+ Guías para Railway, DigitalOcean, AWS, Azure, GCP
+
+
+
+## Arquitectura
+
+| Componente | Puerto | Descripción |
+|-----------|------|-------------|
+| simstudio | 3000 | Aplicación principal |
+| realtime | 3002 | Servidor WebSocket |
+| db | 5432 | PostgreSQL con pgvector |
+| migrations | - | Migraciones de base de datos (se ejecuta una vez) |
diff --git a/apps/docs/content/docs/es/self-hosting/kubernetes.mdx b/apps/docs/content/docs/es/self-hosting/kubernetes.mdx
new file mode 100644
index 0000000000..426f21454f
--- /dev/null
+++ b/apps/docs/content/docs/es/self-hosting/kubernetes.mdx
@@ -0,0 +1,133 @@
+---
+title: Kubernetes
+description: Desplegar Sim Studio con Helm
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Requisitos previos
+
+- Kubernetes 1.19+
+- Helm 3.0+
+- Soporte de aprovisionador PV
+
+## Instalación
+
+```bash
+# Clone repo
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Generate secrets
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+
+# Install
+helm install sim ./helm/sim \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --namespace simstudio --create-namespace
+```
+
+## Valores específicos para la nube
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-aws.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-azure.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-gcp.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+## Configuración clave
+
+```yaml
+# Custom values.yaml
+app:
+ replicaCount: 2
+ env:
+ NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
+ OPENAI_API_KEY: "sk-..."
+
+postgresql:
+ persistence:
+ size: 50Gi
+
+ingress:
+ enabled: true
+ className: nginx
+ tls:
+ enabled: true
+ app:
+ host: sim.yourdomain.com
+```
+
+Consulta `helm/sim/values.yaml` para todas las opciones.
+
+## Base de datos externa
+
+```yaml
+postgresql:
+ enabled: false
+
+externalDatabase:
+ enabled: true
+ host: "your-db-host"
+ port: 5432
+ username: "postgres"
+ password: "your-password"
+ database: "simstudio"
+ sslMode: "require"
+```
+
+## Comandos
+
+```bash
+# Port forward for local access
+kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
+
+# View logs
+kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
+
+# Upgrade
+helm upgrade sim ./helm/sim --namespace simstudio
+
+# Uninstall
+helm uninstall sim --namespace simstudio
+```
diff --git a/apps/docs/content/docs/es/self-hosting/platforms.mdx b/apps/docs/content/docs/es/self-hosting/platforms.mdx
new file mode 100644
index 0000000000..2de93bbed3
--- /dev/null
+++ b/apps/docs/content/docs/es/self-hosting/platforms.mdx
@@ -0,0 +1,124 @@
+---
+title: Plataformas en la nube
+description: Despliega Sim Studio en plataformas en la nube
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Railway
+
+Despliegue con un solo clic con aprovisionamiento automático de PostgreSQL.
+
+[
+
+
+
+](https://railway.com/new/template/sim-studio)
+
+Después del despliegue, añade variables de entorno en el panel de Railway:
+- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (generadas automáticamente)
+- `OPENAI_API_KEY` u otras claves de proveedores de IA
+- Dominio personalizado en Configuración → Redes
+
+## Despliegue en VPS
+
+Para DigitalOcean, AWS EC2, Azure VMs o cualquier servidor Linux:
+
+
+
+**Recomendado:** Droplet de 16 GB RAM, Ubuntu 24.04
+
+```bash
+# Create Droplet via console, then SSH in
+ssh root@your-droplet-ip
+```
+
+
+
+**Recomendado:** t3.xlarge (16 GB RAM), Ubuntu 24.04
+
+```bash
+ssh -i your-key.pem ubuntu@your-ec2-ip
+```
+
+
+
+**Recomendado:** Standard_D4s_v3 (16 GB RAM), Ubuntu 24.04
+
+```bash
+ssh azureuser@your-vm-ip
+```
+
+
+
+
+### Instalar Docker
+
+```bash
+# Install Docker (official method)
+curl -fsSL https://get.docker.com | sudo sh
+sudo usermod -aG docker $USER
+
+# Logout and reconnect, then verify
+docker --version
+```
+
+### Desplegar Sim Studio
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Create .env with secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+
+# Start
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### SSL con Caddy
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+
+# Configure (replace domain)
+echo 'sim.yourdomain.com {
+ reverse_proxy localhost:3000
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}' | sudo tee /etc/caddy/Caddyfile
+
+sudo systemctl restart caddy
+```
+
+Apunta el registro DNS A de tu dominio a la IP de tu servidor.
+
+## Kubernetes (EKS, AKS, GKE)
+
+Consulta la [guía de Kubernetes](/self-hosting/kubernetes) para la implementación con Helm en Kubernetes gestionado.
+
+## Base de datos gestionada (Opcional)
+
+Para producción, utiliza un servicio de PostgreSQL gestionado:
+
+- **AWS RDS** / **Azure Database** / **Cloud SQL** - Habilita la extensión pgvector
+- **Supabase** / **Neon** - pgvector incluido
+
+Establece `DATABASE_URL` en tu entorno:
+
+```bash
+DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
+```
diff --git a/apps/docs/content/docs/es/self-hosting/troubleshooting.mdx b/apps/docs/content/docs/es/self-hosting/troubleshooting.mdx
new file mode 100644
index 0000000000..9fdb0957c9
--- /dev/null
+++ b/apps/docs/content/docs/es/self-hosting/troubleshooting.mdx
@@ -0,0 +1,113 @@
+---
+title: Solución de problemas
+description: Problemas comunes y soluciones
+---
+
+## Falló la conexión a la base de datos
+
+```bash
+# Check database is running
+docker compose ps db
+
+# Test connection
+docker compose exec db psql -U postgres -c "SELECT 1"
+```
+
+Verifica el formato de `DATABASE_URL`: `postgresql://user:pass@host:5432/database`
+
+## Los modelos de Ollama no se muestran
+
+Dentro de Docker, `localhost` = el contenedor, no tu máquina host.
+
+```bash
+# For host-machine Ollama, use:
+OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
+OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
+```
+
+## WebSocket/Tiempo real no funciona
+
+1. Comprueba que `NEXT_PUBLIC_SOCKET_URL` coincida con tu dominio
+2. Verifica que el servicio en tiempo real esté funcionando: `docker compose ps realtime`
+3. Asegúrate de que el proxy inverso pase las actualizaciones de WebSocket (consulta la [guía de Docker](/self-hosting/docker))
+
+## Error 502 Bad Gateway
+
+```bash
+# Check app is running
+docker compose ps simstudio
+docker compose logs simstudio
+
+# Common causes: out of memory, database not ready
+```
+
+## Errores de migración
+
+```bash
+# View migration logs
+docker compose logs migrations
+
+# Run manually
+docker compose exec simstudio bun run db:migrate
+```
+
+## pgvector no encontrado
+
+Usa la imagen correcta de PostgreSQL:
+
+```yaml
+image: pgvector/pgvector:pg17 # NOT postgres:17
+```
+
+## Errores de certificado (CERT_HAS_EXPIRED)
+
+Si ves errores de certificado SSL al llamar a APIs externas:
+
+```bash
+# Update CA certificates in container
+docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
+
+# Or set in environment (not recommended for production)
+NODE_TLS_REJECT_UNAUTHORIZED=0
+```
+
+## Página en blanco después del inicio de sesión
+
+1. Revisa la consola del navegador para ver errores
+2. Verifica que `NEXT_PUBLIC_APP_URL` coincida con tu dominio actual
+3. Borra las cookies del navegador y el almacenamiento local
+4. Comprueba que todos los servicios estén funcionando: `docker compose ps`
+
+## Problemas específicos de Windows
+
+**Errores de Turbopack en Windows:**
+
+```bash
+# Use WSL2 for better compatibility
+wsl --install
+
+# Or disable Turbopack in package.json
+# Change "next dev --turbopack" to "next dev"
+```
+
+**Problemas de fin de línea:**
+
+```bash
+# Configure git to use LF
+git config --global core.autocrlf input
+```
+
+## Ver registros
+
+```bash
+# All services
+docker compose logs -f
+
+# Specific service
+docker compose logs -f simstudio
+```
+
+## Obtener ayuda
+
+- [GitHub Issues](https://github.com/simstudioai/sim/issues)
+- [Discord](https://discord.gg/Hr4UWYEcTT)
diff --git a/apps/docs/content/docs/es/tools/cursor.mdx b/apps/docs/content/docs/es/tools/cursor.mdx
new file mode 100644
index 0000000000..a15a8546de
--- /dev/null
+++ b/apps/docs/content/docs/es/tools/cursor.mdx
@@ -0,0 +1,182 @@
+---
+title: Cursor
+description: Lanza y gestiona agentes en la nube de Cursor para trabajar en
+ repositorios de GitHub
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+{/* MANUAL-CONTENT-START:intro */}
+[Cursor](https://www.cursor.so/) es un IDE con IA y una plataforma basada en la nube que te permite lanzar y gestionar potentes agentes de IA capaces de trabajar directamente en tus repositorios de GitHub. Los agentes de Cursor pueden automatizar tareas de desarrollo, mejorar la productividad de tu equipo y colaborar contigo realizando cambios en el código, respondiendo a instrucciones en lenguaje natural y manteniendo un historial de conversaciones sobre sus actividades.
+
+Con Cursor, puedes:
+
+- **Lanzar agentes en la nube para bases de código**: Crear instantáneamente nuevos agentes de IA que trabajen en tus repositorios en la nube
+- **Delegar tareas de programación usando lenguaje natural**: Guiar a los agentes con instrucciones escritas, modificaciones y aclaraciones
+- **Monitorizar el progreso y los resultados**: Obtener el estado del agente, ver resultados detallados e inspeccionar tareas actuales o completadas
+- **Acceder al historial completo de conversaciones**: Revisar todos los prompts y respuestas de IA para mayor transparencia y capacidad de auditoría
+- **Controlar y gestionar el ciclo de vida del agente**: Listar agentes activos, terminar agentes y gestionar lanzamientos de agentes basados en API y seguimientos
+
+En Sim, la integración con Cursor permite que tus agentes y flujos de trabajo interactúen programáticamente con los agentes en la nube de Cursor. Esto significa que puedes usar Sim para:
+
+- Listar todos los agentes en la nube y explorar su estado actual (`cursor_list_agents`)
+- Recuperar el estado actualizado y los resultados de cualquier agente (`cursor_get_agent`)
+- Ver el historial completo de conversaciones de cualquier agente de programación (`cursor_get_conversation`)
+- Añadir instrucciones de seguimiento o nuevos prompts a un agente en ejecución
+- Gestionar y terminar agentes según sea necesario
+
+Esta integración te ayuda a combinar la inteligencia flexible de los agentes de Sim con las potentes capacidades de automatización de desarrollo de Cursor, haciendo posible escalar el desarrollo impulsado por IA en todos tus proyectos.
+{/* MANUAL-CONTENT-END */}
+
+## Instrucciones de uso
+
+Interactúa con la API de Agentes en la Nube de Cursor para lanzar agentes de IA que pueden trabajar en tus repositorios de GitHub. Permite lanzar agentes, añadir instrucciones de seguimiento, verificar el estado, ver conversaciones y gestionar el ciclo de vida de los agentes.
+
+## Herramientas
+
+### `cursor_list_agents`
+
+Lista todos los agentes en la nube para el usuario autenticado con paginación opcional.
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Sí | Clave API de Cursor |
+| `limit` | number | No | Número de agentes a devolver \(predeterminado: 20, máximo: 100\) |
+| `cursor` | string | No | Cursor de paginación de la respuesta anterior |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `content` | string | Lista legible de agentes |
+| `metadata` | object | Metadatos de la lista de agentes |
+
+### `cursor_get_agent`
+
+Recupera el estado actual y los resultados de un agente en la nube.
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Sí | Clave API de Cursor |
+| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `content` | string | Detalles del agente en formato legible |
+| `metadata` | object | Metadatos del agente |
+
+### `cursor_get_conversation`
+
+Recupera el historial de conversación de un agente en la nube, incluyendo todas las instrucciones del usuario y las respuestas del asistente.
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Sí | Clave API de Cursor |
+| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `content` | string | Historial de conversación en formato legible |
+| `metadata` | object | Metadatos de la conversación |
+
+### `cursor_launch_agent`
+
+Inicia un nuevo agente en la nube para trabajar en un repositorio de GitHub con las instrucciones proporcionadas.
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Sí | Clave API de Cursor |
+| `repository` | string | Sí | URL del repositorio de GitHub \(p. ej., https://github.com/your-org/your-repo\) |
+| `ref` | string | No | Rama, etiqueta o commit desde donde trabajar \(por defecto usa la rama principal\) |
+| `promptText` | string | Sí | El texto de instrucciones para el agente |
+| `promptImages` | string | No | Array JSON de objetos de imagen con datos en base64 y dimensiones |
+| `model` | string | No | Modelo a utilizar \(dejar vacío para selección automática\) |
+| `branchName` | string | No | Nombre de rama personalizado para que el agente utilice |
+| `autoCreatePr` | boolean | No | Crear automáticamente un PR cuando el agente termine |
+| `openAsCursorGithubApp` | boolean | No | Abrir el PR como la aplicación de GitHub de Cursor |
+| `skipReviewerRequest` | boolean | No | Omitir la solicitud de revisores en el PR |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `content` | string | Mensaje de éxito con detalles del agente |
+| `metadata` | object | Metadatos del resultado de lanzamiento |
+
+### `cursor_add_followup`
+
+Añade una instrucción de seguimiento a un agente en la nube existente.
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Sí | Clave API de Cursor |
+| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
+| `followupPromptText` | string | Sí | El texto de instrucción de seguimiento para el agente |
+| `promptImages` | string | No | Array JSON de objetos de imagen con datos en base64 y dimensiones \(máximo 5\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `content` | string | Mensaje de éxito |
+| `metadata` | object | Metadatos del resultado |
+
+### `cursor_stop_agent`
+
+Detener un agente en la nube en ejecución. Esto pausa el agente sin eliminarlo.
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Sí | Clave API de Cursor |
+| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `content` | string | Mensaje de éxito |
+| `metadata` | object | Metadatos del resultado |
+
+### `cursor_delete_agent`
+
+Eliminar permanentemente un agente en la nube. Esta acción no se puede deshacer.
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Sí | Clave API de Cursor |
+| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `content` | string | Mensaje de éxito |
+| `metadata` | object | Metadatos del resultado |
+
+## Notas
+
+- Categoría: `tools`
+- Tipo: `cursor`
diff --git a/apps/docs/content/docs/es/tools/google_groups.mdx b/apps/docs/content/docs/es/tools/google_groups.mdx
new file mode 100644
index 0000000000..174b057017
--- /dev/null
+++ b/apps/docs/content/docs/es/tools/google_groups.mdx
@@ -0,0 +1,217 @@
+---
+title: Google Groups
+description: Administra los Grupos de Google Workspace y sus miembros
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+## Instrucciones de uso
+
+Conéctate a Google Workspace para crear, actualizar y administrar grupos y sus miembros utilizando la API de directorio de Admin SDK.
+
+## Herramientas
+
+### `google_groups_list_groups`
+
+Listar todos los grupos en un dominio de Google Workspace
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `customer` | string | No | ID del cliente o "my_customer" para el dominio del usuario autenticado |
+| `domain` | string | No | Nombre de dominio para filtrar grupos |
+| `maxResults` | number | No | Número máximo de resultados a devolver (1-200) |
+| `pageToken` | string | No | Token para paginación |
+| `query` | string | No | Consulta de búsqueda para filtrar grupos (p. ej., "email:admin*") |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_get_group`
+
+Obtener detalles de un Grupo de Google específico por correo electrónico o ID de grupo
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_create_group`
+
+Crear un nuevo Grupo de Google en el dominio
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `email` | string | Sí | Dirección de correo electrónico para el nuevo grupo (p. ej., equipo@tudominio.com) |
+| `name` | string | Sí | Nombre visible para el grupo |
+| `description` | string | No | Descripción del grupo |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_update_group`
+
+Actualizar un grupo de Google existente
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+| `name` | string | No | Nuevo nombre visible para el grupo |
+| `description` | string | No | Nueva descripción para el grupo |
+| `email` | string | No | Nueva dirección de correo electrónico para el grupo |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_delete_group`
+
+Eliminar un grupo de Google
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo a eliminar |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_list_members`
+
+Listar todos los miembros de un Grupo de Google
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+| `maxResults` | number | No | Número máximo de resultados a devolver \(1-200\) |
+| `pageToken` | string | No | Token para paginación |
+| `roles` | string | No | Filtrar por roles \(separados por comas: OWNER, MANAGER, MEMBER\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Grupos de Google |
+
+### `google_groups_get_member`
+
+Obtener detalles de un miembro específico en un Grupo de Google
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+| `memberKey` | string | Sí | Dirección de correo electrónico del miembro o ID único del miembro |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Grupos de Google |
+
+### `google_groups_add_member`
+
+Añadir un nuevo miembro a un Grupo de Google
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+| `email` | string | Sí | Dirección de correo electrónico del miembro a añadir |
+| `role` | string | No | Rol para el miembro \(MEMBER, MANAGER, o OWNER\). Por defecto es MEMBER. |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_remove_member`
+
+Eliminar un miembro de un grupo de Google
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+| `memberKey` | string | Sí | Dirección de correo electrónico o ID único del miembro a eliminar |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_update_member`
+
+Actualizar un miembro
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+| `memberKey` | string | Sí | Dirección de correo electrónico del miembro o ID único del miembro |
+| `role` | string | Sí | Nuevo rol para el miembro \(MEMBER, MANAGER, o OWNER\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+### `google_groups_has_member`
+
+Comprobar si un usuario es miembro de un grupo de Google
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
+| `memberKey` | string | Sí | Dirección de correo electrónico del miembro o ID único del miembro a comprobar |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `output` | json | Datos de respuesta de la API de Google Groups |
+
+## Notas
+
+- Categoría: `tools`
+- Tipo: `google_groups`
diff --git a/apps/docs/content/docs/es/tools/salesforce.mdx b/apps/docs/content/docs/es/tools/salesforce.mdx
index 82ef5769a7..b6575061dc 100644
--- a/apps/docs/content/docs/es/tools/salesforce.mdx
+++ b/apps/docs/content/docs/es/tools/salesforce.mdx
@@ -135,283 +135,684 @@ Eliminar una cuenta de Salesforce CRM
### `salesforce_get_contacts`
+Obtener contacto(s) de Salesforce - un solo contacto si se proporciona ID, o lista si no
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `contactId` | string | No | ID de contacto \(si se proporciona, devuelve un solo contacto\) |
+| `limit` | string | No | Número de resultados \(predeterminado: 100, máximo: 2000\). Solo para consulta de lista. |
+| `fields` | string | No | Campos separados por comas \(p. ej., "Id,FirstName,LastName,Email,Phone"\) |
+| `orderBy` | string | No | Campo para ordenar \(p. ej., "LastName ASC"\). Solo para consulta de lista. |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `output` | object | Datos de contacto(s) |
### `salesforce_create_contact`
+Crear un nuevo contacto en Salesforce CRM
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `lastName` | string | Sí | Apellido \(obligatorio\) |
+| `firstName` | string | No | Nombre |
+| `email` | string | No | Dirección de correo electrónico |
+| `phone` | string | No | Número de teléfono |
+| `accountId` | string | No | ID de cuenta para asociar con el contacto |
+| `title` | string | No | Sin descripción |
+| `department` | string | No | Departamento |
+| `mailingStreet` | string | No | Calle de correo |
+| `mailingCity` | string | No | Ciudad de correo |
+| `mailingState` | string | No | Estado de correo |
+| `mailingPostalCode` | string | No | Código postal de correo |
+| `mailingCountry` | string | No | País de correo |
+| `description` | string | No | Descripción del contacto |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `output` | object | Datos del contacto creado |
### `salesforce_update_contact`
+Actualizar un contacto existente en Salesforce CRM
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `contactId` | string | Sí | ID del contacto a actualizar (obligatorio) |
+| `lastName` | string | No | Apellido |
+| `firstName` | string | No | Nombre |
+| `email` | string | No | Dirección de correo electrónico |
+| `phone` | string | No | Número de teléfono |
+| `accountId` | string | No | ID de la cuenta para asociar |
+| `title` | string | No | Sin descripción |
+| `department` | string | No | Departamento |
+| `mailingStreet` | string | No | Calle de correo |
+| `mailingCity` | string | No | Ciudad de correo |
+| `mailingState` | string | No | Estado de correo |
+| `mailingPostalCode` | string | No | Código postal de correo |
+| `mailingCountry` | string | No | País de correo |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `output` | object | Datos del contacto actualizado |
### `salesforce_delete_contact`
+Eliminar un contacto de Salesforce CRM
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `contactId` | string | Sí | ID del contacto a eliminar \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `output` | object | Datos del contacto eliminado |
### `salesforce_get_leads`
+Obtener candidato(s) de Salesforce
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `leadId` | string | No | ID del candidato \(opcional\) |
+| `limit` | string | No | Máximo de resultados \(predeterminado: 100\) |
+| `fields` | string | No | Campos separados por comas |
+| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Datos del candidato |
### `salesforce_create_lead`
+Crear un nuevo candidato
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `lastName` | string | Sí | Apellido \(obligatorio\) |
+| `company` | string | Sí | Empresa \(obligatorio\) |
+| `firstName` | string | No | Nombre |
+| `email` | string | No | Sin descripción |
+| `phone` | string | No | Sin descripción |
+| `status` | string | No | Estado del candidato |
+| `leadSource` | string | No | Origen del candidato |
+| `title` | string | No | Sin descripción |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Cliente potencial creado |
### `salesforce_update_lead`
+Actualizar un cliente potencial existente
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `leadId` | string | Sí | ID del cliente potencial (obligatorio) |
+| `lastName` | string | No | Apellido |
+| `company` | string | No | Sin descripción |
+| `firstName` | string | No | Nombre |
+| `email` | string | No | Sin descripción |
+| `phone` | string | No | Sin descripción |
+| `status` | string | No | Estado del cliente potencial |
+| `leadSource` | string | No | Origen del cliente potencial |
+| `title` | string | No | Sin descripción |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Cliente potencial actualizado |
### `salesforce_delete_lead`
+Eliminar un cliente potencial
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `leadId` | string | Sí | ID del cliente potencial (obligatorio) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Cliente potencial eliminado |
### `salesforce_get_opportunities`
+Obtener oportunidad(es) de Salesforce
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `opportunityId` | string | No | ID de oportunidad (opcional) |
+| `limit` | string | No | Máximo de resultados (predeterminado: 100) |
+| `fields` | string | No | Campos separados por comas |
+| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Datos de la oportunidad |
### `salesforce_create_opportunity`
+Crear una nueva oportunidad
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `name` | string | Sí | Nombre de la oportunidad (obligatorio) |
+| `stageName` | string | Sí | Nombre de la etapa (obligatorio) |
+| `closeDate` | string | Sí | Fecha de cierre AAAA-MM-DD (obligatorio) |
+| `accountId` | string | No | ID de la cuenta |
+| `amount` | string | No | Importe (número) |
+| `probability` | string | No | Probabilidad (0-100) |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Oportunidad creada |
### `salesforce_update_opportunity`
+Actualizar una oportunidad existente
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `opportunityId` | string | Sí | ID de la oportunidad \(obligatorio\) |
+| `name` | string | No | Nombre de la oportunidad |
+| `stageName` | string | No | Nombre de la etapa |
+| `closeDate` | string | No | Fecha de cierre AAAA-MM-DD |
+| `accountId` | string | No | ID de la cuenta |
+| `amount` | string | No | Sin descripción |
+| `probability` | string | No | Probabilidad \(0-100\) |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Oportunidad actualizada |
### `salesforce_delete_opportunity`
+Eliminar una oportunidad
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `opportunityId` | string | Sí | ID de la oportunidad \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Oportunidad eliminada |
### `salesforce_get_cases`
+Obtener caso(s) de Salesforce
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `caseId` | string | No | ID del caso \(opcional\) |
+| `limit` | string | No | Resultados máximos \(predeterminado: 100\) |
+| `fields` | string | No | Campos separados por comas |
+| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Éxito |
+| `output` | object | Datos del caso |
### `salesforce_create_case`
+Crear un nuevo caso
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `subject` | string | Sí | Asunto del caso \(obligatorio\) |
+| `status` | string | No | Estado \(p. ej., Nuevo, En proceso, Escalado\) |
+| `priority` | string | No | Prioridad \(p. ej., Baja, Media, Alta\) |
+| `origin` | string | No | Origen \(p. ej., Teléfono, Email, Web\) |
+| `contactId` | string | No | ID del contacto |
+| `accountId` | string | No | ID de la cuenta |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Éxito |
+| `output` | object | Caso creado |
### `salesforce_update_case`
+Actualizar un caso existente
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `caseId` | string | Sí | ID del caso \(obligatorio\) |
+| `subject` | string | No | Asunto del caso |
+| `status` | string | No | Estado |
+| `priority` | string | No | Prioridad |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Éxito |
+| `output` | object | Caso actualizado |
### `salesforce_delete_case`
+Eliminar un caso
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `caseId` | string | Sí | ID del caso \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Éxito |
+| `output` | object | Caso eliminado |
### `salesforce_get_tasks`
+Obtener tarea(s) de Salesforce
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `taskId` | string | No | ID de la tarea \(opcional\) |
+| `limit` | string | No | Máximo de resultados \(predeterminado: 100\) |
+| `fields` | string | No | Campos separados por comas |
+| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `output` | object | Datos de la tarea |
### `salesforce_create_task`
+Crear una nueva tarea
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `subject` | string | Sí | Asunto de la tarea \(obligatorio\) |
+| `status` | string | No | Estado \(p. ej., No iniciada, En progreso, Completada\) |
+| `priority` | string | No | Prioridad \(p. ej., Baja, Normal, Alta\) |
+| `activityDate` | string | No | Fecha de vencimiento AAAA-MM-DD |
+| `whoId` | string | No | ID de contacto/cliente potencial relacionado |
+| `whatId` | string | No | ID de cuenta/oportunidad relacionada |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `output` | object | Tarea creada |
### `salesforce_update_task`
+Actualizar una tarea existente
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `taskId` | string | Sí | ID de la tarea \(obligatorio\) |
+| `subject` | string | No | Asunto de la tarea |
+| `status` | string | No | Estado |
+| `priority` | string | No | Prioridad |
+| `activityDate` | string | No | Fecha de vencimiento AAAA-MM-DD |
+| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Tarea actualizada |
### `salesforce_delete_task`
+Eliminar una tarea
+
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `taskId` | string | Sí | ID de la tarea (obligatorio) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
-| `success` | boolean | Estado de éxito de la operación |
-| `output` | json | Datos del resultado de la operación |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Tarea eliminada |
+
+### `salesforce_list_reports`
+
+Obtener una lista de informes accesibles por el usuario actual
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `folderName` | string | No | Filtrar por nombre de carpeta |
+| `searchTerm` | string | No | Término de búsqueda para filtrar informes por nombre |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Datos de los informes |
+
+### `salesforce_get_report`
+
+Obtener metadatos e información descriptiva para un informe específico
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `reportId` | string | Sí | ID del informe (obligatorio) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Metadatos del informe |
+
+### `salesforce_run_report`
+
+Ejecutar un informe y obtener los resultados
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `reportId` | string | Sí | ID del informe \(obligatorio\) |
+| `includeDetails` | string | No | Incluir filas detalladas \(true/false, predeterminado: true\) |
+| `filters` | string | No | Cadena JSON de filtros de informe para aplicar |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Resultados del informe |
+
+### `salesforce_list_report_types`
+
+Obtener una lista de tipos de informes disponibles
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Datos de tipos de informes |
+
+### `salesforce_list_dashboards`
+
+Obtener una lista de paneles accesibles por el usuario actual
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `folderName` | string | No | Filtrar por nombre de carpeta |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Datos de los paneles |
+
+### `salesforce_get_dashboard`
+
+Obtener detalles y resultados de un panel específico
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `dashboardId` | string | Sí | ID del panel \(obligatorio\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Datos del panel |
+
+### `salesforce_refresh_dashboard`
+
+Actualizar un panel para obtener los datos más recientes
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `dashboardId` | string | Sí | ID del panel \(obligatorio\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Datos del panel actualizado |
+
+### `salesforce_query`
+
+Ejecutar una consulta SOQL personalizada para recuperar datos de Salesforce
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `query` | string | Sí | Consulta SOQL a ejecutar \(p. ej., SELECT Id, Name FROM Account LIMIT 10\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Resultados de la consulta |
+
+### `salesforce_query_more`
+
+Recuperar resultados adicionales de consulta utilizando el nextRecordsUrl de una consulta anterior
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `nextRecordsUrl` | string | Sí | El nextRecordsUrl de una respuesta de consulta anterior |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Resultados de la consulta |
+
+### `salesforce_describe_object`
+
+Obtener metadatos e información de campos para un objeto de Salesforce
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+| `objectName` | string | Sí | Nombre API del objeto \(p. ej., Account, Contact, Lead, Custom_Object__c\) |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Metadatos del objeto |
+
+### `salesforce_list_objects`
+
+Obtener una lista de todos los objetos disponibles de Salesforce
+
+#### Entrada
+
+| Parámetro | Tipo | Obligatorio | Descripción |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | Sin descripción |
+| `instanceUrl` | string | No | Sin descripción |
+
+#### Salida
+
+| Parámetro | Tipo | Descripción |
+| --------- | ---- | ----------- |
+| `success` | boolean | Estado de éxito |
+| `output` | object | Lista de objetos |
## Notas
diff --git a/apps/docs/content/docs/fr/blocks/condition.mdx b/apps/docs/content/docs/fr/blocks/condition.mdx
index 14f0dd6bb7..14c00f5358 100644
--- a/apps/docs/content/docs/fr/blocks/condition.mdx
+++ b/apps/docs/content/docs/fr/blocks/condition.mdx
@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Bonnes pratiques
-- **Ordonner correctement les conditions** : placer les conditions spécifiques avant les générales pour garantir que la logique spécifique prévaut sur les solutions de repli
-- **Inclure une condition par défaut** : ajouter une condition fourre-tout (`true`) comme dernière condition pour gérer les cas non correspondants et éviter que l'exécution du flux de travail ne se bloque
-- **Garder les expressions simples** : utiliser des expressions booléennes claires et directes pour une meilleure lisibilité et un débogage plus facile
-- **Documenter vos conditions** : ajouter des descriptions pour expliquer l'objectif de chaque condition pour une meilleure collaboration en équipe et une maintenance plus facile
-- **Tester les cas limites** : vérifier que les conditions gèrent correctement les valeurs limites en testant avec des valeurs aux extrémités de vos plages de conditions
+- **Ordonnez correctement les conditions** : placez les conditions plus spécifiques avant les générales pour garantir que la logique spécifique prévaut sur les solutions de repli
+- **Utilisez la branche else quand nécessaire** : si aucune condition ne correspond et que la branche else n'est pas connectée, la branche du workflow se terminera gracieusement. Connectez la branche else si vous avez besoin d'un chemin de repli pour les cas non correspondants
+- **Gardez les expressions simples** : utilisez des expressions booléennes claires et directes pour une meilleure lisibilité et un débogage plus facile
+- **Documentez vos conditions** : ajoutez des descriptions pour expliquer l'objectif de chaque condition pour une meilleure collaboration en équipe et une maintenance plus facile
+- **Testez les cas limites** : vérifiez que les conditions gèrent correctement les valeurs limites en testant avec des valeurs aux extrémités de vos plages de conditions
diff --git a/apps/docs/content/docs/fr/introduction/index.mdx b/apps/docs/content/docs/fr/introduction/index.mdx
index 8c757a5a85..e0259ed35b 100644
--- a/apps/docs/content/docs/fr/introduction/index.mdx
+++ b/apps/docs/content/docs/fr/introduction/index.mdx
@@ -72,7 +72,7 @@ Pour les intégrations personnalisées, utilisez notre [support MCP (Model Conte
-## Copilote propulsé par l'IA
+## Copilot
**Posez des questions et obtenez des conseils**
Le copilote répond aux questions sur Sim, explique vos flux de travail et propose des suggestions d'amélioration. Utilisez le symbole `@` pour référencer les flux de travail, les blocs, la documentation, les connaissances et les journaux pour une assistance contextuelle.
diff --git a/apps/docs/content/docs/fr/knowledgebase/index.mdx b/apps/docs/content/docs/fr/knowledgebase/index.mdx
index 21373f4188..0138dfd235 100644
--- a/apps/docs/content/docs/fr/knowledgebase/index.mdx
+++ b/apps/docs/content/docs/fr/knowledgebase/index.mdx
@@ -1,5 +1,7 @@
---
-title: Base de connaissances
+title: Aperçu
+description: Téléchargez, traitez et recherchez dans vos documents grâce à la
+ recherche vectorielle intelligente et au découpage
---
import { Video } from '@/components/ui/video'
diff --git a/apps/docs/content/docs/fr/self-hosting/docker.mdx b/apps/docs/content/docs/fr/self-hosting/docker.mdx
new file mode 100644
index 0000000000..75d049fc73
--- /dev/null
+++ b/apps/docs/content/docs/fr/self-hosting/docker.mdx
@@ -0,0 +1,155 @@
+---
+title: Docker
+description: Déployer Sim Studio avec Docker Compose
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Démarrage rapide
+
+```bash
+# Clone and start
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Ouvrez [http://localhost:3000](http://localhost:3000)
+
+## Configuration de production
+
+### 1. Configurer l'environnement
+
+```bash
+# Generate secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+```
+
+### 2. Démarrer les services
+
+```bash
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### 3. Configurer SSL
+
+
+
+Caddy gère automatiquement les certificats SSL.
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+```
+
+Créez `/etc/caddy/Caddyfile` :
+
+```
+sim.yourdomain.com {
+ reverse_proxy localhost:3000
+
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}
+```
+
+```bash
+sudo systemctl restart caddy
+```
+
+
+
+
+```bash
+# Install
+sudo apt install nginx certbot python3-certbot-nginx -y
+
+# Create /etc/nginx/sites-available/sim
+server {
+ listen 80;
+ server_name sim.yourdomain.com;
+
+ location / {
+ proxy_pass http://127.0.0.1:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /socket.io/ {
+ proxy_pass http://127.0.0.1:3002;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
+
+# Enable and get certificate
+sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
+sudo certbot --nginx -d sim.yourdomain.com
+```
+
+
+
+
+## Ollama
+
+```bash
+# With GPU
+docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
+
+# CPU only
+docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
+```
+
+Télécharger des modèles supplémentaires :
+
+```bash
+docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
+```
+
+### Ollama externe
+
+Si Ollama s'exécute sur votre machine hôte (pas dans Docker) :
+
+```bash
+# macOS/Windows
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+
+# Linux - use your host IP
+OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
+```
+
+
+ À l'intérieur de Docker, `localhost` fait référence au conteneur, pas à votre hôte. Utilisez `host.docker.internal` ou l'IP de votre hôte.
+
+
+## Commandes
+
+```bash
+# View logs
+docker compose -f docker-compose.prod.yml logs -f simstudio
+
+# Stop
+docker compose -f docker-compose.prod.yml down
+
+# Update
+docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
+
+# Backup database
+docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
+```
diff --git a/apps/docs/content/docs/fr/self-hosting/environment-variables.mdx b/apps/docs/content/docs/fr/self-hosting/environment-variables.mdx
new file mode 100644
index 0000000000..e9c542382e
--- /dev/null
+++ b/apps/docs/content/docs/fr/self-hosting/environment-variables.mdx
@@ -0,0 +1,87 @@
+---
+title: Variables d'environnement
+description: Référence de configuration pour Sim Studio
+---
+
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Obligatoires
+
+| Variable | Description |
+|----------|-------------|
+| `DATABASE_URL` | Chaîne de connexion PostgreSQL |
+| `BETTER_AUTH_SECRET` | Secret d'authentification (32 caractères hexadécimaux) : `openssl rand -hex 32` |
+| `BETTER_AUTH_URL` | URL de votre application |
+| `ENCRYPTION_KEY` | Clé de chiffrement (32 caractères hexadécimaux) : `openssl rand -hex 32` |
+| `INTERNAL_API_SECRET` | Secret API interne (32 caractères hexadécimaux) : `openssl rand -hex 32` |
+| `NEXT_PUBLIC_APP_URL` | URL publique de l'application |
+| `NEXT_PUBLIC_SOCKET_URL` | URL WebSocket (par défaut : `http://localhost:3002`) |
+
+## Fournisseurs d'IA
+
+| Variable | Fournisseur |
+|----------|----------|
+| `OPENAI_API_KEY` | OpenAI |
+| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
+| `GEMINI_API_KEY_1` | Google Gemini |
+| `MISTRAL_API_KEY` | Mistral |
+| `OLLAMA_URL` | Ollama (par défaut : `http://localhost:11434`) |
+
+
+ Pour l'équilibrage de charge, ajoutez plusieurs clés avec les suffixes `_1`, `_2`, `_3` (par exemple, `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Fonctionne avec OpenAI, Anthropic et Gemini.
+
+
+
+ Dans Docker, utilisez `OLLAMA_URL=http://host.docker.internal:11434` pour Ollama sur la machine hôte.
+
+
+### Azure OpenAI
+
+| Variable | Description |
+|----------|-------------|
+| `AZURE_OPENAI_API_KEY` | Clé API Azure OpenAI |
+| `AZURE_OPENAI_ENDPOINT` | URL du point de terminaison Azure OpenAI |
+| `AZURE_OPENAI_API_VERSION` | Version de l'API (par exemple, `2024-02-15-preview`) |
+
+### vLLM (auto-hébergé)
+
+| Variable | Description |
+|----------|-------------|
+| `VLLM_BASE_URL` | URL du serveur vLLM (par exemple, `http://localhost:8000/v1`) |
+| `VLLM_API_KEY` | Jeton bearer optionnel pour vLLM |
+
+## Fournisseurs OAuth
+
+| Variable | Description |
+|----------|-------------|
+| `GOOGLE_CLIENT_ID` | ID client OAuth Google |
+| `GOOGLE_CLIENT_SECRET` | Secret client OAuth Google |
+| `GITHUB_CLIENT_ID` | ID client OAuth GitHub |
+| `GITHUB_CLIENT_SECRET` | Secret client OAuth GitHub |
+
+## Optionnel
+
+| Variable | Description |
+|----------|-------------|
+| `API_ENCRYPTION_KEY` | Chiffre les clés API stockées (32 caractères hexadécimaux) : `openssl rand -hex 32` |
+| `COPILOT_API_KEY` | Clé API pour les fonctionnalités copilot |
+| `ADMIN_API_KEY` | Clé API administrateur pour les opérations GitOps |
+| `RESEND_API_KEY` | Service de messagerie pour les notifications |
+| `ALLOWED_LOGIN_DOMAINS` | Restreindre les inscriptions à des domaines (séparés par des virgules) |
+| `ALLOWED_LOGIN_EMAILS` | Restreindre les inscriptions à des emails spécifiques (séparés par des virgules) |
+| `DISABLE_REGISTRATION` | Définir à `true` pour désactiver les inscriptions de nouveaux utilisateurs |
+
+## Exemple de fichier .env
+
+```bash
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=
+BETTER_AUTH_URL=https://sim.yourdomain.com
+ENCRYPTION_KEY=
+INTERNAL_API_SECRET=
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+OPENAI_API_KEY=sk-...
+```
+
+Voir `apps/sim/.env.example` pour toutes les options.
diff --git a/apps/docs/content/docs/fr/self-hosting/index.mdx b/apps/docs/content/docs/fr/self-hosting/index.mdx
new file mode 100644
index 0000000000..0da91c1185
--- /dev/null
+++ b/apps/docs/content/docs/fr/self-hosting/index.mdx
@@ -0,0 +1,50 @@
+---
+title: Auto-hébergement
+description: Déployez Sim Studio sur votre propre infrastructure
+---
+
+import { Card, Cards } from 'fumadocs-ui/components/card'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+Déployez Sim Studio sur votre propre infrastructure avec Docker ou Kubernetes.
+
+## Prérequis
+
+| Ressource | Minimum | Recommandé |
+|----------|---------|-------------|
+| CPU | 2 cœurs | 4+ cœurs |
+| RAM | 12 Go | 16+ Go |
+| Stockage | 20 Go SSD | 50+ Go SSD |
+| Docker | 20.10+ | Dernière version |
+
+## Démarrage rapide
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+Ouvrez [http://localhost:3000](http://localhost:3000)
+
+## Options de déploiement
+
+
+
+ Déployez avec Docker Compose sur n'importe quel serveur
+
+
+ Déployez avec Helm sur des clusters Kubernetes
+
+
+ Guides pour Railway, DigitalOcean, AWS, Azure, GCP
+
+
+
+## Architecture
+
+| Composant | Port | Description |
+|-----------|------|-------------|
+| simstudio | 3000 | Application principale |
+| realtime | 3002 | Serveur WebSocket |
+| db | 5432 | PostgreSQL avec pgvector |
+| migrations | - | Migrations de base de données (exécutées une seule fois) |
diff --git a/apps/docs/content/docs/fr/self-hosting/kubernetes.mdx b/apps/docs/content/docs/fr/self-hosting/kubernetes.mdx
new file mode 100644
index 0000000000..892064eafd
--- /dev/null
+++ b/apps/docs/content/docs/fr/self-hosting/kubernetes.mdx
@@ -0,0 +1,133 @@
+---
+title: Kubernetes
+description: Déployer Sim Studio avec Helm
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Prérequis
+
+- Kubernetes 1.19+
+- Helm 3.0+
+- Support du provisionneur PV
+
+## Installation
+
+```bash
+# Clone repo
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Generate secrets
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+
+# Install
+helm install sim ./helm/sim \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --namespace simstudio --create-namespace
+```
+
+## Valeurs spécifiques au cloud
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-aws.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-azure.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-gcp.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+## Configuration clé
+
+```yaml
+# Custom values.yaml
+app:
+ replicaCount: 2
+ env:
+ NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
+ OPENAI_API_KEY: "sk-..."
+
+postgresql:
+ persistence:
+ size: 50Gi
+
+ingress:
+ enabled: true
+ className: nginx
+ tls:
+ enabled: true
+ app:
+ host: sim.yourdomain.com
+```
+
+Voir `helm/sim/values.yaml` pour toutes les options.
+
+## Base de données externe
+
+```yaml
+postgresql:
+ enabled: false
+
+externalDatabase:
+ enabled: true
+ host: "your-db-host"
+ port: 5432
+ username: "postgres"
+ password: "your-password"
+ database: "simstudio"
+ sslMode: "require"
+```
+
+## Commandes
+
+```bash
+# Port forward for local access
+kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
+
+# View logs
+kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
+
+# Upgrade
+helm upgrade sim ./helm/sim --namespace simstudio
+
+# Uninstall
+helm uninstall sim --namespace simstudio
+```
diff --git a/apps/docs/content/docs/fr/self-hosting/platforms.mdx b/apps/docs/content/docs/fr/self-hosting/platforms.mdx
new file mode 100644
index 0000000000..694d23473a
--- /dev/null
+++ b/apps/docs/content/docs/fr/self-hosting/platforms.mdx
@@ -0,0 +1,124 @@
+---
+title: Plateformes cloud
+description: Déployer Sim Studio sur des plateformes cloud
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Railway
+
+Déploiement en un clic avec provisionnement automatique de PostgreSQL.
+
+[
+
+
+
+](https://railway.com/new/template/sim-studio)
+
+Après le déploiement, ajoutez des variables d'environnement dans le tableau de bord Railway :
+- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (générées automatiquement)
+- `OPENAI_API_KEY` ou d'autres clés de fournisseur d'IA
+- Domaine personnalisé dans Paramètres → Réseau
+
+## Déploiement VPS
+
+Pour DigitalOcean, AWS EC2, Azure VMs, ou tout serveur Linux :
+
+
+
+**Recommandé :** Droplet de 16 Go de RAM, Ubuntu 24.04
+
+```bash
+# Create Droplet via console, then SSH in
+ssh root@your-droplet-ip
+```
+
+
+
+**Recommandé :** t3.xlarge (16 Go de RAM), Ubuntu 24.04
+
+```bash
+ssh -i your-key.pem ubuntu@your-ec2-ip
+```
+
+
+
+**Recommandé :** Standard_D4s_v3 (16 Go de RAM), Ubuntu 24.04
+
+```bash
+ssh azureuser@your-vm-ip
+```
+
+
+
+
+### Installer Docker
+
+```bash
+# Install Docker (official method)
+curl -fsSL https://get.docker.com | sudo sh
+sudo usermod -aG docker $USER
+
+# Logout and reconnect, then verify
+docker --version
+```
+
+### Déployer Sim Studio
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Create .env with secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+
+# Start
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### SSL avec Caddy
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+
+# Configure (replace domain)
+echo 'sim.yourdomain.com {
+ reverse_proxy localhost:3000
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}' | sudo tee /etc/caddy/Caddyfile
+
+sudo systemctl restart caddy
+```
+
+Pointez l'enregistrement DNS A de votre domaine vers l'IP de votre serveur.
+
+## Kubernetes (EKS, AKS, GKE)
+
+Consultez le [guide Kubernetes](/self-hosting/kubernetes) pour le déploiement Helm sur Kubernetes géré.
+
+## Base de données gérée (optionnel)
+
+Pour la production, utilisez un service PostgreSQL géré :
+
+- **AWS RDS** / **Azure Database** / **Cloud SQL** - Activez l'extension pgvector
+- **Supabase** / **Neon** - pgvector inclus
+
+Définissez `DATABASE_URL` dans votre environnement :
+
+```bash
+DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
+```
diff --git a/apps/docs/content/docs/fr/self-hosting/troubleshooting.mdx b/apps/docs/content/docs/fr/self-hosting/troubleshooting.mdx
new file mode 100644
index 0000000000..961c344d87
--- /dev/null
+++ b/apps/docs/content/docs/fr/self-hosting/troubleshooting.mdx
@@ -0,0 +1,113 @@
+---
+title: Dépannage
+description: Problèmes courants et solutions
+---
+
+## Échec de connexion à la base de données
+
+```bash
+# Check database is running
+docker compose ps db
+
+# Test connection
+docker compose exec db psql -U postgres -c "SELECT 1"
+```
+
+Vérifiez le format de `DATABASE_URL` : `postgresql://user:pass@host:5432/database`
+
+## Les modèles Ollama ne s'affichent pas
+
+Dans Docker, `localhost` = le conteneur, pas votre machine hôte.
+
+```bash
+# For host-machine Ollama, use:
+OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
+OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
+```
+
+## WebSocket/Temps réel ne fonctionne pas
+
+1. Vérifiez que `NEXT_PUBLIC_SOCKET_URL` correspond à votre domaine
+2. Vérifiez que le service temps réel est en cours d'exécution : `docker compose ps realtime`
+3. Assurez-vous que le proxy inverse transmet les mises à niveau WebSocket (voir [Guide Docker](/self-hosting/docker))
+
+## Erreur 502 Bad Gateway
+
+```bash
+# Check app is running
+docker compose ps simstudio
+docker compose logs simstudio
+
+# Common causes: out of memory, database not ready
+```
+
+## Erreurs de migration
+
+```bash
+# View migration logs
+docker compose logs migrations
+
+# Run manually
+docker compose exec simstudio bun run db:migrate
+```
+
+## pgvector introuvable
+
+Utilisez l'image PostgreSQL correcte :
+
+```yaml
+image: pgvector/pgvector:pg17 # NOT postgres:17
+```
+
+## Erreurs de certificat (CERT_HAS_EXPIRED)
+
+Si vous voyez des erreurs de certificat SSL lors de l'appel d'API externes :
+
+```bash
+# Update CA certificates in container
+docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
+
+# Or set in environment (not recommended for production)
+NODE_TLS_REJECT_UNAUTHORIZED=0
+```
+
+## Page blanche après connexion
+
+1. Vérifiez la console du navigateur pour les erreurs
+2. Vérifiez que `NEXT_PUBLIC_APP_URL` correspond à votre domaine réel
+3. Effacez les cookies et le stockage local du navigateur
+4. Vérifiez que tous les services sont en cours d'exécution : `docker compose ps`
+
+## Problèmes spécifiques à Windows
+
+**Erreurs Turbopack sur Windows :**
+
+```bash
+# Use WSL2 for better compatibility
+wsl --install
+
+# Or disable Turbopack in package.json
+# Change "next dev --turbopack" to "next dev"
+```
+
+**Problèmes de fin de ligne :**
+
+```bash
+# Configure git to use LF
+git config --global core.autocrlf input
+```
+
+## Consulter les journaux
+
+```bash
+# All services
+docker compose logs -f
+
+# Specific service
+docker compose logs -f simstudio
+```
+
+## Obtenir de l'aide
+
+- [Problèmes GitHub](https://github.com/simstudioai/sim/issues)
+- [Discord](https://discord.gg/Hr4UWYEcTT)
diff --git a/apps/docs/content/docs/fr/tools/cursor.mdx b/apps/docs/content/docs/fr/tools/cursor.mdx
new file mode 100644
index 0000000000..4eac195fd3
--- /dev/null
+++ b/apps/docs/content/docs/fr/tools/cursor.mdx
@@ -0,0 +1,181 @@
+---
+title: Cursor
+description: Lancez et gérez des agents cloud Cursor pour travailler sur des dépôts GitHub
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+{/* MANUAL-CONTENT-START:intro */}
+[Cursor](https://www.cursor.so/) est un IDE IA et une plateforme cloud qui vous permet de lancer et de gérer de puissants agents IA capables de travailler directement sur vos dépôts GitHub. Les agents Cursor peuvent automatiser les tâches de développement, améliorer la productivité de votre équipe et collaborer avec vous en apportant des modifications au code, en répondant aux instructions en langage naturel et en conservant l'historique des conversations sur leurs activités.
+
+Avec Cursor, vous pouvez :
+
+- **Lancer des agents cloud pour les bases de code** : créer instantanément de nouveaux agents IA qui travaillent sur vos dépôts dans le cloud
+- **Déléguer des tâches de codage en langage naturel** : guider les agents avec des instructions écrites, des modifications et des clarifications
+- **Suivre les progrès et les résultats** : récupérer l'état des agents, consulter les résultats détaillés et inspecter les tâches en cours ou terminées
+- **Accéder à l'historique complet des conversations** : examiner toutes les requêtes et réponses de l'IA pour plus de transparence et de traçabilité
+- **Contrôler et gérer le cycle de vie des agents** : lister les agents actifs, terminer des agents et gérer les lancements d'agents et les suivis via API
+
+Dans Sim, l'intégration de Cursor permet à vos agents et flux de travail d'interagir par programmation avec les agents cloud de Cursor. Cela signifie que vous pouvez utiliser Sim pour :
+
+- Lister tous les agents cloud et parcourir leur état actuel (`cursor_list_agents`)
+- Récupérer l'état et les résultats à jour de n'importe quel agent (`cursor_get_agent`)
+- Consulter l'historique complet des conversations pour tout agent de codage (`cursor_get_conversation`)
+- Ajouter des instructions de suivi ou de nouvelles requêtes à un agent en cours d'exécution
+- Gérer et terminer des agents selon les besoins
+
+Cette intégration vous aide à combiner l'intelligence flexible des agents Sim avec les puissantes capacités d'automatisation du développement de Cursor, rendant possible l'extension du développement piloté par l'IA à travers vos projets.
+{/* MANUAL-CONTENT-END */}
+
+## Instructions d'utilisation
+
+Interagissez avec l'API Cursor Cloud Agents pour lancer des agents IA qui peuvent travailler sur vos dépôts GitHub. Prend en charge le lancement d'agents, l'ajout d'instructions complémentaires, la vérification du statut, la visualisation des conversations et la gestion du cycle de vie des agents.
+
+## Outils
+
+### `cursor_list_agents`
+
+Liste tous les agents cloud pour l'utilisateur authentifié avec pagination optionnelle.
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Oui | Clé API Cursor |
+| `limit` | number | Non | Nombre d'agents à retourner \(par défaut : 20, max : 100\) |
+| `cursor` | string | Non | Curseur de pagination de la réponse précédente |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Liste lisible des agents |
+| `metadata` | object | Métadonnées de la liste d'agents |
+
+### `cursor_get_agent`
+
+Récupère le statut actuel et les résultats d'un agent cloud.
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `apiKey` | chaîne | Oui | Clé API Cursor |
+| `agentId` | chaîne | Oui | Identifiant unique pour l'agent cloud \(ex. : bc_abc123\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Détails de l'agent en format lisible |
+| `metadata` | object | Métadonnées de l'agent |
+
+### `cursor_get_conversation`
+
+Récupère l'historique de conversation d'un agent cloud, y compris toutes les instructions de l'utilisateur et les réponses de l'assistant.
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | Oui | Clé API Cursor |
+| `agentId` | string | Oui | Identifiant unique pour l'agent cloud \(ex. : bc_abc123\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Historique de conversation en format lisible |
+| `metadata` | object | Métadonnées de la conversation |
+
+### `cursor_launch_agent`
+
+Démarrer un nouvel agent cloud pour travailler sur un dépôt GitHub avec les instructions données.
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `apiKey` | chaîne | Oui | Clé API Cursor |
+| `repository` | chaîne | Oui | URL du dépôt GitHub \(ex. : https://github.com/your-org/your-repo\) |
+| `ref` | chaîne | Non | Branche, tag ou commit à partir duquel travailler \(par défaut : branche principale\) |
+| `promptText` | chaîne | Oui | Le texte d'instruction pour l'agent |
+| `promptImages` | chaîne | Non | Tableau JSON d'objets d'image avec données base64 et dimensions |
+| `model` | chaîne | Non | Modèle à utiliser \(laisser vide pour sélection automatique\) |
+| `branchName` | chaîne | Non | Nom de branche personnalisé pour l'agent |
+| `autoCreatePr` | booléen | Non | Créer automatiquement une PR lorsque l'agent termine |
+| `openAsCursorGithubApp` | booléen | Non | Ouvrir la PR en tant qu'application GitHub Cursor |
+| `skipReviewerRequest` | booléen | Non | Ignorer la demande de relecteurs sur la PR |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | chaîne | Message de succès avec les détails de l'agent |
+| `metadata` | objet | Métadonnées du résultat de lancement |
+
+### `cursor_add_followup`
+
+Ajouter une instruction complémentaire à un agent cloud existant.
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `apiKey` | chaîne | Oui | Clé API Cursor |
+| `agentId` | chaîne | Oui | Identifiant unique pour l'agent cloud \(ex. : bc_abc123\) |
+| `followupPromptText` | chaîne | Oui | Le texte d'instruction complémentaire pour l'agent |
+| `promptImages` | chaîne | Non | Tableau JSON d'objets d'image avec données base64 et dimensions \(max 5\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Message de succès |
+| `metadata` | object | Métadonnées du résultat |
+
+### `cursor_stop_agent`
+
+Arrêter un agent cloud en cours d'exécution. Cela met l'agent en pause sans le supprimer.
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `apiKey` | chaîne | Oui | Clé API Cursor |
+| `agentId` | chaîne | Oui | Identifiant unique pour l'agent cloud \(ex., bc_abc123\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | chaîne | Message de succès |
+| `metadata` | objet | Métadonnées du résultat |
+
+### `cursor_delete_agent`
+
+Supprimer définitivement un agent cloud. Cette action ne peut pas être annulée.
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `apiKey` | string | Oui | Clé API Cursor |
+| `agentId` | string | Oui | Identifiant unique pour l'agent cloud \(ex., bc_abc123\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `content` | string | Message de succès |
+| `metadata` | object | Métadonnées du résultat |
+
+## Notes
+
+- Catégorie : `tools`
+- Type : `cursor`
diff --git a/apps/docs/content/docs/fr/tools/google_groups.mdx b/apps/docs/content/docs/fr/tools/google_groups.mdx
new file mode 100644
index 0000000000..837c112551
--- /dev/null
+++ b/apps/docs/content/docs/fr/tools/google_groups.mdx
@@ -0,0 +1,217 @@
+---
+title: Google Groups
+description: Gérer les groupes Google Workspace et leurs membres
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+## Instructions d'utilisation
+
+Connectez-vous à Google Workspace pour créer, mettre à jour et gérer les groupes et leurs membres à l'aide de l'API Admin SDK Directory.
+
+## Outils
+
+### `google_groups_list_groups`
+
+Lister tous les groupes dans un domaine Google Workspace
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `customer` | string | Non | ID client ou "my_customer" pour le domaine de l'utilisateur authentifié |
+| `domain` | string | Non | Nom de domaine pour filtrer les groupes |
+| `maxResults` | number | Non | Nombre maximum de résultats à retourner (1-200) |
+| `pageToken` | string | Non | Jeton pour la pagination |
+| `query` | string | Non | Requête de recherche pour filtrer les groupes (ex. : "email:admin*") |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_get_group`
+
+Obtenir les détails d'un groupe Google spécifique par email ou ID de groupe
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Oui | Adresse email du groupe ou ID unique du groupe |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_create_group`
+
+Créer un nouveau groupe Google dans le domaine
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `email` | chaîne | Oui | Adresse e-mail pour le nouveau groupe \(ex., team@yourdomain.com\) |
+| `name` | chaîne | Oui | Nom d'affichage pour le groupe |
+| `description` | chaîne | Non | Description du groupe |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_update_group`
+
+Mettre à jour un groupe Google existant
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
+| `name` | chaîne | Non | Nouveau nom d'affichage pour le groupe |
+| `description` | chaîne | Non | Nouvelle description pour le groupe |
+| `email` | chaîne | Non | Nouvelle adresse e-mail pour le groupe |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_delete_group`
+
+Supprimer un groupe Google
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | ---------- | ----------- |
+| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe à supprimer |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_list_members`
+
+Lister tous les membres d'un groupe Google
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
+| `maxResults` | nombre | Non | Nombre maximum de résultats à retourner \(1-200\) |
+| `pageToken` | chaîne | Non | Jeton pour la pagination |
+| `roles` | chaîne | Non | Filtrer par rôles \(séparés par des virgules : OWNER, MANAGER, MEMBER\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_get_member`
+
+Obtenir les détails d'un membre spécifique dans un groupe Google
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
+| `memberKey` | chaîne | Oui | Adresse e-mail du membre ou identifiant unique du membre |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_add_member`
+
+Ajouter un nouveau membre à un groupe Google
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
+| `email` | chaîne | Oui | Adresse e-mail du membre à ajouter |
+| `role` | chaîne | Non | Rôle pour le membre \(MEMBER, MANAGER, ou OWNER\). Par défaut MEMBER. |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_remove_member`
+
+Supprimer un membre d'un groupe Google
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
+| `memberKey` | string | Oui | Adresse e-mail ou identifiant unique du membre à supprimer |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_update_member`
+
+Mettre à jour un membre
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
+| `memberKey` | string | Oui | Adresse e-mail du membre ou identifiant unique du membre |
+| `role` | string | Oui | Nouveau rôle pour le membre \(MEMBER, MANAGER ou OWNER\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+### `google_groups_has_member`
+
+Vérifier si un utilisateur est membre d'un groupe Google
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
+| `memberKey` | string | Oui | Adresse e-mail du membre ou identifiant unique du membre à vérifier |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `output` | json | Données de réponse de l'API Google Groups |
+
+## Notes
+
+- Catégorie : `tools`
+- Type : `google_groups`
diff --git a/apps/docs/content/docs/fr/tools/salesforce.mdx b/apps/docs/content/docs/fr/tools/salesforce.mdx
index 3242dea2cc..d05a8f0cdd 100644
--- a/apps/docs/content/docs/fr/tools/salesforce.mdx
+++ b/apps/docs/content/docs/fr/tools/salesforce.mdx
@@ -135,283 +135,684 @@ Supprimer un compte de Salesforce CRM
### `salesforce_get_contacts`
+Obtenir un ou des contact(s) depuis Salesforce - contact unique si l'ID est fourni, ou liste si non
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `contactId` | string | Non | ID du contact \(si fourni, renvoie un contact unique\) |
+| `limit` | string | Non | Nombre de résultats \(par défaut : 100, max : 2000\). Uniquement pour les requêtes de liste. |
+| `fields` | string | Non | Champs séparés par des virgules \(ex. : "Id,FirstName,LastName,Email,Phone"\) |
+| `orderBy` | string | Non | Champ pour l'ordre de tri \(ex. : "LastName ASC"\). Uniquement pour les requêtes de liste. |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Données du/des contact(s) |
### `salesforce_create_contact`
+Créer un nouveau contact dans Salesforce CRM
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `lastName` | string | Oui | Nom de famille \(obligatoire\) |
+| `firstName` | string | Non | Prénom |
+| `email` | string | Non | Adresse e-mail |
+| `phone` | string | Non | Numéro de téléphone |
+| `accountId` | string | Non | ID du compte à associer au contact |
+| `title` | string | Non | Pas de description |
+| `department` | string | Non | Département |
+| `mailingStreet` | string | Non | Rue d'adresse postale |
+| `mailingCity` | string | Non | Ville d'adresse postale |
+| `mailingState` | string | Non | État/province d'adresse postale |
+| `mailingPostalCode` | string | Non | Code postal d'adresse postale |
+| `mailingCountry` | string | Non | Pays d'adresse postale |
+| `description` | string | Non | Description du contact |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Données du contact créé |
### `salesforce_update_contact`
+Mettre à jour un contact existant dans Salesforce CRM
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `contactId` | string | Oui | ID du contact à mettre à jour \(obligatoire\) |
+| `lastName` | string | Non | Nom de famille |
+| `firstName` | string | Non | Prénom |
+| `email` | string | Non | Adresse e-mail |
+| `phone` | string | Non | Numéro de téléphone |
+| `accountId` | string | Non | ID du compte à associer |
+| `title` | string | Non | Pas de description |
+| `department` | string | Non | Département |
+| `mailingStreet` | string | Non | Rue de l'adresse postale |
+| `mailingCity` | string | Non | Ville de l'adresse postale |
+| `mailingState` | string | Non | État/province de l'adresse postale |
+| `mailingPostalCode` | string | Non | Code postal de l'adresse postale |
+| `mailingCountry` | string | Non | Pays de l'adresse postale |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Données du contact mis à jour |
### `salesforce_delete_contact`
+Supprimer un contact de Salesforce CRM
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `contactId` | string | Oui | ID du contact à supprimer \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Données du contact supprimé |
### `salesforce_get_leads`
+Obtenir un ou plusieurs prospects de Salesforce
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `leadId` | string | Non | ID du prospect \(facultatif\) |
+| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
+| `fields` | string | Non | Champs séparés par des virgules |
+| `orderBy` | string | Non | Champ pour l'ordre de tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Données du prospect |
### `salesforce_create_lead`
+Créer un nouveau prospect
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `lastName` | string | Oui | Nom de famille \(obligatoire\) |
+| `company` | string | Oui | Société \(obligatoire\) |
+| `firstName` | string | Non | Prénom |
+| `email` | string | Non | Pas de description |
+| `phone` | string | Non | Pas de description |
+| `status` | string | Non | Statut du prospect |
+| `leadSource` | string | Non | Source du prospect |
+| `title` | string | Non | Pas de description |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Prospect créé |
### `salesforce_update_lead`
+Mettre à jour un prospect existant
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `leadId` | string | Oui | ID du prospect \(obligatoire\) |
+| `lastName` | string | Non | Nom de famille |
+| `company` | string | Non | Pas de description |
+| `firstName` | string | Non | Prénom |
+| `email` | string | Non | Pas de description |
+| `phone` | string | Non | Pas de description |
+| `status` | string | Non | Statut du prospect |
+| `leadSource` | string | Non | Source du prospect |
+| `title` | string | Non | Pas de description |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Prospect mis à jour |
### `salesforce_delete_lead`
+Supprimer un prospect
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `leadId` | string | Oui | ID du prospect \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Prospect supprimé |
### `salesforce_get_opportunities`
+Obtenir une ou plusieurs opportunités depuis Salesforce
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `opportunityId` | string | Non | ID de l'opportunité \(facultatif\) |
+| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
+| `fields` | string | Non | Champs séparés par des virgules |
+| `orderBy` | string | Non | Champ pour l'ordre de tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Données de l'opportunité |
### `salesforce_create_opportunity`
+Créer une nouvelle opportunité
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `name` | string | Oui | Nom de l'opportunité \(obligatoire\) |
+| `stageName` | string | Oui | Nom de l'étape \(obligatoire\) |
+| `closeDate` | string | Oui | Date de clôture AAAA-MM-JJ \(obligatoire\) |
+| `accountId` | string | Non | ID du compte |
+| `amount` | string | Non | Montant \(nombre\) |
+| `probability` | string | Non | Probabilité \(0-100\) |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Opportunité créée |
### `salesforce_update_opportunity`
+Mettre à jour une opportunité existante
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `opportunityId` | string | Oui | ID de l'opportunité \(obligatoire\) |
+| `name` | string | Non | Nom de l'opportunité |
+| `stageName` | string | Non | Nom de l'étape |
+| `closeDate` | string | Non | Date de clôture AAAA-MM-JJ |
+| `accountId` | string | Non | ID du compte |
+| `amount` | string | Non | Pas de description |
+| `probability` | string | Non | Probabilité \(0-100\) |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Opportunité mise à jour |
### `salesforce_delete_opportunity`
+Supprimer une opportunité
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `opportunityId` | string | Oui | ID de l'opportunité \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Opportunité supprimée |
### `salesforce_get_cases`
+Obtenir un ou des dossiers depuis Salesforce
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `caseId` | string | Non | ID du dossier \(facultatif\) |
+| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
+| `fields` | string | Non | Champs séparés par des virgules |
+| `orderBy` | string | Non | Champ pour le tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Données du dossier |
### `salesforce_create_case`
+Créer un nouveau dossier
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `subject` | string | Oui | Objet du dossier \(obligatoire\) |
+| `status` | string | Non | Statut \(ex., Nouveau, En cours, Escaladé\) |
+| `priority` | string | Non | Priorité \(ex., Basse, Moyenne, Haute\) |
+| `origin` | string | Non | Origine \(ex., Téléphone, Email, Web\) |
+| `contactId` | string | Non | ID du contact |
+| `accountId` | string | Non | ID du compte |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Dossier créé |
### `salesforce_update_case`
+Mettre à jour un dossier existant
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `caseId` | string | Oui | ID du dossier \(obligatoire\) |
+| `subject` | string | Non | Objet du dossier |
+| `status` | string | Non | Statut |
+| `priority` | string | Non | Priorité |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Dossier mis à jour |
### `salesforce_delete_case`
+Supprimer un dossier
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `caseId` | string | Oui | ID du dossier \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Succès |
+| `output` | object | Dossier supprimé |
### `salesforce_get_tasks`
+Obtenir une ou plusieurs tâches de Salesforce
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `taskId` | string | Non | ID de la tâche \(facultatif\) |
+| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
+| `fields` | string | Non | Champs séparés par des virgules |
+| `orderBy` | string | Non | Champ pour l'ordre de tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Données de la tâche |
### `salesforce_create_task`
+Créer une nouvelle tâche
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `subject` | string | Oui | Objet de la tâche \(obligatoire\) |
+| `status` | string | Non | Statut \(ex., Non commencée, En cours, Terminée\) |
+| `priority` | string | Non | Priorité \(ex., Basse, Normale, Haute\) |
+| `activityDate` | string | Non | Date d'échéance AAAA-MM-JJ |
+| `whoId` | string | Non | ID du contact/prospect associé |
+| `whatId` | string | Non | ID du compte/opportunité associé |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `output` | object | Tâche créée |
### `salesforce_update_task`
+Mettre à jour une tâche existante
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `taskId` | string | Oui | ID de la tâche \(obligatoire\) |
+| `subject` | string | Non | Objet de la tâche |
+| `status` | string | Non | Statut |
+| `priority` | string | Non | Priorité |
+| `activityDate` | string | Non | Date d'échéance AAAA-MM-JJ |
+| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Tâche mise à jour |
### `salesforce_delete_task`
+Supprimer une tâche
+
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `taskId` | string | Oui | ID de la tâche \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Statut de réussite de l'opération |
-| `output` | json | Données résultant de l'opération |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Tâche supprimée |
+
+### `salesforce_list_reports`
+
+Obtenir une liste des rapports accessibles par l'utilisateur actuel
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `folderName` | string | Non | Filtrer par nom de dossier |
+| `searchTerm` | string | Non | Terme de recherche pour filtrer les rapports par nom |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Données des rapports |
+
+### `salesforce_get_report`
+
+Obtenir les métadonnées et les informations descriptives d'un rapport spécifique
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `reportId` | string | Oui | ID du rapport \(obligatoire\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Métadonnées du rapport |
+
+### `salesforce_run_report`
+
+Exécuter un rapport et récupérer les résultats
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `reportId` | string | Oui | ID du rapport \(obligatoire\) |
+| `includeDetails` | string | Non | Inclure les lignes détaillées \(true/false, par défaut : true\) |
+| `filters` | string | Non | Chaîne JSON des filtres de rapport à appliquer |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Résultats du rapport |
+
+### `salesforce_list_report_types`
+
+Obtenir une liste des types de rapports disponibles
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Données des types de rapports |
+
+### `salesforce_list_dashboards`
+
+Obtenir une liste des tableaux de bord accessibles par l'utilisateur actuel
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `folderName` | string | Non | Filtrer par nom de dossier |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Données des tableaux de bord |
+
+### `salesforce_get_dashboard`
+
+Obtenir les détails et les résultats d'un tableau de bord spécifique
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `dashboardId` | string | Oui | ID du tableau de bord (obligatoire) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Données du tableau de bord |
+
+### `salesforce_refresh_dashboard`
+
+Actualiser un tableau de bord pour obtenir les données les plus récentes
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `dashboardId` | string | Oui | ID du tableau de bord (obligatoire) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Données du tableau de bord actualisé |
+
+### `salesforce_query`
+
+Exécuter une requête SOQL personnalisée pour récupérer des données de Salesforce
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `query` | string | Oui | Requête SOQL à exécuter (ex. : SELECT Id, Name FROM Account LIMIT 10) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Résultats de la requête |
+
+### `salesforce_query_more`
+
+Récupérer des résultats de requête supplémentaires en utilisant le nextRecordsUrl d'une requête précédente
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `nextRecordsUrl` | string | Oui | Le nextRecordsUrl d'une réponse de requête précédente |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Résultats de la requête |
+
+### `salesforce_describe_object`
+
+Obtenir les métadonnées et les informations sur les champs d'un objet Salesforce
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+| `objectName` | string | Oui | Nom API de l'objet \(ex., Account, Contact, Lead, Custom_Object__c\) |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Métadonnées de l'objet |
+
+### `salesforce_list_objects`
+
+Obtenir une liste de tous les objets Salesforce disponibles
+
+#### Entrée
+
+| Paramètre | Type | Obligatoire | Description |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | Non | Pas de description |
+| `instanceUrl` | string | Non | Pas de description |
+
+#### Sortie
+
+| Paramètre | Type | Description |
+| --------- | ---- | ----------- |
+| `success` | boolean | Statut de réussite |
+| `output` | object | Liste des objets |
## Notes
diff --git a/apps/docs/content/docs/ja/blocks/condition.mdx b/apps/docs/content/docs/ja/blocks/condition.mdx
index db1705b21b..a1f5b6cfbe 100644
--- a/apps/docs/content/docs/ja/blocks/condition.mdx
+++ b/apps/docs/content/docs/ja/blocks/condition.mdx
@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## ベストプラクティス
-- **条件を正しく順序付ける**: より具体的な条件を一般的な条件の前に配置し、特定のロジックがフォールバックよりも優先されるようにします
-- **デフォルト条件を含める**: 最後の条件として包括的な条件(`true`)を追加し、マッチしないケースを処理してワークフローの実行が停止しないようにします
-- **式をシンプルに保つ**: 読みやすさとデバッグのしやすさのために、明確で簡潔な真偽式を使用します
+- **条件を正しく順序付ける**: より具体的な条件を一般的な条件の前に配置して、特定のロジックがフォールバックよりも優先されるようにします
+- **必要に応じてelse分岐を使用する**: 条件が一致せず、else分岐が接続されていない場合、ワークフロー分岐は正常に終了します。一致しないケースのフォールバックパスが必要な場合は、else分岐を接続してください
+- **式をシンプルに保つ**: 読みやすさとデバッグのしやすさのために、明確で簡潔なブール式を使用します
- **条件を文書化する**: チームのコラボレーションとメンテナンスを向上させるために、各条件の目的を説明する説明を追加します
- **エッジケースをテストする**: 条件範囲の境界値でテストすることで、条件が境界値を正しく処理することを確認します
diff --git a/apps/docs/content/docs/ja/introduction/index.mdx b/apps/docs/content/docs/ja/introduction/index.mdx
index d606340de9..dd37ac43fe 100644
--- a/apps/docs/content/docs/ja/introduction/index.mdx
+++ b/apps/docs/content/docs/ja/introduction/index.mdx
@@ -72,7 +72,7 @@ Simは複数のカテゴリにわたる80以上のサービスとネイティブ
-## AI搭載コパイロット
+## Copilot
**質問と指導を受ける**
コパイロットはSimに関する質問に答え、ワークフローを説明し、改善のための提案を提供します。`@`記号を使用してワークフロー、ブロック、ドキュメント、ナレッジ、ログを参照し、文脈に応じたサポートを受けられます。
diff --git a/apps/docs/content/docs/ja/knowledgebase/index.mdx b/apps/docs/content/docs/ja/knowledgebase/index.mdx
index 875c222a06..80ae42a228 100644
--- a/apps/docs/content/docs/ja/knowledgebase/index.mdx
+++ b/apps/docs/content/docs/ja/knowledgebase/index.mdx
@@ -1,5 +1,6 @@
---
-title: ナレッジベース
+title: 概要
+description: インテリジェントなベクトル検索とチャンキングを使用して、ドキュメントをアップロード、処理、検索
---
import { Video } from '@/components/ui/video'
diff --git a/apps/docs/content/docs/ja/self-hosting/docker.mdx b/apps/docs/content/docs/ja/self-hosting/docker.mdx
new file mode 100644
index 0000000000..c39ba2fc1b
--- /dev/null
+++ b/apps/docs/content/docs/ja/self-hosting/docker.mdx
@@ -0,0 +1,155 @@
+---
+title: Docker
+description: Docker Composeを使用してSim Studioをデプロイする
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## クイックスタート
+
+```bash
+# Clone and start
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+[http://localhost:3000](http://localhost:3000)を開く
+
+## 本番環境のセットアップ
+
+### 1. 環境の設定
+
+```bash
+# Generate secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+```
+
+### 2. サービスの起動
+
+```bash
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### 3. SSLの設定
+
+
+
+Caddyは自動的にSSL証明書を処理します。
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+```
+
+`/etc/caddy/Caddyfile`を作成します:
+
+```
+sim.yourdomain.com {
+ reverse_proxy localhost:3000
+
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}
+```
+
+```bash
+sudo systemctl restart caddy
+```
+
+
+
+
+```bash
+# Install
+sudo apt install nginx certbot python3-certbot-nginx -y
+
+# Create /etc/nginx/sites-available/sim
+server {
+ listen 80;
+ server_name sim.yourdomain.com;
+
+ location / {
+ proxy_pass http://127.0.0.1:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /socket.io/ {
+ proxy_pass http://127.0.0.1:3002;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
+
+# Enable and get certificate
+sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
+sudo certbot --nginx -d sim.yourdomain.com
+```
+
+
+
+
+## Ollama
+
+```bash
+# With GPU
+docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
+
+# CPU only
+docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
+```
+
+追加モデルを取得:
+
+```bash
+docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
+```
+
+### 外部Ollama
+
+Ollamaがホストマシン上で実行されている場合(Dockerではない):
+
+```bash
+# macOS/Windows
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+
+# Linux - use your host IP
+OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
+```
+
+
+ Docker内では、`localhost`はホストではなくコンテナを指します。`host.docker.internal`またはホストのIPを使用してください。
+
+
+## コマンド
+
+```bash
+# View logs
+docker compose -f docker-compose.prod.yml logs -f simstudio
+
+# Stop
+docker compose -f docker-compose.prod.yml down
+
+# Update
+docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
+
+# Backup database
+docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
+```
diff --git a/apps/docs/content/docs/ja/self-hosting/environment-variables.mdx b/apps/docs/content/docs/ja/self-hosting/environment-variables.mdx
new file mode 100644
index 0000000000..ccdb20de2c
--- /dev/null
+++ b/apps/docs/content/docs/ja/self-hosting/environment-variables.mdx
@@ -0,0 +1,87 @@
+---
+title: 環境変数
+description: Sim Studioの設定リファレンス
+---
+
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## 必須項目
+
+| 変数 | 説明 |
+|----------|-------------|
+| `DATABASE_URL` | PostgreSQL接続文字列 |
+| `BETTER_AUTH_SECRET` | 認証シークレット(32桁の16進数): `openssl rand -hex 32` |
+| `BETTER_AUTH_URL` | アプリのURL |
+| `ENCRYPTION_KEY` | 暗号化キー(32桁の16進数): `openssl rand -hex 32` |
+| `INTERNAL_API_SECRET` | 内部APIシークレット(32桁の16進数): `openssl rand -hex 32` |
+| `NEXT_PUBLIC_APP_URL` | 公開アプリURL |
+| `NEXT_PUBLIC_SOCKET_URL` | WebSocket URL(デフォルト: `http://localhost:3002`) |
+
+## AIプロバイダー
+
+| 変数 | プロバイダー |
+|----------|----------|
+| `OPENAI_API_KEY` | OpenAI |
+| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
+| `GEMINI_API_KEY_1` | Google Gemini |
+| `MISTRAL_API_KEY` | Mistral |
+| `OLLAMA_URL` | Ollama(デフォルト: `http://localhost:11434`) |
+
+
+ 負荷分散のために、`_1`、`_2`、`_3`のサフィックスを持つ複数のキーを追加できます(例:`OPENAI_API_KEY_1`、`OPENAI_API_KEY_2`)。OpenAI、Anthropic、Geminiで動作します。
+
+
+
+ Dockerでは、ホストマシンのOllamaに接続するために`OLLAMA_URL=http://host.docker.internal:11434`を使用してください。
+
+
+### Azure OpenAI
+
+| 変数 | 説明 |
+|----------|-------------|
+| `AZURE_OPENAI_API_KEY` | Azure OpenAI APIキー |
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAIエンドポイントURL |
+| `AZURE_OPENAI_API_VERSION` | APIバージョン(例:`2024-02-15-preview`) |
+
+### vLLM(セルフホスト)
+
+| 変数 | 説明 |
+|----------|-------------|
+| `VLLM_BASE_URL` | vLLMサーバーURL(例:`http://localhost:8000/v1`) |
+| `VLLM_API_KEY` | vLLM用のオプションベアラートークン |
+
+## OAuth プロバイダー
+
+| 変数 | 説明 |
+|----------|-------------|
+| `GOOGLE_CLIENT_ID` | Google OAuthクライアントID |
+| `GOOGLE_CLIENT_SECRET` | Google OAuthクライアントシークレット |
+| `GITHUB_CLIENT_ID` | GitHub OAuthクライアントID |
+| `GITHUB_CLIENT_SECRET` | GitHub OAuthクライアントシークレット |
+
+## オプション
+
+| 変数 | 説明 |
+|----------|-------------|
+| `API_ENCRYPTION_KEY` | 保存されたAPIキーを暗号化します(32桁の16進数): `openssl rand -hex 32` |
+| `COPILOT_API_KEY` | コパイロット機能用のAPIキー |
+| `ADMIN_API_KEY` | GitOps操作用の管理者APIキー |
+| `RESEND_API_KEY` | 通知用のメールサービス |
+| `ALLOWED_LOGIN_DOMAINS` | サインアップをドメインに制限(カンマ区切り) |
+| `ALLOWED_LOGIN_EMAILS` | サインアップを特定のメールに制限(カンマ区切り) |
+| `DISABLE_REGISTRATION` | 新規ユーザーのサインアップを無効にするには `true` に設定 |
+
+## .envの例
+
+```bash
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=
+BETTER_AUTH_URL=https://sim.yourdomain.com
+ENCRYPTION_KEY=
+INTERNAL_API_SECRET=
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+OPENAI_API_KEY=sk-...
+```
+
+すべてのオプションについては `apps/sim/.env.example` を参照してください。
diff --git a/apps/docs/content/docs/ja/self-hosting/index.mdx b/apps/docs/content/docs/ja/self-hosting/index.mdx
new file mode 100644
index 0000000000..a97e7380f7
--- /dev/null
+++ b/apps/docs/content/docs/ja/self-hosting/index.mdx
@@ -0,0 +1,50 @@
+---
+title: セルフホスティング
+description: 自社のインフラストラクチャにSim Studioをデプロイ
+---
+
+import { Card, Cards } from 'fumadocs-ui/components/card'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+DockerまたはKubernetesを使用して、自社のインフラストラクチャにSim Studioをデプロイします。
+
+## 要件
+
+| リソース | 最小 | 推奨 |
+|----------|---------|-------------|
+| CPU | 2コア | 4+コア |
+| RAM | 12 GB | 16+ GB |
+| ストレージ | 20 GB SSD | 50+ GB SSD |
+| Docker | 20.10+ | 最新版 |
+
+## クイックスタート
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+[http://localhost:3000](http://localhost:3000)を開く
+
+## デプロイオプション
+
+
+
+ 任意のサーバーでDocker Composeを使用してデプロイ
+
+
+ KubernetesクラスターでHelmを使用してデプロイ
+
+
+ Railway、DigitalOcean、AWS、Azure、GCPのガイド
+
+
+
+## アーキテクチャ
+
+| コンポーネント | ポート | 説明 |
+|-----------|------|-------------|
+| simstudio | 3000 | メインアプリケーション |
+| realtime | 3002 | WebSocketサーバー |
+| db | 5432 | pgvector搭載のPostgreSQL |
+| migrations | - | データベースマイグレーション(一度だけ実行) |
diff --git a/apps/docs/content/docs/ja/self-hosting/kubernetes.mdx b/apps/docs/content/docs/ja/self-hosting/kubernetes.mdx
new file mode 100644
index 0000000000..95e1638694
--- /dev/null
+++ b/apps/docs/content/docs/ja/self-hosting/kubernetes.mdx
@@ -0,0 +1,133 @@
+---
+title: Kubernetes
+description: Helmを使用してSim Studioをデプロイする
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## 前提条件
+
+- Kubernetes 1.19+
+- Helm 3.0+
+- PVプロビジョナーのサポート
+
+## インストール
+
+```bash
+# Clone repo
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Generate secrets
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+
+# Install
+helm install sim ./helm/sim \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --namespace simstudio --create-namespace
+```
+
+## クラウド固有の値
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-aws.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-azure.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-gcp.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+## 主要な設定
+
+```yaml
+# Custom values.yaml
+app:
+ replicaCount: 2
+ env:
+ NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
+ OPENAI_API_KEY: "sk-..."
+
+postgresql:
+ persistence:
+ size: 50Gi
+
+ingress:
+ enabled: true
+ className: nginx
+ tls:
+ enabled: true
+ app:
+ host: sim.yourdomain.com
+```
+
+すべてのオプションについては `helm/sim/values.yaml` を参照してください。
+
+## 外部データベース
+
+```yaml
+postgresql:
+ enabled: false
+
+externalDatabase:
+ enabled: true
+ host: "your-db-host"
+ port: 5432
+ username: "postgres"
+ password: "your-password"
+ database: "simstudio"
+ sslMode: "require"
+```
+
+## コマンド
+
+```bash
+# Port forward for local access
+kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
+
+# View logs
+kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
+
+# Upgrade
+helm upgrade sim ./helm/sim --namespace simstudio
+
+# Uninstall
+helm uninstall sim --namespace simstudio
+```
diff --git a/apps/docs/content/docs/ja/self-hosting/platforms.mdx b/apps/docs/content/docs/ja/self-hosting/platforms.mdx
new file mode 100644
index 0000000000..b6779c4a63
--- /dev/null
+++ b/apps/docs/content/docs/ja/self-hosting/platforms.mdx
@@ -0,0 +1,124 @@
+---
+title: クラウドプラットフォーム
+description: クラウドプラットフォームにSim Studioをデプロイする
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Railway
+
+ワンクリックデプロイメントで自動的にPostgreSQLをプロビジョニングします。
+
+[
+
+
+
+](https://railway.com/new/template/sim-studio)
+
+デプロイ後、Railwayダッシュボードで環境変数を追加してください:
+- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (自動生成)
+- `OPENAI_API_KEY` または他のAIプロバイダーキー
+- 設定 → ネットワーキングでカスタムドメイン
+
+## VPSデプロイメント
+
+DigitalOcean、AWS EC2、Azure VMsまたは任意のLinuxサーバー向け:
+
+
+
+**推奨:** 16 GB RAMドロップレット、Ubuntu 24.04
+
+```bash
+# Create Droplet via console, then SSH in
+ssh root@your-droplet-ip
+```
+
+
+
+**推奨:** t3.xlarge (16 GB RAM)、Ubuntu 24.04
+
+```bash
+ssh -i your-key.pem ubuntu@your-ec2-ip
+```
+
+
+
+**推奨:** Standard_D4s_v3 (16 GB RAM)、Ubuntu 24.04
+
+```bash
+ssh azureuser@your-vm-ip
+```
+
+
+
+
+### Dockerのインストール
+
+```bash
+# Install Docker (official method)
+curl -fsSL https://get.docker.com | sudo sh
+sudo usermod -aG docker $USER
+
+# Logout and reconnect, then verify
+docker --version
+```
+
+### Sim Studioのデプロイ
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Create .env with secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+
+# Start
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### CaddyによるSSL
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+
+# Configure (replace domain)
+echo 'sim.yourdomain.com {
+ reverse_proxy localhost:3000
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}' | sudo tee /etc/caddy/Caddyfile
+
+sudo systemctl restart caddy
+```
+
+ドメインのDNS AレコードをサーバーのIPアドレスに向けてください。
+
+## Kubernetes (EKS, AKS, GKE)
+
+[Kubernetesガイド](/self-hosting/kubernetes)でマネージドKubernetesへのHelmデプロイメントについて確認してください。
+
+## マネージドデータベース(オプション)
+
+本番環境では、マネージドPostgreSQLサービスを使用してください:
+
+- **AWS RDS** / **Azure Database** / **Cloud SQL** - pgvector拡張機能を有効化
+- **Supabase** / **Neon** - pgvector搭載済み
+
+環境に`DATABASE_URL`を設定してください:
+
+```bash
+DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
+```
diff --git a/apps/docs/content/docs/ja/self-hosting/troubleshooting.mdx b/apps/docs/content/docs/ja/self-hosting/troubleshooting.mdx
new file mode 100644
index 0000000000..23b0c7a888
--- /dev/null
+++ b/apps/docs/content/docs/ja/self-hosting/troubleshooting.mdx
@@ -0,0 +1,113 @@
+---
+title: トラブルシューティング
+description: 一般的な問題と解決策
+---
+
+## データベース接続に失敗
+
+```bash
+# Check database is running
+docker compose ps db
+
+# Test connection
+docker compose exec db psql -U postgres -c "SELECT 1"
+```
+
+`DATABASE_URL` 形式を確認してください: `postgresql://user:pass@host:5432/database`
+
+## Ollamaモデルが表示されない
+
+Docker内では、`localhost` = ホストマシンではなく、コンテナを指します。
+
+```bash
+# For host-machine Ollama, use:
+OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
+OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
+```
+
+## WebSocket/リアルタイム機能が動作しない
+
+1. `NEXT_PUBLIC_SOCKET_URL` がドメインと一致しているか確認する
+2. リアルタイムサービスが実行されているか確認する: `docker compose ps realtime`
+3. リバースプロキシがWebSocketアップグレードを通過させていることを確認する([Dockerガイド](/self-hosting/docker)を参照)
+
+## 502 Bad Gateway
+
+```bash
+# Check app is running
+docker compose ps simstudio
+docker compose logs simstudio
+
+# Common causes: out of memory, database not ready
+```
+
+## マイグレーションエラー
+
+```bash
+# View migration logs
+docker compose logs migrations
+
+# Run manually
+docker compose exec simstudio bun run db:migrate
+```
+
+## pgvectorが見つからない
+
+正しいPostgreSQLイメージを使用してください:
+
+```yaml
+image: pgvector/pgvector:pg17 # NOT postgres:17
+```
+
+## 証明書エラー(CERT_HAS_EXPIRED)
+
+外部APIを呼び出す際にSSL証明書エラーが表示される場合:
+
+```bash
+# Update CA certificates in container
+docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
+
+# Or set in environment (not recommended for production)
+NODE_TLS_REJECT_UNAUTHORIZED=0
+```
+
+## ログイン後の空白ページ
+
+1. ブラウザコンソールでエラーを確認する
+2. `NEXT_PUBLIC_APP_URL` が実際のドメインと一致しているか確認する
+3. ブラウザのCookieとローカルストレージをクリアする
+4. すべてのサービスが実行されているか確認する: `docker compose ps`
+
+## Windows特有の問題
+
+**WindowsでのTurbopackエラー:**
+
+```bash
+# Use WSL2 for better compatibility
+wsl --install
+
+# Or disable Turbopack in package.json
+# Change "next dev --turbopack" to "next dev"
+```
+
+**改行の問題:**
+
+```bash
+# Configure git to use LF
+git config --global core.autocrlf input
+```
+
+## ログを表示
+
+```bash
+# All services
+docker compose logs -f
+
+# Specific service
+docker compose logs -f simstudio
+```
+
+## ヘルプを得る
+
+- [GitHub Issues](https://github.com/simstudioai/sim/issues)
+- [Discord](https://discord.gg/Hr4UWYEcTT)
diff --git a/apps/docs/content/docs/ja/tools/cursor.mdx b/apps/docs/content/docs/ja/tools/cursor.mdx
new file mode 100644
index 0000000000..20a7e0d361
--- /dev/null
+++ b/apps/docs/content/docs/ja/tools/cursor.mdx
@@ -0,0 +1,181 @@
+---
+title: Cursor
+description: GitHubリポジトリで作業するためのCursorクラウドエージェントを起動および管理する
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+{/* MANUAL-CONTENT-START:intro */}
+[Cursor](https://www.cursor.so/)はAI IDEおよびクラウドベースのプラットフォームで、GitHubリポジトリで直接作業できる強力なAIエージェントを起動・管理することができます。Cursorエージェントは開発タスクを自動化し、チームの生産性を向上させ、コード変更の実施、自然言語指示への応答、活動に関する会話履歴の維持によってあなたと協力します。
+
+Cursorでは以下のことができます:
+
+- **コードベース用のクラウドエージェントを起動**: クラウド上であなたのリポジトリで作業する新しいAIエージェントを即座に作成
+- **自然言語を使用してコーディングタスクを委任**: 書面による指示、修正、説明でエージェントを導く
+- **進捗と出力を監視**: エージェントのステータスを取得し、詳細な結果を表示し、現在または完了したタスクを検査
+- **完全な会話履歴にアクセス**: 透明性と監査可能性のためにすべてのプロンプトとAI応答をレビュー
+- **エージェントのライフサイクルを制御・管理**: アクティブなエージェントをリスト表示し、エージェントを終了し、APIベースのエージェント起動とフォローアップを管理
+
+Simでは、Cursor統合によりエージェントとワークフローがCursorクラウドエージェントとプログラム的に対話できるようになります。つまり、Simを使用して以下のことができます:
+
+- すべてのクラウドエージェントをリスト表示し、現在の状態を閲覧 (`cursor_list_agents`)
+- 任意のエージェントの最新ステータスと出力を取得 (`cursor_get_agent`)
+- 任意のコーディングエージェントの完全な会話履歴を表示 (`cursor_get_conversation`)
+- 実行中のエージェントにフォローアップ指示や新しいプロンプトを追加
+- 必要に応じてエージェントを管理・終了
+
+この統合により、Simエージェントの柔軟なインテリジェンスとCursorの強力な開発自動化機能を組み合わせることができ、プロジェクト全体でAI駆動の開発をスケールすることが可能になります。
+{/* MANUAL-CONTENT-END */}
+
+## 使用方法
+
+Cursor Cloud Agents APIを使用して、GitHubリポジトリで作業できるAIエージェントを起動します。エージェントの起動、フォローアップ指示の追加、ステータスの確認、会話の表示、およびエージェントのライフサイクル管理をサポートしています。
+
+## ツール
+
+### `cursor_list_agents`
+
+認証されたユーザーのすべてのクラウドエージェントをオプションのページネーションで一覧表示します。
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | はい | Cursor APIキー |
+| `limit` | number | いいえ | 返すエージェントの数(デフォルト:20、最大:100) |
+| `cursor` | string | いいえ | 前の応答からのページネーションカーソル |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `content` | string | 人間が読めるエージェントのリスト |
+| `metadata` | object | エージェントリストのメタデータ |
+
+### `cursor_get_agent`
+
+クラウドエージェントの現在のステータスと結果を取得します。
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | はい | Cursor APIキー |
+| `agentId` | string | はい | クラウドエージェントの一意の識別子(例:bc_abc123) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `content` | string | 人間が読めるエージェントの詳細 |
+| `metadata` | object | エージェントのメタデータ |
+
+### `cursor_get_conversation`
+
+クラウドエージェントの会話履歴(すべてのユーザープロンプトとアシスタントの応答を含む)を取得します。
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | はい | Cursor APIキー |
+| `agentId` | string | はい | クラウドエージェントの一意の識別子(例:bc_abc123) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `content` | string | 人間が読める会話履歴 |
+| `metadata` | object | 会話のメタデータ |
+
+### `cursor_launch_agent`
+
+指定された指示でGitHubリポジトリに取り組む新しいクラウドエージェントを開始します。
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | はい | Cursor APIキー |
+| `repository` | string | はい | GitHubリポジトリURL(例:https://github.com/your-org/your-repo) |
+| `ref` | string | いいえ | 作業するブランチ、タグ、またはコミット(デフォルトはデフォルトブランチ) |
+| `promptText` | string | はい | エージェントへの指示テキスト |
+| `promptImages` | string | いいえ | base64データと寸法を持つ画像オブジェクトのJSON配列 |
+| `model` | string | いいえ | 使用するモデル(自動選択の場合は空のままにする) |
+| `branchName` | string | いいえ | エージェントが使用するカスタムブランチ名 |
+| `autoCreatePr` | boolean | いいえ | エージェントが終了したときに自動的にPRを作成する |
+| `openAsCursorGithubApp` | boolean | いいえ | Cursor GitHub AppとしてPRを開く |
+| `skipReviewerRequest` | boolean | いいえ | PRでのレビュアーのリクエストをスキップする |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `content` | string | エージェントの詳細を含む成功メッセージ |
+| `metadata` | object | 起動結果のメタデータ |
+
+### `cursor_add_followup`
+
+既存のクラウドエージェントにフォローアップ指示を追加します。
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | はい | Cursor APIキー |
+| `agentId` | string | はい | クラウドエージェントの一意の識別子(例:bc_abc123) |
+| `followupPromptText` | string | はい | エージェントへのフォローアップ指示テキスト |
+| `promptImages` | string | いいえ | base64データと寸法を持つ画像オブジェクトのJSON配列(最大5つ) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `content` | string | 成功メッセージ |
+| `metadata` | object | 結果メタデータ |
+
+### `cursor_stop_agent`
+
+実行中のクラウドエージェントを停止します。これはエージェントを削除せずに一時停止します。
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | はい | Cursor APIキー |
+| `agentId` | string | はい | クラウドエージェントの一意の識別子(例:bc_abc123) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `content` | string | 成功メッセージ |
+| `metadata` | object | 結果メタデータ |
+
+### `cursor_delete_agent`
+
+クラウドエージェントを完全に削除します。この操作は元に戻せません。
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | はい | Cursor APIキー |
+| `agentId` | string | はい | クラウドエージェントの一意の識別子(例:bc_abc123) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `content` | string | 成功メッセージ |
+| `metadata` | object | 結果メタデータ |
+
+## 注意事項
+
+- カテゴリー: `tools`
+- タイプ: `cursor`
diff --git a/apps/docs/content/docs/ja/tools/google_groups.mdx b/apps/docs/content/docs/ja/tools/google_groups.mdx
new file mode 100644
index 0000000000..6f36eeb808
--- /dev/null
+++ b/apps/docs/content/docs/ja/tools/google_groups.mdx
@@ -0,0 +1,217 @@
+---
+title: Google グループ
+description: Google Workspace グループとそのメンバーを管理する
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+## 使用方法
+
+Google Workspaceに接続して、Admin SDK Directory APIを使用してグループとそのメンバーを作成、更新、管理します。
+
+## ツール
+
+### `google_groups_list_groups`
+
+Google Workspaceドメイン内のすべてのグループを一覧表示する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `customer` | string | いいえ | 顧客IDまたは認証済みユーザーのドメインの場合は「my_customer」 |
+| `domain` | string | いいえ | グループをフィルタリングするドメイン名 |
+| `maxResults` | number | いいえ | 返す結果の最大数(1-200) |
+| `pageToken` | string | いいえ | ページネーション用のトークン |
+| `query` | string | いいえ | グループをフィルタリングする検索クエリ(例:「email:admin*」) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google グループ API レスポンスデータ |
+
+### `google_groups_get_group`
+
+メールアドレスまたはグループIDで特定のGoogle グループの詳細を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google グループ API レスポンスデータ |
+
+### `google_groups_create_group`
+
+ドメイン内に新しいGoogle グループを作成する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `email` | string | はい | 新しいグループのメールアドレス(例:team@yourdomain.com) |
+| `name` | string | はい | グループの表示名 |
+| `description` | string | いいえ | グループの説明 |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIのレスポンスデータ |
+
+### `google_groups_update_group`
+
+既存のGoogleグループを更新する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+| `name` | string | いいえ | グループの新しい表示名 |
+| `description` | string | いいえ | グループの新しい説明 |
+| `email` | string | いいえ | グループの新しいメールアドレス |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIのレスポンスデータ |
+
+### `google_groups_delete_group`
+
+Googleグループを削除する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | 削除するグループのメールアドレスまたは一意のグループID |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIのレスポンスデータ |
+
+### `google_groups_list_members`
+
+Google グループのすべてのメンバーを一覧表示する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+| `maxResults` | number | いいえ | 返す結果の最大数(1-200) |
+| `pageToken` | string | いいえ | ページネーション用のトークン |
+| `roles` | string | いいえ | ロールによるフィルタリング(カンマ区切り: OWNER, MANAGER, MEMBER) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIのレスポンスデータ |
+
+### `google_groups_get_member`
+
+Google グループ内の特定のメンバーの詳細を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+| `memberKey` | string | はい | メンバーのメールアドレスまたは一意のメンバーID |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIのレスポンスデータ |
+
+### `google_groups_add_member`
+
+Google グループに新しいメンバーを追加する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+| `email` | string | はい | 追加するメンバーのメールアドレス |
+| `role` | string | いいえ | メンバーのロール(MEMBER、MANAGER、またはOWNER)。デフォルトはMEMBER。 |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIレスポンスデータ |
+
+### `google_groups_remove_member`
+
+Google Groupからメンバーを削除する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+| `memberKey` | string | はい | 削除するメンバーのメールアドレスまたは一意のID |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIレスポンスデータ |
+
+### `google_groups_update_member`
+
+メンバーを更新する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+| `memberKey` | string | はい | メンバーのメールアドレスまたは一意のメンバーID |
+| `role` | string | はい | メンバーの新しい役割 \(MEMBER、MANAGER、またはOWNER\) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups APIレスポンスデータ |
+
+### `google_groups_has_member`
+
+ユーザーがGoogle Groupのメンバーかどうかを確認する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
+| `memberKey` | string | はい | 確認するメンバーのメールアドレスまたは一意のメンバーID |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `output` | json | Google グループ API レスポンスデータ |
+
+## 注意事項
+
+- カテゴリー: `tools`
+- タイプ: `google_groups`
diff --git a/apps/docs/content/docs/ja/tools/salesforce.mdx b/apps/docs/content/docs/ja/tools/salesforce.mdx
index 68732cfeb0..4b538d631f 100644
--- a/apps/docs/content/docs/ja/tools/salesforce.mdx
+++ b/apps/docs/content/docs/ja/tools/salesforce.mdx
@@ -135,283 +135,684 @@ Salesforce CRMからアカウントを削除する
### `salesforce_get_contacts`
+Salesforceから取引先責任者を取得 - IDが提供されている場合は単一の取引先責任者、そうでない場合はリスト
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `contactId` | string | いいえ | 取引先責任者ID(提供されている場合、単一の取引先責任者を返す) |
+| `limit` | string | いいえ | 結果の数(デフォルト:100、最大:2000)。リストクエリの場合のみ。 |
+| `fields` | string | いいえ | カンマ区切りのフィールド(例:"Id,FirstName,LastName,Email,Phone") |
+| `orderBy` | string | いいえ | 並べ替えるフィールド(例:"LastName ASC")。リストクエリの場合のみ。 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 取引先責任者データ |
### `salesforce_create_contact`
+Salesforce CRMに新しい取引先責任者を作成する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `lastName` | string | はい | 姓(必須) |
+| `firstName` | string | いいえ | 名 |
+| `email` | string | いいえ | メールアドレス |
+| `phone` | string | いいえ | 電話番号 |
+| `accountId` | string | いいえ | 関連付ける取引先ID |
+| `title` | string | いいえ | 説明なし |
+| `department` | string | いいえ | 部署 |
+| `mailingStreet` | string | いいえ | 郵送先住所 |
+| `mailingCity` | string | いいえ | 郵送先市区町村 |
+| `mailingState` | string | いいえ | 郵送先都道府県 |
+| `mailingPostalCode` | string | いいえ | 郵送先郵便番号 |
+| `mailingCountry` | string | いいえ | 郵送先国 |
+| `description` | string | いいえ | 取引先責任者の説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 作成された取引先責任者データ |
### `salesforce_update_contact`
+Salesforce CRMの既存の取引先責任者を更新する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `contactId` | string | はい | 更新する取引先責任者ID(必須) |
+| `lastName` | string | いいえ | 姓 |
+| `firstName` | string | いいえ | 名 |
+| `email` | string | いいえ | メールアドレス |
+| `phone` | string | いいえ | 電話番号 |
+| `accountId` | string | いいえ | 関連付けるアカウントID |
+| `title` | string | いいえ | 説明なし |
+| `department` | string | いいえ | 部署 |
+| `mailingStreet` | string | いいえ | 郵送先住所 |
+| `mailingCity` | string | いいえ | 郵送先市区町村 |
+| `mailingState` | string | いいえ | 郵送先都道府県 |
+| `mailingPostalCode` | string | いいえ | 郵送先郵便番号 |
+| `mailingCountry` | string | いいえ | 郵送先国 |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 更新された取引先責任者データ |
### `salesforce_delete_contact`
+Salesforce CRMから取引先責任者を削除する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `contactId` | string | はい | 削除する取引先責任者ID(必須) |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 削除された取引先責任者データ |
### `salesforce_get_leads`
+Salesforceからリードを取得する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `leadId` | string | いいえ | リードID(オプション) |
+| `limit` | string | いいえ | 最大結果数(デフォルト:100) |
+| `fields` | string | いいえ | カンマ区切りフィールド |
+| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | リードデータ |
### `salesforce_create_lead`
+新しいリードを作成する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `lastName` | string | はい | 姓(必須) |
+| `company` | string | はい | 会社名(必須) |
+| `firstName` | string | いいえ | 名 |
+| `email` | string | いいえ | 説明なし |
+| `phone` | string | いいえ | 説明なし |
+| `status` | string | いいえ | リードステータス |
+| `leadSource` | string | いいえ | リードソース |
+| `title` | string | いいえ | 説明なし |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 作成されたリード |
### `salesforce_update_lead`
+既存のリードを更新する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `leadId` | string | はい | リードID(必須) |
+| `lastName` | string | いいえ | 姓 |
+| `company` | string | いいえ | 説明なし |
+| `firstName` | string | いいえ | 名 |
+| `email` | string | いいえ | 説明なし |
+| `phone` | string | いいえ | 説明なし |
+| `status` | string | いいえ | リードステータス |
+| `leadSource` | string | いいえ | リードソース |
+| `title` | string | いいえ | 説明なし |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 更新されたリード |
### `salesforce_delete_lead`
+リードを削除する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `leadId` | string | はい | リードID(必須) |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 削除されたリード |
### `salesforce_get_opportunities`
+Salesforceから商談を取得する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `opportunityId` | string | いいえ | 商談ID(オプション) |
+| `limit` | string | いいえ | 最大結果数(デフォルト:100) |
+| `fields` | string | いいえ | カンマ区切りのフィールド |
+| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 商談データ |
### `salesforce_create_opportunity`
+新しい商談を作成する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `name` | string | はい | 商談名(必須) |
+| `stageName` | string | はい | ステージ名(必須) |
+| `closeDate` | string | はい | 完了予定日 YYYY-MM-DD(必須) |
+| `accountId` | string | いいえ | アカウントID |
+| `amount` | string | いいえ | 金額(数値) |
+| `probability` | string | いいえ | 確度(0-100) |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 作成された商談 |
### `salesforce_update_opportunity`
+既存の商談を更新する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `opportunityId` | string | はい | 商談ID(必須) |
+| `name` | string | いいえ | 商談名 |
+| `stageName` | string | いいえ | ステージ名 |
+| `closeDate` | string | いいえ | 完了予定日 YYYY-MM-DD |
+| `accountId` | string | いいえ | アカウントID |
+| `amount` | string | いいえ | 説明なし |
+| `probability` | string | いいえ | 確度(0-100) |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 更新された商談 |
### `salesforce_delete_opportunity`
+商談を削除する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `opportunityId` | string | はい | 商談ID(必須) |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 削除された商談 |
### `salesforce_get_cases`
+Salesforceからケースを取得する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `caseId` | string | いいえ | ケースID(任意) |
+| `limit` | string | いいえ | 最大結果数(デフォルト:100) |
+| `fields` | string | いいえ | カンマ区切りフィールド |
+| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `success` | boolean | 成功 |
+| `output` | object | ケースデータ |
### `salesforce_create_case`
+新しいケースを作成する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `subject` | string | はい | ケース件名(必須) |
+| `status` | string | いいえ | ステータス(例:新規、対応中、エスカレーション) |
+| `priority` | string | いいえ | 優先度(例:低、中、高) |
+| `origin` | string | いいえ | 発生源(例:電話、メール、ウェブ) |
+| `contactId` | string | いいえ | 取引先責任者ID |
+| `accountId` | string | いいえ | 取引先ID |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `success` | boolean | 成功 |
+| `output` | object | 作成されたケース |
### `salesforce_update_case`
+既存のケースを更新する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `caseId` | string | はい | ケースID(必須) |
+| `subject` | string | いいえ | ケース件名 |
+| `status` | string | いいえ | ステータス |
+| `priority` | string | いいえ | 優先度 |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 更新されたケース |
### `salesforce_delete_case`
+ケースを削除する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `caseId` | string | はい | ケースID(必須) |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 削除されたケース |
### `salesforce_get_tasks`
+Salesforceからタスクを取得する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `taskId` | string | いいえ | タスクID(オプション) |
+| `limit` | string | いいえ | 最大結果数(デフォルト:100) |
+| `fields` | string | いいえ | カンマ区切りのフィールド |
+| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | タスクデータ |
### `salesforce_create_task`
+新しいタスクを作成する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `subject` | string | はい | タスク件名(必須) |
+| `status` | string | いいえ | ステータス(例:未開始、進行中、完了) |
+| `priority` | string | いいえ | 優先度(例:低、普通、高) |
+| `activityDate` | string | いいえ | 期日 YYYY-MM-DD |
+| `whoId` | string | いいえ | 関連する取引先責任者/リードID |
+| `whatId` | string | いいえ | 関連する取引先/商談ID |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | 作成されたタスク |
### `salesforce_update_task`
+既存のタスクを更新する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `taskId` | string | はい | タスクID(必須) |
+| `subject` | string | いいえ | タスク件名 |
+| `status` | string | いいえ | ステータス |
+| `priority` | string | いいえ | 優先度 |
+| `activityDate` | string | いいえ | 期日 YYYY-MM-DD |
+| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `success` | boolean | 成功 |
+| `output` | object | 更新されたタスク |
### `salesforce_delete_task`
+タスクを削除する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `taskId` | string | はい | タスクID(必須) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功 |
+| `output` | object | 削除されたタスク |
+
+### `salesforce_list_reports`
+
+現在のユーザーがアクセスできるレポートのリストを取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `folderName` | string | いいえ | フォルダ名でフィルタリング |
+| `searchTerm` | string | いいえ | 名前でレポートをフィルタリングする検索語 |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功ステータス |
+| `output` | object | レポートデータ |
+
+### `salesforce_get_report`
+
+特定のレポートのメタデータと説明情報を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `reportId` | string | はい | レポートID(必須) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | レポートメタデータ |
+
+### `salesforce_run_report`
+
+レポートを実行して結果を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `reportId` | string | はい | レポートID(必須) |
+| `includeDetails` | string | いいえ | 詳細行を含める(true/false、デフォルト:true) |
+| `filters` | string | いいえ | 適用するレポートフィルターのJSON文字列 |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | レポート結果 |
+
+### `salesforce_list_report_types`
+
+利用可能なレポートタイプの一覧を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | レポートタイプデータ |
+
+### `salesforce_list_dashboards`
+
+現在のユーザーがアクセスできるダッシュボードの一覧を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `folderName` | string | いいえ | フォルダ名でフィルタリング |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | ダッシュボードデータ |
+
+### `salesforce_get_dashboard`
+
+特定のダッシュボードの詳細と結果を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `dashboardId` | string | はい | ダッシュボードID(必須) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | ダッシュボードデータ |
+
+### `salesforce_refresh_dashboard`
+
+最新データを取得するためにダッシュボードを更新する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `dashboardId` | string | はい | ダッシュボードID(必須) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | 更新されたダッシュボードデータ |
+
+### `salesforce_query`
+
+Salesforceからデータを取得するためのカスタムSOQLクエリを実行する
+
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `query` | string | はい | 実行するSOQLクエリ(例:SELECT Id, Name FROM Account LIMIT 10) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | クエリ結果 |
+
+### `salesforce_query_more`
+
+前回のクエリからnextRecordsUrlを使用して追加のクエリ結果を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `nextRecordsUrl` | string | はい | 前回のクエリレスポンスからのnextRecordsUrl |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | クエリ結果 |
+
+### `salesforce_describe_object`
+
+Salesforceオブジェクトのメタデータとフィールド情報を取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
+| `objectName` | string | はい | オブジェクトのAPI名(例:Account、Contact、Lead、Custom_Object__c) |
+
+#### 出力
+
+| パラメータ | 型 | 説明 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 操作成功ステータス |
+| `output` | object | オブジェクトメタデータ |
+
+### `salesforce_list_objects`
+
+利用可能なすべてのSalesforceオブジェクトのリストを取得する
+
+#### 入力
+
+| パラメータ | 型 | 必須 | 説明 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | いいえ | 説明なし |
+| `instanceUrl` | string | いいえ | 説明なし |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
-| `output` | json | 操作結果データ |
+| `output` | object | オブジェクトリスト |
## 注意事項
diff --git a/apps/docs/content/docs/zh/blocks/condition.mdx b/apps/docs/content/docs/zh/blocks/condition.mdx
index 14eb12730b..a66e1ac2bd 100644
--- a/apps/docs/content/docs/zh/blocks/condition.mdx
+++ b/apps/docs/content/docs/zh/blocks/condition.mdx
@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## 最佳实践
-- **正确排序条件**:将更具体的条件放在一般条件之前,以确保特定逻辑优先于回退逻辑
-- **包含默认条件**:添加一个兜底条件(`true`)作为最后一个条件,以处理未匹配的情况并防止工作流执行卡住
-- **保持表达式简单**:使用清晰、直观的布尔表达式以提高可读性并简化调试
-- **记录条件**:添加描述以解释每个条件的目的,从而提高团队协作和维护效率
+- **正确排列条件顺序**:将更具体的条件放在一般条件之前,以确保特定逻辑优先于回退逻辑
+- **在需要时使用 else 分支**:如果没有条件匹配且 else 分支未连接,工作流分支将优雅地结束。如果需要为未匹配的情况提供回退路径,请连接 else 分支
+- **保持表达式简单**:使用清晰、直观的布尔表达式以提高可读性和调试的便利性
+- **为条件添加文档说明**:添加描述以解释每个条件的目的,从而提高团队协作和维护效率
- **测试边界情况**:通过测试条件范围边界值,验证条件是否正确处理边界值
diff --git a/apps/docs/content/docs/zh/introduction/index.mdx b/apps/docs/content/docs/zh/introduction/index.mdx
index e1443b7953..2b396f51bf 100644
--- a/apps/docs/content/docs/zh/introduction/index.mdx
+++ b/apps/docs/content/docs/zh/introduction/index.mdx
@@ -72,7 +72,7 @@ Sim 提供了跨多个类别的 80 多种服务的原生集成:
-## AI 驱动的 Copilot
+## Copilot
**提问并获取指导**
Copilot 回答关于 Sim 的问题,解释您的工作流程,并提供改进建议。使用 `@` 符号引用工作流程、模块、文档、知识和日志,以获得上下文帮助。
diff --git a/apps/docs/content/docs/zh/knowledgebase/index.mdx b/apps/docs/content/docs/zh/knowledgebase/index.mdx
index e58905c22f..17c5bcf06c 100644
--- a/apps/docs/content/docs/zh/knowledgebase/index.mdx
+++ b/apps/docs/content/docs/zh/knowledgebase/index.mdx
@@ -1,5 +1,6 @@
---
-title: 知识库
+title: 概览
+description: 通过智能向量搜索和分块功能上传、处理并搜索您的文档
---
import { Video } from '@/components/ui/video'
diff --git a/apps/docs/content/docs/zh/self-hosting/docker.mdx b/apps/docs/content/docs/zh/self-hosting/docker.mdx
new file mode 100644
index 0000000000..353487bbde
--- /dev/null
+++ b/apps/docs/content/docs/zh/self-hosting/docker.mdx
@@ -0,0 +1,155 @@
+---
+title: Docker
+description: 使用 Docker Compose 部署 Sim Studio
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## 快速开始
+
+```bash
+# Clone and start
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+打开 [http://localhost:3000](http://localhost:3000)
+
+## 生产环境设置
+
+### 1. 配置环境
+
+```bash
+# Generate secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+```
+
+### 2. 启动服务
+
+```bash
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### 3. 设置 SSL
+
+
+
+Caddy 会自动处理 SSL 证书。
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+```
+
+创建 `/etc/caddy/Caddyfile`:
+
+```
+sim.yourdomain.com {
+ reverse_proxy localhost:3000
+
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}
+```
+
+```bash
+sudo systemctl restart caddy
+```
+
+
+
+
+```bash
+# Install
+sudo apt install nginx certbot python3-certbot-nginx -y
+
+# Create /etc/nginx/sites-available/sim
+server {
+ listen 80;
+ server_name sim.yourdomain.com;
+
+ location / {
+ proxy_pass http://127.0.0.1:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /socket.io/ {
+ proxy_pass http://127.0.0.1:3002;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
+
+# Enable and get certificate
+sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
+sudo certbot --nginx -d sim.yourdomain.com
+```
+
+
+
+
+## Ollama
+
+```bash
+# With GPU
+docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
+
+# CPU only
+docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
+```
+
+拉取其他模型:
+
+```bash
+docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
+```
+
+### 外部 Ollama
+
+如果 Ollama 在您的主机上运行(而不是在 Docker 中):
+
+```bash
+# macOS/Windows
+OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
+
+# Linux - use your host IP
+OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
+```
+
+
+ 在 Docker 内,`localhost` 指的是容器,而不是您的主机。请使用 `host.docker.internal` 或您的主机 IP。
+
+
+## 命令
+
+```bash
+# View logs
+docker compose -f docker-compose.prod.yml logs -f simstudio
+
+# Stop
+docker compose -f docker-compose.prod.yml down
+
+# Update
+docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
+
+# Backup database
+docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
+```
diff --git a/apps/docs/content/docs/zh/self-hosting/environment-variables.mdx b/apps/docs/content/docs/zh/self-hosting/environment-variables.mdx
new file mode 100644
index 0000000000..330e3b5b3f
--- /dev/null
+++ b/apps/docs/content/docs/zh/self-hosting/environment-variables.mdx
@@ -0,0 +1,87 @@
+---
+title: 环境变量
+description: Sim Studio 的配置参考
+---
+
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## 必需
+
+| 变量 | 描述 |
+|----------|-------------|
+| `DATABASE_URL` | PostgreSQL 连接字符串 |
+| `BETTER_AUTH_SECRET` | 认证密钥(32 个十六进制字符):`openssl rand -hex 32` |
+| `BETTER_AUTH_URL` | 您的应用程序 URL |
+| `ENCRYPTION_KEY` | 加密密钥(32 个十六进制字符):`openssl rand -hex 32` |
+| `INTERNAL_API_SECRET` | 内部 API 密钥(32 个十六进制字符):`openssl rand -hex 32` |
+| `NEXT_PUBLIC_APP_URL` | 公共应用程序 URL |
+| `NEXT_PUBLIC_SOCKET_URL` | WebSocket URL(默认值:`http://localhost:3002`) |
+
+## AI 提供商
+
+| 变量 | 提供商 |
+|----------|----------|
+| `OPENAI_API_KEY` | OpenAI |
+| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
+| `GEMINI_API_KEY_1` | Google Gemini |
+| `MISTRAL_API_KEY` | Mistral |
+| `OLLAMA_URL` | Ollama(默认值:`http://localhost:11434`) |
+
+
+ 为了负载均衡,请添加带有 `_1`、`_2`、`_3` 后缀的多个密钥(例如,`OPENAI_API_KEY_1`、`OPENAI_API_KEY_2`)。适用于 OpenAI、Anthropic 和 Gemini。
+
+
+
+ 在 Docker 中,使用 `OLLAMA_URL=http://host.docker.internal:11434` 作为主机机器的 Ollama。
+
+
+### Azure OpenAI
+
+| 变量 | 描述 |
+|----------|-------------|
+| `AZURE_OPENAI_API_KEY` | Azure OpenAI API 密钥 |
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI 端点 URL |
+| `AZURE_OPENAI_API_VERSION` | API 版本(例如,`2024-02-15-preview`) |
+
+### vLLM(自托管)
+
+| 变量 | 描述 |
+|----------|-------------|
+| `VLLM_BASE_URL` | vLLM 服务器 URL(例如,`http://localhost:8000/v1`) |
+| `VLLM_API_KEY` | vLLM 的可选 Bearer Token |
+
+## OAuth 提供商
+
+| 变量 | 描述 |
+|----------|-------------|
+| `GOOGLE_CLIENT_ID` | Google OAuth 客户端 ID |
+| `GOOGLE_CLIENT_SECRET` | Google OAuth 客户端密钥 |
+| `GITHUB_CLIENT_ID` | GitHub OAuth 客户端 ID |
+| `GITHUB_CLIENT_SECRET` | GitHub OAuth 客户端密钥 |
+
+## 可选
+
+| 变量 | 描述 |
+|----------|-------------|
+| `API_ENCRYPTION_KEY` | 加密存储的 API 密钥(32 个十六进制字符):`openssl rand -hex 32` |
+| `COPILOT_API_KEY` | 用于 copilot 功能的 API 密钥 |
+| `ADMIN_API_KEY` | 用于 GitOps 操作的管理员 API 密钥 |
+| `RESEND_API_KEY` | 用于通知的电子邮件服务 |
+| `ALLOWED_LOGIN_DOMAINS` | 限制注册到特定域(逗号分隔) |
+| `ALLOWED_LOGIN_EMAILS` | 限制注册到特定电子邮件(逗号分隔) |
+| `DISABLE_REGISTRATION` | 设置为 `true` 以禁用新用户注册 |
+
+## 示例 .env
+
+```bash
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=
+BETTER_AUTH_URL=https://sim.yourdomain.com
+ENCRYPTION_KEY=
+INTERNAL_API_SECRET=
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+OPENAI_API_KEY=sk-...
+```
+
+查看 `apps/sim/.env.example` 以获取所有选项。
diff --git a/apps/docs/content/docs/zh/self-hosting/index.mdx b/apps/docs/content/docs/zh/self-hosting/index.mdx
new file mode 100644
index 0000000000..0a66f0ad47
--- /dev/null
+++ b/apps/docs/content/docs/zh/self-hosting/index.mdx
@@ -0,0 +1,50 @@
+---
+title: 自托管
+description: 在您自己的基础设施上部署 Sim Studio
+---
+
+import { Card, Cards } from 'fumadocs-ui/components/card'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+使用 Docker 或 Kubernetes 在您自己的基础设施上部署 Sim Studio。
+
+## 要求
+
+| 资源 | 最低要求 | 推荐配置 |
+|----------|---------|-------------|
+| CPU | 2 核 | 4 核及以上 |
+| 内存 | 12 GB | 16 GB 及以上 |
+| 存储 | 20 GB SSD | 50 GB 及以上 SSD |
+| Docker | 20.10+ | 最新版本 |
+
+## 快速开始
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+docker compose -f docker-compose.prod.yml up -d
+```
+
+打开 [http://localhost:3000](http://localhost:3000)
+
+## 部署选项
+
+
+
+ 使用 Docker Compose 在任何服务器上部署
+
+
+ 使用 Helm 在 Kubernetes 集群上部署
+
+
+ Railway、DigitalOcean、AWS、Azure、GCP 指南
+
+
+
+## 架构
+
+| 组件 | 端口 | 描述 |
+|-----------|------|-------------|
+| simstudio | 3000 | 主应用程序 |
+| realtime | 3002 | WebSocket 服务器 |
+| db | 5432 | 带有 pgvector 的 PostgreSQL |
+| migrations | - | 数据库迁移(运行一次) |
diff --git a/apps/docs/content/docs/zh/self-hosting/kubernetes.mdx b/apps/docs/content/docs/zh/self-hosting/kubernetes.mdx
new file mode 100644
index 0000000000..69c88e967c
--- /dev/null
+++ b/apps/docs/content/docs/zh/self-hosting/kubernetes.mdx
@@ -0,0 +1,133 @@
+---
+title: Kubernetes
+description: 使用 Helm 部署 Sim Studio
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## 前置条件
+
+- Kubernetes 1.19+
+- Helm 3.0+
+- 支持 PV 提供程序
+
+## 安装
+
+```bash
+# Clone repo
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Generate secrets
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+
+# Install
+helm install sim ./helm/sim \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --namespace simstudio --create-namespace
+```
+
+## 云特定值
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-aws.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-azure.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+```bash
+helm install sim ./helm/sim \
+ --values ./helm/sim/examples/values-gcp.yaml \
+ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
+ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
+ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
+ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
+ --namespace simstudio --create-namespace
+```
+
+
+
+
+## 关键配置
+
+```yaml
+# Custom values.yaml
+app:
+ replicaCount: 2
+ env:
+ NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
+ OPENAI_API_KEY: "sk-..."
+
+postgresql:
+ persistence:
+ size: 50Gi
+
+ingress:
+ enabled: true
+ className: nginx
+ tls:
+ enabled: true
+ app:
+ host: sim.yourdomain.com
+```
+
+查看 `helm/sim/values.yaml` 了解所有选项。
+
+## 外部数据库
+
+```yaml
+postgresql:
+ enabled: false
+
+externalDatabase:
+ enabled: true
+ host: "your-db-host"
+ port: 5432
+ username: "postgres"
+ password: "your-password"
+ database: "simstudio"
+ sslMode: "require"
+```
+
+## 命令
+
+```bash
+# Port forward for local access
+kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
+
+# View logs
+kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
+
+# Upgrade
+helm upgrade sim ./helm/sim --namespace simstudio
+
+# Uninstall
+helm uninstall sim --namespace simstudio
+```
diff --git a/apps/docs/content/docs/zh/self-hosting/platforms.mdx b/apps/docs/content/docs/zh/self-hosting/platforms.mdx
new file mode 100644
index 0000000000..5165cc5259
--- /dev/null
+++ b/apps/docs/content/docs/zh/self-hosting/platforms.mdx
@@ -0,0 +1,124 @@
+---
+title: 云平台
+description: 在云平台上部署 Sim Studio
+---
+
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
+import { Callout } from 'fumadocs-ui/components/callout'
+
+## Railway
+
+一键部署并自动配置 PostgreSQL。
+
+[
+
+
+
+](https://railway.com/new/template/sim-studio)
+
+部署后,在 Railway 仪表板中添加环境变量:
+- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET`(自动生成)
+- `OPENAI_API_KEY` 或其他 AI 提供商密钥
+- 自定义域名:设置 → 网络
+
+## VPS 部署
+
+适用于 DigitalOcean、AWS EC2、Azure VMs 或任何 Linux 服务器:
+
+
+
+**推荐配置:** 16 GB RAM Droplet,Ubuntu 24.04
+
+```bash
+# Create Droplet via console, then SSH in
+ssh root@your-droplet-ip
+```
+
+
+
+**推荐配置:** t3.xlarge(16 GB RAM),Ubuntu 24.04
+
+```bash
+ssh -i your-key.pem ubuntu@your-ec2-ip
+```
+
+
+
+**推荐配置:** Standard_D4s_v3(16 GB RAM),Ubuntu 24.04
+
+```bash
+ssh azureuser@your-vm-ip
+```
+
+
+
+
+### 安装 Docker
+
+```bash
+# Install Docker (official method)
+curl -fsSL https://get.docker.com | sudo sh
+sudo usermod -aG docker $USER
+
+# Logout and reconnect, then verify
+docker --version
+```
+
+### 部署 Sim Studio
+
+```bash
+git clone https://github.com/simstudioai/sim.git && cd sim
+
+# Create .env with secrets
+cat > .env << EOF
+DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
+BETTER_AUTH_SECRET=$(openssl rand -hex 32)
+ENCRYPTION_KEY=$(openssl rand -hex 32)
+INTERNAL_API_SECRET=$(openssl rand -hex 32)
+NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
+BETTER_AUTH_URL=https://sim.yourdomain.com
+NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
+EOF
+
+# Start
+docker compose -f docker-compose.prod.yml up -d
+```
+
+### 使用 Caddy 配置 SSL
+
+```bash
+# Install Caddy
+sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
+sudo apt update && sudo apt install caddy
+
+# Configure (replace domain)
+echo 'sim.yourdomain.com {
+ reverse_proxy localhost:3000
+ handle /socket.io/* {
+ reverse_proxy localhost:3002
+ }
+}' | sudo tee /etc/caddy/Caddyfile
+
+sudo systemctl restart caddy
+```
+
+将您的域名的 DNS A 记录指向您的服务器 IP。
+
+## Kubernetes(EKS、AKS、GKE)
+
+有关在托管 Kubernetes 上使用 Helm 部署的详细信息,请参阅 [Kubernetes 指南](/self-hosting/kubernetes)。
+
+## 托管数据库(可选)
+
+在生产环境中,请使用托管的 PostgreSQL 服务:
+
+- **AWS RDS** / **Azure Database** / **Cloud SQL** - 启用 pgvector 扩展
+- **Supabase** / **Neon** - 已包含 pgvector
+
+在您的环境中设置 `DATABASE_URL`:
+
+```bash
+DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
+```
diff --git a/apps/docs/content/docs/zh/self-hosting/troubleshooting.mdx b/apps/docs/content/docs/zh/self-hosting/troubleshooting.mdx
new file mode 100644
index 0000000000..292231de2e
--- /dev/null
+++ b/apps/docs/content/docs/zh/self-hosting/troubleshooting.mdx
@@ -0,0 +1,113 @@
+---
+title: 故障排除
+description: 常见问题及解决方案
+---
+
+## 数据库连接失败
+
+```bash
+# Check database is running
+docker compose ps db
+
+# Test connection
+docker compose exec db psql -U postgres -c "SELECT 1"
+```
+
+验证 `DATABASE_URL` 格式:`postgresql://user:pass@host:5432/database`
+
+## Ollama 模型未显示
+
+在 Docker 中,`localhost` = 容器,而不是您的主机。
+
+```bash
+# For host-machine Ollama, use:
+OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
+OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
+```
+
+## WebSocket/实时功能无法正常工作
+
+1. 检查 `NEXT_PUBLIC_SOCKET_URL` 是否与您的域名匹配
+2. 验证实时服务是否正在运行:`docker compose ps realtime`
+3. 确保反向代理支持 WebSocket 升级(参见 [Docker 指南](/self-hosting/docker))
+
+## 502 错误网关
+
+```bash
+# Check app is running
+docker compose ps simstudio
+docker compose logs simstudio
+
+# Common causes: out of memory, database not ready
+```
+
+## 迁移错误
+
+```bash
+# View migration logs
+docker compose logs migrations
+
+# Run manually
+docker compose exec simstudio bun run db:migrate
+```
+
+## 找不到 pgvector
+
+使用正确的 PostgreSQL 镜像:
+
+```yaml
+image: pgvector/pgvector:pg17 # NOT postgres:17
+```
+
+## 证书错误 (CERT_HAS_EXPIRED)
+
+如果调用外部 API 时出现 SSL 证书错误:
+
+```bash
+# Update CA certificates in container
+docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
+
+# Or set in environment (not recommended for production)
+NODE_TLS_REJECT_UNAUTHORIZED=0
+```
+
+## 登录后出现空白页面
+
+1. 检查浏览器控制台是否有错误
+2. 验证 `NEXT_PUBLIC_APP_URL` 是否与您的实际域名匹配
+3. 清除浏览器的 Cookie 和本地存储
+4. 检查所有服务是否正在运行:`docker compose ps`
+
+## Windows 特定问题
+
+**Windows 上的 Turbopack 错误:**
+
+```bash
+# Use WSL2 for better compatibility
+wsl --install
+
+# Or disable Turbopack in package.json
+# Change "next dev --turbopack" to "next dev"
+```
+
+**行尾问题:**
+
+```bash
+# Configure git to use LF
+git config --global core.autocrlf input
+```
+
+## 查看日志
+
+```bash
+# All services
+docker compose logs -f
+
+# Specific service
+docker compose logs -f simstudio
+```
+
+## 获取帮助
+
+- [GitHub Issues](https://github.com/simstudioai/sim/issues)
+- [Discord](https://discord.gg/Hr4UWYEcTT)
diff --git a/apps/docs/content/docs/zh/tools/cursor.mdx b/apps/docs/content/docs/zh/tools/cursor.mdx
new file mode 100644
index 0000000000..29df46d5f0
--- /dev/null
+++ b/apps/docs/content/docs/zh/tools/cursor.mdx
@@ -0,0 +1,181 @@
+---
+title: Cursor
+description: 启动并管理 Cursor 云代理以处理 GitHub 仓库
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+{/* MANUAL-CONTENT-START:intro */}
+[Cursor](https://www.cursor.so/) 是一个 AI 集成开发环境(IDE)和基于云的平台,允许您启动和管理能够直接在您的 GitHub 仓库上工作的强大 AI 代理。Cursor 代理可以自动化开发任务,提高团队的生产力,并通过进行代码更改、响应自然语言指令以及维护其活动的对话历史与您协作。
+
+使用 Cursor,您可以:
+
+- **为代码库启动云代理**:即时创建在云端处理您仓库的 AI 代理
+- **使用自然语言委派编码任务**:通过书面指令、修改和澄清来指导代理
+- **监控进度和输出**:获取代理状态,查看详细结果,并检查当前或已完成的任务
+- **访问完整的对话历史**:审查所有提示和 AI 响应,以确保透明性和可审计性
+- **控制和管理代理生命周期**:列出活动代理,终止代理,并管理基于 API 的代理启动和后续操作
+
+在 Sim 中,Cursor 集成使您的代理和工作流能够以编程方式与 Cursor 云代理交互。这意味着您可以使用 Sim 来:
+
+- 列出所有云代理并浏览其当前状态(`cursor_list_agents`)
+- 获取任何代理的最新状态和输出(`cursor_get_agent`)
+- 查看任何编码代理的完整对话历史(`cursor_get_conversation`)
+- 为正在运行的代理添加后续指令或新提示
+- 根据需要管理和终止代理
+
+此集成帮助您将 Sim 代理的灵活智能与 Cursor 的强大开发自动化功能相结合,使您能够在项目中扩展 AI 驱动的开发。
+{/* MANUAL-CONTENT-END */}
+
+## 使用说明
+
+与 Cursor 云代理 API 交互,启动可以在您的 GitHub 仓库上工作的 AI 代理。支持启动代理、添加后续指令、检查状态、查看对话以及管理代理生命周期。
+
+## 工具
+
+### `cursor_list_agents`
+
+列出经过身份验证的用户的所有云代理,并支持可选的分页功能。
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | 是 | Cursor API 密钥 |
+| `limit` | number | 否 | 要返回的代理数量 \(默认值:20,最大值:100\) |
+| `cursor` | string | 否 | 上一个响应的分页游标 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `content` | string | 可读的代理列表 |
+| `metadata` | object | 代理列表元数据 |
+
+### `cursor_get_agent`
+
+检索云代理的当前状态和结果。
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | 是 | Cursor API 密钥 |
+| `agentId` | string | 是 | 云代理的唯一标识符 \(例如,bc_abc123\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `content` | string | 可读的代理详细信息 |
+| `metadata` | object | 代理元数据 |
+
+### `cursor_get_conversation`
+
+检索云代理的对话历史,包括所有用户提示和助手响应。
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | 是 | Cursor API 密钥 |
+| `agentId` | string | 是 | 云代理的唯一标识符 \(例如,bc_abc123\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `content` | string | 可读的对话历史 |
+| `metadata` | object | 对话元数据 |
+
+### `cursor_launch_agent`
+
+启动一个新的云代理,根据给定的指令处理 GitHub 仓库。
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | 是 | Cursor API 密钥 |
+| `repository` | string | 是 | GitHub 仓库 URL \(例如,https://github.com/your-org/your-repo\) |
+| `ref` | string | 否 | 要处理的分支、标签或提交 \(默认为默认分支\) |
+| `promptText` | string | 是 | 代理的指令文本 |
+| `promptImages` | string | 否 | 包含 base64 数据和尺寸的图像对象的 JSON 数组 |
+| `model` | string | 否 | 要使用的模型 \(留空以自动选择\) |
+| `branchName` | string | 否 | 代理使用的自定义分支名称 |
+| `autoCreatePr` | boolean | 否 | 当代理完成时自动创建 PR |
+| `openAsCursorGithubApp` | boolean | 否 | 以 Cursor GitHub App 的身份打开 PR |
+| `skipReviewerRequest` | boolean | 否 | 跳过在 PR 上请求审阅者 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `content` | string | 包含代理详细信息的成功消息 |
+| `metadata` | object | 启动结果的元数据 |
+
+### `cursor_add_followup`
+
+为现有的云代理添加后续指令。
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | 是 | Cursor API 密钥 |
+| `agentId` | string | 是 | 云代理的唯一标识符 \(例如,bc_abc123\) |
+| `followupPromptText` | string | 是 | 代理的后续指令文本 |
+| `promptImages` | string | 否 | 包含 base64 数据和尺寸的图像对象的 JSON 数组 \(最多 5 个\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `content` | string | 成功消息 |
+| `metadata` | object | 结果元数据 |
+
+### `cursor_stop_agent`
+
+停止运行中的云代理。这将暂停代理,但不会删除它。
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | 是 | Cursor API 密钥 |
+| `agentId` | string | 是 | 云代理的唯一标识符 \(例如:bc_abc123\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `content` | string | 成功消息 |
+| `metadata` | object | 结果元数据 |
+
+### `cursor_delete_agent`
+
+永久删除云代理。此操作无法撤销。
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `apiKey` | string | 是 | Cursor API 密钥 |
+| `agentId` | string | 是 | 云代理的唯一标识符 \(例如,bc_abc123\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `content` | string | 成功消息 |
+| `metadata` | object | 结果元数据 |
+
+## 注意事项
+
+- 类别:`tools`
+- 类型:`cursor`
diff --git a/apps/docs/content/docs/zh/tools/google_groups.mdx b/apps/docs/content/docs/zh/tools/google_groups.mdx
new file mode 100644
index 0000000000..4e61b6a5c6
--- /dev/null
+++ b/apps/docs/content/docs/zh/tools/google_groups.mdx
@@ -0,0 +1,217 @@
+---
+title: Google 群组
+description: 管理 Google Workspace 群组及其成员
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+## 使用说明
+
+连接到 Google Workspace,使用 Admin SDK Directory API 创建、更新和管理群组及其成员。
+
+## 工具
+
+### `google_groups_list_groups`
+
+列出 Google Workspace 域中的所有群组
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `customer` | string | 否 | 客户 ID 或 "my_customer" 表示已认证用户的域 |
+| `domain` | string | 否 | 用于筛选群组的域名 |
+| `maxResults` | number | 否 | 返回的最大结果数 \(1-200\) |
+| `pageToken` | string | 否 | 分页的令牌 |
+| `query` | string | 否 | 用于筛选群组的搜索查询 \(例如:"email:admin*"\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_get_group`
+
+通过电子邮件或群组 ID 获取特定 Google 群组的详细信息
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组电子邮件地址或唯一群组 ID |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_create_group`
+
+在域中创建一个新的 Google 群组
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `email` | string | 是 | 新组的电子邮件地址 \(例如:team@yourdomain.com\) |
+| `name` | string | 是 | 组的显示名称 |
+| `description` | string | 否 | 组的描述 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_update_group`
+
+更新现有的 Google 群组
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
+| `name` | string | 否 | 群组的新显示名称 |
+| `description` | string | 否 | 群组的新描述 |
+| `email` | string | 否 | 群组的新电子邮件地址 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_delete_group`
+
+删除 Google 群组
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 要删除的群组电子邮件地址或唯一群组 ID |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_list_members`
+
+列出 Google 群组的所有成员
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
+| `maxResults` | number | 否 | 返回的最大结果数 \(1-200\) |
+| `pageToken` | string | 否 | 分页的令牌 |
+| `roles` | string | 否 | 按角色筛选 \(逗号分隔:OWNER, MANAGER, MEMBER\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_get_member`
+
+获取 Google 群组中特定成员的详细信息
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
+| `memberKey` | string | 是 | 成员的电子邮件地址或唯一成员 ID |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_add_member`
+
+向 Google 群组添加新成员
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
+| `email` | string | 是 | 要添加成员的电子邮件地址 |
+| `role` | string | 否 | 成员的角色 \(MEMBER, MANAGER, 或 OWNER\)。默认为 MEMBER。 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_remove_member`
+
+从 Google 群组中移除成员
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
+| `memberKey` | string | 是 | 要移除的成员的电子邮件地址或唯一 ID |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_update_member`
+
+更新成员信息
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
+| `memberKey` | string | 是 | 成员的电子邮件地址或唯一成员 ID |
+| `role` | string | 是 | 成员的新角色 \(MEMBER, MANAGER, 或 OWNER\) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+### `google_groups_has_member`
+
+检查用户是否为 Google 群组的成员
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
+| `memberKey` | string | 是 | 要检查的成员的电子邮件地址或唯一成员 ID |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `output` | json | Google Groups API 响应数据 |
+
+## 注意事项
+
+- 类别:`tools`
+- 类型:`google_groups`
diff --git a/apps/docs/content/docs/zh/tools/salesforce.mdx b/apps/docs/content/docs/zh/tools/salesforce.mdx
index 6a11cf602e..747cb69d5c 100644
--- a/apps/docs/content/docs/zh/tools/salesforce.mdx
+++ b/apps/docs/content/docs/zh/tools/salesforce.mdx
@@ -135,285 +135,686 @@ Salesforce 工具非常适合需要简化销售、账户管理、潜在客户生
### `salesforce_get_contacts`
+从 Salesforce 获取联系人 - 如果提供了 ID,则返回单个联系人;如果未提供,则返回列表
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `contactId` | string | 否 | 联系人 ID(如果提供,则返回单个联系人) |
+| `limit` | string | 否 | 返回结果数量(默认:100,最大:2000)。仅适用于列表查询。 |
+| `fields` | string | 否 | 逗号分隔的字段(例如:"Id,FirstName,LastName,Email,Phone") |
+| `orderBy` | string | 否 | 排序字段(例如:"LastName ASC")。仅适用于列表查询。 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `output` | object | 联系人数据 |
### `salesforce_create_contact`
+在 Salesforce CRM 中创建新联系人
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `lastName` | string | 是 | 姓(必需) |
+| `firstName` | string | 否 | 名 |
+| `email` | string | 否 | 电子邮件地址 |
+| `phone` | string | 否 | 电话号码 |
+| `accountId` | string | 否 | 要关联的账户 ID |
+| `title` | string | 否 | 无描述 |
+| `department` | string | 否 | 部门 |
+| `mailingStreet` | string | 否 | 邮寄街道 |
+| `mailingCity` | string | 否 | 邮寄城市 |
+| `mailingState` | string | 否 | 邮寄州 |
+| `mailingPostalCode` | string | 否 | 邮寄邮政编码 |
+| `mailingCountry` | string | 否 | 邮寄国家 |
+| `description` | string | 否 | 联系人描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `output` | object | 创建的联系人数据 |
### `salesforce_update_contact`
+更新 Salesforce CRM 中的现有联系人
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `contactId` | string | 是 | 要更新的联系人 ID(必需) |
+| `lastName` | string | 否 | 姓 |
+| `firstName` | string | 否 | 名 |
+| `email` | string | 否 | 邮箱地址 |
+| `phone` | string | 否 | 电话号码 |
+| `accountId` | string | 否 | 要关联的账户 ID |
+| `title` | string | 否 | 无描述 |
+| `department` | string | 否 | 部门 |
+| `mailingStreet` | string | 否 | 邮寄街道 |
+| `mailingCity` | string | 否 | 邮寄城市 |
+| `mailingState` | string | 否 | 邮寄州 |
+| `mailingPostalCode` | string | 否 | 邮寄邮政编码 |
+| `mailingCountry` | string | 否 | 邮寄国家 |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `output` | object | 更新的联系人数据 |
### `salesforce_delete_contact`
+从 Salesforce CRM 中删除联系人
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `contactId` | string | 是 | 要删除的联系人 ID(必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `output` | object | 已删除的联系人数据 |
### `salesforce_get_leads`
+从 Salesforce 获取潜在客户
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `leadId` | string | 否 | 潜在客户 ID(可选) |
+| `limit` | string | 否 | 最大结果数(默认:100) |
+| `fields` | string | 否 | 逗号分隔的字段 |
+| `orderBy` | string | 否 | 排序字段 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功状态 |
+| `output` | object | 潜在客户数据 |
### `salesforce_create_lead`
+创建新潜在客户
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `lastName` | string | 是 | 姓(必需) |
+| `company` | string | 是 | 公司(必需) |
+| `firstName` | string | 否 | 名 |
+| `email` | string | 否 | 无描述 |
+| `phone` | string | 否 | 无描述 |
+| `status` | string | 否 | 潜在客户状态 |
+| `leadSource` | string | 否 | 潜在客户来源 |
+| `title` | string | 否 | 无描述 |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 创建的潜在客户 |
### `salesforce_update_lead`
+更新现有的潜在客户
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `leadId` | string | 是 | 潜在客户 ID(必需) |
+| `lastName` | string | 否 | 姓氏 |
+| `company` | string | 否 | 无描述 |
+| `firstName` | string | 否 | 名字 |
+| `email` | string | 否 | 无描述 |
+| `phone` | string | 否 | 无描述 |
+| `status` | string | 否 | 潜在客户状态 |
+| `leadSource` | string | 否 | 潜在客户来源 |
+| `title` | string | 否 | 无描述 |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 更新的潜在客户 |
### `salesforce_delete_lead`
+删除潜在客户
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `leadId` | string | 是 | 潜在客户 ID(必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 已删除的潜在客户 |
### `salesforce_get_opportunities`
+从 Salesforce 获取机会
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `opportunityId` | string | 否 | 机会 ID(可选) |
+| `limit` | string | 否 | 最大结果数(默认:100) |
+| `fields` | string | 否 | 逗号分隔的字段 |
+| `orderBy` | string | 否 | 排序字段 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 机会数据 |
### `salesforce_create_opportunity`
+创建新机会
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `name` | string | 是 | 机会名称(必需) |
+| `stageName` | string | 是 | 阶段名称(必需) |
+| `closeDate` | string | 是 | 关闭日期 YYYY-MM-DD(必需) |
+| `accountId` | string | 否 | 账户 ID |
+| `amount` | string | 否 | 金额(数字) |
+| `probability` | string | 否 | 概率(0-100) |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 创建的机会 |
### `salesforce_update_opportunity`
+更新现有的机会
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `opportunityId` | string | 是 | 机会 ID (必需) |
+| `name` | string | 否 | 机会名称 |
+| `stageName` | string | 否 | 阶段名称 |
+| `closeDate` | string | 否 | 关闭日期 YYYY-MM-DD |
+| `accountId` | string | 否 | 账户 ID |
+| `amount` | string | 否 | 无描述 |
+| `probability` | string | 否 | 概率(0-100) |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 更新的机会 |
### `salesforce_delete_opportunity`
+删除一个机会
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `opportunityId` | string | 是 | 机会 ID (必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 删除的机会 |
### `salesforce_get_cases`
+从 Salesforce 获取案例
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `caseId` | string | 否 | 案例 ID(可选) |
+| `limit` | string | 否 | 最大结果数(默认:100) |
+| `fields` | string | 否 | 逗号分隔的字段 |
+| `orderBy` | string | 否 | 排序字段 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 案例数据 |
### `salesforce_create_case`
+创建新案例
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `subject` | string | 是 | 案例主题(必需) |
+| `status` | string | 否 | 状态(例如:新建、处理中、已升级) |
+| `priority` | string | 否 | 优先级(例如:低、中、高) |
+| `origin` | string | 否 | 来源(例如:电话、电子邮件、网页) |
+| `contactId` | string | 否 | 联系人 ID |
+| `accountId` | string | 否 | 账户 ID |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 创建的案例 |
### `salesforce_update_case`
+更新现有案例
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `caseId` | string | 是 | 案例 ID(必需) |
+| `subject` | string | 否 | 案例主题 |
+| `status` | string | 否 | 状态 |
+| `priority` | string | 否 | 优先级 |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 更新的案例 |
### `salesforce_delete_case`
+删除案例
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `caseId` | string | 是 | 案例 ID(必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 已删除的案例 |
### `salesforce_get_tasks`
+从 Salesforce 获取任务
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `taskId` | string | 否 | 任务 ID(可选) |
+| `limit` | string | 否 | 最大结果数(默认:100) |
+| `fields` | string | 否 | 逗号分隔的字段 |
+| `orderBy` | string | 否 | 按字段排序 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 任务数据 |
### `salesforce_create_task`
+创建新任务
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `subject` | string | 是 | 任务主题(必需) |
+| `status` | string | 否 | 状态(例如:未开始、进行中、已完成) |
+| `priority` | string | 否 | 优先级(例如:低、普通、高) |
+| `activityDate` | string | 否 | 截止日期 YYYY-MM-DD |
+| `whoId` | string | 否 | 相关联系人/潜在客户 ID |
+| `whatId` | string | 否 | 相关账户/机会 ID |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 已创建的任务 |
### `salesforce_update_task`
+更新现有任务
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `taskId` | string | 是 | 任务 ID(必需) |
+| `subject` | string | 否 | 任务主题 |
+| `status` | string | 否 | 状态 |
+| `priority` | string | 否 | 优先级 |
+| `activityDate` | string | 否 | 截止日期 YYYY-MM-DD |
+| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 更新的任务 |
### `salesforce_delete_task`
+删除任务
+
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `taskId` | string | 是 | 任务 ID(必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
-| `success` | boolean | 操作成功状态 |
-| `output` | json | 操作结果数据 |
+| `success` | boolean | 成功 |
+| `output` | object | 已删除的任务 |
+
+### `salesforce_list_reports`
+
+获取当前用户可访问的报告列表
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `folderName` | string | 否 | 按文件夹名称筛选 |
+| `searchTerm` | string | 否 | 按名称筛选报告的搜索词 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 报告数据 |
+
+### `salesforce_get_report`
+
+获取特定报告的元数据和描述信息
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `reportId` | string | 是 | 报告 ID(必需) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 报告元数据 |
+
+### `salesforce_run_report`
+
+执行报告并检索结果
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `reportId` | string | 是 | 报告 ID(必需) |
+| `includeDetails` | string | 否 | 包含详细行(true/false,默认值:true) |
+| `filters` | string | 否 | 要应用的报告过滤器的 JSON 字符串 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 报告结果 |
+
+### `salesforce_list_report_types`
+
+获取可用报告类型的列表
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 报告类型数据 |
+
+### `salesforce_list_dashboards`
+
+获取当前用户可访问的仪表板列表
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `folderName` | string | 否 | 按文件夹名称过滤 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 仪表板数据 |
+
+### `salesforce_get_dashboard`
+
+获取特定仪表板的详细信息和结果
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `dashboardId` | string | 是 | 仪表板 ID (必需) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 仪表板数据 |
+
+### `salesforce_refresh_dashboard`
+
+刷新仪表板以获取最新数据
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `dashboardId` | string | 是 | 仪表板 ID (必需) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 刷新后的仪表板数据 |
+
+### `salesforce_query`
+
+执行自定义 SOQL 查询以从 Salesforce 检索数据
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `query` | string | 是 | 要执行的 SOQL 查询(例如:SELECT Id, Name FROM Account LIMIT 10) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 查询结果 |
+
+### `salesforce_query_more`
+
+使用上一次查询的 nextRecordsUrl 检索更多查询结果
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `nextRecordsUrl` | string | 是 | 上一次查询响应中的 nextRecordsUrl |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 查询结果 |
+
+### `salesforce_describe_object`
+
+获取 Salesforce 对象的元数据和字段信息
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+| `objectName` | string | 是 | 对象的 API 名称(例如:Account、Contact、Lead、Custom_Object__c) |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 对象元数据 |
+
+### `salesforce_list_objects`
+
+获取所有可用的 Salesforce 对象列表
+
+#### 输入
+
+| 参数 | 类型 | 必需 | 描述 |
+| --------- | ---- | -------- | ----------- |
+| `idToken` | string | 否 | 无描述 |
+| `instanceUrl` | string | 否 | 无描述 |
+
+#### 输出
+
+| 参数 | 类型 | 描述 |
+| --------- | ---- | ----------- |
+| `success` | boolean | 成功状态 |
+| `output` | object | 对象列表 |
## 注意事项
-- 类别: `tools`
-- 类型: `salesforce`
+- 类别:`tools`
+- 类型:`salesforce`
diff --git a/apps/docs/i18n.lock b/apps/docs/i18n.lock
index 682f71032f..770afc9cc5 100644
--- a/apps/docs/i18n.lock
+++ b/apps/docs/i18n.lock
@@ -4382,7 +4382,8 @@ checksums:
content/26: 2a887132ff0d5bbf448d743b7a25a7b3
content/27: eca2e960c67c9918ce37cf65abe1f114
3a760527a9e1a7b3b1301cbc07f3e738:
- meta/title: ae1419e3ad32c5504122516c7e7b9bf0
+ meta/title: 30c54e4dc4ce599b87d94be34a8617f5
+ meta/description: c82dc86de28f9579a59fca5632654382
content/0: 5bd8bded22a12230b63ee51c0a9b9bd5
content/1: 2cc3c41321402d1c2c615d2efbe5d567
content/2: f6ba7ccd55a61b252bbff3391eba64cd
@@ -4437,7 +4438,7 @@ checksums:
content/17: d869967bc0a87215c81a91aaaf6cc544
content/18: c4442ebf954ce44792673e999fa5ae57
content/19: 8a5bf876779f64c00bbfe410a76b876d
- content/20: 3baabe4ef51745247512b0226e9722da
+ content/20: cb438f299cfb632a6fd51ae7c4c8b4cf
content/21: aaa108acd920202f8fe7dc5234db1c19
content/22: 6539573d8f58d31b8195280f668ddbc8
content/23: d658c027072bdb8ac9d579e238635e37
@@ -5121,7 +5122,7 @@ checksums:
content/36: d0867474e7471bfc16e5c83fc76cee6d
content/37: 1e842e38ba5a718348720c584db2220a
content/38: b2a4a0c279f47d58a2456f25a1e1c6f9
- content/39: a81fff6a729e47d493c2bc07ebc5245d
+ content/39: 5118934bc313f3e604af5274d14c5035
ba44e50cb1c597d3c5756a3e2fb0ca6a:
meta/title: 01d9819514e27056dcc69463194b63d2
content/0: e4684b7201c2aed215c82606e9eaa293
@@ -6501,107 +6502,193 @@ checksums:
content/31: bcadfc362b69078beee0088e5936c98b
content/32: 14c0df89bf4b2633315bf0d2a8b3987d
content/33: 061400fbe790008a9b7bb4e568d25313
- content/34: 371d0e46b4bd2c23f559b8bc112f6955
- content/35: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/36: bcadfc362b69078beee0088e5936c98b
- content/37: 4a2e706133d34b12b398b9ab2a895001
- content/38: a4dfe4fd01ca877c873338d48d9ec162
- content/39: 371d0e46b4bd2c23f559b8bc112f6955
- content/40: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/41: bcadfc362b69078beee0088e5936c98b
- content/42: 4a2e706133d34b12b398b9ab2a895001
- content/43: f39442609c0ddf9da722270da2f75c96
- content/44: 371d0e46b4bd2c23f559b8bc112f6955
- content/45: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/46: bcadfc362b69078beee0088e5936c98b
- content/47: 4a2e706133d34b12b398b9ab2a895001
- content/48: 25c3374c18698301689fb849f7262afd
- content/49: 371d0e46b4bd2c23f559b8bc112f6955
- content/50: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/51: bcadfc362b69078beee0088e5936c98b
- content/52: 4a2e706133d34b12b398b9ab2a895001
- content/53: d70f7ca67dc0127139a74324e72d5cfa
- content/54: 371d0e46b4bd2c23f559b8bc112f6955
- content/55: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/56: bcadfc362b69078beee0088e5936c98b
- content/57: 4a2e706133d34b12b398b9ab2a895001
- content/58: e1841ff010b3addb5251d10bc9e2a4a8
+ content/34: 18cf0db1eebe0c999fbf15f1059634fe
+ content/35: 371d0e46b4bd2c23f559b8bc112f6955
+ content/36: a019ab5fbf31b53e720276c823cee5ad
+ content/37: bcadfc362b69078beee0088e5936c98b
+ content/38: b509768bb76919395b6fb80fd1e59831
+ content/39: a4dfe4fd01ca877c873338d48d9ec162
+ content/40: e8da1f9b212d48d11bd6b3a92fab35f0
+ content/41: 371d0e46b4bd2c23f559b8bc112f6955
+ content/42: 96950606febb7ff7d49a22c694ffbe8c
+ content/43: bcadfc362b69078beee0088e5936c98b
+ content/44: 59c08999f9c404330ebd8f8a7d21e1a1
+ content/45: f39442609c0ddf9da722270da2f75c96
+ content/46: 6d29ca777cebe3231492841fa17de96a
+ content/47: 371d0e46b4bd2c23f559b8bc112f6955
+ content/48: a235cd33346b35e001f6bc7b9f4718bb
+ content/49: bcadfc362b69078beee0088e5936c98b
+ content/50: a9096a341b00ce4f4891daaca2586d1c
+ content/51: 25c3374c18698301689fb849f7262afd
+ content/52: a2e198c8c422957b62334a545ba5fff0
+ content/53: 371d0e46b4bd2c23f559b8bc112f6955
+ content/54: 034bc531f82e732846e3c25f2cb4112c
+ content/55: bcadfc362b69078beee0088e5936c98b
+ content/56: 684a228552c8090b312d2ef80e185240
+ content/57: d70f7ca67dc0127139a74324e72d5cfa
+ content/58: 52f7e98edef0fd236e7d08aa59978878
content/59: 371d0e46b4bd2c23f559b8bc112f6955
- content/60: d71b6bb8e2dd6ce98101aec6a1dd77f2
+ content/60: ada3e2dca07a23757a99b573c891c2f8
content/61: bcadfc362b69078beee0088e5936c98b
- content/62: 4a2e706133d34b12b398b9ab2a895001
- content/63: ec33ee506164782e079a5c3d1fdb3805
- content/64: 371d0e46b4bd2c23f559b8bc112f6955
- content/65: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/66: bcadfc362b69078beee0088e5936c98b
- content/67: 4a2e706133d34b12b398b9ab2a895001
- content/68: 62763db101bf33507d3b9e2b15f9011a
- content/69: 371d0e46b4bd2c23f559b8bc112f6955
- content/70: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/71: bcadfc362b69078beee0088e5936c98b
- content/72: 4a2e706133d34b12b398b9ab2a895001
- content/73: 434790df3914ff2403a5e92260a49395
- content/74: 371d0e46b4bd2c23f559b8bc112f6955
- content/75: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/76: bcadfc362b69078beee0088e5936c98b
- content/77: 4a2e706133d34b12b398b9ab2a895001
- content/78: 412852540e461afcf7588e1e54c13d3d
- content/79: 371d0e46b4bd2c23f559b8bc112f6955
- content/80: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/81: bcadfc362b69078beee0088e5936c98b
- content/82: 4a2e706133d34b12b398b9ab2a895001
- content/83: 63972cc4d72025f47aa6b329cf5f8290
- content/84: 371d0e46b4bd2c23f559b8bc112f6955
- content/85: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/86: bcadfc362b69078beee0088e5936c98b
- content/87: 4a2e706133d34b12b398b9ab2a895001
- content/88: 383217087ecc00735f5f0e498c676395
+ content/62: f24b33eb878cea74220118388cbe66ae
+ content/63: e1841ff010b3addb5251d10bc9e2a4a8
+ content/64: 8ed96afe1fcde026359d12cb769eba4d
+ content/65: 371d0e46b4bd2c23f559b8bc112f6955
+ content/66: d7e72d73f55ff400aae5dd71d87289c1
+ content/67: bcadfc362b69078beee0088e5936c98b
+ content/68: 0b65a5803483e3c656eddf173fb9a504
+ content/69: ec33ee506164782e079a5c3d1fdb3805
+ content/70: aa1327893e96d2e120dd06b5db7fd25f
+ content/71: 371d0e46b4bd2c23f559b8bc112f6955
+ content/72: 23913a58dcb81ea42a93a588fba9ff6c
+ content/73: bcadfc362b69078beee0088e5936c98b
+ content/74: 58f51278335501d47230071b11663410
+ content/75: 62763db101bf33507d3b9e2b15f9011a
+ content/76: dc0bb7ba0e924f787ec7fb8c59b86007
+ content/77: 371d0e46b4bd2c23f559b8bc112f6955
+ content/78: ab7a75b7a9d50e3cc0bb0d49a8acafea
+ content/79: bcadfc362b69078beee0088e5936c98b
+ content/80: a16ff36b57debf98549f27e2938e5fa1
+ content/81: 434790df3914ff2403a5e92260a49395
+ content/82: 1dead520cd1a2fad55caa46db7879cc7
+ content/83: 371d0e46b4bd2c23f559b8bc112f6955
+ content/84: 79fb8fdcb85d78b252bdfcaa8a87b634
+ content/85: bcadfc362b69078beee0088e5936c98b
+ content/86: 7314f75c28332dea6fa37f56a1e104d2
+ content/87: 412852540e461afcf7588e1e54c13d3d
+ content/88: e7dd1afe7c48184ba6c4e179d47a1acb
content/89: 371d0e46b4bd2c23f559b8bc112f6955
- content/90: d71b6bb8e2dd6ce98101aec6a1dd77f2
+ content/90: 9a213157fe766442bb94d63a4913b3b0
content/91: bcadfc362b69078beee0088e5936c98b
- content/92: 4a2e706133d34b12b398b9ab2a895001
- content/93: c27f212932a83b343c35d3a2c7f332d2
- content/94: 371d0e46b4bd2c23f559b8bc112f6955
- content/95: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/96: bcadfc362b69078beee0088e5936c98b
- content/97: 4a2e706133d34b12b398b9ab2a895001
- content/98: 5ab51f77464b13249fa4ad9072090bd6
- content/99: 371d0e46b4bd2c23f559b8bc112f6955
- content/100: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/101: bcadfc362b69078beee0088e5936c98b
- content/102: 4a2e706133d34b12b398b9ab2a895001
- content/103: 47655d20e26f3c4f3bf7e67fbc669719
- content/104: 371d0e46b4bd2c23f559b8bc112f6955
- content/105: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/106: bcadfc362b69078beee0088e5936c98b
- content/107: 4a2e706133d34b12b398b9ab2a895001
- content/108: 2b3a08646ecf5bc2689081de63c89350
- content/109: 371d0e46b4bd2c23f559b8bc112f6955
- content/110: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/111: bcadfc362b69078beee0088e5936c98b
- content/112: 4a2e706133d34b12b398b9ab2a895001
- content/113: 5447fd9b1af11c698a8b153bd90cfc10
- content/114: 371d0e46b4bd2c23f559b8bc112f6955
- content/115: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/116: bcadfc362b69078beee0088e5936c98b
- content/117: 4a2e706133d34b12b398b9ab2a895001
- content/118: 3cee2834919b0519b79a9a5d466bfedd
+ content/92: f50627fa2c9fb75436c751d1e1d8ee46
+ content/93: 63972cc4d72025f47aa6b329cf5f8290
+ content/94: ece657a13e2b366dc71302cb86c9654c
+ content/95: 371d0e46b4bd2c23f559b8bc112f6955
+ content/96: 69e5202d94190d6c371fb65eadf190d2
+ content/97: bcadfc362b69078beee0088e5936c98b
+ content/98: acf742acec926b11a8b6ec7d0bb9b1ea
+ content/99: 383217087ecc00735f5f0e498c676395
+ content/100: 10ed4bfd54581eef5fe7143d05c2e18a
+ content/101: 371d0e46b4bd2c23f559b8bc112f6955
+ content/102: 9ec9d7a05190f09754ecbf7a95ea9e21
+ content/103: bcadfc362b69078beee0088e5936c98b
+ content/104: 1badbadbec8323f7d90b282b45d4fff2
+ content/105: c27f212932a83b343c35d3a2c7f332d2
+ content/106: 6608ca534d752592738c5959967b297e
+ content/107: 371d0e46b4bd2c23f559b8bc112f6955
+ content/108: 9289173ea3d3ebd9d861518c945ad11c
+ content/109: bcadfc362b69078beee0088e5936c98b
+ content/110: 21b270b30cc7117001d760b5fa164b3c
+ content/111: 5ab51f77464b13249fa4ad9072090bd6
+ content/112: dada37d12334ba3eb4d79ada3d4d1b8a
+ content/113: 371d0e46b4bd2c23f559b8bc112f6955
+ content/114: 94ffeb9cd631e63a8d938504a306dbae
+ content/115: bcadfc362b69078beee0088e5936c98b
+ content/116: e3466779eefb5a5444e91b241e8016ee
+ content/117: 47655d20e26f3c4f3bf7e67fbc669719
+ content/118: d6655a220249a9070bdc3f48defdc258
content/119: 371d0e46b4bd2c23f559b8bc112f6955
- content/120: d71b6bb8e2dd6ce98101aec6a1dd77f2
+ content/120: 322ef0d468ed677f30fd1a7ce5082f4f
content/121: bcadfc362b69078beee0088e5936c98b
- content/122: 4a2e706133d34b12b398b9ab2a895001
- content/123: 5a1e3da373873351a92d4986b649baf4
- content/124: 371d0e46b4bd2c23f559b8bc112f6955
- content/125: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/126: bcadfc362b69078beee0088e5936c98b
- content/127: 4a2e706133d34b12b398b9ab2a895001
- content/128: 420d56aab6760592bc5fc15eb17b0bb0
- content/129: 371d0e46b4bd2c23f559b8bc112f6955
- content/130: d71b6bb8e2dd6ce98101aec6a1dd77f2
- content/131: bcadfc362b69078beee0088e5936c98b
- content/132: 4a2e706133d34b12b398b9ab2a895001
- content/133: b3f310d5ef115bea5a8b75bf25d7ea9a
- content/134: a575a9bbb6adc3614e075cfe43a9dad9
+ content/122: 60b28498e0f6145a602a5eac695fd9e8
+ content/123: 2b3a08646ecf5bc2689081de63c89350
+ content/124: 19dbb4c197de0be572c705c714dacbcd
+ content/125: 371d0e46b4bd2c23f559b8bc112f6955
+ content/126: a34a46c7a128a774f3ef981ef21d7895
+ content/127: bcadfc362b69078beee0088e5936c98b
+ content/128: e813715d6bca3d0bff6248e630328cc6
+ content/129: 5447fd9b1af11c698a8b153bd90cfc10
+ content/130: 1ba2c46eef9787197d13ee2cab16ba1e
+ content/131: 371d0e46b4bd2c23f559b8bc112f6955
+ content/132: b354f23bae5f679e00314ad7b77f6dce
+ content/133: bcadfc362b69078beee0088e5936c98b
+ content/134: 48e0012b76ea32ebedc3a374b8f3ebf7
+ content/135: 3cee2834919b0519b79a9a5d466bfedd
+ content/136: 01aa5fc7468b1550c1220280b89670d4
+ content/137: 371d0e46b4bd2c23f559b8bc112f6955
+ content/138: 78353b7fbbfefed6d3ef282b1518e125
+ content/139: bcadfc362b69078beee0088e5936c98b
+ content/140: 671d2167bd997bf7695d4d4b8ed31e2a
+ content/141: 5a1e3da373873351a92d4986b649baf4
+ content/142: 426c2a21e6c81cd1af5c85c6210f5acf
+ content/143: 371d0e46b4bd2c23f559b8bc112f6955
+ content/144: c2121ca1e8478658f85585d421d72b11
+ content/145: bcadfc362b69078beee0088e5936c98b
+ content/146: 1faeb74ada0cc6f71c5d3164dd0e9000
+ content/147: 420d56aab6760592bc5fc15eb17b0bb0
+ content/148: 852944e64ebf66693bcc7c481e29a17e
+ content/149: 371d0e46b4bd2c23f559b8bc112f6955
+ content/150: fbc8013bcfc518c7c0fa5b7c0c2716c0
+ content/151: bcadfc362b69078beee0088e5936c98b
+ content/152: 4e3b4ca915120fb0d4447927f89cc291
+ content/153: 754d4f8d75b844298aed2b79618d9129
+ content/154: ab1ac4a2b2bf86dad67d8b672e293a4e
+ content/155: 371d0e46b4bd2c23f559b8bc112f6955
+ content/156: 7ddb8c77a9b218a78d3f65d72f5265c9
+ content/157: bcadfc362b69078beee0088e5936c98b
+ content/158: ecfca300796cf3b369e90fb35b63b144
+ content/159: 7f6a03dca71ef1eb9e156dfd4a1f77e1
+ content/160: 45d7dcf3663691a09d963c4a1cc9c23b
+ content/161: 371d0e46b4bd2c23f559b8bc112f6955
+ content/162: f86869bf5c27562c7b7bf1544b21ff4b
+ content/163: bcadfc362b69078beee0088e5936c98b
+ content/164: 714311657d473d9a0fd1a8e396e44b22
+ content/165: 2bfd4048d729ab4db20cb54a37a07290
+ content/166: c19eb093b847fe9ef9f9cac53961af68
+ content/167: 371d0e46b4bd2c23f559b8bc112f6955
+ content/168: b010aa231976354c2c2e5ee7184c9927
+ content/169: bcadfc362b69078beee0088e5936c98b
+ content/170: d429fd601fe01e36a23d0c29acdea72d
+ content/171: f1dafa02590e8218c18d95055eea9ea9
+ content/172: 705bb937841afd1f0e64305e3c3460ee
+ content/173: 371d0e46b4bd2c23f559b8bc112f6955
+ content/174: 634db00b2e0fcb753f20c338f34313ca
+ content/175: bcadfc362b69078beee0088e5936c98b
+ content/176: 11f50d87ddbc4ddbda712e38a41ab8eb
+ content/177: d3dabb641b05ca5eabc8f4d177dfc82e
+ content/178: bfa0d5b1ec884d8a42fef66ba23b6eeb
+ content/179: 371d0e46b4bd2c23f559b8bc112f6955
+ content/180: 97d3ca5b5e434d4dd7d7e29e5f72fdfe
+ content/181: bcadfc362b69078beee0088e5936c98b
+ content/182: ff5b4b13ad2cb1c22dceb123eb00e700
+ content/183: 537b72ddd0a26379d0b0ab3618ac4b99
+ content/184: 54fbe210b8272881394def0e68acb300
+ content/185: 371d0e46b4bd2c23f559b8bc112f6955
+ content/186: eca6e3991f28fee68a07e92c61064693
+ content/187: bcadfc362b69078beee0088e5936c98b
+ content/188: 9b85ce580c3e7ef2ed02eb5986bd982c
+ content/189: 249e3e1c1ceb337014c40c9587834504
+ content/190: f82155a066f1902bc5f47f228f7f2507
+ content/191: 371d0e46b4bd2c23f559b8bc112f6955
+ content/192: eca6e3991f28fee68a07e92c61064693
+ content/193: bcadfc362b69078beee0088e5936c98b
+ content/194: 9ac81ba2615e78d981313bd066c0e7ce
+ content/195: 6c1cb8caa156a1270ba95c255549058f
+ content/196: 1e2203c6b2398f8e4d26d3c6e2bc7f9a
+ content/197: 371d0e46b4bd2c23f559b8bc112f6955
+ content/198: 4d7b9733eb25896fd9164a896355e641
+ content/199: bcadfc362b69078beee0088e5936c98b
+ content/200: 2b816b45e5ac21d335405e9c2b17642b
+ content/201: 0dffa8d835db9ce2ee41a2a6e69efb31
+ content/202: 65aa571a8a832a683236f360971b3cd5
+ content/203: 371d0e46b4bd2c23f559b8bc112f6955
+ content/204: f7009d0b073656cc456e6671c229f94d
+ content/205: bcadfc362b69078beee0088e5936c98b
+ content/206: 2b816b45e5ac21d335405e9c2b17642b
+ content/207: 11168096d5edb1f826080453e2996d4a
+ content/208: 0951f95604bb649b50eb0ff05e93f6ae
+ content/209: 371d0e46b4bd2c23f559b8bc112f6955
+ content/210: 302c501a18a71ae74511dc4c997ed833
+ content/211: bcadfc362b69078beee0088e5936c98b
+ content/212: f938cb9d792d611a7801c9501fb3e314
+ content/213: 3422cb7d11dd2bb9091679288ad8dbce
+ content/214: be814a3fae2403d49fbd63cd09cbff87
+ content/215: 371d0e46b4bd2c23f559b8bc112f6955
+ content/216: 634db00b2e0fcb753f20c338f34313ca
+ content/217: bcadfc362b69078beee0088e5936c98b
+ content/218: 6e6bab9df38a8003940563ab7e009d43
+ content/219: b3f310d5ef115bea5a8b75bf25d7ea9a
+ content/220: a575a9bbb6adc3614e075cfe43a9dad9
6f170cd9c9fab731de5f71cf485eeef5:
meta/title: 341fcb2f99eb4527e898e7cc78eaedd8
meta/description: 75e5f7b311444862e09f872b1a3797d1
@@ -48857,3 +48944,291 @@ checksums:
content/51: f23cc6d685827f8880df368d65c9ee88
content/52: b3f310d5ef115bea5a8b75bf25d7ea9a
content/53: dce0795eb6bcefcb20aa65529826adab
+ 01c58bab23799551db5e78192e636ca4:
+ meta/title: 93ac99150c9bd9eaa51699942c068143
+ meta/description: 58537dba08c9c56fc7eab16e2a3cffcd
+ content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
+ content/1: 3badb4388552c1830eb8f023fa6560e0
+ content/2: 821e6394b0a953e2b0842b04ae8f3105
+ content/3: e87e6120d74ada777b70557384b046d7
+ content/4: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
+ content/5: a60f3195d4d499d3c96a3471fd3e6f6e
+ content/6: e9374911aee840916bfd750f3955dac7
+ content/7: 371d0e46b4bd2c23f559b8bc112f6955
+ content/8: ddf9f33ab00bc63ba73d935e9a9461bb
+ content/9: bcadfc362b69078beee0088e5936c98b
+ content/10: 83f29573676a9d1ccb33827ff578858a
+ content/11: 16b29a33d78bf2344e7b204ca98c0b82
+ content/12: 97b8063d8d1ada29666d9ec525694a93
+ content/13: 371d0e46b4bd2c23f559b8bc112f6955
+ content/14: 6e295e5dcf37ad9f7ee602652b93c5bf
+ content/15: bcadfc362b69078beee0088e5936c98b
+ content/16: 83f29573676a9d1ccb33827ff578858a
+ content/17: 20c596f9dbdfe0cfa2d8f5b60dc71514
+ content/18: 1448d524a996ed212e7dbdbe967339b0
+ content/19: 371d0e46b4bd2c23f559b8bc112f6955
+ content/20: 0c9e28a7cf7bcfee1fe692fbd502dbb4
+ content/21: bcadfc362b69078beee0088e5936c98b
+ content/22: 83f29573676a9d1ccb33827ff578858a
+ content/23: 24e966f4a7a7f97a355b749b74377e64
+ content/24: 968f985de62e1fe2ad21716d7c215fc3
+ content/25: 371d0e46b4bd2c23f559b8bc112f6955
+ content/26: fd89e6852579854ef98eef8a76a261c5
+ content/27: bcadfc362b69078beee0088e5936c98b
+ content/28: 83f29573676a9d1ccb33827ff578858a
+ content/29: 84bf7ab082392d81a1347dd4091df95c
+ content/30: e6d21b5e83b22fcee600f5863114f76b
+ content/31: 371d0e46b4bd2c23f559b8bc112f6955
+ content/32: 6357650b93cf00ec79d620f75336a063
+ content/33: bcadfc362b69078beee0088e5936c98b
+ content/34: 83f29573676a9d1ccb33827ff578858a
+ content/35: 9a047f1df87fa8980889528cd4a7e23e
+ content/36: 10a705b69f4de0cafb55b579a091ff3e
+ content/37: 371d0e46b4bd2c23f559b8bc112f6955
+ content/38: a5f1409dffad379396a370fc6ef0b784
+ content/39: bcadfc362b69078beee0088e5936c98b
+ content/40: 83f29573676a9d1ccb33827ff578858a
+ content/41: bc110e334defc4a0037ba2ef9fb75083
+ content/42: 741dfd7becb14378932cbad9bd3f1aaa
+ content/43: 371d0e46b4bd2c23f559b8bc112f6955
+ content/44: 5bf2c3d8e1f679dffa34a23728e09d3d
+ content/45: bcadfc362b69078beee0088e5936c98b
+ content/46: 83f29573676a9d1ccb33827ff578858a
+ content/47: 007ad7c8b597dd1638c1d730ae57c18b
+ content/48: 66114378d52751790fb30b1d4a49fb4b
+ content/49: 371d0e46b4bd2c23f559b8bc112f6955
+ content/50: 2f9f05b2bdf789cfda1791b0a274de0f
+ content/51: bcadfc362b69078beee0088e5936c98b
+ content/52: 83f29573676a9d1ccb33827ff578858a
+ content/53: b812f4c86dc86447c8aab2e6fc7ee77b
+ content/54: d368e64c4bc2884ac4af1c046d3043fc
+ content/55: 371d0e46b4bd2c23f559b8bc112f6955
+ content/56: 8d96a5aad7ba60cbbe6c4ab8b816c8f5
+ content/57: bcadfc362b69078beee0088e5936c98b
+ content/58: 83f29573676a9d1ccb33827ff578858a
+ content/59: bb444040039dcefd837703242f69b3f2
+ content/60: 2ca4d597cf85afe0c09441a1c55dc588
+ content/61: 371d0e46b4bd2c23f559b8bc112f6955
+ content/62: f90f9e7f20b3c4bf832a71e2dcbc1fe5
+ content/63: bcadfc362b69078beee0088e5936c98b
+ content/64: 83f29573676a9d1ccb33827ff578858a
+ content/65: cdbf6d4d4066c241de37637f23944a51
+ content/66: 1f1ad7c241c27cf4614ba7b0b67fb584
+ content/67: 371d0e46b4bd2c23f559b8bc112f6955
+ content/68: 705cf28994842a8a455ba3dcb61c3f11
+ content/69: bcadfc362b69078beee0088e5936c98b
+ content/70: 83f29573676a9d1ccb33827ff578858a
+ content/71: b3f310d5ef115bea5a8b75bf25d7ea9a
+ content/72: fe020be6c017a995d0355c99c6b034ec
+ 1b5c57a63b2d38e097b7f96b6e054db8:
+ meta/title: 6ac3805eb202cc0ba10ad61dc1ada045
+ meta/description: 9d15289c788bd388e5227cb563a740f8
+ content/0: 788f3a864cc39c0564d51e1d1cd9ff22
+ content/1: 9be86f736d43388837d7c3255cb3c4d7
+ content/2: a0ee15c76fc6046002638b95447f96ff
+ content/3: fa1ff96b560f555e4e3d8ab8dde2f48a
+ content/4: 49147be50168a5a9cd29f74dd0e5070c
+ content/5: 23d12cd73347011326dc510f5a64ebd1
+ content/6: 734332de1f2a5bf11a2592b719c364dd
+ content/7: 95250f345f24a86e24bb4a709e2e974f
+ content/8: e793c95422005f54a9cec21d6970d2c8
+ content/9: 05d7473426b6c16f74e7d198cfbb0cb7
+ content/10: 1b9cf53ffdbe9f66a832adb01b9adb7d
+ content/11: 49686bc01877cc2cd905c07856b60968
+ content/12: ee8f3e459896a02058984d223f84782c
+ content/13: 7e67336940094a9c4af1d33718425bbb
+ content/14: 4079240d26f493561febcc0d2424a762
+ content/15: b792a368c791d2a4057053cacdce118c
+ content/16: 6e96fbce23fe7557a17e9ef86a1281d5
+ content/17: 55e329c2041769a139ad41bb1a5f330d
+ content/18: 50c787548872a04fd1284e031435abc1
+ content/19: 1e66bd6d20fe0e2dcf92f6d0f5368747
+ content/20: 1144109c71063d3ff843c8d4c9e5b152
+ content/21: 1d03eb3c81f0ec3b7d180d86bba23ca1
+ content/22: 9727c72124aebb118edec2d78b2b262d
+ content/23: 1f217cf2f2ea40bf0b288371646c4b64
+ content/24: 4264d1f59e6bccb77c960392dd14c9c7
+ content/25: 9dd663b7aaa0e1aa54acfb5c049b5a55
+ content/26: 0b58eed72d57c695813a8ab2d4e38a43
+ content/27: 1e733832735cc568035073b340ca0f4e
+ content/28: 0468f49bf9ba3fc25037ed47c84502ef
+ 5b14b034c1063c2e7d408c6cadba9a1e:
+ meta/title: a2d9323f87fb0028f39dfa4e0a4d7d71
+ meta/description: 9cbe42b3ae37e29953900095a016c90c
+ content/0: f0b49646269c72e3fe82ab242d31a5c7
+ content/1: d5ac1176896f40c95ee6a9dbb0f04e60
+ content/2: 437859c58d04ea7fe3f7c17ff07f651c
+ content/3: c7315dc914f85a8e7be1553e0458fdda
+ content/4: 1352ef721cad0d6ba681bec14478a120
+ content/5: ba8058adffeed2edded227ade405b3d6
+ content/6: cc0443e0cd2d1957adbc5ec41f170c81
+ content/7: 24f8cc625d382d32b9322ec76916d45d
+ content/8: 942e3d5cbc20729b09a5bb19681b7601
+ content/9: 2992441e06f8c7759773e82e93175cd1
+ content/10: 44194a2283027a76dba38f110adc41ad
+ content/11: 6d9288feed6ba2a6d269e161c4a8bb19
+ content/12: 8e6335029427810edaa8f328852b7fbe
+ content/13: 676041ae8ea3d298396e956e2a8c976f
+ content/14: 1b2df0a6fe68827b69139eef5d37bde9
+ content/15: 3304a33dfb626c6e2267c062e8956a9d
+ content/16: 46babdd66f738104252f4d4aed6d7263
+ content/17: 79d5a9ec463aa51c206c437c84413a73
+ content/18: e33326c95467278a9f4f959bf29a7ea7
+ content/19: 25f161b9292300600117ef6f752ede94
+ content/20: 0dd2c95befe432a96884cbd329ba6614
+ content/21: d79e98f5ea058b22817a2815a5204c2f
+ content/22: 5530aae43311704e87cbd7356f652671
+ content/23: 77a8b64f9daee5eb35869a722582af9c
+ content/24: 26375dab1609c61de13bb061f3474c95
+ content/25: 618d7c3aeb14efcbf76ee9fdb0a9cb5d
+ content/26: c0467f69ad04c70f4c5bf7f514150f0a
+ content/27: 4b806fdc34160e17187a83a78e569c8a
+ content/28: bc385d2332fc9efb9d21755f8704010b
+ content/29: 373512a6210dcae033357e1028b90982
+ 729af5a9a28e14b58b4111da6b0cf13c:
+ meta/title: 9a975fb182899afd051bbf56347dd3b3
+ meta/description: 650a17c852f56056e62fba9adede01bc
+ content/0: f0b49646269c72e3fe82ab242d31a5c7
+ content/1: 48361e78de327edccd0c2d8c6c4a5494
+ content/2: 9f533c3891128fe351189c94f6e495f5
+ content/3: 391128dee61b5d0d43eba88567aaef42
+ content/4: 07022204d43750c73c0753d75683b16f
+ content/5: 3ea053b7a26239743b6de62a32cd7a46
+ content/6: 23fbbe701b60dfbd781f44ca2c5dee8b
+ content/7: a23cd40dcc0fb6090eeb9db009ec391e
+ content/8: a71f699ec09f09214f0798a4ca775fca
+ content/9: c867eb1641a114009759c5e9f06c31c5
+ content/10: 6f4f745603fc23f24eae43e367735da3
+ content/11: 79dbacaf848d294223c6f1cd11fd91c3
+ content/12: 3304a33dfb626c6e2267c062e8956a9d
+ content/13: 051222c54b5add12c2048c57840e25e0
+ content/14: 7d576599d17b8ff76fc3eb51bcfb5584
+ content/15: 7cfde5ca8164b52afe12074a95b6a21f
+ content/16: 8908a88d73243c63da438ce43ed49870
+ content/17: 61372fd9711dfe5eec9e3799a7c90a2b
+ content/18: 746bc0998b79e81e1912e049a05865d3
+ content/19: 6a8d5f97b70a4555b499868575f11141
+ fd6f0d1fa41bbacc06386340625ff1aa:
+ meta/title: 58e9ce12b9b0e0f84f98a118dd3da37b
+ meta/description: 6aa21d9bb5dd3152b2cd6de8b7775e0d
+ content/0: 3a9daa61782f1fc39bef9469f491d27d
+ content/1: ac754b22b6a1ed9e6926ae4081498b4f
+ content/2: d62c9575cc66feec7589fba95c9f7aee
+ content/3: c0f38deb15a6623bff04226b4783af98
+ content/4: 56ea06288e338a3c329d80a0f845b4a0
+ content/5: 1d183e4b16ea089353690b86ee5d9123
+ content/6: 6480198c8935f30f57cec7f1f761a97d
+ content/7: 3cb16053db18d8997d72e38b8e336438
+ content/8: 0fa272de829c5b8672a44f6a9f87bd99
+ content/9: a3c2e315559c0df45b4ca39a9a02236f
+ content/10: 7c1e364f142a0f32c546b7129bd18afa
+ 93422a94ee0e64a54dd84ce24b99efcb:
+ meta/title: 538ccbbb5bae1e2b53c526e8e06feef3
+ meta/description: 32cd350ff688ff53a1c29518531c003e
+ content/0: 232be69c8f3053a40f695f9c9dcb3f2e
+ content/1: 9a97f1047f7127c9caa6777d78893113
+ content/2: 811be6985fdcb266ed9092ad2b1812e3
+ content/3: a341918411b2287ced6505929d64d80f
+ content/4: 022680615d8fba85da1779987de58ffe
+ content/5: c3bad935a189da8bf2b92b505e4d7dde
+ content/6: b19c0618fb41641a515afeb5ffc2ebf6
+ content/7: 95a511d4bf1ee9ab32758ae877389e81
+ content/8: 8db40d50eb73a860c1378e83c777a7a9
+ content/9: 6fc08be0705b3bc0d210e43730c3354b
+ content/10: 704c33c06da3370c9726df90b4db11eb
+ content/11: c0561ecff29648963a26914cf51a1ccf
+ content/12: bc941a7212c82f7952a980b8cdff173e
+ content/13: 6a25e03bdd4286e44f269b04276b10de
+ content/14: 4fff00e8bd2dd9613576c3d47279b3aa
+ content/15: d25efcec9572e48cc3f4434bf9b7dd9a
+ content/16: 7b7ce6b9f12b4044d6e64e9ffefb3601
+ content/17: 995ae47689f0c1f6fa8dc8190e106b38
+ fbb88b12cbab45a5fa14ba520d197fb3:
+ meta/title: 0de4d47501a7bb79ab94f9da307dc6fc
+ meta/description: 6367e2f210e1bcf2007e5fa79ea0e6b9
+ content/0: f0b49646269c72e3fe82ab242d31a5c7
+ content/1: 56ea06288e338a3c329d80a0f845b4a0
+ content/2: cf4f06554ad7f32852a1b4b93617cb01
+ content/3: 6480198c8935f30f57cec7f1f761a97d
+ content/4: d431b26b4ceb2cf0609b411d10a05d3a
+ content/5: efb59989d3e662dbf0b28fa00e4c2ae6
+ content/6: c5e556fb0b6ed41712484f3e961c5544
+ content/7: 100aefe3f9f07261c7769c39b82dffb1
+ content/8: 4f6ba176ad2f6ccce074889450706363
+ content/9: 84a1da3d7d5fe492f9eec281ab8b384e
+ content/10: c9a712b04a3d268c0906c74338b6ba4e
+ content/11: 86c3411ef57bcac05a9760d903acd84a
+ content/12: 5520363779134f65b88a06c55fc8cdcc
+ content/13: 51a33ae79443b6237c2ca32ad84d1379
+ content/14: a2004e35fe83b1fd8848d1eb6e4bf94c
+ content/15: f71dcc42ca7c2a65544e1d4c6e552e76
+ content/16: 616b46c575631095adcaed620829dcac
+ content/17: 3304a33dfb626c6e2267c062e8956a9d
+ content/18: 8290e53f98b03dbbca8f0c5673291607
+ content/19: de7cfb42a8774fdf07cee29f4b4b06c4
+ content/20: ea29416edca1c463b0fe50ccd95dc2ac
+ content/21: 370ac33cf8edc549eb3d4b3dd767121b
+ content/22: ec531368a8d1acf9978aa6266d454142
+ content/23: b49fc056af3cd1853d9d31854b1d515f
+ content/24: 5f8c8cc500a8362fb14fd816af7f52b2
+ content/25: 38f65a5fcf96ad88df123dc0ae4e6556
+ content/26: 746bc0998b79e81e1912e049a05865d3
+ content/27: 755eb21269b2c83655633ce1984f3558
+ 20fd655a4cdb94d147d82d1611c06fe4:
+ meta/title: 7fd4a31b926070c583e409649b88694a
+ meta/description: 6f0a0fc3324606b469e022a8fa4e8ff8
+ content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
+ content/1: 75591c909ad874c4733b4c66568a53d8
+ content/2: 531114f37d53ef0bf5b675f38b925230
+ content/3: b34ccdd230dee482b3248637160adbee
+ content/4: ddc6f320044838c1f703e845b4abdaf8
+ content/5: 672cb353d026fd23304d45ea8c4a0d4a
+ content/6: beb43d40f5f9562cbea54b50362f0d4e
+ content/7: cb788116b070ca702e05dadc46540579
+ content/8: 821e6394b0a953e2b0842b04ae8f3105
+ content/9: 010470c994fc10eaf5a369342533ed0d
+ content/10: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
+ content/11: 9085af1052bbd1b4a6a3e3e593fdc1e3
+ content/12: ceace0cdd474e50130ec1fc069399350
+ content/13: 371d0e46b4bd2c23f559b8bc112f6955
+ content/14: d097acee816b07cb65bad33fec5a83a5
+ content/15: bcadfc362b69078beee0088e5936c98b
+ content/16: 5c96636264d7f57ddac41000f6384ba4
+ content/17: eab48ffdef9c89f3a3df243d04b94b81
+ content/18: a6a0e6b8cf6c28587bee3a06e09c6af3
+ content/19: 371d0e46b4bd2c23f559b8bc112f6955
+ content/20: 9e0c0ba2b0df2454be80c83108047631
+ content/21: bcadfc362b69078beee0088e5936c98b
+ content/22: 353db07a018a23f8a3b6acbd2565ad09
+ content/23: c5e41b2d64f87372d5eb3c2c5071dcaa
+ content/24: 97885674f5c5f1325a53513bf07602b4
+ content/25: 371d0e46b4bd2c23f559b8bc112f6955
+ content/26: 9e0c0ba2b0df2454be80c83108047631
+ content/27: bcadfc362b69078beee0088e5936c98b
+ content/28: 6ede6c73bbbc3df5ac212c4c2368959e
+ content/29: 6b44b3e0ce956141e531afd7e41e6b1a
+ content/30: 1b9eb7896b93f0565f90f6f15218ab94
+ content/31: 371d0e46b4bd2c23f559b8bc112f6955
+ content/32: 50dffb7983dcba194c867e70e786e2d5
+ content/33: bcadfc362b69078beee0088e5936c98b
+ content/34: c54b98c7e00b649b379fbc226171e0a9
+ content/35: d1563f58482787d79fbea1e64cbc2b41
+ content/36: e01b0952cd4c9511030a4fc719549d89
+ content/37: 371d0e46b4bd2c23f559b8bc112f6955
+ content/38: c144056cae63b46734bb4d75b7fdace4
+ content/39: bcadfc362b69078beee0088e5936c98b
+ content/40: bcb37c2bc190c3c12e5c721d376909f7
+ content/41: 33e09b436c7bed97b18a331521cf9791
+ content/42: 8c9ee32d66137a7e2ab3997c6c01ee24
+ content/43: 371d0e46b4bd2c23f559b8bc112f6955
+ content/44: 9e0c0ba2b0df2454be80c83108047631
+ content/45: bcadfc362b69078beee0088e5936c98b
+ content/46: bcb37c2bc190c3c12e5c721d376909f7
+ content/47: 034ddae6d1cf3f0e7c3837ea13daf3bc
+ content/48: 55795caec4263261082c7f4dc5af0182
+ content/49: 371d0e46b4bd2c23f559b8bc112f6955
+ content/50: 9e0c0ba2b0df2454be80c83108047631
+ content/51: bcadfc362b69078beee0088e5936c98b
+ content/52: bcb37c2bc190c3c12e5c721d376909f7
+ content/53: b3f310d5ef115bea5a8b75bf25d7ea9a
+ content/54: dafdefed393d3f02fe15ef832c922450
diff --git a/apps/sim/app/api/auth/oauth/utils.test.ts b/apps/sim/app/api/auth/oauth/utils.test.ts
index c76ed05bff..af55886267 100644
--- a/apps/sim/app/api/auth/oauth/utils.test.ts
+++ b/apps/sim/app/api/auth/oauth/utils.test.ts
@@ -5,9 +5,15 @@
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-describe('OAuth Utils', () => {
- const mockSession = { user: { id: 'test-user-id' } }
- const mockDb = {
+const mockSession = { user: { id: 'test-user-id' } }
+const mockGetSession = vi.fn()
+
+vi.mock('@/lib/auth', () => ({
+ getSession: () => mockGetSession(),
+}))
+
+vi.mock('@sim/db', () => ({
+ db: {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
@@ -15,33 +21,41 @@ describe('OAuth Utils', () => {
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockReturnThis(),
- }
- const mockRefreshOAuthToken = vi.fn()
- const mockLogger = {
+ },
+}))
+
+vi.mock('@/lib/oauth/oauth', () => ({
+ refreshOAuthToken: vi.fn(),
+}))
+
+vi.mock('@/lib/logs/console/logger', () => ({
+ createLogger: vi.fn().mockReturnValue({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
- }
+ }),
+}))
+
+import { db } from '@sim/db'
+import { createLogger } from '@/lib/logs/console/logger'
+import { refreshOAuthToken } from '@/lib/oauth/oauth'
+import {
+ getCredential,
+ getUserId,
+ refreshAccessTokenIfNeeded,
+ refreshTokenIfNeeded,
+} from '@/app/api/auth/oauth/utils'
+
+const mockDb = db as any
+const mockRefreshOAuthToken = refreshOAuthToken as any
+const mockLogger = (createLogger as any)()
+describe('OAuth Utils', () => {
beforeEach(() => {
- vi.resetModules()
-
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue(mockSession),
- }))
-
- vi.doMock('@sim/db', () => ({
- db: mockDb,
- }))
-
- vi.doMock('@/lib/oauth/oauth', () => ({
- refreshOAuthToken: mockRefreshOAuthToken,
- }))
-
- vi.doMock('@/lib/logs/console/logger', () => ({
- createLogger: vi.fn().mockReturnValue(mockLogger),
- }))
+ vi.clearAllMocks()
+ mockGetSession.mockResolvedValue(mockSession)
+ mockDb.limit.mockReturnValue([])
})
afterEach(() => {
@@ -50,8 +64,6 @@ describe('OAuth Utils', () => {
describe('getUserId', () => {
it('should get user ID from session when no workflowId is provided', async () => {
- const { getUserId } = await import('@/app/api/auth/oauth/utils')
-
const userId = await getUserId('request-id')
expect(userId).toBe('test-user-id')
@@ -60,8 +72,6 @@ describe('OAuth Utils', () => {
it('should get user ID from workflow when workflowId is provided', async () => {
mockDb.limit.mockReturnValueOnce([{ userId: 'workflow-owner-id' }])
- const { getUserId } = await import('@/app/api/auth/oauth/utils')
-
const userId = await getUserId('request-id', 'workflow-id')
expect(mockDb.select).toHaveBeenCalled()
@@ -72,11 +82,7 @@ describe('OAuth Utils', () => {
})
it('should return undefined if no session is found', async () => {
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue(null),
- }))
-
- const { getUserId } = await import('@/app/api/auth/oauth/utils')
+ mockGetSession.mockResolvedValueOnce(null)
const userId = await getUserId('request-id')
@@ -87,8 +93,6 @@ describe('OAuth Utils', () => {
it('should return undefined if workflow is not found', async () => {
mockDb.limit.mockReturnValueOnce([])
- const { getUserId } = await import('@/app/api/auth/oauth/utils')
-
const userId = await getUserId('request-id', 'nonexistent-workflow-id')
expect(userId).toBeUndefined()
@@ -101,8 +105,6 @@ describe('OAuth Utils', () => {
const mockCredential = { id: 'credential-id', userId: 'test-user-id' }
mockDb.limit.mockReturnValueOnce([mockCredential])
- const { getCredential } = await import('@/app/api/auth/oauth/utils')
-
const credential = await getCredential('request-id', 'credential-id', 'test-user-id')
expect(mockDb.select).toHaveBeenCalled()
@@ -116,8 +118,6 @@ describe('OAuth Utils', () => {
it('should return undefined when credential is not found', async () => {
mockDb.limit.mockReturnValueOnce([])
- const { getCredential } = await import('@/app/api/auth/oauth/utils')
-
const credential = await getCredential('request-id', 'nonexistent-id', 'test-user-id')
expect(credential).toBeUndefined()
@@ -135,8 +135,6 @@ describe('OAuth Utils', () => {
providerId: 'google',
}
- const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
expect(mockRefreshOAuthToken).not.toHaveBeenCalled()
@@ -159,8 +157,6 @@ describe('OAuth Utils', () => {
refreshToken: 'new-refresh-token',
})
- const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
@@ -183,8 +179,6 @@ describe('OAuth Utils', () => {
mockRefreshOAuthToken.mockResolvedValueOnce(null)
- const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
await expect(
refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
).rejects.toThrow('Failed to refresh token')
@@ -201,8 +195,6 @@ describe('OAuth Utils', () => {
providerId: 'google',
}
- const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
expect(mockRefreshOAuthToken).not.toHaveBeenCalled()
@@ -222,8 +214,6 @@ describe('OAuth Utils', () => {
}
mockDb.limit.mockReturnValueOnce([mockCredential])
- const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
expect(mockRefreshOAuthToken).not.toHaveBeenCalled()
@@ -247,8 +237,6 @@ describe('OAuth Utils', () => {
refreshToken: 'new-refresh-token',
})
- const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
@@ -260,8 +248,6 @@ describe('OAuth Utils', () => {
it('should return null if credential not found', async () => {
mockDb.limit.mockReturnValueOnce([])
- const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
const token = await refreshAccessTokenIfNeeded('nonexistent-id', 'test-user-id', 'request-id')
expect(token).toBeNull()
@@ -281,8 +267,6 @@ describe('OAuth Utils', () => {
mockRefreshOAuthToken.mockResolvedValueOnce(null)
- const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
-
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
expect(token).toBeNull()
diff --git a/apps/sim/app/api/billing/credits/route.ts b/apps/sim/app/api/billing/credits/route.ts
new file mode 100644
index 0000000000..31d9089f5e
--- /dev/null
+++ b/apps/sim/app/api/billing/credits/route.ts
@@ -0,0 +1,65 @@
+import { type NextRequest, NextResponse } from 'next/server'
+import { z } from 'zod'
+import { getSession } from '@/lib/auth'
+import { getCreditBalance } from '@/lib/billing/credits/balance'
+import { purchaseCredits } from '@/lib/billing/credits/purchase'
+import { createLogger } from '@/lib/logs/console/logger'
+
+const logger = createLogger('CreditsAPI')
+
+const PurchaseSchema = z.object({
+ amount: z.number().min(10).max(1000),
+ requestId: z.string().uuid(),
+})
+
+export async function GET() {
+ const session = await getSession()
+ if (!session?.user?.id) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ try {
+ const { balance, entityType, entityId } = await getCreditBalance(session.user.id)
+ return NextResponse.json({
+ success: true,
+ data: { balance, entityType, entityId },
+ })
+ } catch (error) {
+ logger.error('Failed to get credit balance', { error, userId: session.user.id })
+ return NextResponse.json({ error: 'Failed to get credit balance' }, { status: 500 })
+ }
+}
+
+export async function POST(request: NextRequest) {
+ const session = await getSession()
+ if (!session?.user?.id) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ try {
+ const body = await request.json()
+ const validation = PurchaseSchema.safeParse(body)
+
+ if (!validation.success) {
+ return NextResponse.json(
+ { error: 'Invalid amount. Must be between $10 and $1000' },
+ { status: 400 }
+ )
+ }
+
+ const result = await purchaseCredits({
+ userId: session.user.id,
+ amountDollars: validation.data.amount,
+ requestId: validation.data.requestId,
+ })
+
+ if (!result.success) {
+ return NextResponse.json({ error: result.error }, { status: 400 })
+ }
+
+ return NextResponse.json({ success: true })
+ } catch (error) {
+ logger.error('Failed to purchase credits', { error, userId: session.user.id })
+ return NextResponse.json({ error: 'Failed to purchase credits' }, { status: 500 })
+ }
+}
diff --git a/apps/sim/app/api/billing/route.ts b/apps/sim/app/api/billing/route.ts
index b9c7bb4b7f..33e1559afa 100644
--- a/apps/sim/app/api/billing/route.ts
+++ b/apps/sim/app/api/billing/route.ts
@@ -7,6 +7,76 @@ import { getSimplifiedBillingSummary } from '@/lib/billing/core/billing'
import { getOrganizationBillingData } from '@/lib/billing/core/organization'
import { createLogger } from '@/lib/logs/console/logger'
+/**
+ * Gets the effective billing blocked status for a user.
+ * If user is in an org, also checks if the org owner is blocked.
+ */
+async function getEffectiveBillingStatus(userId: string): Promise<{
+ billingBlocked: boolean
+ billingBlockedReason: 'payment_failed' | 'dispute' | null
+ blockedByOrgOwner: boolean
+}> {
+ // Check user's own status
+ const userStatsRows = await db
+ .select({
+ blocked: userStats.billingBlocked,
+ blockedReason: userStats.billingBlockedReason,
+ })
+ .from(userStats)
+ .where(eq(userStats.userId, userId))
+ .limit(1)
+
+ const userBlocked = userStatsRows.length > 0 ? !!userStatsRows[0].blocked : false
+ const userBlockedReason = userStatsRows.length > 0 ? userStatsRows[0].blockedReason : null
+
+ if (userBlocked) {
+ return {
+ billingBlocked: true,
+ billingBlockedReason: userBlockedReason,
+ blockedByOrgOwner: false,
+ }
+ }
+
+ // Check if user is in an org where owner is blocked
+ const memberships = await db
+ .select({ organizationId: member.organizationId })
+ .from(member)
+ .where(eq(member.userId, userId))
+
+ for (const m of memberships) {
+ const owners = await db
+ .select({ userId: member.userId })
+ .from(member)
+ .where(and(eq(member.organizationId, m.organizationId), eq(member.role, 'owner')))
+ .limit(1)
+
+ if (owners.length > 0 && owners[0].userId !== userId) {
+ const ownerStats = await db
+ .select({
+ blocked: userStats.billingBlocked,
+ blockedReason: userStats.billingBlockedReason,
+ })
+ .from(userStats)
+ .where(eq(userStats.userId, owners[0].userId))
+ .limit(1)
+
+ if (ownerStats.length > 0 && ownerStats[0].blocked) {
+ return {
+ billingBlocked: true,
+ billingBlockedReason: ownerStats[0].blockedReason,
+ blockedByOrgOwner: true,
+ }
+ }
+ }
+ }
+
+ return {
+ billingBlocked: false,
+ billingBlockedReason: null,
+ blockedByOrgOwner: false,
+ }
+}
+
const logger = createLogger('UnifiedBillingAPI')
/**
@@ -45,15 +115,13 @@ export async function GET(request: NextRequest) {
if (context === 'user') {
// Get user billing (may include organization if they're part of one)
billingData = await getSimplifiedBillingSummary(session.user.id, contextId || undefined)
- // Attach billingBlocked status for the current user
- const stats = await db
- .select({ blocked: userStats.billingBlocked })
- .from(userStats)
- .where(eq(userStats.userId, session.user.id))
- .limit(1)
+ // Attach effective billing blocked status (includes org owner check)
+ const billingStatus = await getEffectiveBillingStatus(session.user.id)
billingData = {
...billingData,
- billingBlocked: stats.length > 0 ? !!stats[0].blocked : false,
+ billingBlocked: billingStatus.billingBlocked,
+ billingBlockedReason: billingStatus.billingBlockedReason,
+ blockedByOrgOwner: billingStatus.blockedByOrgOwner,
}
} else {
// Get user role in organization for permission checks first
@@ -104,17 +172,15 @@ export async function GET(request: NextRequest) {
const userRole = memberRecord[0].role
- // Include the requesting user's blocked flag as well so UI can reflect it
- const stats = await db
- .select({ blocked: userStats.billingBlocked })
- .from(userStats)
- .where(eq(userStats.userId, session.user.id))
- .limit(1)
+ // Get effective billing blocked status (includes org owner check)
+ const billingStatus = await getEffectiveBillingStatus(session.user.id)
// Merge blocked flag into data for convenience
billingData = {
...billingData,
- billingBlocked: stats.length > 0 ? !!stats[0].blocked : false,
+ billingBlocked: billingStatus.billingBlocked,
+ billingBlockedReason: billingStatus.billingBlockedReason,
+ blockedByOrgOwner: billingStatus.blockedByOrgOwner,
}
return NextResponse.json({
@@ -123,6 +189,8 @@ export async function GET(request: NextRequest) {
data: billingData,
userRole,
billingBlocked: billingData.billingBlocked,
+ billingBlockedReason: billingData.billingBlockedReason,
+ blockedByOrgOwner: billingData.blockedByOrgOwner,
})
}
diff --git a/apps/sim/app/api/billing/update-cost/route.ts b/apps/sim/app/api/billing/update-cost/route.ts
index c22a21cdbb..3beed143f4 100644
--- a/apps/sim/app/api/billing/update-cost/route.ts
+++ b/apps/sim/app/api/billing/update-cost/route.ts
@@ -3,6 +3,7 @@ import { userStats } from '@sim/db/schema'
import { eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
+import { deductFromCredits } from '@/lib/billing/credits/balance'
import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
import { checkInternalApiKey } from '@/lib/copilot/utils'
import { isBillingEnabled } from '@/lib/core/config/environment'
@@ -90,13 +91,18 @@ export async function POST(req: NextRequest) {
)
return NextResponse.json({ error: 'User stats record not found' }, { status: 500 })
}
- // Update existing user stats record
+
+ const { creditsUsed, overflow } = await deductFromCredits(userId, cost)
+ if (creditsUsed > 0) {
+ logger.info(`[${requestId}] Deducted cost from credits`, { userId, creditsUsed, overflow })
+ }
+ const costToStore = overflow
+
const updateFields = {
- totalCost: sql`total_cost + ${cost}`,
- currentPeriodCost: sql`current_period_cost + ${cost}`,
- // Copilot usage tracking increments
- totalCopilotCost: sql`total_copilot_cost + ${cost}`,
- currentPeriodCopilotCost: sql`current_period_copilot_cost + ${cost}`,
+ totalCost: sql`total_cost + ${costToStore}`,
+ currentPeriodCost: sql`current_period_cost + ${costToStore}`,
+ totalCopilotCost: sql`total_copilot_cost + ${costToStore}`,
+ currentPeriodCopilotCost: sql`current_period_copilot_cost + ${costToStore}`,
totalCopilotCalls: sql`total_copilot_calls + 1`,
lastActive: new Date(),
}
diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts
index a8aff5f9fb..f16bc9be87 100644
--- a/apps/sim/app/api/function/execute/route.test.ts
+++ b/apps/sim/app/api/function/execute/route.test.ts
@@ -1,47 +1,60 @@
-import { NextRequest } from 'next/server'
/**
* Tests for function execution API route
*
* @vitest-environment node
*/
+import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createMockRequest } from '@/app/api/__test-utils__/utils'
const mockCreateContext = vi.fn()
const mockRunInContext = vi.fn()
-const mockLogger = {
- info: vi.fn(),
- error: vi.fn(),
- warn: vi.fn(),
- debug: vi.fn(),
-}
+const mockScript = vi.fn()
+const mockExecuteInE2B = vi.fn()
+
+vi.mock('@/lib/logs/console/logger', () => ({
+ createLogger: vi.fn(() => ({
+ info: vi.fn(),
+ error: vi.fn(),
+ warn: vi.fn(),
+ debug: vi.fn(),
+ })),
+}))
+
+vi.mock('vm', () => ({
+ createContext: vi.fn(),
+ Script: vi.fn(),
+}))
+
+vi.mock('@/lib/execution/e2b', () => ({
+ executeInE2B: vi.fn(),
+}))
+
+import { createContext, Script } from 'vm'
+import { validateProxyUrl } from '@/lib/core/security/input-validation'
+import { executeInE2B } from '@/lib/execution/e2b'
+import { createLogger } from '@/lib/logs/console/logger'
+import { POST } from './route'
+
+const mockedCreateContext = vi.mocked(createContext)
+const mockedScript = vi.mocked(Script)
+const mockedExecuteInE2B = vi.mocked(executeInE2B)
+const mockedCreateLogger = vi.mocked(createLogger)
describe('Function Execute API Route', () => {
beforeEach(() => {
- vi.resetModules()
- vi.resetAllMocks()
-
- vi.doMock('vm', () => ({
- createContext: mockCreateContext,
- Script: vi.fn().mockImplementation(() => ({
- runInContext: mockRunInContext,
- })),
- }))
-
- vi.doMock('@/lib/logs/console/logger', () => ({
- createLogger: vi.fn().mockReturnValue(mockLogger),
- }))
-
- vi.doMock('@/lib/execution/e2b', () => ({
- executeInE2B: vi.fn().mockResolvedValue({
- result: 'e2b success',
- stdout: 'e2b output',
- sandboxId: 'test-sandbox-id',
- }),
- }))
+ vi.clearAllMocks()
+ mockedCreateContext.mockReturnValue({})
mockRunInContext.mockResolvedValue('vm success')
- mockCreateContext.mockReturnValue({})
+ mockedScript.mockImplementation((): any => ({
+ runInContext: mockRunInContext,
+ }))
+ mockedExecuteInE2B.mockResolvedValue({
+ result: 'e2b success',
+ stdout: 'e2b output',
+ sandboxId: 'test-sandbox-id',
+ })
})
afterEach(() => {
@@ -54,20 +67,17 @@ describe('Function Execute API Route', () => {
code: 'return "test"',
})
- const { POST } = await import('@/app/api/function/execute/route')
await POST(req)
- expect(mockCreateContext).toHaveBeenCalled()
- const contextArgs = mockCreateContext.mock.calls[0][0]
+ expect(mockedCreateContext).toHaveBeenCalled()
+ const contextArgs = mockedCreateContext.mock.calls[0][0]
expect(contextArgs).toHaveProperty('fetch')
- expect(typeof contextArgs.fetch).toBe('function')
+ expect(typeof (contextArgs as any).fetch).toBe('function')
- expect(contextArgs.fetch.name).toBe('secureFetch')
+ expect((contextArgs as any).fetch?.name).toBe('secureFetch')
})
it.concurrent('should block SSRF attacks through secure fetch wrapper', async () => {
- const { validateProxyUrl } = await import('@/lib/core/security/input-validation')
-
expect(validateProxyUrl('http://169.254.169.254/latest/meta-data/').isValid).toBe(false)
expect(validateProxyUrl('http://127.0.0.1:8080/admin').isValid).toBe(false)
expect(validateProxyUrl('http://192.168.1.1/config').isValid).toBe(false)
@@ -75,16 +85,12 @@ describe('Function Execute API Route', () => {
})
it.concurrent('should allow legitimate external URLs', async () => {
- const { validateProxyUrl } = await import('@/lib/core/security/input-validation')
-
expect(validateProxyUrl('https://api.github.com/user').isValid).toBe(true)
expect(validateProxyUrl('https://httpbin.org/get').isValid).toBe(true)
expect(validateProxyUrl('https://example.com/api').isValid).toBe(true)
})
it.concurrent('should block dangerous protocols', async () => {
- const { validateProxyUrl } = await import('@/lib/core/security/input-validation')
-
expect(validateProxyUrl('file:///etc/passwd').isValid).toBe(false)
expect(validateProxyUrl('ftp://internal.server/files').isValid).toBe(false)
expect(validateProxyUrl('gopher://old.server/menu').isValid).toBe(false)
@@ -98,7 +104,6 @@ describe('Function Execute API Route', () => {
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -113,7 +118,6 @@ describe('Function Execute API Route', () => {
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -127,12 +131,11 @@ describe('Function Execute API Route', () => {
code: 'return "test"',
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
+ const data = await response.json()
expect(response.status).toBe(200)
- // The logger now logs execution success, not the request details
- expect(mockLogger.info).toHaveBeenCalled()
+ expect(data.success).toBe(true)
})
})
@@ -145,11 +148,9 @@ describe('Function Execute API Route', () => {
},
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
- // The code should be resolved to: return "secret-key-123"
})
it.concurrent('should resolve tag variables with syntax', async () => {
@@ -160,11 +161,9 @@ describe('Function Execute API Route', () => {
},
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
- // The code should be resolved with the email object
})
it.concurrent('should NOT treat email addresses as template variables', async () => {
@@ -178,11 +177,9 @@ describe('Function Execute API Route', () => {
},
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
- // Should not try to replace as a template variable
})
it.concurrent('should only match valid variable names in angle brackets', async () => {
@@ -194,11 +191,9 @@ describe('Function Execute API Route', () => {
},
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
- // Should replace and but not
})
})
@@ -230,7 +225,6 @@ describe('Function Execute API Route', () => {
params: gmailData,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
@@ -255,7 +249,6 @@ describe('Function Execute API Route', () => {
params: complexEmailData,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
@@ -273,11 +266,9 @@ describe('Function Execute API Route', () => {
isCustomTool: true,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
- // For custom tools, parameters should be directly accessible as variables
})
})
@@ -289,7 +280,6 @@ describe('Function Execute API Route', () => {
headers: { 'Content-Type': 'application/json' },
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(500)
@@ -301,15 +291,11 @@ describe('Function Execute API Route', () => {
timeout: 10000,
})
- const { POST } = await import('@/app/api/function/execute/route')
- await POST(req)
+ const response = await POST(req)
+ const data = await response.json()
- expect(mockLogger.info).toHaveBeenCalledWith(
- expect.stringMatching(/\[.*\] Function execution request/),
- expect.objectContaining({
- timeout: 10000,
- })
- )
+ expect(response.status).toBe(200)
+ expect(data.success).toBe(true)
})
it.concurrent('should handle empty parameters object', async () => {
@@ -318,7 +304,6 @@ describe('Function Execute API Route', () => {
params: {},
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
@@ -327,31 +312,25 @@ describe('Function Execute API Route', () => {
describe('Enhanced Error Handling', () => {
it('should provide detailed syntax error with line content', async () => {
- // Mock VM Script to throw a syntax error
- const mockScript = vi.fn().mockImplementation(() => {
- const error = new Error('Invalid or unexpected token')
- error.name = 'SyntaxError'
- error.stack = `user-function.js:5
+ const syntaxError = new Error('Invalid or unexpected token')
+ syntaxError.name = 'SyntaxError'
+ syntaxError.stack = `user-function.js:5
description: "This has a missing closing quote
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Invalid or unexpected token
at new Script (node:vm:117:7)
at POST (/path/to/route.ts:123:24)`
- throw error
- })
- vi.doMock('vm', () => ({
- createContext: mockCreateContext,
- Script: mockScript,
- }))
+ mockedScript.mockImplementationOnce(() => {
+ throw syntaxError
+ })
const req = createMockRequest('POST', {
code: 'const obj = {\n name: "test",\n description: "This has a missing closing quote\n};\nreturn obj;',
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -363,7 +342,6 @@ SyntaxError: Invalid or unexpected token
expect(data.error).toContain('Invalid or unexpected token')
expect(data.error).toContain('(Check for missing quotes, brackets, or semicolons)')
- // Check debug information
expect(data.debug).toBeDefined()
expect(data.debug.line).toBe(3)
expect(data.debug.errorType).toBe('SyntaxError')
@@ -371,7 +349,6 @@ SyntaxError: Invalid or unexpected token
})
it('should provide detailed runtime error with line and column', async () => {
- // Create the error object first
const runtimeError = new Error("Cannot read properties of null (reading 'someMethod')")
runtimeError.name = 'TypeError'
runtimeError.stack = `TypeError: Cannot read properties of null (reading 'someMethod')
@@ -379,22 +356,13 @@ SyntaxError: Invalid or unexpected token
at user-function.js:9:3
at Script.runInContext (node:vm:147:14)`
- // Mock successful script creation but runtime error
- const mockScript = vi.fn().mockImplementation(() => ({
- runInContext: vi.fn().mockRejectedValue(runtimeError),
- }))
-
- vi.doMock('vm', () => ({
- createContext: mockCreateContext,
- Script: mockScript,
- }))
+ mockRunInContext.mockRejectedValueOnce(runtimeError)
const req = createMockRequest('POST', {
code: 'const obj = null;\nreturn obj.someMethod();',
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -405,7 +373,6 @@ SyntaxError: Invalid or unexpected token
expect(data.error).toContain('return obj.someMethod();')
expect(data.error).toContain('Cannot read properties of null')
- // Check debug information
expect(data.debug).toBeDefined()
expect(data.debug.line).toBe(2)
expect(data.debug.column).toBe(16)
@@ -414,28 +381,19 @@ SyntaxError: Invalid or unexpected token
})
it('should handle ReferenceError with enhanced details', async () => {
- // Create the error object first
const referenceError = new Error('undefinedVariable is not defined')
referenceError.name = 'ReferenceError'
referenceError.stack = `ReferenceError: undefinedVariable is not defined
at user-function.js:4:8
at Script.runInContext (node:vm:147:14)`
- const mockScript = vi.fn().mockImplementation(() => ({
- runInContext: vi.fn().mockRejectedValue(referenceError),
- }))
-
- vi.doMock('vm', () => ({
- createContext: mockCreateContext,
- Script: mockScript,
- }))
+ mockRunInContext.mockRejectedValueOnce(referenceError)
const req = createMockRequest('POST', {
code: 'const x = 42;\nreturn undefinedVariable + x;',
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -448,24 +406,18 @@ SyntaxError: Invalid or unexpected token
})
it('should handle errors without line content gracefully', async () => {
- const mockScript = vi.fn().mockImplementation(() => {
- const error = new Error('Generic error without stack trace')
- error.name = 'Error'
- // No stack trace
- throw error
- })
+ const genericError = new Error('Generic error without stack trace')
+ genericError.name = 'Error'
- vi.doMock('vm', () => ({
- createContext: mockCreateContext,
- Script: mockScript,
- }))
+ mockedScript.mockImplementationOnce(() => {
+ throw genericError
+ })
const req = createMockRequest('POST', {
code: 'return "test";',
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -473,7 +425,6 @@ SyntaxError: Invalid or unexpected token
expect(data.success).toBe(false)
expect(data.error).toBe('Generic error without stack trace')
- // Should still have debug info, but without line details
expect(data.debug).toBeDefined()
expect(data.debug.errorType).toBe('Error')
expect(data.debug.line).toBeUndefined()
@@ -481,58 +432,47 @@ SyntaxError: Invalid or unexpected token
})
it('should extract line numbers from different stack trace formats', async () => {
- const mockScript = vi.fn().mockImplementation(() => {
- const error = new Error('Test error')
- error.name = 'Error'
- error.stack = `Error: Test error
+ const testError = new Error('Test error')
+ testError.name = 'Error'
+ testError.stack = `Error: Test error
at user-function.js:7:25
at async function
at Script.runInContext (node:vm:147:14)`
- throw error
- })
- vi.doMock('vm', () => ({
- createContext: mockCreateContext,
- Script: mockScript,
- }))
+ mockedScript.mockImplementationOnce(() => {
+ throw testError
+ })
const req = createMockRequest('POST', {
code: 'const a = 1;\nconst b = 2;\nconst c = 3;\nconst d = 4;\nreturn a + b + c + d;',
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
expect(response.status).toBe(500)
expect(data.success).toBe(false)
- // Line 7 in VM should map to line 5 in user code (7 - 3 + 1 = 5)
expect(data.debug.line).toBe(5)
expect(data.debug.column).toBe(25)
expect(data.debug.lineContent).toBe('return a + b + c + d;')
})
it.concurrent('should provide helpful suggestions for common syntax errors', async () => {
- const mockScript = vi.fn().mockImplementation(() => {
- const error = new Error('Unexpected end of input')
- error.name = 'SyntaxError'
- error.stack = 'user-function.js:4\nSyntaxError: Unexpected end of input'
- throw error
- })
+ const syntaxError = new Error('Unexpected end of input')
+ syntaxError.name = 'SyntaxError'
+ syntaxError.stack = 'user-function.js:4\nSyntaxError: Unexpected end of input'
- vi.doMock('vm', () => ({
- createContext: mockCreateContext,
- Script: mockScript,
- }))
+ mockedScript.mockImplementationOnce(() => {
+ throw syntaxError
+ })
const req = createMockRequest('POST', {
code: 'const obj = {\n name: "test"\n// Missing closing brace',
timeout: 5000,
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -546,7 +486,6 @@ SyntaxError: Invalid or unexpected token
describe('Utility Functions', () => {
it.concurrent('should properly escape regex special characters', async () => {
- // This tests the escapeRegExp function indirectly
const req = createMockRequest('POST', {
code: 'return {{special.chars+*?}}',
envVars: {
@@ -554,15 +493,12 @@ SyntaxError: Invalid or unexpected token
},
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
- // Should handle special regex characters in variable names
})
it.concurrent('should handle JSON serialization edge cases', async () => {
- // Test with complex but not circular data first
const req = createMockRequest('POST', {
code: 'return ',
params: {
@@ -578,7 +514,6 @@ SyntaxError: Invalid or unexpected token
},
})
- const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
diff --git a/apps/sim/app/api/schedules/route.test.ts b/apps/sim/app/api/schedules/route.test.ts
index 650eb0017b..bfc65ec1c2 100644
--- a/apps/sim/app/api/schedules/route.test.ts
+++ b/apps/sim/app/api/schedules/route.test.ts
@@ -4,148 +4,207 @@
* @vitest-environment node
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import {
- createMockRequest,
- mockExecutionDependencies,
- sampleWorkflowState,
-} from '@/app/api/__test-utils__/utils'
+import { createMockRequest, mockExecutionDependencies } from '@/app/api/__test-utils__/utils'
+
+const {
+ mockGetSession,
+ mockGetUserEntityPermissions,
+ mockSelectLimit,
+ mockInsertValues,
+ mockOnConflictDoUpdate,
+ mockInsert,
+ mockUpdate,
+ mockDelete,
+ mockTransaction,
+ mockRandomUUID,
+ mockGetScheduleTimeValues,
+ mockGetSubBlockValue,
+ mockGenerateCronExpression,
+ mockCalculateNextRunTime,
+ mockValidateCronExpression,
+} = vi.hoisted(() => ({
+ mockGetSession: vi.fn(),
+ mockGetUserEntityPermissions: vi.fn(),
+ mockSelectLimit: vi.fn(),
+ mockInsertValues: vi.fn(),
+ mockOnConflictDoUpdate: vi.fn(),
+ mockInsert: vi.fn(),
+ mockUpdate: vi.fn(),
+ mockDelete: vi.fn(),
+ mockTransaction: vi.fn(),
+ mockRandomUUID: vi.fn(),
+ mockGetScheduleTimeValues: vi.fn(),
+ mockGetSubBlockValue: vi.fn(),
+ mockGenerateCronExpression: vi.fn(),
+ mockCalculateNextRunTime: vi.fn(),
+ mockValidateCronExpression: vi.fn(),
+}))
+
+vi.mock('@/lib/auth', () => ({
+ getSession: mockGetSession,
+}))
+
+vi.mock('@/lib/workspaces/permissions/utils', () => ({
+ getUserEntityPermissions: mockGetUserEntityPermissions,
+}))
+
+vi.mock('@sim/db', () => ({
+ db: {
+ select: vi.fn().mockReturnValue({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: mockSelectLimit,
+ }),
+ }),
+ }),
+ insert: mockInsert,
+ update: mockUpdate,
+ delete: mockDelete,
+ },
+}))
+
+vi.mock('@sim/db/schema', () => ({
+ workflow: {
+ id: 'workflow_id',
+ userId: 'user_id',
+ workspaceId: 'workspace_id',
+ },
+ workflowSchedule: {
+ id: 'schedule_id',
+ workflowId: 'workflow_id',
+ blockId: 'block_id',
+ cronExpression: 'cron_expression',
+ nextRunAt: 'next_run_at',
+ status: 'status',
+ },
+}))
+
+vi.mock('drizzle-orm', () => ({
+ eq: vi.fn((...args) => ({ type: 'eq', args })),
+ and: vi.fn((...args) => ({ type: 'and', args })),
+}))
+
+vi.mock('crypto', () => ({
+ randomUUID: mockRandomUUID,
+ default: {
+ randomUUID: mockRandomUUID,
+ },
+}))
+
+vi.mock('@/lib/workflows/schedules/utils', () => ({
+ getScheduleTimeValues: mockGetScheduleTimeValues,
+ getSubBlockValue: mockGetSubBlockValue,
+ generateCronExpression: mockGenerateCronExpression,
+ calculateNextRunTime: mockCalculateNextRunTime,
+ validateCronExpression: mockValidateCronExpression,
+ BlockState: {},
+}))
+
+vi.mock('@/lib/core/utils/request', () => ({
+ generateRequestId: vi.fn(() => 'test-request-id'),
+}))
+
+vi.mock('@/lib/logs/console/logger', () => ({
+ createLogger: vi.fn(() => ({
+ info: vi.fn(),
+ warn: vi.fn(),
+ error: vi.fn(),
+ debug: vi.fn(),
+ })),
+}))
+
+vi.mock('@/lib/core/telemetry', () => ({
+ trackPlatformEvent: vi.fn(),
+}))
+
+import { db } from '@sim/db'
+import { POST } from '@/app/api/schedules/route'
describe('Schedule Configuration API Route', () => {
beforeEach(() => {
- vi.resetModules()
+ vi.clearAllMocks()
+
+ ;(db as any).transaction = mockTransaction
mockExecutionDependencies()
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: {
- id: 'user-id',
- email: 'test@example.com',
- },
- }),
+ mockGetSession.mockResolvedValue({
+ user: {
+ id: 'user-id',
+ email: 'test@example.com',
+ },
+ })
+
+ mockGetUserEntityPermissions.mockResolvedValue('admin')
+
+ mockSelectLimit.mockReturnValue([
+ {
+ id: 'workflow-id',
+ userId: 'user-id',
+ workspaceId: null,
+ },
+ ])
+
+ mockInsertValues.mockImplementation(() => ({
+ onConflictDoUpdate: mockOnConflictDoUpdate,
}))
+ mockOnConflictDoUpdate.mockResolvedValue({})
+
+ mockInsert.mockReturnValue({
+ values: mockInsertValues,
+ })
- vi.doMock('@/lib/workspaces/permissions/utils', () => ({
- getUserEntityPermissions: vi.fn().mockResolvedValue('admin'), // User has admin permissions
+ mockUpdate.mockImplementation(() => ({
+ set: vi.fn().mockImplementation(() => ({
+ where: vi.fn().mockResolvedValue([]),
+ })),
}))
- const _workflowStateWithSchedule = {
- ...sampleWorkflowState,
- blocks: {
- ...sampleWorkflowState.blocks,
- 'starter-id': {
- ...sampleWorkflowState.blocks['starter-id'],
- subBlocks: {
- ...sampleWorkflowState.blocks['starter-id'].subBlocks,
- startWorkflow: { id: 'startWorkflow', type: 'dropdown', value: 'schedule' },
- scheduleType: { id: 'scheduleType', type: 'dropdown', value: 'daily' },
- scheduleTime: { id: 'scheduleTime', type: 'time-input', value: '09:30' },
- dailyTime: { id: 'dailyTime', type: 'time-input', value: '09:30' },
- },
- },
- },
- }
-
- vi.doMock('@sim/db', () => {
- let callCount = 0
- const mockInsert = {
- values: vi.fn().mockImplementation(() => ({
- onConflictDoUpdate: vi.fn().mockResolvedValue({}),
- })),
- }
+ mockDelete.mockImplementation(() => ({
+ where: vi.fn().mockResolvedValue([]),
+ }))
- const mockDb = {
- select: vi.fn().mockImplementation(() => ({
- from: vi.fn().mockImplementation(() => ({
- where: vi.fn().mockImplementation(() => ({
- limit: vi.fn().mockImplementation(() => {
- callCount++
- // First call: workflow lookup for authorization
- if (callCount === 1) {
- return [
- {
- id: 'workflow-id',
- userId: 'user-id',
- workspaceId: null, // User owns the workflow directly
- },
- ]
- }
- // Second call: existing schedule lookup - return existing schedule for update test
- return [
- {
- id: 'existing-schedule-id',
- workflowId: 'workflow-id',
- blockId: 'starter-id',
- cronExpression: '0 9 * * *',
- nextRunAt: new Date(),
- status: 'active',
- },
- ]
- }),
- })),
- })),
- })),
- insert: vi.fn().mockReturnValue(mockInsert),
- update: vi.fn().mockImplementation(() => ({
- set: vi.fn().mockImplementation(() => ({
- where: vi.fn().mockResolvedValue([]),
- })),
- })),
- delete: vi.fn().mockImplementation(() => ({
- where: vi.fn().mockResolvedValue([]),
- })),
- transaction: vi.fn().mockImplementation(async (callback) => {
- const tx = {
- insert: vi.fn().mockReturnValue(mockInsert),
- }
- return callback(tx)
+ mockTransaction.mockImplementation(async (callback) => {
+ const tx = {
+ insert: vi.fn().mockReturnValue({
+ values: mockInsertValues,
}),
}
-
- return { db: mockDb }
+ return callback(tx)
})
- vi.doMock('crypto', () => ({
- randomUUID: vi.fn(() => 'test-uuid'),
- default: {
- randomUUID: vi.fn(() => 'test-uuid'),
- },
- }))
+ mockRandomUUID.mockReturnValue('test-uuid')
+
+ mockGetScheduleTimeValues.mockReturnValue({
+ scheduleTime: '09:30',
+ minutesInterval: 15,
+ hourlyMinute: 0,
+ dailyTime: [9, 30],
+ weeklyDay: 1,
+ weeklyTime: [9, 30],
+ monthlyDay: 1,
+ monthlyTime: [9, 30],
+ })
- vi.doMock('@/lib/workflows/schedules/utils', () => ({
- getScheduleTimeValues: vi.fn().mockReturnValue({
+ mockGetSubBlockValue.mockImplementation((block: any, id: string) => {
+ const subBlocks = {
+ startWorkflow: 'schedule',
+ scheduleType: 'daily',
scheduleTime: '09:30',
- minutesInterval: 15,
- hourlyMinute: 0,
- dailyTime: [9, 30],
- weeklyDay: 1,
- weeklyTime: [9, 30],
- monthlyDay: 1,
- monthlyTime: [9, 30],
- }),
- getSubBlockValue: vi.fn().mockImplementation((block: any, id: string) => {
- const subBlocks = {
- startWorkflow: 'schedule',
- scheduleType: 'daily',
- scheduleTime: '09:30',
- dailyTime: '09:30',
- }
- return subBlocks[id as keyof typeof subBlocks] || ''
- }),
- generateCronExpression: vi.fn().mockReturnValue('0 9 * * *'),
- calculateNextRunTime: vi.fn().mockReturnValue(new Date()),
- validateCronExpression: vi.fn().mockReturnValue({ isValid: true }),
- BlockState: {},
- }))
+ dailyTime: '09:30',
+ }
+ return subBlocks[id as keyof typeof subBlocks] || ''
+ })
+
+ mockGenerateCronExpression.mockReturnValue('0 9 * * *')
+ mockCalculateNextRunTime.mockReturnValue(new Date())
+ mockValidateCronExpression.mockReturnValue({ isValid: true })
})
afterEach(() => {
vi.clearAllMocks()
})
- /**
- * Test creating a new schedule
- */
it('should create a new schedule successfully', async () => {
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
@@ -166,8 +225,6 @@ describe('Schedule Configuration API Route', () => {
},
})
- const { POST } = await import('@/app/api/schedules/route')
-
const response = await POST(req)
expect(response).toBeDefined()
@@ -177,38 +234,16 @@ describe('Schedule Configuration API Route', () => {
expect(responseData).toHaveProperty('message', 'Schedule updated')
expect(responseData).toHaveProperty('cronExpression', '0 9 * * *')
expect(responseData).toHaveProperty('nextRunAt')
-
- // We can't verify the utility functions were called directly
- // since we're mocking them at the module level
- // Instead, we just verify that the response has the expected properties
})
- /**
- * Test error handling
- */
it('should handle errors gracefully', async () => {
- vi.doMock('@sim/db', () => ({
- db: {
- select: vi.fn().mockImplementation(() => ({
- from: vi.fn().mockImplementation(() => ({
- where: vi.fn().mockImplementation(() => ({
- limit: vi.fn().mockImplementation(() => []),
- })),
- })),
- })),
- insert: vi.fn().mockImplementation(() => {
- throw new Error('Database error')
- }),
- },
- }))
+ mockSelectLimit.mockReturnValue([])
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
state: { blocks: {}, edges: [], loops: {} },
})
- const { POST } = await import('@/app/api/schedules/route')
-
const response = await POST(req)
expect(response.status).toBeGreaterThanOrEqual(400)
@@ -216,21 +251,14 @@ describe('Schedule Configuration API Route', () => {
expect(data).toHaveProperty('error')
})
- /**
- * Test authentication requirement
- */
it('should require authentication', async () => {
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue(null),
- }))
+ mockGetSession.mockResolvedValue(null)
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
state: { blocks: {}, edges: [], loops: {} },
})
- const { POST } = await import('@/app/api/schedules/route')
-
const response = await POST(req)
expect(response.status).toBe(401)
@@ -238,16 +266,11 @@ describe('Schedule Configuration API Route', () => {
expect(data).toHaveProperty('error', 'Unauthorized')
})
- /**
- * Test invalid data handling
- */
it('should validate input data', async () => {
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
})
- const { POST } = await import('@/app/api/schedules/route')
-
const response = await POST(req)
expect(response.status).toBe(400)
diff --git a/apps/sim/app/api/tools/drive/file/route.ts b/apps/sim/app/api/tools/drive/file/route.ts
index edc1e77f1e..62eb9686c7 100644
--- a/apps/sim/app/api/tools/drive/file/route.ts
+++ b/apps/sim/app/api/tools/drive/file/route.ts
@@ -57,6 +57,35 @@ export async function GET(request: NextRequest) {
}
)
+ if (!response.ok && response.status === 404) {
+ logger.info(`[${requestId}] File not found, checking if it's a shared drive`)
+ const driveResponse = await fetch(
+ `https://www.googleapis.com/drive/v3/drives/${fileId}?fields=id,name`,
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ )
+
+ if (driveResponse.ok) {
+ const driveData = await driveResponse.json()
+ logger.info(`[${requestId}] Found shared drive: ${driveData.name}`)
+ return NextResponse.json(
+ {
+ file: {
+ id: driveData.id,
+ name: driveData.name,
+ mimeType: 'application/vnd.google-apps.folder',
+ iconLink:
+ 'https://ssl.gstatic.com/docs/doclist/images/icon_11_shared_collection_list_1.png',
+ },
+ },
+ { status: 200 }
+ )
+ }
+ }
+
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } }))
logger.error(`[${requestId}] Google Drive API error`, {
@@ -112,12 +141,12 @@ export async function GET(request: NextRequest) {
if (!file.exportLinks) {
file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}/export?mimeType=${encodeURIComponent(
format
- )}`
+ )}&supportsAllDrives=true`
} else {
file.downloadUrl = file.exportLinks[format]
}
} else {
- file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`
+ file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media&supportsAllDrives=true`
}
return NextResponse.json({ file }, { status: 200 })
diff --git a/apps/sim/app/api/tools/drive/files/route.ts b/apps/sim/app/api/tools/drive/files/route.ts
index a142821b14..33ecb6f105 100644
--- a/apps/sim/app/api/tools/drive/files/route.ts
+++ b/apps/sim/app/api/tools/drive/files/route.ts
@@ -12,6 +12,62 @@ function escapeForDriveQuery(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
}
+interface SharedDrive {
+ id: string
+ name: string
+ kind: string
+}
+
+interface DriveFile {
+ id: string
+ name: string
+ mimeType: string
+ iconLink?: string
+ webViewLink?: string
+ thumbnailLink?: string
+ createdTime?: string
+ modifiedTime?: string
+ size?: string
+ owners?: any[]
+ parents?: string[]
+}
+
+/**
+ * Fetches shared drives the user has access to
+ */
+async function fetchSharedDrives(accessToken: string, requestId: string): Promise {
+ try {
+ const response = await fetch(
+ 'https://www.googleapis.com/drive/v3/drives?pageSize=100&fields=drives(id,name)',
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ )
+
+ if (!response.ok) {
+ logger.warn(`[${requestId}] Failed to fetch shared drives`, {
+ status: response.status,
+ })
+ return []
+ }
+
+ const data = await response.json()
+ const drives: SharedDrive[] = data.drives || []
+
+ return drives.map((drive) => ({
+ id: drive.id,
+ name: drive.name,
+ mimeType: 'application/vnd.google-apps.folder',
+ iconLink: 'https://ssl.gstatic.com/docs/doclist/images/icon_11_shared_collection_list_1.png',
+ }))
+ } catch (error) {
+ logger.error(`[${requestId}] Error fetching shared drives`, error)
+ return []
+ }
+}
+
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
logger.info(`[${requestId}] Google Drive files request received`)
@@ -65,7 +121,7 @@ export async function GET(request: NextRequest) {
const q = encodeURIComponent(qParts.join(' and '))
const response = await fetch(
- `https://www.googleapis.com/drive/v3/files?q=${q}&supportsAllDrives=true&includeItemsFromAllDrives=true&spaces=drive&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)`,
+ `https://www.googleapis.com/drive/v3/files?q=${q}&corpora=allDrives&supportsAllDrives=true&includeItemsFromAllDrives=true&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
@@ -88,14 +144,26 @@ export async function GET(request: NextRequest) {
}
const data = await response.json()
- let files = data.files || []
+ let files: DriveFile[] = data.files || []
if (mimeType === 'application/vnd.google-apps.spreadsheet') {
files = files.filter(
- (file: any) => file.mimeType === 'application/vnd.google-apps.spreadsheet'
+ (file: DriveFile) => file.mimeType === 'application/vnd.google-apps.spreadsheet'
)
} else if (mimeType === 'application/vnd.google-apps.document') {
- files = files.filter((file: any) => file.mimeType === 'application/vnd.google-apps.document')
+ files = files.filter(
+ (file: DriveFile) => file.mimeType === 'application/vnd.google-apps.document'
+ )
+ }
+
+ const isRootFolderListing =
+ !folderId && mimeType === 'application/vnd.google-apps.folder' && !query
+ if (isRootFolderListing) {
+ const sharedDrives = await fetchSharedDrives(accessToken, requestId)
+ if (sharedDrives.length > 0) {
+ logger.info(`[${requestId}] Found ${sharedDrives.length} shared drives`)
+ files = [...sharedDrives, ...files]
+ }
}
return NextResponse.json({ files }, { status: 200 })
diff --git a/apps/sim/app/api/usage/route.ts b/apps/sim/app/api/usage/route.ts
index 90909c571a..4ca818e787 100644
--- a/apps/sim/app/api/usage/route.ts
+++ b/apps/sim/app/api/usage/route.ts
@@ -111,7 +111,10 @@ export async function PUT(request: NextRequest) {
const userId = session.user.id
if (context === 'user') {
- await updateUserUsageLimit(userId, limit)
+ const result = await updateUserUsageLimit(userId, limit)
+ if (!result.success) {
+ return NextResponse.json({ error: result.error }, { status: 400 })
+ }
} else if (context === 'organization') {
// organizationId is guaranteed to exist by Zod refinement
const hasPermission = await isOrganizationOwnerOrAdmin(session.user.id, organizationId!)
diff --git a/apps/sim/app/api/workflows/[id]/route.test.ts b/apps/sim/app/api/workflows/[id]/route.test.ts
index f2412f32e4..def0eff81d 100644
--- a/apps/sim/app/api/workflows/[id]/route.test.ts
+++ b/apps/sim/app/api/workflows/[id]/route.test.ts
@@ -8,45 +8,62 @@
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-describe('Workflow By ID API Route', () => {
- const mockLogger = {
+const mockGetSession = vi.fn()
+const mockLoadWorkflowFromNormalizedTables = vi.fn()
+const mockGetWorkflowById = vi.fn()
+const mockGetWorkflowAccessContext = vi.fn()
+const mockDbDelete = vi.fn()
+const mockDbUpdate = vi.fn()
+
+vi.mock('@/lib/auth', () => ({
+ getSession: () => mockGetSession(),
+}))
+
+vi.mock('@/lib/logs/console/logger', () => ({
+ createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
+ })),
+}))
+
+vi.mock('@/lib/workflows/persistence/utils', () => ({
+ loadWorkflowFromNormalizedTables: (workflowId: string) =>
+ mockLoadWorkflowFromNormalizedTables(workflowId),
+}))
+
+vi.mock('@/lib/workflows/utils', async () => {
+ const actual =
+ await vi.importActual('@/lib/workflows/utils')
+
+ return {
+ ...actual,
+ getWorkflowById: (workflowId: string) => mockGetWorkflowById(workflowId),
+ getWorkflowAccessContext: (workflowId: string, userId?: string) =>
+ mockGetWorkflowAccessContext(workflowId, userId),
}
+})
- const mockGetWorkflowById = vi.fn()
- const mockGetWorkflowAccessContext = vi.fn()
+vi.mock('@sim/db', () => ({
+ db: {
+ delete: () => mockDbDelete(),
+ update: () => mockDbUpdate(),
+ },
+ workflow: {},
+}))
+import { DELETE, GET, PUT } from './route'
+
+describe('Workflow By ID API Route', () => {
beforeEach(() => {
- vi.resetModules()
+ vi.clearAllMocks()
vi.stubGlobal('crypto', {
randomUUID: vi.fn().mockReturnValue('mock-request-id-12345678'),
})
- vi.doMock('@/lib/logs/console/logger', () => ({
- createLogger: vi.fn().mockReturnValue(mockLogger),
- }))
-
- vi.doMock('@/lib/workflows/persistence/utils', () => ({
- loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(null),
- }))
-
- mockGetWorkflowById.mockReset()
- mockGetWorkflowAccessContext.mockReset()
-
- vi.doMock('@/lib/workflows/utils', async () => {
- const actual =
- await vi.importActual('@/lib/workflows/utils')
-
- return {
- ...actual,
- getWorkflowById: mockGetWorkflowById,
- getWorkflowAccessContext: mockGetWorkflowAccessContext,
- }
- })
+ mockLoadWorkflowFromNormalizedTables.mockResolvedValue(null)
})
afterEach(() => {
@@ -55,14 +72,11 @@ describe('Workflow By ID API Route', () => {
describe('GET /api/workflows/[id]', () => {
it('should return 401 when user is not authenticated', async () => {
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue(null),
- }))
+ mockGetSession.mockResolvedValue(null)
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
- const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(401)
@@ -71,14 +85,12 @@ describe('Workflow By ID API Route', () => {
})
it('should return 404 when workflow does not exist', async () => {
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(null)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(null)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: null,
workspaceOwnerId: null,
workspacePermission: null,
@@ -89,7 +101,6 @@ describe('Workflow By ID API Route', () => {
const req = new NextRequest('http://localhost:3000/api/workflows/nonexistent')
const params = Promise.resolve({ id: 'nonexistent' })
- const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(404)
@@ -113,27 +124,12 @@ describe('Workflow By ID API Route', () => {
isFromNormalizedTables: true,
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
-
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
- workflow: mockWorkflow,
- workspaceOwnerId: null,
- workspacePermission: null,
- isOwner: true,
- isWorkspaceOwner: false,
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
})
- vi.doMock('@/lib/workflows/persistence/utils', () => ({
- loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
- }))
-
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -141,10 +137,11 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
+ mockLoadWorkflowFromNormalizedTables.mockResolvedValue(mockNormalizedData)
+
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
- const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(200)
@@ -168,27 +165,12 @@ describe('Workflow By ID API Route', () => {
isFromNormalizedTables: true,
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
-
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
- workflow: mockWorkflow,
- workspaceOwnerId: 'workspace-456',
- workspacePermission: 'admin',
- isOwner: false,
- isWorkspaceOwner: false,
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
})
- vi.doMock('@/lib/workflows/persistence/utils', () => ({
- loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
- }))
-
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'read',
@@ -196,15 +178,11 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
- vi.doMock('@/lib/workspaces/permissions/utils', () => ({
- getUserEntityPermissions: vi.fn().mockResolvedValue('read'),
- hasAdminPermission: vi.fn().mockResolvedValue(false),
- }))
+ mockLoadWorkflowFromNormalizedTables.mockResolvedValue(mockNormalizedData)
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
- const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(200)
@@ -220,14 +198,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: 'workspace-456',
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: null,
@@ -238,7 +214,6 @@ describe('Workflow By ID API Route', () => {
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
- const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(403)
@@ -262,14 +237,12 @@ describe('Workflow By ID API Route', () => {
isFromNormalizedTables: true,
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -277,14 +250,11 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
- vi.doMock('@/lib/workflows/persistence/utils', () => ({
- loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
- }))
+ mockLoadWorkflowFromNormalizedTables.mockResolvedValue(mockNormalizedData)
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
- const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(200)
@@ -303,14 +273,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: null,
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -318,14 +286,9 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
- vi.doMock('@sim/db', () => ({
- db: {
- delete: vi.fn().mockReturnValue({
- where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
- }),
- },
- workflow: {},
- }))
+ mockDbDelete.mockReturnValue({
+ where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
+ })
global.fetch = vi.fn().mockResolvedValue({
ok: true,
@@ -336,7 +299,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
- const { DELETE } = await import('@/app/api/workflows/[id]/route')
const response = await DELETE(req, { params })
expect(response.status).toBe(200)
@@ -352,14 +314,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: 'workspace-456',
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'admin',
@@ -367,14 +327,9 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
- vi.doMock('@sim/db', () => ({
- db: {
- delete: vi.fn().mockReturnValue({
- where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
- }),
- },
- workflow: {},
- }))
+ mockDbDelete.mockReturnValue({
+ where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
+ })
global.fetch = vi.fn().mockResolvedValue({
ok: true,
@@ -385,7 +340,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
- const { DELETE } = await import('@/app/api/workflows/[id]/route')
const response = await DELETE(req, { params })
expect(response.status).toBe(200)
@@ -401,14 +355,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: 'workspace-456',
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: null,
@@ -421,7 +373,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
- const { DELETE } = await import('@/app/api/workflows/[id]/route')
const response = await DELETE(req, { params })
expect(response.status).toBe(403)
@@ -442,14 +393,12 @@ describe('Workflow By ID API Route', () => {
const updateData = { name: 'Updated Workflow' }
const updatedWorkflow = { ...mockWorkflow, ...updateData, updatedAt: new Date() }
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -457,18 +406,13 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
- vi.doMock('@sim/db', () => ({
- db: {
- update: vi.fn().mockReturnValue({
- set: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- returning: vi.fn().mockResolvedValue([updatedWorkflow]),
- }),
- }),
+ mockDbUpdate.mockReturnValue({
+ set: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([updatedWorkflow]),
}),
- },
- workflow: {},
- }))
+ }),
+ })
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123', {
method: 'PUT',
@@ -476,7 +420,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
- const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(200)
@@ -495,14 +438,12 @@ describe('Workflow By ID API Route', () => {
const updateData = { name: 'Updated Workflow' }
const updatedWorkflow = { ...mockWorkflow, ...updateData, updatedAt: new Date() }
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'write',
@@ -510,18 +451,13 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
- vi.doMock('@sim/db', () => ({
- db: {
- update: vi.fn().mockReturnValue({
- set: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- returning: vi.fn().mockResolvedValue([updatedWorkflow]),
- }),
- }),
+ mockDbUpdate.mockReturnValue({
+ set: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([updatedWorkflow]),
}),
- },
- workflow: {},
- }))
+ }),
+ })
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123', {
method: 'PUT',
@@ -529,7 +465,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
- const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(200)
@@ -547,14 +482,12 @@ describe('Workflow By ID API Route', () => {
const updateData = { name: 'Updated Workflow' }
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'read',
@@ -568,7 +501,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
- const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(403)
@@ -584,14 +516,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: null,
}
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
- mockGetWorkflowAccessContext.mockResolvedValueOnce({
+ mockGetWorkflowById.mockResolvedValue(mockWorkflow)
+ mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -599,7 +529,6 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
- // Invalid data - empty name
const invalidData = { name: '' }
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123', {
@@ -608,7 +537,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
- const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(400)
@@ -619,24 +547,20 @@ describe('Workflow By ID API Route', () => {
describe('Error handling', () => {
it.concurrent('should handle database errors gracefully', async () => {
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({
- user: { id: 'user-123' },
- }),
- }))
+ mockGetSession.mockResolvedValue({
+ user: { id: 'user-123' },
+ })
- mockGetWorkflowById.mockRejectedValueOnce(new Error('Database connection timeout'))
+ mockGetWorkflowById.mockRejectedValue(new Error('Database connection timeout'))
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
- const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(500)
const data = await response.json()
expect(data.error).toBe('Internal server error')
- expect(mockLogger.error).toHaveBeenCalled()
})
})
})
diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts
index 8cf89b337a..e62c245da1 100644
--- a/apps/sim/app/api/workflows/[id]/route.ts
+++ b/apps/sim/app/api/workflows/[id]/route.ts
@@ -152,7 +152,23 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ data: finalWorkflowData }, { status: 200 })
}
- return NextResponse.json({ error: 'Workflow has no normalized data' }, { status: 400 })
+
+ const emptyWorkflowData = {
+ ...workflowData,
+ state: {
+ deploymentStatuses: {},
+ blocks: {},
+ edges: [],
+ loops: {},
+ parallels: {},
+ lastSaved: Date.now(),
+ isDeployed: workflowData.isDeployed || false,
+ deployedAt: workflowData.deployedAt,
+ },
+ variables: workflowData.variables || {},
+ }
+
+ return NextResponse.json({ data: emptyWorkflowData }, { status: 200 })
} catch (error: any) {
const elapsed = Date.now() - startTime
logger.error(`[${requestId}] Error fetching workflow ${workflowId} after ${elapsed}ms`, error)
diff --git a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts
index 4616cf54bc..ecbb92a977 100644
--- a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts
+++ b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts
@@ -1,6 +1,5 @@
import { NextRequest } from 'next/server'
import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { mockAuth, mockConsoleLogger } from '@/app/api/__test-utils__/utils'
/**
* Tests for workspace invitation by ID API route
@@ -9,137 +8,154 @@ import { mockAuth, mockConsoleLogger } from '@/app/api/__test-utils__/utils'
* @vitest-environment node
*/
-describe('Workspace Invitation [invitationId] API Route', () => {
- const mockUser = {
- id: 'user-123',
- email: 'test@example.com',
- name: 'Test User',
- }
-
- const mockWorkspace = {
- id: 'workspace-456',
- name: 'Test Workspace',
- }
-
- const mockInvitation = {
- id: 'invitation-789',
- workspaceId: 'workspace-456',
- email: 'invited@example.com',
- inviterId: 'inviter-321',
- status: 'pending',
- token: 'token-abc123',
- permissions: 'read',
- expiresAt: new Date(Date.now() + 86400000), // 1 day from now
- createdAt: new Date(),
- updatedAt: new Date(),
- }
-
- let mockDbResults: any[] = []
- let mockGetSession: any
- let mockHasWorkspaceAdminAccess: any
- let mockTransaction: any
-
- beforeEach(async () => {
- vi.resetModules()
- vi.resetAllMocks()
-
- mockDbResults = []
- mockConsoleLogger()
- mockAuth(mockUser)
-
- vi.doMock('crypto', () => ({
- randomUUID: vi.fn().mockReturnValue('mock-uuid-1234'),
- }))
-
- mockGetSession = vi.fn()
- vi.doMock('@/lib/auth', () => ({
- getSession: mockGetSession,
- }))
-
- mockHasWorkspaceAdminAccess = vi.fn()
- vi.doMock('@/lib/workspaces/permissions/utils', () => ({
- hasWorkspaceAdminAccess: mockHasWorkspaceAdminAccess,
- }))
-
- vi.doMock('@/lib/core/config/env', () => {
- const mockEnv = {
- NEXT_PUBLIC_APP_URL: 'https://test.sim.ai',
- BILLING_ENABLED: false,
- }
- return {
- env: mockEnv,
- isTruthy: (value: string | boolean | number | undefined) =>
- typeof value === 'string'
- ? value.toLowerCase() === 'true' || value === '1'
- : Boolean(value),
- getEnv: (variable: string) =>
- mockEnv[variable as keyof typeof mockEnv] ?? process.env[variable],
- }
- })
-
- mockTransaction = vi.fn()
- const mockDbChain = {
- select: vi.fn().mockReturnThis(),
- from: vi.fn().mockReturnThis(),
- where: vi.fn().mockReturnThis(),
- then: vi.fn().mockImplementation((callback: any) => {
- const result = mockDbResults.shift() || []
- return callback ? callback(result) : Promise.resolve(result)
- }),
- insert: vi.fn().mockReturnThis(),
+const mockGetSession = vi.fn()
+const mockHasWorkspaceAdminAccess = vi.fn()
+
+let dbSelectResults: any[] = []
+let dbSelectCallIndex = 0
+
+const mockDbSelect = vi.fn().mockImplementation(() => ({
+ from: vi.fn().mockReturnThis(),
+ where: vi.fn().mockReturnThis(),
+ then: vi.fn().mockImplementation((callback: (rows: any[]) => any) => {
+ const result = dbSelectResults[dbSelectCallIndex] || []
+ dbSelectCallIndex++
+ return Promise.resolve(callback ? callback(result) : result)
+ }),
+}))
+
+const mockDbInsert = vi.fn().mockImplementation(() => ({
+ values: vi.fn().mockResolvedValue(undefined),
+}))
+
+const mockDbUpdate = vi.fn().mockImplementation(() => ({
+ set: vi.fn().mockReturnThis(),
+ where: vi.fn().mockResolvedValue(undefined),
+}))
+
+const mockDbDelete = vi.fn().mockImplementation(() => ({
+ where: vi.fn().mockResolvedValue(undefined),
+}))
+
+const mockDbTransaction = vi.fn().mockImplementation(async (callback: any) => {
+ await callback({
+ insert: vi.fn().mockReturnValue({
values: vi.fn().mockResolvedValue(undefined),
- update: vi.fn().mockReturnThis(),
- set: vi.fn().mockReturnThis(),
- delete: vi.fn().mockReturnThis(),
- transaction: mockTransaction,
- }
-
- vi.doMock('@sim/db', () => ({
- db: mockDbChain,
- }))
-
- vi.doMock('@sim/db/schema', () => ({
- workspaceInvitation: {
- id: 'id',
- workspaceId: 'workspaceId',
- email: 'email',
- inviterId: 'inviterId',
- status: 'status',
- token: 'token',
- permissions: 'permissions',
- expiresAt: 'expiresAt',
- },
- workspace: {
- id: 'id',
- name: 'name',
- },
- user: {
- id: 'id',
- email: 'email',
- },
- permissions: {
- id: 'id',
- entityType: 'entityType',
- entityId: 'entityId',
- userId: 'userId',
- permissionType: 'permissionType',
- },
- }))
-
- vi.doMock('drizzle-orm', () => ({
- eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
- and: vi.fn((...args) => ({ type: 'and', args })),
- }))
+ }),
+ update: vi.fn().mockReturnValue({
+ set: vi.fn().mockReturnValue({
+ where: vi.fn().mockResolvedValue(undefined),
+ }),
+ }),
+ })
+})
+
+vi.mock('@/lib/auth', () => ({
+ getSession: () => mockGetSession(),
+}))
+
+vi.mock('@/lib/workspaces/permissions/utils', () => ({
+ hasWorkspaceAdminAccess: (userId: string, workspaceId: string) =>
+ mockHasWorkspaceAdminAccess(userId, workspaceId),
+}))
+
+vi.mock('@/lib/logs/console/logger', () => ({
+ createLogger: vi.fn().mockReturnValue({
+ debug: vi.fn(),
+ info: vi.fn(),
+ warn: vi.fn(),
+ error: vi.fn(),
+ }),
+}))
+
+vi.mock('@/lib/core/utils/urls', () => ({
+ getBaseUrl: vi.fn().mockReturnValue('https://test.sim.ai'),
+}))
+
+vi.mock('@sim/db', () => ({
+ db: {
+ select: () => mockDbSelect(),
+ insert: (table: any) => mockDbInsert(table),
+ update: (table: any) => mockDbUpdate(table),
+ delete: (table: any) => mockDbDelete(table),
+ transaction: (callback: any) => mockDbTransaction(callback),
+ },
+}))
+
+vi.mock('@sim/db/schema', () => ({
+ workspaceInvitation: {
+ id: 'id',
+ workspaceId: 'workspaceId',
+ email: 'email',
+ inviterId: 'inviterId',
+ status: 'status',
+ token: 'token',
+ permissions: 'permissions',
+ expiresAt: 'expiresAt',
+ },
+ workspace: {
+ id: 'id',
+ name: 'name',
+ },
+ user: {
+ id: 'id',
+ email: 'email',
+ },
+ permissions: {
+ id: 'id',
+ entityType: 'entityType',
+ entityId: 'entityId',
+ userId: 'userId',
+ permissionType: 'permissionType',
+ },
+}))
+
+vi.mock('drizzle-orm', () => ({
+ eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
+ and: vi.fn((...args) => ({ type: 'and', args })),
+}))
+
+vi.mock('crypto', () => ({
+ randomUUID: vi.fn().mockReturnValue('mock-uuid-1234'),
+}))
+
+import { DELETE, GET } from './route'
+
+const mockUser = {
+ id: 'user-123',
+ email: 'test@example.com',
+ name: 'Test User',
+}
+
+const mockWorkspace = {
+ id: 'workspace-456',
+ name: 'Test Workspace',
+}
+
+const mockInvitation = {
+ id: 'invitation-789',
+ workspaceId: 'workspace-456',
+ email: 'invited@example.com',
+ inviterId: 'inviter-321',
+ status: 'pending',
+ token: 'token-abc123',
+ permissions: 'read',
+ expiresAt: new Date(Date.now() + 86400000), // 1 day from now
+ createdAt: new Date(),
+ updatedAt: new Date(),
+}
+
+describe('Workspace Invitation [invitationId] API Route', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ dbSelectResults = []
+ dbSelectCallIndex = 0
})
describe('GET /api/workspaces/invitations/[invitationId]', () => {
it('should return invitation details when called without token', async () => {
- const { GET } = await import('./route')
-
mockGetSession.mockResolvedValue({ user: mockUser })
-
- mockDbResults.push([mockInvitation])
- mockDbResults.push([mockWorkspace])
+ dbSelectResults = [[mockInvitation], [mockWorkspace]]
const request = new NextRequest('http://localhost/api/workspaces/invitations/invitation-789')
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -157,8 +173,6 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should redirect to login when unauthenticated with token', async () => {
- const { GET } = await import('./route')
-
mockGetSession.mockResolvedValue(null)
const request = new NextRequest(
@@ -174,27 +188,30 @@ describe('Workspace Invitation [invitationId] API Route', () => {
)
})
- it('should accept invitation when called with valid token', async () => {
- const { GET } = await import('./route')
+ it('should return 401 when unauthenticated without token', async () => {
+ mockGetSession.mockResolvedValue(null)
+ const request = new NextRequest('http://localhost/api/workspaces/invitations/invitation-789')
+ const params = Promise.resolve({ invitationId: 'invitation-789' })
+
+ const response = await GET(request, { params })
+ const data = await response.json()
+
+ expect(response.status).toBe(401)
+ expect(data).toEqual({ error: 'Unauthorized' })
+ })
+
+ it('should accept invitation when called with valid token', async () => {
mockGetSession.mockResolvedValue({
user: { ...mockUser, email: 'invited@example.com' },
})
- mockDbResults.push([mockInvitation])
- mockDbResults.push([mockWorkspace])
- mockDbResults.push([{ ...mockUser, email: 'invited@example.com' }])
- mockDbResults.push([])
-
- mockTransaction.mockImplementation(async (callback: any) => {
- await callback({
- insert: vi.fn().mockReturnThis(),
- values: vi.fn().mockResolvedValue(undefined),
- update: vi.fn().mockReturnThis(),
- set: vi.fn().mockReturnThis(),
- where: vi.fn().mockResolvedValue(undefined),
- })
- })
+ dbSelectResults = [
+ [mockInvitation], // invitation lookup
+ [mockWorkspace], // workspace lookup
+ [{ ...mockUser, email: 'invited@example.com' }], // user lookup
+ [], // existing permission check (empty = no existing)
+ ]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/token-abc123?token=token-abc123'
@@ -208,8 +225,6 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should redirect to error page when invitation expired', async () => {
- const { GET } = await import('./route')
-
mockGetSession.mockResolvedValue({
user: { ...mockUser, email: 'invited@example.com' },
})
@@ -219,8 +234,7 @@ describe('Workspace Invitation [invitationId] API Route', () => {
expiresAt: new Date(Date.now() - 86400000), // 1 day ago
}
- mockDbResults.push([expiredInvitation])
- mockDbResults.push([mockWorkspace])
+ dbSelectResults = [[expiredInvitation], [mockWorkspace]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/token-abc123?token=token-abc123'
@@ -236,15 +250,15 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should redirect to error page when email mismatch', async () => {
- const { GET } = await import('./route')
-
mockGetSession.mockResolvedValue({
user: { ...mockUser, email: 'wrong@example.com' },
})
- mockDbResults.push([mockInvitation])
- mockDbResults.push([mockWorkspace])
- mockDbResults.push([{ ...mockUser, email: 'wrong@example.com' }])
+ dbSelectResults = [
+ [mockInvitation],
+ [mockWorkspace],
+ [{ ...mockUser, email: 'wrong@example.com' }],
+ ]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/token-abc123?token=token-abc123'
@@ -258,19 +272,29 @@ describe('Workspace Invitation [invitationId] API Route', () => {
'https://test.sim.ai/invite/invitation-789?error=email-mismatch'
)
})
+
+ it('should return 404 when invitation not found', async () => {
+ mockGetSession.mockResolvedValue({ user: mockUser })
+ dbSelectResults = [[]] // Empty result
+
+ const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent')
+ const params = Promise.resolve({ invitationId: 'non-existent' })
+
+ const response = await GET(request, { params })
+ const data = await response.json()
+
+ expect(response.status).toBe(404)
+ expect(data).toEqual({ error: 'Invitation not found or has expired' })
+ })
})
describe('DELETE /api/workspaces/invitations/[invitationId]', () => {
it('should return 401 when user is not authenticated', async () => {
- const { DELETE } = await import('./route')
-
mockGetSession.mockResolvedValue(null)
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
- {
- method: 'DELETE',
- }
+ { method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -282,11 +306,8 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should return 404 when invitation does not exist', async () => {
- const { DELETE } = await import('./route')
-
mockGetSession.mockResolvedValue({ user: mockUser })
-
- mockDbResults.push([])
+ dbSelectResults = [[]]
const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent', {
method: 'DELETE',
@@ -301,18 +322,13 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should return 403 when user lacks admin access', async () => {
- const { DELETE } = await import('./route')
-
mockGetSession.mockResolvedValue({ user: mockUser })
mockHasWorkspaceAdminAccess.mockResolvedValue(false)
-
- mockDbResults.push([mockInvitation])
+ dbSelectResults = [[mockInvitation]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
- {
- method: 'DELETE',
- }
+ { method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -325,19 +341,15 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should return 400 when trying to delete non-pending invitation', async () => {
- const { DELETE } = await import('./route')
-
mockGetSession.mockResolvedValue({ user: mockUser })
mockHasWorkspaceAdminAccess.mockResolvedValue(true)
const acceptedInvitation = { ...mockInvitation, status: 'accepted' }
- mockDbResults.push([acceptedInvitation])
+ dbSelectResults = [[acceptedInvitation]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
- {
- method: 'DELETE',
- }
+ { method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -349,18 +361,13 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should successfully delete pending invitation when user has admin access', async () => {
- const { DELETE } = await import('./route')
-
mockGetSession.mockResolvedValue({ user: mockUser })
mockHasWorkspaceAdminAccess.mockResolvedValue(true)
-
- mockDbResults.push([mockInvitation])
+ dbSelectResults = [[mockInvitation]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
- {
- method: 'DELETE',
- }
+ { method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -370,61 +377,5 @@ describe('Workspace Invitation [invitationId] API Route', () => {
expect(response.status).toBe(200)
expect(data).toEqual({ success: true })
})
-
- it('should return 500 when database error occurs', async () => {
- vi.resetModules()
-
- const mockErrorDb = {
- select: vi.fn().mockReturnThis(),
- from: vi.fn().mockReturnThis(),
- where: vi.fn().mockReturnThis(),
- then: vi.fn().mockRejectedValue(new Error('Database connection failed')),
- }
-
- vi.doMock('@sim/db', () => ({ db: mockErrorDb }))
- vi.doMock('@/lib/auth', () => ({
- getSession: vi.fn().mockResolvedValue({ user: mockUser }),
- }))
- vi.doMock('@/lib/workspaces/permissions/utils', () => ({
- hasWorkspaceAdminAccess: vi.fn(),
- }))
- vi.doMock('@/lib/core/config/env', () => {
- const mockEnv = {
- NEXT_PUBLIC_APP_URL: 'https://test.sim.ai',
- BILLING_ENABLED: false,
- }
- return {
- env: mockEnv,
- isTruthy: (value: string | boolean | number | undefined) =>
- typeof value === 'string'
- ? value.toLowerCase() === 'true' || value === '1'
- : Boolean(value),
- getEnv: (variable: string) =>
- mockEnv[variable as keyof typeof mockEnv] ?? process.env[variable],
- }
- })
- vi.doMock('@sim/db/schema', () => ({
- workspaceInvitation: { id: 'id' },
- }))
- vi.doMock('drizzle-orm', () => ({
- eq: vi.fn(),
- }))
-
- const { DELETE } = await import('./route')
-
- const request = new NextRequest(
- 'http://localhost/api/workspaces/invitations/invitation-789',
- {
- method: 'DELETE',
- }
- )
- const params = Promise.resolve({ invitationId: 'invitation-789' })
-
- const response = await DELETE(request, { params })
- const data = await response.json()
-
- expect(response.status).toBe(500)
- expect(data).toEqual({ error: 'Failed to delete invitation' })
- })
})
})
diff --git a/apps/sim/app/workspace/[workspaceId]/layout.tsx b/apps/sim/app/workspace/[workspaceId]/layout.tsx
index 18e9d21ce1..4d013ee8ad 100644
--- a/apps/sim/app/workspace/[workspaceId]/layout.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/layout.tsx
@@ -14,12 +14,14 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
-
-
-
-
{children}
-
-
+
+
+
+
+
+ {children}
+
+
>
diff --git a/apps/sim/app/workspace/[workspaceId]/templates/layout.tsx b/apps/sim/app/workspace/[workspaceId]/templates/layout.tsx
index b1177e3651..f21d673fca 100644
--- a/apps/sim/app/workspace/[workspaceId]/templates/layout.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/templates/layout.tsx
@@ -1,3 +1,7 @@
export default function TemplatesLayout({ children }: { children: React.ReactNode }) {
- return
{children}
+ return (
+
+
{children}
+
+ )
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx
index 9d5eec3f5b..a236fcd75b 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx
@@ -36,6 +36,12 @@ const SCOPE_DESCRIPTIONS: Record = {
'https://www.googleapis.com/auth/forms.responses.readonly': 'View responses to your Google Forms',
'https://www.googleapis.com/auth/ediscovery': 'Access Google Vault for eDiscovery',
'https://www.googleapis.com/auth/devstorage.read_only': 'Read files from Google Cloud Storage',
+ 'https://www.googleapis.com/auth/admin.directory.group': 'Manage Google Workspace groups',
+ 'https://www.googleapis.com/auth/admin.directory.group.member':
+ 'Manage Google Workspace group memberships',
+ 'https://www.googleapis.com/auth/admin.directory.group.readonly': 'View Google Workspace groups',
+ 'https://www.googleapis.com/auth/admin.directory.group.member.readonly':
+ 'View Google Workspace group memberships',
'read:confluence-content.all': 'Read all Confluence content',
'read:confluence-space.summary': 'Read Confluence space information',
'read:space:confluence': 'View Confluence spaces',
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx
index 06820c35ee..0ef466bf25 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx
@@ -2,7 +2,7 @@
import type { ReactNode } from 'react'
import { splitReferenceSegment } from '@/lib/workflows/sanitization/references'
-import { REFERENCE } from '@/executor/consts'
+import { REFERENCE } from '@/executor/constants'
import { createCombinedPattern } from '@/executor/utils/reference-validation'
import { normalizeBlockName } from '@/stores/workflows/utils'
@@ -67,7 +67,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
if (matchText.startsWith(REFERENCE.ENV_VAR_START)) {
nodes.push(
-
+
{matchText}
)
@@ -77,7 +77,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
if (split && shouldHighlightReference(split.reference)) {
pushPlainText(split.leading)
nodes.push(
-
+
{split.reference}
)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx
index 45af409b14..0e06f45350 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx
@@ -16,7 +16,7 @@ import { useTriggerConfigAggregation } from '@/hooks/use-trigger-config-aggregat
import { useWebhookManagement } from '@/hooks/use-webhook-management'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { getTrigger, isTriggerValid } from '@/triggers'
-import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/consts'
+import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants'
import { ShortInput } from '../short-input/short-input'
const logger = createLogger('TriggerSave')
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx
index 36584ec458..daeb57f9f7 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx
@@ -21,6 +21,7 @@ import {
import { useShallow } from 'zustand/react/shallow'
import {
Button,
+ Code,
Input,
Popover,
PopoverContent,
@@ -28,7 +29,6 @@ import {
PopoverScrollArea,
PopoverTrigger,
Tooltip,
- VirtualizedCodeViewer,
} from '@/components/emcn'
import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
import { createCommands } from '@/app/workspace/[workspaceId]/utils/commands-utils'
@@ -258,7 +258,7 @@ const OutputCodeContent = React.memo(function OutputCodeContent({
contentRef,
}: OutputCodeContentProps) {
return (
-
)
})
@@ -578,7 +579,7 @@ export function Terminal() {
}, [matchCount])
/**
- * Handles match count change from VirtualizedCodeViewer.
+ * Handles match count change from Code.Viewer.
*/
const handleMatchCountChange = useCallback((count: number) => {
setMatchCount(count)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
index bf54a375c8..bdd29fc9de 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
@@ -5,6 +5,7 @@ import { Badge } from '@/components/emcn/components/badge/badge'
import { Tooltip } from '@/components/emcn/components/tooltip/tooltip'
import { getEnv, isTruthy } from '@/lib/core/config/env'
import { cn } from '@/lib/core/utils/cn'
+import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'
import { createMcpToolId } from '@/lib/mcp/utils'
import { getProviderIdFromServiceId } from '@/lib/oauth'
@@ -242,6 +243,7 @@ const SubBlockRow = ({
rawValue,
workspaceId,
workflowId,
+ blockId,
allSubBlockValues,
}: {
title: string
@@ -250,6 +252,7 @@ const SubBlockRow = ({
rawValue?: unknown
workspaceId?: string
workflowId?: string
+ blockId?: string
allSubBlockValues?: Record
}) => {
const getStringValue = useCallback(
@@ -353,6 +356,17 @@ const SubBlockRow = ({
return tool?.name ?? null
}, [subBlock?.type, rawValue, mcpToolsData])
+ const webhookUrlDisplayValue = useMemo(() => {
+ if (subBlock?.id !== 'webhookUrlDisplay' || !blockId) {
+ return null
+ }
+ const baseUrl = getBaseUrl()
+ const triggerPath = allSubBlockValues?.triggerPath?.value as string | undefined
+ return triggerPath
+ ? `${baseUrl}/api/webhooks/trigger/${triggerPath}`
+ : `${baseUrl}/api/webhooks/trigger/${blockId}`
+ }, [subBlock?.id, blockId, allSubBlockValues])
+
const allVariables = useVariablesStore((state) => state.variables)
const variablesDisplayValue = useMemo(() => {
@@ -393,6 +407,7 @@ const SubBlockRow = ({
workflowSelectionName ||
mcpServerDisplayName ||
mcpToolDisplayName ||
+ webhookUrlDisplayValue ||
selectorDisplayName
const displayValue = maskedValue || hydratedName || (isSelectorType && value ? '-' : value)
@@ -1002,6 +1017,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
rawValue={rawValue}
workspaceId={workspaceId}
workflowId={currentWorkflowId}
+ blockId={id}
allSubBlockValues={subBlockState}
/>
)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/layout.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/layout.tsx
index 368f2a2bab..3f5daa7650 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/layout.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/layout.tsx
@@ -2,7 +2,7 @@ import { ErrorBoundary } from '@/app/workspace/[workspaceId]/w/[workflowId]/comp
export default function WorkflowLayout({ children }: { children: React.ReactNode }) {
return (
-
+ {children}
)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
index 896cbffef4..f4346a73b0 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
@@ -40,7 +40,7 @@ import {
} from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
import { useSocket } from '@/app/workspace/providers/socket-provider'
import { getBlock } from '@/blocks'
-import { isAnnotationOnlyBlock } from '@/executor/consts'
+import { isAnnotationOnlyBlock } from '@/executor/constants'
import { useWorkspaceEnvironment } from '@/hooks/queries/environment'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useStreamCleanup } from '@/hooks/use-stream-cleanup'
@@ -1295,14 +1295,18 @@ const WorkflowContent = React.memo(() => {
return
}
+ // Check if we encountered an error loading this specific workflow to prevent infinite retries
+ const hasLoadError = hydration.phase === 'error' && hydration.workflowId === currentId
+
// Check if we need to load the workflow state:
// 1. Different workflow than currently active
// 2. Same workflow but hydration phase is not 'ready' (e.g., after a quick refresh)
const needsWorkflowLoad =
- activeWorkflowId !== currentId ||
- (activeWorkflowId === currentId &&
- hydration.phase !== 'ready' &&
- hydration.phase !== 'state-loading')
+ !hasLoadError &&
+ (activeWorkflowId !== currentId ||
+ (activeWorkflowId === currentId &&
+ hydration.phase !== 'ready' &&
+ hydration.phase !== 'state-loading'))
if (needsWorkflowLoad) {
const { clearDiff } = useWorkflowDiffStore.getState()
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/footer-navigation/footer-navigation.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/footer-navigation/footer-navigation.tsx
index b973e85d1a..1c6dc3d60a 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/footer-navigation/footer-navigation.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/footer-navigation/footer-navigation.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useCallback, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'
import clsx from 'clsx'
import { Database, HelpCircle, Layout, LibraryBig, Settings } from 'lucide-react'
import Link from 'next/link'
@@ -33,6 +33,13 @@ export function FooterNavigation() {
const [isHelpModalOpen, setIsHelpModalOpen] = useState(false)
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false)
+ // Listen for external events to open modals
+ useEffect(() => {
+ const handleOpenHelpModal = () => setIsHelpModalOpen(true)
+ window.addEventListener('open-help-modal', handleOpenHelpModal)
+ return () => window.removeEventListener('open-help-modal', handleOpenHelpModal)
+ }, [])
+
const navigationItems: FooterNavigationItem[] = [
{
id: 'logs',
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/shared/usage-header.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/shared/usage-header.tsx
index 371395d6c4..090664a126 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/shared/usage-header.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/shared/usage-header.tsx
@@ -20,7 +20,10 @@ interface UsageHeaderProps {
progressValue?: number
seatsText?: string
isBlocked?: boolean
+ blockedReason?: 'payment_failed' | 'dispute' | null
+ blockedByOrgOwner?: boolean
onResolvePayment?: () => void
+ onContactSupport?: () => void
status?: 'ok' | 'warning' | 'exceeded' | 'blocked'
percentUsed?: number
}
@@ -37,7 +40,10 @@ export function UsageHeader({
progressValue,
seatsText,
isBlocked,
+ blockedReason,
+ blockedByOrgOwner,
onResolvePayment,
+ onContactSupport,
status,
percentUsed,
}: UsageHeaderProps) {
@@ -114,7 +120,24 @@ export function UsageHeader({
{/* Status messages */}
- {isBlocked && (
+ {isBlocked && blockedReason === 'dispute' && (
+
+
+ Account frozen. Please contact support to resolve this issue.
+
+ {onContactSupport && (
+
+ )}
+
)}
- {/* Cost Breakdown */}
- {/* TODO: Re-enable CostBreakdown component in the next billing period
- once sufficient copilot cost data has been collected for accurate display.
- Currently hidden to avoid confusion with initial zero values.
- */}
- {/*
- {subscriptionData?.usage && typeof subscriptionData.usage.copilotCost === 'number' && (
-
-
-
- )}
- */}
-
{/* Team Member Notice */}
{permissions.showTeamMemberView && (