Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ OPENAIAPI_KEY= # Get from OpenAI. Used in component generation and h
PROVIDER_KEY_SECRET=API_KEY_SECRET_FOR_LOCAL_DEVELOPMENT_ONLY
API_KEY_SECRET=API_KEY_SECRET_FOR_LOCAL_DEVELOPMENT_ONLY

PORT=3001 # Default local API port
PORT=8261 # Default local API port

## Email
RESEND_API_KEY=
Expand Down
4 changes: 2 additions & 2 deletions apps/api/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Detailed guidance for Claude Code agents working inside `apps/api`, the NestJS O
## Essential Commands

```bash
npm run dev # Start Nest in watch mode (port 3000 by default)
npm run dev # Start Nest in watch mode (port 8261 by default)
npm run build # Compile to dist/ for production
npm run start:prod # Run the compiled build
npm run generate-config # Bootstraps runtime config snapshot
Expand Down Expand Up @@ -98,7 +98,7 @@ apps/api/src
3. **Add DTOs + tests** before wiring controllers.
4. **Wire services** and ensure providers are registered inside the module.
5. **Add Swagger decorators** for every route (summary, description, auth requirements).
6. **Verify locally** with `npm run dev` and the Swagger UI at `http://localhost:3000/api`.
6. **Verify locally** with `npm run dev` and the Swagger UI at `http://localhost:8261/api`.
7. **Run lint, type-check, and tests** before committing.

## Common Pitfalls
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ async function bootstrap() {
await Sentry.close(2000).then(() => process.exit(1));
});

console.log("Starting server on port", process.env.PORT || 3000);
await app.listen(process.env.PORT || 3000);
console.log("Starting server on port", process.env.PORT || 8261);
await app.listen(process.env.PORT || 8261);
Comment thread
michaelmagan marked this conversation as resolved.
}

function configureSwagger(app: INestApplication) {
Expand Down
6 changes: 3 additions & 3 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=

## Hydra Smoketest Setup
NEXT_PUBLIC_TAMBO_API_URL=http://localhost:3001
NEXT_PUBLIC_TAMBO_API_URL=http://localhost:8261
# Get this after you setup your local environment
NEXT_PUBLIC_TAMBO_API_KEY=

Expand All @@ -59,7 +59,7 @@ GITHUB_TOKEN=
## Nextauth login stuff
# Run `openssl rand -hex 8` to generate a random secret
NEXTAUTH_SECRET=...
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_URL=http://localhost:8260
# Get these from a developer. You must use the "developer" clients here, not
# "production" clients, so that they support localhost-based redirects
GITHUB_CLIENT_ID=...
Expand All @@ -73,4 +73,4 @@ NEXT_PUBLIC_SENTRY_ORG=
NEXT_PUBLIC_SENTRY_PROJECT=
# The SENTRY_AUTH_TOKEN variable is picked up by the Sentry Build Plugin.
# It's used for authentication when uploading source maps.
SENTRY_AUTH_TOKEN=
SENTRY_AUTH_TOKEN=
2 changes: 1 addition & 1 deletion apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ npm install
npm run dev
```

The dev server runs on `http://localhost:3000`.
The dev server runs on `http://localhost:8260`.

## Commands

Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/robots.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://localhost:3000";
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://localhost:8260";

return {
rules: {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
try {
// Use hardcoded domain for production instead of dynamic headers
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://localhost:3000";
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://localhost:8260";

// Get blog posts
const posts = await getPostListItems();
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const siteConfig = {
name: "tambo-ai",
description:
"An open-source AI orchestration framework for your React front end.",
url: env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
url: env.NEXT_PUBLIC_APP_URL || "http://localhost:8260",
keywords: [
"AI-Powered React Components",
"Contextual UI Generation",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const env = createEnv({
GOOGLE_CLIENT_SECRET: z.string().transform(allowEmptyString).optional(),
/** Generate with `openssl rand -hex 32` */
NEXTAUTH_SECRET: z.string().min(8),
/** URL of the client app so we can redirect back to it after auth, e.g. https://tambo.co or http://localhost:3000 */
/** URL of the client app so we can redirect back to it after auth, e.g. https://tambo.co or http://localhost:8260 */
NEXTAUTH_URL: z.string().url(),
/** Email address to send emails from. Required if using email authentication. */
EMAIL_FROM_DEFAULT: z.string().transform(allowEmptyString).optional(),
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/urlSecurity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe("urlSecurity", () => {
test("skips validation when ALLOW_LOCAL_MCP_SERVERS is set", async () => {
jest.doMock("@/lib/env", () => ({ env: { ALLOW_LOCAL_MCP_SERVERS: "1" } }));
const { validateSafeURL } = await import("@/lib/urlSecurity");
const res = await validateSafeURL("http://localhost:3000");
const res = await validateSafeURL("http://localhost:8260");
expect(res.safe).toBe(true);
});

Expand Down
4 changes: 2 additions & 2 deletions apps/web/lib/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe("utils", () => {
});

test("absoluteUrl uses siteConfig.url fallback", () => {
expect(absoluteUrl("/x")).toBe("http://localhost:3000/x");
expect(absoluteUrl("/x")).toBe("http://localhost:8260/x");
});

test("constructMetadata builds expected metadata", () => {
Expand All @@ -24,7 +24,7 @@ describe("utils", () => {
// @ts-expect-error openGraph is present
expect(meta.openGraph.title).toBe("Hello");
// @ts-expect-error alternates exists
expect(meta.alternates.canonical).toBe("http://localhost:3000/a");
expect(meta.alternates.canonical).toBe("http://localhost:8260/a");
});

test("formatDate produces Today for same-day", () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"dev": "next dev -p 8260",
"build": "SKIP_ENV_VALIDATION=true next build --no-lint",
"start": "next start",
"lint": "eslint",
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"build": "next build",
"check-types": "tsc --noEmit",
"dev": "next dev --turbo",
"dev": "next dev --turbo -p 8263",
"lint": "eslint .",
"start": "next start",
"postinstall": "fumadocs-mdx",
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
"test": "turbo test",
"dev": "turbo dev --filter=@tambo-ai/showcase --filter=@tambo-ai/docs",
"dev:cloud": "turbo dev --filter=@tambo-ai-cloud/web --filter=@tambo-ai-cloud/api",
"dev:cloud:full": "turbo dev --filter=@tambo-ai-cloud/web --filter=@tambo-ai-cloud/api --filter=@tambo-ai/showcase --filter=@tambo-ai/docs",
"dev:local": "./scripts/dev-local.sh",
"dev:web": "turbo dev --filter=@tambo-ai-cloud/web",
"dev:api": "turbo dev --filter=@tambo-ai-cloud/api",
"dev:docs": "turbo dev --filter=@tambo-ai/docs",
"sync:showcase": "tsx scripts/sync-showcase-components.ts",
"sync:showcase:watch": "tsx scripts/sync-showcase-components.ts --watch"
},
Expand Down
58 changes: 58 additions & 0 deletions scripts/dev-local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/bash
# Start full local dev environment with file upload support
# Usage: ./scripts/dev-local.sh

set -e

echo "Starting local dev environment..."

echo "Checking that ports 8260-8263 are free (web/api/showcase/docs)..."
for port in 8260 8261 8262 8263; do
if ! python3 - "$port" <<'PY'
import socket
import sys

port = int(sys.argv[1])
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
sock.bind(("127.0.0.1", port))
except OSError:
sys.exit(1)
finally:
sock.close()
PY
then
echo "Port ${port} is already in use. Stop the process using it, then re-run this script."
exit 1
fi
done
Comment thread
lachieh marked this conversation as resolved.

# 1. Start Postgres (if not running)
if ! docker ps | grep -q tambo_postgres; then
echo "Starting Postgres..."
docker compose --env-file docker.env up postgres -d
sleep 3
fi
Comment thread
lachieh marked this conversation as resolved.

# 2. Start Supabase (if not running)
if ! supabase status 2>/dev/null | grep -q "supabase local development setup is running"; then
echo "Starting Supabase..."
supabase start
fi
Comment thread
lachieh marked this conversation as resolved.
Comment thread
lachieh marked this conversation as resolved.

# 3. Run migrations (idempotent)
echo "Running migrations..."
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/tambo" npm run db:migrate -w packages/db 2>/dev/null || true

Comment thread
lachieh marked this conversation as resolved.
# 4. Start all dev servers
echo ""
echo "Starting dev servers..."
echo " Web: http://localhost:8260"
echo " API: http://localhost:8261"
echo " Showcase: http://localhost:8262"
echo " Docs: http://localhost:8263"
echo " Supabase: http://127.0.0.1:54423"
echo ""

npm run dev:cloud:full
2 changes: 1 addition & 1 deletion showcase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"license": "MIT",
"scripts": {
"dev": "next dev",
"dev": "next dev -p 8262",
"build": "next build",
"postbuild": "next-sitemap",
"start": "next start",
Expand Down
7 changes: 4 additions & 3 deletions supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ file_size_limit = "50MiB"
enabled = true
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
# site_url = "http://127.0.0.1:3000"
site_url = "http://localhost:3000"
# Keep this in sync with the local `apps/web` dev server port and your OAuth provider redirect URIs.
# site_url = "http://127.0.0.1:8260"
site_url = "http://localhost:8260"
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://127.0.0.1:3000", "http://localhost:3000", "http://localhost:3000/cli-auth", "http://localhost:3000/dashboard"]
additional_redirect_urls = ["https://127.0.0.1:8260", "http://localhost:8260", "http://localhost:8260/cli-auth", "http://localhost:8260/dashboard"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
jwt_expiry = 3600
# If disabled, the refresh token will never expire.
Expand Down
Loading