@tellet/create 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +195 -0
  2. package/dist/ai/generate.d.ts +33 -0
  3. package/dist/ai/generate.js +108 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +337 -0
  6. package/dist/scaffold/project.d.ts +44 -0
  7. package/dist/scaffold/project.js +318 -0
  8. package/package.json +48 -0
  9. package/template/Dockerfile +35 -0
  10. package/template/app/(dashboard)/agents/page.tsx +14 -0
  11. package/template/app/(dashboard)/conversations/[id]/page.tsx +103 -0
  12. package/template/app/(dashboard)/conversations/page.tsx +50 -0
  13. package/template/app/(dashboard)/dashboard/page.tsx +102 -0
  14. package/template/app/(dashboard)/layout.tsx +15 -0
  15. package/template/app/(dashboard)/settings/page.tsx +46 -0
  16. package/template/app/(site)/layout.tsx +3 -0
  17. package/template/app/(site)/page.tsx +25 -0
  18. package/template/app/api/chat/route.ts +129 -0
  19. package/template/app/api/cron/route.ts +29 -0
  20. package/template/app/api/orchestrator/route.ts +139 -0
  21. package/template/app/globals.css +30 -0
  22. package/template/app/layout.tsx +18 -0
  23. package/template/components/chat/ChatWidget.tsx +109 -0
  24. package/template/components/chat/Markdown.tsx +136 -0
  25. package/template/components/dashboard/AgentChat.tsx +192 -0
  26. package/template/components/dashboard/AgentsListClient.tsx +86 -0
  27. package/template/components/dashboard/DashboardAgentGrid.tsx +73 -0
  28. package/template/components/dashboard/OrchestratorChat.tsx +251 -0
  29. package/template/components/dashboard/Sidebar.tsx +44 -0
  30. package/template/components/dashboard/StatsCards.tsx +40 -0
  31. package/template/components/dashboard/Welcome.tsx +139 -0
  32. package/template/components/sections/Agents.tsx +67 -0
  33. package/template/components/sections/CTA.tsx +46 -0
  34. package/template/components/sections/FAQ.tsx +81 -0
  35. package/template/components/sections/Features.tsx +51 -0
  36. package/template/components/sections/Footer.tsx +22 -0
  37. package/template/components/sections/Hero.tsx +86 -0
  38. package/template/components/sections/Icons.tsx +29 -0
  39. package/template/components/ui/Button.tsx +26 -0
  40. package/template/docker-compose.yml +32 -0
  41. package/template/infra/bin/app.ts +16 -0
  42. package/template/infra/cdk.json +6 -0
  43. package/template/infra/lib/tellet-stack.ts +216 -0
  44. package/template/infra/package.json +20 -0
  45. package/template/infra/tsconfig.json +16 -0
  46. package/template/lib/db.ts +37 -0
  47. package/template/lib/engine/default.ts +227 -0
  48. package/template/lib/engine/index.ts +17 -0
  49. package/template/lib/mcp/client.ts +97 -0
  50. package/template/lib/mcp/knowledge.ts +84 -0
  51. package/template/lib/mcp/registry.ts +106 -0
  52. package/template/lib/orchestrator/executor.ts +202 -0
  53. package/template/lib/orchestrator/tools.ts +245 -0
  54. package/template/lib/providers/anthropic.ts +41 -0
  55. package/template/lib/providers/index.ts +36 -0
  56. package/template/lib/providers/openai.ts +46 -0
  57. package/template/lib/scheduler.ts +115 -0
  58. package/template/lib/supabase.ts +30 -0
  59. package/template/lib/tellet.ts +45 -0
  60. package/template/lib/utils.ts +6 -0
  61. package/template/next.config.ts +7 -0
  62. package/template/public/widget.js +172 -0
  63. package/template/railway.toml +9 -0
  64. package/template/tsconfig.json +21 -0
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import { SiteIcon } from "./Icons";
5
+ import config from "../../tellet.json";
6
+
7
+ export function Features() {
8
+ const features = config.site.features as { title: string; description: string; icon: string }[];
9
+ if (!features || features.length === 0) return null;
10
+
11
+ return (
12
+ <section className="py-24 px-6">
13
+ <div className="max-w-5xl mx-auto">
14
+ <motion.div
15
+ className="text-center mb-16"
16
+ initial={{ opacity: 0, y: 20 }}
17
+ whileInView={{ opacity: 1, y: 0 }}
18
+ viewport={{ once: true }}
19
+ transition={{ duration: 0.5 }}
20
+ >
21
+ <h2 className="text-3xl md:text-4xl font-bold tracking-tight">
22
+ Why choose us
23
+ </h2>
24
+ </motion.div>
25
+
26
+ <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-5">
27
+ {features.map((feature, i) => (
28
+ <motion.div
29
+ key={feature.title}
30
+ className="group rounded-xl border border-border bg-bg-secondary/40 p-6 transition-all duration-200 hover:border-border-hover hover:-translate-y-0.5"
31
+ initial={{ opacity: 0, y: 20 }}
32
+ whileInView={{ opacity: 1, y: 0 }}
33
+ viewport={{ once: true }}
34
+ transition={{ delay: i * 0.06, duration: 0.4 }}
35
+ >
36
+ <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-accent/10 text-accent mb-4">
37
+ <SiteIcon name={feature.icon} />
38
+ </div>
39
+ <h3 className="text-lg font-semibold text-text-primary mb-2">
40
+ {feature.title}
41
+ </h3>
42
+ <p className="text-sm text-text-secondary leading-relaxed">
43
+ {feature.description}
44
+ </p>
45
+ </motion.div>
46
+ ))}
47
+ </div>
48
+ </div>
49
+ </section>
50
+ );
51
+ }
@@ -0,0 +1,22 @@
1
+ import config from "../../tellet.json";
2
+
3
+ export function Footer() {
4
+ return (
5
+ <footer className="border-t border-border py-8 px-6">
6
+ <div className="max-w-5xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4">
7
+ <div className="flex items-center gap-2">
8
+ <span className="text-sm font-semibold">{config.company.name}</span>
9
+ <span className="text-xs text-text-tertiary">&middot;</span>
10
+ <span className="text-xs text-text-tertiary">
11
+ Powered by <span className="text-text-secondary">tellet</span>
12
+ </span>
13
+ </div>
14
+ <div className="flex items-center gap-4 text-xs text-text-tertiary">
15
+ <a href="/dashboard" className="hover:text-text-secondary transition-colors">
16
+ Dashboard
17
+ </a>
18
+ </div>
19
+ </div>
20
+ </footer>
21
+ );
22
+ }
@@ -0,0 +1,86 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import config from "../../tellet.json";
5
+
6
+ export function Hero() {
7
+ return (
8
+ <section className="relative min-h-[85vh] flex items-center justify-center px-6 overflow-hidden">
9
+ <div
10
+ className="absolute inset-0"
11
+ style={{
12
+ background:
13
+ "radial-gradient(ellipse 80% 60% at 50% 40%, rgba(139,92,246,0.08) 0%, transparent 70%)",
14
+ }}
15
+ />
16
+
17
+ <div className="relative z-10 text-center max-w-3xl mx-auto">
18
+ <motion.div
19
+ initial={{ opacity: 0, y: 24 }}
20
+ animate={{ opacity: 1, y: 0 }}
21
+ transition={{ duration: 0.5 }}
22
+ >
23
+ <div className="inline-flex items-center gap-2 rounded-full border border-border bg-bg-secondary/50 px-4 py-1.5 mb-8">
24
+ <span className="w-2 h-2 rounded-full bg-green-400 animate-pulse" />
25
+ <span className="text-xs text-text-secondary">
26
+ {config.agents.length} AI agents online
27
+ </span>
28
+ </div>
29
+
30
+ <h1 className="text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-[1.1]">
31
+ {config.company.name}
32
+ </h1>
33
+ </motion.div>
34
+
35
+ <motion.p
36
+ className="mt-3 text-xl md:text-2xl text-accent font-medium"
37
+ initial={{ opacity: 0, y: 16 }}
38
+ animate={{ opacity: 1, y: 0 }}
39
+ transition={{ duration: 0.5, delay: 0.1 }}
40
+ >
41
+ {config.site.tagline}
42
+ </motion.p>
43
+
44
+ <motion.p
45
+ className="mt-4 text-lg text-text-secondary max-w-xl mx-auto leading-relaxed"
46
+ initial={{ opacity: 0, y: 16 }}
47
+ animate={{ opacity: 1, y: 0 }}
48
+ transition={{ duration: 0.5, delay: 0.2 }}
49
+ >
50
+ {config.site.subtitle}
51
+ </motion.p>
52
+
53
+ <motion.div
54
+ className="mt-10 flex flex-col sm:flex-row items-center justify-center gap-4"
55
+ initial={{ opacity: 0, y: 16 }}
56
+ animate={{ opacity: 1, y: 0 }}
57
+ transition={{ duration: 0.5, delay: 0.3 }}
58
+ >
59
+ <button
60
+ onClick={() => {
61
+ const el = document.getElementById("chat-trigger");
62
+ el?.click();
63
+ }}
64
+ className="inline-flex items-center gap-2 rounded-xl bg-accent px-8 py-4 text-sm font-medium text-white hover:bg-accent-hover transition-all shadow-[0_0_30px_var(--color-accent-glow)] cursor-pointer"
65
+ >
66
+ <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
67
+ <path strokeLinecap="round" strokeLinejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
68
+ </svg>
69
+ Chat with us
70
+ </button>
71
+ <a
72
+ href="/dashboard"
73
+ className="inline-flex items-center gap-2 rounded-xl bg-bg-secondary border border-border px-8 py-4 text-sm font-medium text-text-primary hover:border-border-hover transition-all"
74
+ >
75
+ Dashboard
76
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
77
+ <path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
78
+ </svg>
79
+ </a>
80
+ </motion.div>
81
+ </div>
82
+
83
+ <div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-bg-primary to-transparent" />
84
+ </section>
85
+ );
86
+ }
@@ -0,0 +1,29 @@
1
+ const iconPaths: Record<string, string> = {
2
+ sparkles:
3
+ "M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 0 0-2.455 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z",
4
+ shield:
5
+ "M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z",
6
+ zap: "m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z",
7
+ heart:
8
+ "M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z",
9
+ globe:
10
+ "M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5a17.92 17.92 0 0 1-8.716-2.247m0 0A8.966 8.966 0 0 1 3 12c0-1.264.26-2.466.73-3.558",
11
+ chart:
12
+ "M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z",
13
+ clock:
14
+ "M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
15
+ users:
16
+ "M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z",
17
+ star: "M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z",
18
+ target:
19
+ "M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7Z",
20
+ };
21
+
22
+ export function SiteIcon({ name, className = "w-6 h-6" }: { name: string; className?: string }) {
23
+ const d = iconPaths[name] || iconPaths.sparkles;
24
+ return (
25
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
26
+ <path strokeLinecap="round" strokeLinejoin="round" d={d} />
27
+ </svg>
28
+ );
29
+ }
@@ -0,0 +1,26 @@
1
+ import { cn } from "@/lib/utils";
2
+ import { type ButtonHTMLAttributes } from "react";
3
+
4
+ type Variant = "primary" | "secondary" | "ghost";
5
+
6
+ const variants: Record<Variant, string> = {
7
+ primary: "bg-accent text-white hover:bg-accent-hover shadow-[0_0_20px_var(--color-accent-glow)]",
8
+ secondary: "bg-bg-secondary text-text-primary border border-border hover:border-border-hover",
9
+ ghost: "text-text-secondary hover:text-text-primary",
10
+ };
11
+
12
+ export function Button({
13
+ variant = "primary",
14
+ href,
15
+ className,
16
+ children,
17
+ ...props
18
+ }: { variant?: Variant; href?: string } & ButtonHTMLAttributes<HTMLButtonElement>) {
19
+ const classes = cn(
20
+ "inline-flex items-center justify-center rounded-lg px-6 py-3 text-sm font-medium transition-all duration-200 cursor-pointer",
21
+ variants[variant],
22
+ className
23
+ );
24
+ if (href) return <a href={href} className={classes}>{children}</a>;
25
+ return <button className={classes} {...props}>{children}</button>;
26
+ }
@@ -0,0 +1,32 @@
1
+ services:
2
+ app:
3
+ build: .
4
+ ports:
5
+ - "3000:3000"
6
+ environment:
7
+ - DATABASE_URL=postgresql://tellet:tellet@db:5432/tellet
8
+ - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
9
+ - OPENAI_API_KEY=${OPENAI_API_KEY:-}
10
+ depends_on:
11
+ db:
12
+ condition: service_healthy
13
+
14
+ db:
15
+ image: pgvector/pgvector:pg17
16
+ ports:
17
+ - "5432:5432"
18
+ environment:
19
+ - POSTGRES_USER=tellet
20
+ - POSTGRES_PASSWORD=tellet
21
+ - POSTGRES_DB=tellet
22
+ volumes:
23
+ - pgdata:/var/lib/postgresql/data
24
+ - ./supabase/migrations:/docker-entrypoint-initdb.d
25
+ healthcheck:
26
+ test: ["CMD-SHELL", "pg_isready -U tellet"]
27
+ interval: 5s
28
+ timeout: 3s
29
+ retries: 5
30
+
31
+ volumes:
32
+ pgdata:
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import "source-map-support/register";
3
+ import * as cdk from "aws-cdk-lib";
4
+ import { TelletStack } from "../lib/tellet-stack";
5
+
6
+ const app = new cdk.App();
7
+
8
+ const projectName = app.node.tryGetContext("projectName") || "tellet";
9
+
10
+ new TelletStack(app, `${projectName}-stack`, {
11
+ env: {
12
+ account: process.env.CDK_DEFAULT_ACCOUNT,
13
+ region: process.env.CDK_DEFAULT_REGION || "us-east-1",
14
+ },
15
+ projectName,
16
+ });
@@ -0,0 +1,6 @@
1
+ {
2
+ "app": "npx ts-node bin/app.ts",
3
+ "context": {
4
+ "@aws-cdk/core:stackRelativeExports": true
5
+ }
6
+ }
@@ -0,0 +1,216 @@
1
+ import * as cdk from "aws-cdk-lib";
2
+ import * as ec2 from "aws-cdk-lib/aws-ec2";
3
+ import * as rds from "aws-cdk-lib/aws-rds";
4
+ import * as lambda from "aws-cdk-lib/aws-lambda";
5
+ import * as apigateway from "aws-cdk-lib/aws-apigatewayv2";
6
+ import * as apiIntegrations from "aws-cdk-lib/aws-apigatewayv2-integrations";
7
+ import * as s3 from "aws-cdk-lib/aws-s3";
8
+ import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
9
+ import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
10
+ import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
11
+ import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
12
+ import { Construct } from "constructs";
13
+
14
+ interface TelletStackProps extends cdk.StackProps {
15
+ projectName: string;
16
+ }
17
+
18
+ export class TelletStack extends cdk.Stack {
19
+ constructor(scope: Construct, id: string, props: TelletStackProps) {
20
+ super(scope, id, props);
21
+
22
+ const { projectName } = props;
23
+
24
+ // ==========================================
25
+ // VPC — Minimal, no NAT Gateway ($0)
26
+ // ==========================================
27
+ const vpc = new ec2.Vpc(this, "Vpc", {
28
+ maxAzs: 2,
29
+ natGateways: 0, // Save ~$30/mo
30
+ subnetConfiguration: [
31
+ {
32
+ name: "public",
33
+ subnetType: ec2.SubnetType.PUBLIC,
34
+ },
35
+ {
36
+ name: "isolated",
37
+ subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
38
+ },
39
+ ],
40
+ });
41
+
42
+ // ==========================================
43
+ // Secrets Manager — API keys
44
+ // ==========================================
45
+ const secrets = new secretsmanager.Secret(this, "Secrets", {
46
+ secretName: `${projectName}/api-keys`,
47
+ description: "tellet API keys",
48
+ generateSecretString: {
49
+ secretStringTemplate: JSON.stringify({
50
+ ANTHROPIC_API_KEY: "REPLACE_ME",
51
+ OPENAI_API_KEY: "",
52
+ }),
53
+ generateStringKey: "placeholder",
54
+ },
55
+ });
56
+
57
+ // ==========================================
58
+ // RDS PostgreSQL — Free Tier (~$0)
59
+ // ==========================================
60
+ const dbSecurityGroup = new ec2.SecurityGroup(this, "DbSg", {
61
+ vpc,
62
+ description: "RDS security group",
63
+ });
64
+
65
+ const database = new rds.DatabaseInstance(this, "Database", {
66
+ engine: rds.DatabaseInstanceEngine.postgres({
67
+ version: rds.PostgresEngineVersion.VER_17,
68
+ }),
69
+ instanceType: ec2.InstanceType.of(
70
+ ec2.InstanceClass.T4G,
71
+ ec2.InstanceSize.MICRO // Free Tier
72
+ ),
73
+ vpc,
74
+ vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
75
+ securityGroups: [dbSecurityGroup],
76
+ databaseName: "tellet",
77
+ credentials: rds.Credentials.fromGeneratedSecret("tellet", {
78
+ secretName: `${projectName}/db-credentials`,
79
+ }),
80
+ allocatedStorage: 20, // Free Tier max
81
+ maxAllocatedStorage: 20,
82
+ backupRetention: cdk.Duration.days(7),
83
+ deletionProtection: false, // Set true for production
84
+ removalPolicy: cdk.RemovalPolicy.DESTROY, // Change for production
85
+ });
86
+
87
+ // ==========================================
88
+ // Lambda — Next.js App (~$0 with free tier)
89
+ // ==========================================
90
+ const appFunction = new lambda.DockerImageFunction(this, "AppFunction", {
91
+ code: lambda.DockerImageCode.fromImageAsset(".."), // Parent dir (project root)
92
+ memorySize: 512,
93
+ timeout: cdk.Duration.seconds(30),
94
+ environment: {
95
+ NODE_ENV: "production",
96
+ DATABASE_URL: `postgresql://tellet:${database.secret?.secretValueFromJson("password").unsafeUnwrap()}@${database.dbInstanceEndpointAddress}:${database.dbInstanceEndpointPort}/tellet`,
97
+ SECRETS_ARN: secrets.secretArn,
98
+ },
99
+ vpc,
100
+ vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
101
+ architecture: lambda.Architecture.ARM_64, // 20% cheaper
102
+ });
103
+
104
+ // Grant Lambda access to secrets and DB
105
+ secrets.grantRead(appFunction);
106
+ database.connections.allowDefaultPortFrom(appFunction);
107
+
108
+ // ==========================================
109
+ // Agent Worker Lambda — Long tasks (15min)
110
+ // ==========================================
111
+ const workerFunction = new lambda.DockerImageFunction(this, "WorkerFunction", {
112
+ code: lambda.DockerImageCode.fromImageAsset(".."),
113
+ memorySize: 1024,
114
+ timeout: cdk.Duration.minutes(15), // Max Lambda timeout
115
+ environment: {
116
+ NODE_ENV: "production",
117
+ DATABASE_URL: `postgresql://tellet:${database.secret?.secretValueFromJson("password").unsafeUnwrap()}@${database.dbInstanceEndpointAddress}:${database.dbInstanceEndpointPort}/tellet`,
118
+ SECRETS_ARN: secrets.secretArn,
119
+ WORKER_MODE: "true",
120
+ },
121
+ vpc,
122
+ vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
123
+ architecture: lambda.Architecture.ARM_64,
124
+ });
125
+
126
+ secrets.grantRead(workerFunction);
127
+ database.connections.allowDefaultPortFrom(workerFunction);
128
+
129
+ // ==========================================
130
+ // API Gateway — HTTP API
131
+ // ==========================================
132
+ const httpApi = new apigateway.HttpApi(this, "HttpApi", {
133
+ apiName: `${projectName}-api`,
134
+ corsPreflight: {
135
+ allowOrigins: ["*"],
136
+ allowMethods: [apigateway.CorsHttpMethod.ANY],
137
+ allowHeaders: ["*"],
138
+ },
139
+ });
140
+
141
+ const appIntegration = new apiIntegrations.HttpLambdaIntegration(
142
+ "AppIntegration",
143
+ appFunction
144
+ );
145
+
146
+ httpApi.addRoutes({
147
+ path: "/{proxy+}",
148
+ methods: [apigateway.HttpMethod.ANY],
149
+ integration: appIntegration,
150
+ });
151
+
152
+ httpApi.addRoutes({
153
+ path: "/",
154
+ methods: [apigateway.HttpMethod.ANY],
155
+ integration: appIntegration,
156
+ });
157
+
158
+ // ==========================================
159
+ // S3 — Static assets
160
+ // ==========================================
161
+ const staticBucket = new s3.Bucket(this, "StaticBucket", {
162
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
163
+ autoDeleteObjects: true,
164
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
165
+ });
166
+
167
+ // ==========================================
168
+ // CloudFront — CDN
169
+ // ==========================================
170
+ const distribution = new cloudfront.Distribution(this, "Distribution", {
171
+ defaultBehavior: {
172
+ origin: new origins.HttpOrigin(
173
+ `${httpApi.httpApiId}.execute-api.${this.region}.amazonaws.com`
174
+ ),
175
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
176
+ cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
177
+ originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
178
+ },
179
+ additionalBehaviors: {
180
+ "/_next/static/*": {
181
+ origin: origins.S3BucketOrigin.withOriginAccessControl(staticBucket),
182
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
183
+ cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
184
+ },
185
+ },
186
+ });
187
+
188
+ // ==========================================
189
+ // Outputs
190
+ // ==========================================
191
+ new cdk.CfnOutput(this, "SiteUrl", {
192
+ value: `https://${distribution.distributionDomainName}`,
193
+ description: "Your AI company URL",
194
+ });
195
+
196
+ new cdk.CfnOutput(this, "ApiUrl", {
197
+ value: httpApi.url || "",
198
+ description: "API Gateway URL",
199
+ });
200
+
201
+ new cdk.CfnOutput(this, "DatabaseEndpoint", {
202
+ value: database.dbInstanceEndpointAddress,
203
+ description: "RDS endpoint",
204
+ });
205
+
206
+ new cdk.CfnOutput(this, "SecretsArn", {
207
+ value: secrets.secretArn,
208
+ description: "Secrets Manager ARN — update with your API keys",
209
+ });
210
+
211
+ new cdk.CfnOutput(this, "EstimatedMonthlyCost", {
212
+ value: "$5-15 (Lambda free tier + RDS free tier + CloudFront free tier)",
213
+ description: "Estimated monthly cost",
214
+ });
215
+ }
216
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "tellet-infra",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "synth": "cdk synth",
8
+ "deploy": "cdk deploy --all --require-approval never",
9
+ "destroy": "cdk destroy --all"
10
+ },
11
+ "dependencies": {
12
+ "aws-cdk-lib": "^2.180.0",
13
+ "constructs": "^10.4.0"
14
+ },
15
+ "devDependencies": {
16
+ "aws-cdk": "^2.180.0",
17
+ "typescript": "^5.0.0",
18
+ "@types/node": "^22.0.0"
19
+ }
20
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["es2022"],
6
+ "declaration": true,
7
+ "strict": true,
8
+ "noImplicitAny": true,
9
+ "strictNullChecks": true,
10
+ "noImplicitThis": true,
11
+ "outDir": "dist",
12
+ "rootDir": ".",
13
+ "skipLibCheck": true
14
+ },
15
+ "include": ["bin/**/*.ts", "lib/**/*.ts"]
16
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Database abstraction layer.
3
+ * - Quick Start mode: uses Supabase client
4
+ * - Cloud/Enterprise mode: uses DATABASE_URL with direct pg
5
+ *
6
+ * All server-side code should import from here instead of lib/supabase directly.
7
+ */
8
+
9
+ import { createServerSupabase } from "./supabase";
10
+
11
+ export type QueryResult<T = Record<string, unknown>> = {
12
+ data: T[] | null;
13
+ error: { message: string } | null;
14
+ count?: number;
15
+ };
16
+
17
+ function getMode(): "supabase" | "direct" {
18
+ return process.env.DATABASE_URL ? "direct" : "supabase";
19
+ }
20
+
21
+ /**
22
+ * Get a Supabase-compatible client.
23
+ * In Cloud mode with DATABASE_URL, we still use Supabase client
24
+ * pointed at the local/remote PostgreSQL via PostgREST or direct connection.
25
+ *
26
+ * For now, both modes use Supabase client — the DATABASE_URL mode
27
+ * will be implemented when we add direct pg support.
28
+ * This abstraction exists so we can swap without changing call sites.
29
+ */
30
+ export async function getDB() {
31
+ // Both modes currently use Supabase client.
32
+ // Cloud mode: user sets NEXT_PUBLIC_SUPABASE_URL to their PostgREST endpoint
33
+ // or we add direct pg support later.
34
+ return createServerSupabase();
35
+ }
36
+
37
+ export { getMode };