An AI-powered workflow automation platform — create, record, and run desktop & browser workflows using voice, chat, or visual recording, powered by the EchoPrism vision-language agent.
Explore the docs »
View Demo
·
Report Bug
·
Request Feature
Table of Contents
Echo is an AI-powered workflow automation platform. Create and edit desktop and browser workflows (from recordings, voice, or chat), then run them via the EchoPrism vision-language agent — which executes steps (navigate, click, type, scroll) on your desktop. Use the web dashboard to manage workflows and runs, and the Electron desktop app for voice-driven control and running your workflows locally.
To run the full stack locally or deploy from scratch, follow the phases below.
Install the following tools before proceeding:
- Node.js 18+ — nodejs.org or
nvm install 18 - pnpm —
npm install -g pnpm - Python 3.11+ — python.org or
pyenv install 3.11 - Docker — for building and deploying images
- gcloud CLI — Install guide
gcloud auth login gcloud auth application-default login
- Firebase CLI
npm install -g firebase-tools firebase login
- Doppler (optional but recommended) — for secrets management
brew install dopplerhq/cli/doppler doppler login
Doc index: https://auth0.com/llms.txt. Echo can use Auth0 Token Vault so third-party API tokens stay in Auth0: users link Auth0 once (Firebase remains Echo login), then connect Slack, GitHub, or Google via Connected Accounts; the backend exchanges an Auth0 refresh token for a short-lived provider token (refresh token exchange).
Official (Auth0 Docs): Connected Accounts for Token Vault — when Connected Accounts is enabled for a connection, Auth0 uses /me/v1/connected-accounts (My Account API) to store tokens in the vault, not the social /authorize login flow alone; identities vs connected_accounts on the user profile. Configure Token Vault, My Account API, MRRT.
Auth0 AI (product): Token Vault overview, Google integration, Call others’ APIs quickstart. Echo follows the “applications with refresh tokens” pattern (Regular Web Application + stored Auth0 refresh token), not the SPA “access-token-only” Token Vault variant.
| Authentication (Dashboard) | Connected Accounts for Token Vault | Behavior (per Auth0) |
|---|---|---|
| On | Off | /authorize login only; identities. |
| Off | On | My Account Connected Accounts flow only; vault tokens; connection not a login IdP. |
| On | On | Both login and vault; Echo Connect defaults to My Account connect/complete (set AUTH0_VAULT_USE_MY_ACCOUNT_CONNECT=0 for legacy /authorize Connect only). |
Important: Link Auth0 stores an Auth0 refresh token on the user document. Provider tokens (Google, etc.) are written to Token Vault only after the user completes a Connect account flow for that provider; Universal Login alone does not populate the vault (how it works).
/authorize vs My Account Connected Accounts: Auth0 documents Connected Accounts for Token Vault using the My Account API (POST https://<tenant>/me/v1/connected-accounts/connect, then …/complete). Echo’s default Connect path uses that flow (requires My Account API + MRRT for audience https://<tenant>/me/). Set AUTH0_VAULT_USE_MY_ACCOUNT_CONNECT=0 only if you need the legacy /authorize?connection=<provider> path (requires the connection’s Purpose to include Authentication).
Universal Login (/authorize) |
Connected Accounts (/me/v1/connected-accounts/*) |
|
|---|---|---|
| Purpose | Identify the user (session). | Delegate access; Token Vault storage. |
| Auth0 storage | Session / identities. | connected_accounts (vault). |
| Agent / Google APIs | Not sufficient alone for vault RT. | connect → user consents → complete seals the vault. |
My Account path in Echo: connect returns connect_uri + auth_session; after redirect, GET /api/auth0/callback?connect_code=…&state=… runs …/connected-accounts/complete server-side (with PKCE code_verifier). Skipping callback (or a broken state) means the vault stays empty. The backend exchanges the user’s Auth0 refresh token for a My Account API access token using scopes openid profile offline_access create:me:connected_accounts read:me:connected_accounts delete:me:connected_accounts — align MRRT / Application Access on Auth0 My Account API with those Connected Accounts permissions. For Connect Google, Echo omits upstream scopes in the My Account request unless AUTH0_MY_ACCOUNT_GOOGLE_SCOPES is set, so Auth0 requests the Google permissions you enabled under Social → Google in the Dashboard (and your GCP OAuth consent screen). authorization_params uses prompt=consent only. Optional env override: comma-separated scope URLs; do not add offline_access unless it is on your GCP consent screen—use Auth0 Offline Access on the connection for federated refresh tokens.
- In Auth0: create a Regular Web Application, register an API (audience), enable Token Vault / Connected Accounts on your Social connections (Slack, GitHub, and Google), and add callback URL
https://<your-backend>/api/auth0/callback(local:http://localhost:8000/api/auth0/callback). - Google (Gmail / Calendar / Drive-style APIs): In Google Cloud Console, enable each API you need (e.g. Google Calendar API for
calendar_list/calendar_freebusy). → APIs & Services → Credentials, create an OAuth 2.0 Client ID (Web application). Set Authorized redirect URI tohttps://<AUTH0_DOMAIN>/login/callbackand Authorized JavaScript origin tohttps://<AUTH0_DOMAIN>(not the Echo backend). In Auth0 → Authentication → Social → Google, paste the Google client ID/secret. Under Purpose, choose Authentication and Connected Accounts for Token Vault (both)—Echo Connect Google defaults to the My Account connected-accounts flow; setAUTH0_VAULT_USE_MY_ACCOUNT_CONNECT=0only if you use legacy/authorizewithconnection=google-oauth2, which Auth0 only allows when Authentication is enabled on that connection (Connected Accounts for Token Vault alone causes The connection is not active for authentication on that legacy path). Under Permissions, enable Offline Access so Google can issue refresh tokens for Token Vault. For free/busy queries (api_callmethodcalendar_freebusy), add scopehttps://www.googleapis.com/auth/calendar.freebusyon the connection (or a broader Calendar scope). To keep Link Auth0 on email/password (so Universal Login does not offer Google), setAUTH0_LINK_CONNECTIONtoUsername-Password-Authentication(or?connection=onGET /api/auth0/link-url). Do not rely onNEXT_PUBLIC_AUTH0_LINK_CONNECTIONfor the default web app—it is deprecated (seescripts/doppler-env-reference.md). Attach the connection to your Echo application. Echo uses integration idgoogle, mapped to Auth0 connectiongoogle-oauth2by default (AUTH0_CONNECTION_GOOGLEoverrides the Auth0 connection name). - Set environment variables on the backend (and Echo Prism agent if it runs
api_callwith Firestore):AUTH0_DOMAIN,AUTH0_CLIENT_ID,AUTH0_CLIENT_SECRET,AUTH0_AUDIENCE. Users Link Auth0 with any authentication connection enabled on the Echo Regular Web Application (step 2: Google must include Authentication in Purpose for Connect to work), then Connect Google from Integrations so Token Vault stores a Google refresh token for API calls—see troubleshooting below if exchange fails. Seescripts/doppler-env-reference.md. - Local backend:
pnpm run dev:backendsetsPYTHONPATH=../agentso integration connectors load fromecho_prism_agent/integrations/. - In the web app Integrations page: Link Auth0 for integrations, then Connect (Token Vault) per provider.
- Firebase authenticates users to the Echo web app; protected API calls use a Firebase ID token.
- Auth0 is for integrations only: Link Auth0 (
GET /api/auth0/link-url) stores an Auth0 refresh token on the user document; Connect Google / Slack / GitHub runs the federated OAuth that populates Token Vault.
Link Auth0 opens Auth0 Universal Login with no connection parameter by default, so users see every connection whose Purpose includes Authentication—typically Google for this project. Optional env AUTH0_LINK_CONNECTION (or ?connection= on GET /api/auth0/link-url) forces a specific IdP for Link (use only if you need database login, a second Google connection, etc.).
Recommended operator order (Firebase login + Auth0 Token Vault, Google for Link):
- In Auth0 → Authentication → Social → Google, enable Authentication (and Connected Accounts for Token Vault for API access). Under Applications, enable your Echo Regular Web Application on the Google connection.
- In Integrations, press Connect on an integration (e.g. Google): sign in with Google at Universal Login to Link Auth0; confirm Auth0 linked.
- Finish Connect Google for Token Vault (the app may open this automatically after link). Confirm Google shows Connected.
- If Google is Token Vault only (no Authentication) and you cannot change it, keep My Account Connect enabled (default)—see troubleshooting below.
Connectors (Echo Prism agent + /api/integrations/{id}/call): Slack — list_channels, post_message. GitHub — list_repos, create_issue. Google — convenience methods: userinfo, calendar_list, calendar_freebusy, gmail_list_labels, gmail_send, drive_list_files; plus rest (alias google_rest) for any Google API on a *.googleapis.com host (verb, url, optional params, json, timeout_seconds). Enable the Google Cloud APIs and matching OAuth scopes in Auth0 (Calendar, Gmail, Drive, Sheets, Slides, Contacts, Tasks, sign-in profile — see google_scopes.py); otherwise Google returns 403. calendar_freebusy expects timeMin and timeMax (RFC3339), optional timeZone (default UTC), optional items (default [{"id":"primary"}]). gmail_send expects to (recipient email), optional subject, body or text, optional cc, bcc, html (multipart if html is set); requires Gmail.Send scope. Integrations are Auth0 Token Vault only (no classic Slack/GitHub OAuth redirect to Echo). Legacy Firestore-stored OAuth tokens are ignored unless ECHO_INTEGRATIONS_TOKEN_VAULT_ONLY=0 (see scripts/doppler-env-reference.md).
Google — maximum OAuth scope surface (Auth0 / Google Cloud): If you enable everything in the Google connection and consent screen, Echo aligns with at most the scope groups in agent/echo_prism_agent/integrations/google_scopes.py — Calendar (full / read / events / settings / add-ons), Gmail (labels through full mailbox), Drive (metadata through full Drive), Sheets, Slides, Contacts (including directory read-only), and Tasks. In practice, enable only the toggles your product needs; tokens only carry scopes the user consented to.
Troubleshooting: access_denied on return to Echo usually means the user cancelled the provider screen, or the Social connection is not enabled for your Auth0 app (Authentication → Social → [Slack/GitHub/Google] → Applications → enable your Regular Web Application).
OAuth error (invalid_request): the connection is not enabled: The Connect authorize URL uses connection=github or connection=google-oauth2 (or overrides from AUTH0_CONNECTION_*). In Auth0, open Authentication → Social → [GitHub/Google] → Applications and toggle on your Echo Regular Web Application—the same app whose Client ID matches backend AUTH0_CLIENT_ID. If your connection uses a custom name, set AUTH0_CONNECTION_GITHUB / AUTH0_CONNECTION_GOOGLE to that exact name.
OAuth error (invalid_request): The connection is not active for authentication: This applies to the legacy /authorize Connect path when AUTH0_VAULT_USE_MY_ACCOUNT_CONNECT=0. Auth0 treats /authorize with connection= as an authentication transaction, so vault-only social connections fail on that path. Option A: set Google / GitHub to Authentication and Connected Accounts for Token Vault (both on). Option B (vault-only IdPs): keep My Account Connect (default; do not set AUTH0_VAULT_USE_MY_ACCOUNT_CONNECT=0). Echo uses Auth0’s My Account API (POST https://<tenant>/me/v1/connected-accounts/connect per Connected Accounts), matching mount_connected_account_routes / start_connect_account in auth0-server-python ConnectedAccounts.md. You must activate My Account API, configure MRRT so the user’s Auth0 refresh token can request audience https://<tenant>/me/ with scopes including create:me:connected_accounts, then Link Auth0 again if needed. Optional: AUTH0_MY_ACCOUNT_GOOGLE_SCOPES (comma-separated Google scopes for the connect request).
Federated connection Refresh Token not found (401 on /oauth/token): Auth0 has no Google (etc.) federated refresh token in Token Vault for this user—even though Echo may show connected. Complete Connect after Link Auth0 (Integrations → Connect Google). If your tenant is oriented around Connected Accounts only, avoid AUTH0_VAULT_USE_MY_ACCOUNT_CONNECT=0 — the legacy /authorize?connection= path may not persist vault tokens the way the default My Account flow does (POST …/me/v1/connected-accounts/connect + callback connect_code; see troubleshooting for not active for authentication). Verify in Dashboard: User Management → Users → [user] → Connected Accounts—if empty, Token Vault has nothing to exchange yet. Also ensure Social → Google has Connected Accounts for Token Vault + Offline Access; Grant types on Echo Web include Authorization Code, Refresh Token, Token Vault. Revoke Google third-party access and re-run Connect if needed. Management API: GET /api/v2/users/{auth0_user_id}/connected-accounts.
If you see Access denied: Service not found: …, the AUTH0_AUDIENCE env var does not match any API in Auth0. In the dashboard go to APIs, open your API (or Create API), and set AUTH0_AUDIENCE to that API’s Identifier exactly (often a URL like https://echo-api — not the hex id shown in the error). Recreate the API if it was deleted, then restart the backend so the new value loads.
OAuth error (invalid_request): Client "…" is not authorized to access resource server "…": The backend sends audience= on /authorize (from AUTH0_AUDIENCE). That value must be the Identifier of an API listed under Applications → APIs in Auth0, and your Echo Regular Web Application must be authorized for that API (Applications → [your app] → APIs → toggle the API on). If you used a placeholder like https://<tenant>.auth0.com/me/, create a Custom API (APIs → Create API) with a stable Identifier (e.g. https://echo-api), authorize the app, set AUTH0_AUDIENCE to that Identifier, and restart the backend. Alternatively, temporarily unset AUTH0_AUDIENCE only for local debugging (not recommended for production Token Vault flows).
-
Go to Google Cloud Console and create or select a project with billing enabled.
-
In APIs & Services → Enable APIs, enable:
- Cloud Run API
- Cloud Scheduler API
- Firestore API
- Cloud Storage API
- Gemini API
-
Go to Cloud Storage → Buckets, create a bucket with Uniform bucket-level access, and note the name (e.g.
echo-assets-prod).
-
Go to Firebase Console and create a new project or link your existing GCP project.
-
Enable authentication: Authentication → Sign-in method → enable Email/Password and Google.
-
Create Firestore: Firestore Database → Create database → choose Native mode.
-
Register your web app: Project Settings → Your apps → Add web app (</>) and copy the config object.
-
Deploy Firestore rules from the project root:
cd firebase && firebase deploy --only firestore:rules
Use the default compute service account for Cloud Run and ensure it has:
- Firestore: Cloud Datastore User (or Firestore roles)
- Storage: Storage Object Admin
- Cloud Run Jobs: Run Jobs Executor
- Go to Google AI Studio
- Sign in, select your GCP project, and create an API key
- Copy the key — you'll need it for
GEMINI_API_KEY
Clone and install:
git clone https://github.com/JasonMun7/echo.git
cd echo
pnpm install
pnpm run install:backendOption A: Doppler (recommended)
doppler setup # select project and dev configThen run each service in a separate terminal:
# Terminal 1 – backend
pnpm run dev:backend
# Terminal 2 – frontend
pnpm run dev
# Terminal 3 – desktop app
pnpm run dev:desktop
# Terminal 4 – Echo Prism agent (LangGraph + OpenRouter + Gemini; `agent/`)
pnpm run dev:agent
# Terminal 5 – LiveKit voice worker (optional; run from repo root so `agent.*` imports resolve)
pnpm run dev:livekit-agentSet NEXT_PUBLIC_ECHO_AGENT_URL (web) and VITE_ECHO_AGENT_URL (desktop) to http://localhost:8083 in Doppler for local agent access. Set OPENROUTER_API_KEY for GUI inference (Kimi + muscle-mem); override with ECHOPRISM_MUSCLE_MODEL if needed. Install the sibling package: from agent/ run pip install -e ../muscle-mem-agent (required for the Worker, semantic verification, and tool registry). See agent/echo_prism_agent/muscle/MUSCLE_MIGRATION.md for the migration map.
Option B: .env files
# Web app
cd apps/web && cp .env.local.example .env.local
# Edit .env.local with Firebase config and NEXT_PUBLIC_API_URL=http://localhost:8000
# Backend
cd backend && cp .env.example .env
# Edit .env with ECHO_GCP_PROJECT_ID, ECHO_GCS_BUCKET, GEMINI_API_KEYLocal URLs:
- Frontend: http://localhost:3000
- Backend: http://localhost:8000
- Echo Prism agent: http://localhost:8083
Environment Variables Reference:
| Variable | Required | Description |
|---|---|---|
ECHO_GCP_PROJECT_ID |
Yes | GCP project ID |
ECHO_GCS_BUCKET |
Yes | GCS bucket name |
GEMINI_API_KEY |
Yes | Gemini API key |
NEXT_PUBLIC_API_URL |
Yes | Backend URL (web) |
NEXT_PUBLIC_ECHO_AGENT_URL |
Yes | Echo Prism agent URL (web) |
NEXT_PUBLIC_FIREBASE_* |
Yes | Firebase config (web) |
VITE_API_URL |
Yes | Backend URL (desktop) |
VITE_ECHO_AGENT_URL |
Yes | Echo Prism agent URL (desktop) |
OPENROUTER_API_KEY |
Recommended | OpenRouter key for LangGraph/UI-Tars inference |
LIVEKIT_URL |
Voice only | LiveKit server URL |
LIVEKIT_API_KEY |
Voice only | LiveKit API key |
LIVEKIT_API_SECRET |
Voice only | LiveKit API secret |
ECHO_CLOUD_RUN_REGION |
No | Default us-central1 |
See scripts/doppler-env-reference.md for the full reference.
pnpm run deploy
# or with explicit env:
GEMINI_API_KEY=your-key ECHO_GCS_BUCKET=your-bucket \
./scripts/deploy.sh YOUR_GCP_PROJECT_ID us-central1The script builds and pushes Docker images, deploys frontend and backend as Cloud Run services, and deploys the Echo Prism agent (LangGraph) to Cloud Run (pnpm run deploy:agent).
To deploy the LiveKit voice worker (optional):
pnpm run deploy:livekit-agentRequires LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_AGENT_SECRET, ECHOPRISM_AGENT_URL, and GEMINI_API_KEY.
Visit the live demo to check out our web app. Make sure to follow the instructions in our releases page to ensure the desktop app can be ran.
- Create a workflow — record a screen capture, describe steps via chat, or use voice on the desktop app
- Edit steps — review and modify the auto-generated workflow steps in the dashboard
- Run — trigger a run from the desktop app; EchoPrism executes each step via vision-language grounding
- Monitor — watch the execution and click Ctrl + Shift + V to interrupt for user steering
- Mobile app automation — Allow Echo to automate tasks on phones as well
- Fine tuning — Improve model accuracy by training on user data with Vertex AI
- Expanded integrations — Add third-party app connectors like Slack, Notion, and G-Suite
- Workflow marketplace — Create a library of community-shared automations users can install and customize
- Schedule workflows — Allow users to schedule workflows to run at specific times
- Reduce costs — Optimize OpenRouter / Gemini calls for vision steps
See the open issues for a full list of proposed features and known issues.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also open an issue with the tag "enhancement". Don't forget to give the project a star!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt for more information.
Jason Mun — jason.mun484@gmail.com · LinkedIn
Andrew Cheung — andrewcheung360@gmail.com · LinkedIn
Project Link: https://github.com/JasonMun7/echo
- OpenRouter — UI-Tars–compatible models for LangGraph inference
- LiveKit — Real-time voice and video infrastructure
- Gemini — Vision-language model powering EchoPrism
- UI-TARS — GUI agent model for automated UI interaction
- Best-README-Template


