Agree on the partnership before you commit to it
Find out what you and your business partner are actually agreeing to. Each of you answers on your own, you see where your expectations differ, and you build a shared brief to take to your lawyer.
Tether answers calls and chats, books real appointments into your Google Calendar, and texts you what it cannot handle.
BusinessGitHub @Lam-ThaiOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
An autonomous AI voice receptionist for local businesses that handles missed calls, answers FAQs, and books calendar appointments. Engineered for zero AI operational costs using Twilio and the Google Gemini API (Free Tier). Built on a modern, type-safe stack featuring Next.js, TypeScript, Drizzle ORM, and PostgreSQL.
Taken from the repository README
Loading the full README.
Read the full README ↗AI receptionist for local service businesses. See PRD.md for the product and CLAUDE.md for engineering conventions.
Requirements:
.nvmrc pins 24)npm i -g pnpm@12; the exact version is pinned in package.json → packageManager)uv for apps/apidocker compose), or a Neon dev branchpnpm install # installs JS/TS dependencies and the Husky git hooks
docker compose up --wait # starts local Postgres and waits for its healthcheck (apps/api/.env's DATABASE_URL points here by default)
cp apps/api/.env.example apps/api/.env # fill in DATABASE_URL etc.
cp apps/web/.env.example apps/web/.env.local
uv sync --directory apps/api # installs Python dependencies
uv run --directory apps/api alembic upgrade head # apply all migrations
pnpm dev # runs apps/web (Next.js) and apps/api (uvicorn --reload) together
Open http://localhost:3000 for the landing page, and http://localhost:3000/status for API: healthy · Database: connected.
apps/api won't start without the Stripe settings (see apps/api/.env.example). Use test-mode keys only; a live key is rejected unless ENVIRONMENT=production.
In the Stripe Dashboard (test mode), copy the secret key into STRIPE_SECRET_KEY, and create a recurring monthly price; put its id in STRIPE_PRICE_ID.
Set APP_BASE_URL=http://localhost:3000.
Forward webhooks to the local API and copy the whsec_... it prints into STRIPE_WEBHOOK_SECRET:
stripe login
stripe listen --forward-to localhost:8000/api/v1/webhooks/stripe
Pay with test card 4242 4242 4242 4242 (any future date, any CVC). The free trial needs no card.
The test chat at /dashboard/agent calls real Gemini through apps/api. Create a key in Google AI Studio and set GEMINI_API_KEY in apps/api/.env (see apps/api/.env.example for the model and timeout settings). Without a key the API still starts locally, but every reply is the "having trouble responding" fallback. Keep ENVIRONMENT=local locally; production requires the key and is only for the deployed API.
Optional live smoke test against real Gemini:
uv run --directory apps/api pytest -m live
Other root commands:
pnpm test # runs Vitest across all projects (tooling + apps/web)
pnpm typecheck # strict TypeScript check of root tooling
See the root CLAUDE.md for the full command reference (per-app lint/typecheck/test/build commands).
git push runs the Husky pre-push hook (.husky/pre-push) and is rejected if any check fails:
pnpm test, the Vitest suite.apps/api/scripts/test.sh: the API suite with CI-like settings (your .env is not read, so no real key or remote database reaches a test), migrations, then pytest in parallel. It needs the local Postgres (docker compose up -d, which on Windows needs WSL) and refuses a remote TEST_DATABASE_URL unless TEST_DATABASE_ALLOW_REMOTE=1.Both halves together take about 40 seconds.
The suite includes tooling/repo-hygiene.test.ts, which fails if any env file other than .env.example is tracked (.env*, .envrc, *.env).
If a git GUI client reports pnpm: command not found from the hook, it isn't loading your shell's PATH (common with nvm/fnm). Add the PATH setup to ~/.config/husky/init.sh, which Husky sources before every hook.
In a real emergency you can skip the hook with git push --no-verify. CI still has to pass before merge.
CI (.github/workflows/api.yml, .github/workflows/web.yml) lints, type-checks, tests, and builds both apps on every PR. Actually deploying is a one-time manual setup:
postgresql://...). You'll set this as DATABASE_URL in the API's Vercel project — the backend normalizes it to the asyncpg driver automatically.apps/api): new project, Root Directory = apps/api. Vercel auto-detects the FastAPI app from main.py (no vercel.json needed). Env vars: DATABASE_URL (from Neon), CORS_ORIGINS (the web app's deployed URL, once known), ENVIRONMENT=production, APP_VERSION (e.g. a git SHA or release tag).apps/web): new project, Root Directory = apps/web. Env vars: NEXT_PUBLIC_API_BASE_URL (the API project's deployed URL from step 2).CORS_ORIGINS with the web app's actual deployed URL from step 3, then redeploy the API.uv run alembic upgrade head against the Neon database once (locally, with DATABASE_URL pointed at Neon) before relying on it — migrations are never run automatically on startup or in CI against Neon.Once both are deployed, the web URL should show API: healthy · Database: connected.